Skip to content

Commit 4c78f12

Browse files
committed
Added most of the documentation necessary for a public release. (#2)
1 parent d150cb2 commit 4c78f12

File tree

8 files changed

+187
-22
lines changed

8 files changed

+187
-22
lines changed

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
The MIT License
2+
3+
Copyright © 2017 Pras Velagapudi.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in
13+
all copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
THE SOFTWARE.

README.md

Lines changed: 135 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,137 @@
1-
# A-Frame Tilemap Component
1+
# aframe-tilemap
22

3-
An A-Frame component that renders a static tilemap from a set of models.
3+
A set of [A-Frame](https://aframe.io/) components that render a static tilemap
4+
from a specially coded image source and a set of reference entities. It
5+
includes three components with different underlying implementations: [`tilemap-cloned`](#cloned-tilemaps-tilemap-cloned), [`tilemap-merged`](#merged-tilemaps-tilemap-merged), and [`tilemap-instanced`](#instanced-tilemaps-tilemap-instanced).
46

7+
<p align="center"><img src="examples/assets/screenshot.png" alt="screenshot"></p>
8+
9+
## Usage
10+
11+
```html
12+
<a-scene>
13+
<a-assets>
14+
<a-asset-item id="milkTruck" src="assets/CesiumMilkTruck.glb"></a-asset-item>
15+
<img id="tilemap-image" src="maps/tilemap-128px.png">
16+
</a-assets>
17+
<a-entity tilemap-instanced="src: #tilemap-image" position="0 0 -20">
18+
<a-entity tile="id: 1" geometry="primitive: box" visible="false" scale="1 1 3"></a-entity>
19+
<a-entity tile="id: 2; readyEvent: model-loaded" gltf-model="#milkTruck" visible="false" scale="0.5 0.5 0.5"></a-entity>
20+
<a-entity tile="id: 3" geometry="primitive: sphere" visible="false"></a-entity>
21+
</a-entity>
22+
<a-sky color="#333"></a-sky>
23+
</a-scene>
24+
```
25+
26+
## API
27+
28+
Tilemaps are specified using a combination of `tilemap-*` and `tile` components.
29+
An entity with the `tilemap-*` component represents the tilemap itself, and
30+
should be transformed and attached to the desired part of the scene graph.
31+
32+
Entities that are direct children of the `tilemap-*` can include the `tile`
33+
component to indicate that they represent one of the tile IDs that can be
34+
specified in the tilemap. Their transformation relative to the origin of the
35+
tilemap is replicated in each of their instances.
36+
37+
Usually, these tile entities should also have the component `visible="false"`.
38+
39+
### Tilemap Properties
40+
41+
| Property | Type | Description | Default Value |
42+
| ---------- | --------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- |
43+
| src | `asset` | An image asset from which to derive the tile values | |
44+
| tileWidth | `number` | Width of each tile in scene units | 10 |
45+
| tileHeight | `number` | Height of each tile in scene units | 10 |
46+
| origin | `vec2` | Origin of the tilemap in normalized image coordinates. A value of (0,0) will render a tilemap whose entity center is the top-left corner, while a value of (1,1) will center the entity at the bottom right | `{x:0.5,y:0.5}` |
47+
| debug | `boolean` | Enables additional debug printing to console | `false` |
48+
49+
### Tilemap Events
50+
51+
| Property | Description |
52+
| ------------ | ---------------------------------------------------------------------------------------------- |
53+
| model-loaded | Tilemap completed loading.<br>_This emulates the behavior of other model loaders in A-Frame._ |
54+
55+
### Tile Properties
56+
57+
| Property | Type | Description |
58+
| ---------- | -------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
59+
| id | `int` | The ID value in the tilemap that this entity represents |
60+
| readyEvent | `string` | If specified, the tilemap will wait for this entity to emit this event before loading. This is needed for tiles that load assets (e.g. GLTF models).<br>_For most A-Frame loaders, listen for the event `model-loaded`._ |
61+
62+
## Tilemap Image Format
63+
64+
| Example: Square 128px Randomized-ID Tilemap |
65+
| -------------------------------------------------------------------------------------------------------- |
66+
| ![tilemap](examples/maps/tilemap-128px.png) |
67+
| IDs: *random, 0 to 3* |
68+
| Rotation: *0 to 360 deg from left to right* |
69+
70+
The image tilemap that determines where each tile is rendered contains only
71+
two components: `ID` and `Rotation`. Images are expected to contain one byte
72+
per channel of either RGB or RGBA information.
73+
74+
| Channel | Size | Description |
75+
| ------------- | -------- | ---------------------------------------------- |
76+
| **R** (Red) | `1 byte` | HIGH byte of the tile ID |
77+
| **G** (Green) | `1 byte` | LOW byte of the tile ID |
78+
| **B** (Blue) | `1 byte` | Normalized rotation (`0 = 0°`, `255 = 358.5°`) |
79+
| **A** (Alpha) | `1 byte` | _Unused_ |
80+
81+
The equations for determining the tile offets from the pixel value of the image are:
82+
```
83+
TILE_ID = 256 * pixel.R + pixel.G
84+
TILE_THETA = PI / 128.0 * pixel.B
85+
```
86+
87+
Each pixel of the image represents one tile, with each column spaced at
88+
`tileWidth` intervals and each row spaced at `tileHeight` intervals.
89+
90+
## Implementations
91+
92+
### Cloned Tilemaps: [`tilemap-cloned`](src/tilemap-cloned.js)
93+
94+
In the cloned tilemap, each tile location in the map is rendered by cloning the
95+
`THREE.Object3D` represented in the reference tile with the matching ID.
96+
97+
This approach exactly reuses the `THREE.Geometry` and `THREE.Material`
98+
associated with the reference tile, so it will not interfere with shaders or
99+
other complex models. However, it is the least performant, as each tile is
100+
handled as a completely different node in the scene graph.
101+
102+
### Merged Tilemaps: [`tilemap-merged`](src/tilemap-merged.js)
103+
104+
In the merged tilemap, each reference tile is decomposed into a list of
105+
reference meshes. The geometry of each of these meshes is then duplicated at
106+
each matching tile location, creating a single merged mesh that contains
107+
geometry for all instances of the reference tile.
108+
109+
This method is much more performant than cloning, because each reference tile
110+
mesh simply pushes one large geometry buffer to the GPU. However, it is very
111+
memory intensive, because it must maintain the full geometric representation
112+
of every tile location in memory.
113+
114+
### Instanced Tilemaps: [`tilemap-instanced`](src/tilemap-instanced.js)
115+
116+
In the instanced tilemap, each reference tile is pushed to the GPU, where a
117+
vertex shader is applied to each child mesh that instead renders it at each
118+
tile location.
119+
120+
This is the most performant of the three methods, as it only pushes each
121+
reference tile to the GPU once, and it also only needs a small amount of memory
122+
to maintain a buffer of tile locations to which each tile will be rendered.
123+
124+
One downside of this approach is that it requires the direct insertion of a
125+
vertex shader into the material that is used to render the object. This limits
126+
the material options for these objects. The implementation tries to replicate
127+
the functionality of standard `THREE` source materials as best it can, but some
128+
effects will not work properly, and all vertex shader effects are removed.
129+
130+
Another downside is that the rendered instances are not available outside
131+
the GPU for other applications, such as raycasts or other physical checks.
132+
For applications that require those, cloned or merged tilemaps may be a
133+
better choice.
134+
135+
## License
136+
137+
This library is free software and is distributed under an MIT License.

examples/assets/screenshot.png

100 KB
Loading

examples/index.html

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,24 +39,24 @@
3939

4040

4141
<a-entity tilemap-instanced="src: #tilemap-image" position="0 0 -20">
42-
<a-entity tile="id: 1" geometry="primitive: box; buffer: false" visible="false" scale="1 1 3"></a-entity>
43-
<a-entity tile="id: 2; isLoaded: true" gltf-model="#milkTruck" visible="false" scale="0.5 0.5 0.5"></a-entity>
44-
<a-entity tile="id: 3" geometry="primitive: sphere; buffer: false" visible="false"></a-entity>
42+
<a-entity tile="id: 1" geometry="primitive: box" visible="false" scale="1 1 3"></a-entity>
43+
<a-entity tile="id: 2; readyEvent: model-loaded" gltf-model="#milkTruck" visible="false" scale="0.5 0.5 0.5"></a-entity>
44+
<a-entity tile="id: 3" geometry="primitive: sphere" visible="false"></a-entity>
4545
</a-entity>
4646
<!--
4747
<a-entity instanced-tilemap="src: #tilemap-image" position="256 0 -20">
4848
<a-entity tile="id: 1" geometry="primitive: box; buffer: false" visible="false" scale="1 1 3"></a-entity>
49-
<a-entity tile="id: 2; isLoaded: true" gltf-model="#milkTruck" scale="0.5 0.5 0.5"></a-entity>
49+
<a-entity tile="id: 2; loadedEvent: model-loaded" gltf-model="#milkTruck" scale="0.5 0.5 0.5"></a-entity>
5050
<a-entity tile="id: 3" geometry="primitive: sphere; buffer: false" visible="false"></a-entity>
5151
</a-entity>
5252
<a-entity instanced-tilemap="src: #tilemap-image" position="256 256 -20">
5353
<a-entity tile="id: 1" geometry="primitive: box; buffer: false" visible="false" scale="1 1 3"></a-entity>
54-
<a-entity tile="id: 2; isLoaded: true" gltf-model="#milkTruck" scale="0.5 0.5 0.5"></a-entity>
54+
<a-entity tile="id: 2; loadedEvent: model-loaded" gltf-model="#milkTruck" scale="0.5 0.5 0.5"></a-entity>
5555
<a-entity tile="id: 3" geometry="primitive: sphere; buffer: false" visible="false"></a-entity>
5656
</a-entity>
5757
<a-entity instanced-tilemap="src: #tilemap-image" position="0 256 -20">
5858
<a-entity tile="id: 1" geometry="primitive: box; buffer: false" visible="false" scale="1 1 3"></a-entity>
59-
<a-entity tile="id: 2; isLoaded: true" gltf-model="#milkTruck" scale="0.5 0.5 0.5"></a-entity>
59+
<a-entity tile="id: 2; loadedEvent: model-loaded" gltf-model="#milkTruck" scale="0.5 0.5 0.5"></a-entity>
6060
<a-entity tile="id: 3" geometry="primitive: sphere; buffer: false" visible="false"></a-entity>
6161
</a-entity>
6262
-->

src/index.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,6 @@ import './tilemap-merged';
88
AFRAME.registerComponent('tile', {
99
schema: {
1010
id: { type: 'int' },
11-
isLoaded: { type: 'boolean', default: false },
11+
readyEvent: { type: 'string', default: '' },
1212
},
1313
});

src/tilemap-cloned.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,13 @@ AFRAME.registerComponent('tilemap-cloned', {
2020
for (const child of el.children) {
2121
const tile = child.components.tile;
2222
if (tile) {
23-
tiles[tile.data] = tile;
23+
tiles[tile.data.id] = tile;
2424
}
2525
}
2626

2727
// TODO: add event handler for new children.
2828
this.constructClones();
29+
this.el.emit('model-loaded');
2930
},
3031

3132
constructClones: function() {

src/tilemap-instanced.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -31,10 +31,14 @@ AFRAME.registerComponent('tilemap-instanced', {
3131

3232
// TODO: add event handler for new children.
3333
// Construct tilemap after a number of pre-processing steps.
34-
this.constructTiles().then(() => {
35-
this.constructInstances();
36-
this.constructMeshes();
37-
});
34+
this.constructTiles()
35+
.then(() => {
36+
this.constructInstances();
37+
this.constructMeshes();
38+
})
39+
.then(() => {
40+
this.el.emit('model-loaded');
41+
});
3842
},
3943

4044
// Take all map geometry and add it as meshes to the scene.
@@ -202,8 +206,9 @@ AFRAME.registerComponent('tilemap-instanced', {
202206
resolve();
203207
};
204208

205-
if (tile.entity.data.isLoaded) {
206-
tile.entity.el.addEventListener('model-loaded', e => {
209+
const readyEvent = tile.entity.data.readyEvent;
210+
if (readyEvent) {
211+
tile.entity.el.addEventListener(readyEvent, e => {
207212
// For some reason, there is some additional time for the
208213
// transformations in the mesh.matrixWorld to update after the
209214
// 'model-loaded' event is emitted.

src/tilemap-merged.js

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,14 @@ AFRAME.registerComponent('tilemap-merged', {
2626

2727
// TODO: add event handler for new children.
2828
// Construct tilemap after a number of pre-processing steps.
29-
this.constructTiles().then(() => {
30-
this.constructGeometry();
31-
this.constructMeshes();
32-
});
29+
this.constructTiles()
30+
.then(() => {
31+
this.constructGeometry();
32+
this.constructMeshes();
33+
})
34+
.then(() => {
35+
this.el.emit('model-loaded');
36+
});
3337
},
3438

3539
// Take all map geometry and add it as meshes to the scene.
@@ -172,8 +176,9 @@ AFRAME.registerComponent('tilemap-merged', {
172176
resolve();
173177
};
174178

175-
if (tile.data.isLoaded) {
176-
tile.el.addEventListener('model-loaded', e => {
179+
const readyEvent = tile.data.readyEvent;
180+
if (readyEvent) {
181+
tile.el.addEventListener(readyEvent, e => {
177182
// For some reason, there is some additional time for the
178183
// transformations in the mesh.matrixWorld to update after the
179184
// 'model-loaded' event is emitted.

0 commit comments

Comments
 (0)