├── .gitignore
├── LICENSE
├── README.md
├── assets
├── images
│ ├── apainter-banner.png
│ ├── brush_atlas.json
│ ├── brush_atlas.png
│ ├── controller-diffuse.png
│ ├── controller-pressed.png
│ ├── controller-specular.png
│ ├── floor.jpg
│ ├── logo.png
│ ├── messages.png
│ ├── sky.jpg
│ ├── tooltip.png
│ ├── ui-hover.png
│ ├── ui-normal.png
│ └── ui-pressed.png
├── models
│ ├── controller-tip.glb
│ ├── controller_vive.json
│ ├── logo.mtl
│ ├── logo.obj
│ ├── oculus-left-controller.mtl
│ ├── oculus-left-controller.obj
│ ├── oculus-right-controller.mtl
│ ├── oculus-right-controller.obj
│ ├── teleportHitEntity.obj
│ ├── ui.obj
│ ├── vive-controller.glb
│ ├── vive-controller.mtl
│ └── vive-controller.obj
└── sounds
│ ├── ui_click0.ogg
│ ├── ui_click1.ogg
│ ├── ui_menu.ogg
│ ├── ui_paint.ogg
│ ├── ui_tick.ogg
│ ├── ui_tick0.ogg
│ └── ui_undo.ogg
├── brushes
├── line_gradient.png
├── line_grunge1.png
├── line_grunge2.png
├── line_grunge3.png
├── lines1.png
├── lines2.png
├── lines3.png
├── lines4.png
├── lines5.png
├── silky_flat.png
├── silky_textured.png
├── squared_textured.png
├── stamp_bush.png
├── stamp_column.png
├── stamp_dots.png
├── stamp_fur1.png
├── stamp_fur2.png
├── stamp_gear.png
├── stamp_grass.png
├── stamp_grunge1.png
├── stamp_grunge2.png
├── stamp_grunge3.png
├── stamp_grunge4.png
├── stamp_grunge5.png
├── stamp_leaf1.png
├── stamp_leaf2.png
├── stamp_leaf3.png
├── stamp_snow.png
├── stamp_squares.png
├── stamp_star.png
├── thumb_cubes.gif
├── thumb_flat.gif
├── thumb_line_gradient.gif
├── thumb_line_grunge1.gif
├── thumb_line_grunge2.gif
├── thumb_line_grunge3.gif
├── thumb_lines1.gif
├── thumb_lines2.gif
├── thumb_lines3.gif
├── thumb_lines4.gif
├── thumb_lines5.gif
├── thumb_rainbow.png
├── thumb_silky_flat.gif
├── thumb_silky_textured.gif
├── thumb_single_sphere.png
├── thumb_smooth.gif
├── thumb_spheres.gif
├── thumb_squared_textured.gif
├── thumb_stamp_bush.gif
├── thumb_stamp_column.gif
├── thumb_stamp_dots.gif
├── thumb_stamp_fur1.png
├── thumb_stamp_fur2.png
├── thumb_stamp_gear.gif
├── thumb_stamp_grass.png
├── thumb_stamp_grunge5.gif
├── thumb_stamp_leaf2.gif
├── thumb_stamp_leaf3.gif
├── thumb_stamp_snow.png
├── thumb_stamp_squares.gif
└── thumb_stamp_star.png
├── build.js
├── css
└── main.css
├── demo.apa
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
├── img
├── icon-128.png
├── icon-256.png
├── icon-32.png
├── icon-512.png
├── icon-64.png
├── icon-original.png
├── icon.png
└── icon.psd
├── index.html
├── manifest.webmanifest
├── package-lock.json
├── package.json
├── paintings
├── dancer.apa
└── dancer.json
├── sounds
├── ui_click0.ogg
├── ui_click1.ogg
├── ui_menu.ogg
├── ui_paint.ogg
├── ui_tick.ogg
├── ui_tick0.ogg
└── ui_undo.ogg
├── src
├── atlas.js
├── binarymanager.js
├── brushes
│ ├── cubes.js
│ ├── line.js
│ ├── rainbow.js
│ ├── single-sphere.js
│ ├── spheres.js
│ └── stamp.js
├── components
│ ├── brush-tip.js
│ ├── brush.js
│ ├── json-model.js
│ ├── logo-model.js
│ ├── paint-controls.js
│ ├── ui-raycaster.js
│ ├── ui.js
│ └── vr-analytics.js
├── dragndrop.js
├── index.js
├── onloaded.js
├── sharedbuffergeometry.js
├── sharedbuffergeometrymanager.js
├── systems
│ ├── brush.js
│ ├── painter.js
│ └── ui.js
├── ui2d.js
└── utils.js
├── vendor
├── aframe-blink-controls.js
├── aframe-input-mapping-component.js
├── aframe-input-mapping-component.min.js
├── aframe-master.min.js
├── aframe-master.min.js.map
├── aframe-tooltip-component.js
├── ga.js
└── saveas.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .cache
3 | gh-pages/
4 | node_modules
5 | npm-debug.log*
6 | *.wav
7 | *.bat
8 | .vs
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License
2 |
3 | Copyright © 2015-2016 A-Frame authors.
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.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # A-Painter
2 |
3 | Paint in VR in your browser. [Read more!](https://blog.mozvr.com/a-painter/)
4 |
5 | [](https://blog.mozvr.com/a-painter/)
6 |
7 | [VIEW GALLERY](https://github.com/aframevr/a-painter/issues/99)
8 |
9 | ## Usage
10 |
11 | - Grab a [WebXR-enabled browser](https://immersiveweb.dev/).
12 | - Head to [https://aframe.io/a-painter/](https://aframe.io/a-painter/) and start painting. See the [blog post](https://blog.mozvr.com/a-painter/) for some instructions.
13 | - Painted something beautiful? Share it on [this GitHub issue](https://github.com/aframevr/a-painter/issues/99)!
14 |
15 | ## Local Development
16 |
17 | ```bash
18 | git clone git@github.com:aframevr/a-painter && cd a-painter
19 | npm install
20 | npm start
21 | ```
22 |
23 | Then, load [`http://localhost:8080`](http://localhost:8080) in your browser.
24 |
25 | ## URL parameters
26 |
27 | - **url** (url) Loads a painting in apa format
28 | - **urljson** (url) Loads a painting in json format
29 | - **sky** (image url) Changes the sky texture (empty to remove sky)
30 | - **floor** (image url) Changes the floor texture (empty to remove)
31 | - **bgcolor** (Hex color without the #) Background color
32 |
33 | Example: [https://aframe.io/a-painter/?sky=&floor=http://i.imgur.com/w9BylL0.jpg&bgcolor=24caff&url=https://ucarecdn.com/0b45b93b-e651-42d8-ba49-b2df907575f3/](https://aframe.io/a-painter/?sky=&floor=http://i.imgur.com/w9BylL0.jpg&bgcolor=24caff&url=https://ucarecdn.com/0b45b93b-e651-42d8-ba49-b2df907575f3/)
34 |
35 | ## Brush API
36 |
37 | ### Brush Interface
38 |
39 | To create a new brush, implement the following interface:
40 |
41 | ```javascript
42 | BrushInterface.prototype = {
43 | init: function () {},
44 | addPoint: function (position, orientation, pointerPosition, pressure, timestamp) {},
45 | tick: function (timeOffset, delta) {}
46 | };
47 | ```
48 |
49 | - **init** (): Use this for initializing variables, materials, etc. for your brush.
50 |
51 | - **addPoint** (*Mandatory*): It will be called every time the brush should add a new point to the stroke. You should return `true` if you've added something to the scene and `false` otherwise. To add some mesh to the scene, every brush has an injected `object3D` attribute that can be used to add children to the scene.
52 | - **position** (*vector3*): Controller position.
53 | - **orientation** (*quaternion*): Controller orientation.
54 | - **pointerPosition** (*vector3*): Position of the pointer where the brush should start painting.
55 | - **pressure** (*float[0..1]*): Trigger pressure.
56 | - **timestamp** (*int*): Elapsed milliseconds since the starting of A-Painter.
57 |
58 | - **tick** (*Optional*): Is called on every frame.
59 | - **timeOffset** (*int*): Elapsed milliseconds since the starting of A-Painter.
60 | - **delta** (*int*): Delta time in milliseconds since the last frame.
61 |
62 | *Development Tip*: set your brush as the default brush at the top of
63 | `src/components/brush.js` (`brush: {default: 'yourbrush'}`) while developing so
64 | you don't have to re-select it every time you reload.
65 |
66 | ### Common Data
67 |
68 | Every brush will have some common data injected with the following default values:
69 |
70 | ```javascript
71 | this.data = {
72 | points: [],
73 | size: brushSize,
74 | prevPosition: null,
75 | prevPointerPosition: null,
76 | numPoints: 0,
77 | maxPoints: 1000,
78 | color: color.clone()
79 | };
80 | ```
81 |
82 | - **points** (*Array of vector3*): List of control points already painted in the current stroke with this brush. (It's updated on every call to `addPoint`.)
83 | - **size** (*float*): Brush size. (It's defined when the stroke is created.)
84 | - **prevPosition** (*vector3*): The latest controller position (from the last `addPoint` call).
85 | - **prevPointerPosition** (*vector3*): The latest pointer position (from the last `addPoint` call).
86 | - **numPoints** (*int*): Length of `points` array.
87 | - **color** (*color*): Base color to be used on the brush. (It's defined when the stroke is created.)
88 |
89 | ### Registering a New Brush
90 |
91 | To register a new brush we should call `AFRAME.registerBrush`:
92 |
93 | ```javascript
94 | AFRAME.registerBrush(brushName, brushDefinition, options);
95 | ```
96 |
97 | Register brush needs three parameters:
98 |
99 | - **brushName** (*string*): The unique brush name.
100 | - **brushDefinition** (*object*): The custom implementation of the previously defined `brushDefinition`.
101 | - **options** (*object* [Optional]):
102 | - **thumbnail** (*string*): Path to the thumbnail image file.
103 | - **spacing** (*float*): Minimum distance, in meters, from the previous point needed to call `addPoint`.
104 | - **maxPoints** (*integer*): If defined, `addPoint` won't be called after reaching that number of points.
105 |
106 | ## File Format
107 |
108 | A-Painter uses the following custom binary file format to store the drawings and its strokes.
109 |
110 | ```text
111 | string magic ('apainter')
112 | uint16 version (currently 1)
113 | uint8 num_brushes_used
114 | [num_brushed_used] x {
115 | string brush_name
116 | }
117 | uint32 num_strokes
118 | [num_strokes] x {
119 | uint8 brush_index (Based on the previous definition order)
120 | float32x3 color (rgb)
121 | float32 size
122 | uint32 num_points
123 | [num_points] x {
124 | float32x3 position (vector3)
125 | float32x4 orientation (quaternion)
126 | float32 intensity
127 | uint32 timestamp
128 | }
129 | }
130 |
131 | string = uint8 (size) + size * uint8
132 | ```
133 |
--------------------------------------------------------------------------------
/assets/images/apainter-banner.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/apainter-banner.png
--------------------------------------------------------------------------------
/assets/images/brush_atlas.json:
--------------------------------------------------------------------------------
1 | {
2 | "meta": {
3 | "image": "brush_atlas.png",
4 | "size": {"w":3584,"h":2944},
5 | "scale": "1"
6 | },
7 | "frames": {
8 | "stamp_grass.png":
9 | {
10 | "frame": {"x":0,"y":128,"w":1536,"h":512},
11 | "rotated": false,
12 | "trimmed": false,
13 | "spriteSourceSize": {"x":0,"y":0,"w":1536,"h":512},
14 | "sourceSize": {"w":1536,"h":512}
15 | },
16 | "lines4.png":
17 | {
18 | "frame": {"x":0,"y":0,"w":2048,"h":128},
19 | "rotated": false,
20 | "trimmed": false,
21 | "spriteSourceSize": {"x":0,"y":0,"w":2048,"h":128},
22 | "sourceSize": {"w":2048,"h":128}
23 | },
24 | "stamp_fur2.png":
25 | {
26 | "frame": {"x":0,"y":640,"w":1536,"h":512},
27 | "rotated": false,
28 | "trimmed": false,
29 | "spriteSourceSize": {"x":0,"y":0,"w":1536,"h":512},
30 | "sourceSize": {"w":1536,"h":512}
31 | },
32 | "stamp_bush.png":
33 | {
34 | "frame": {"x":0,"y":1152,"w":1024,"h":512},
35 | "rotated": false,
36 | "trimmed": false,
37 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":512},
38 | "sourceSize": {"w":1024,"h":512}
39 | },
40 | "stamp_gear.png":
41 | {
42 | "frame": {"x":1024,"y":1152,"w":1024,"h":512},
43 | "rotated": false,
44 | "trimmed": false,
45 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":512},
46 | "sourceSize": {"w":1024,"h":512}
47 | },
48 | "stamp_fur1.png":
49 | {
50 | "frame": {"x":2048,"y":0,"w":1024,"h":512},
51 | "rotated": false,
52 | "trimmed": false,
53 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":512},
54 | "sourceSize": {"w":1024,"h":512}
55 | },
56 | "lines3.png":
57 | {
58 | "frame": {"x":2048,"y":512,"w":1024,"h":256},
59 | "rotated": false,
60 | "trimmed": false,
61 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
62 | "sourceSize": {"w":1024,"h":256}
63 | },
64 | "line_gradient.png":
65 | {
66 | "frame": {"x":2048,"y":768,"w":1024,"h":256},
67 | "rotated": false,
68 | "trimmed": false,
69 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
70 | "sourceSize": {"w":1024,"h":256}
71 | },
72 | "lines5.png":
73 | {
74 | "frame": {"x":2048,"y":1024,"w":1024,"h":256},
75 | "rotated": false,
76 | "trimmed": false,
77 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
78 | "sourceSize": {"w":1024,"h":256}
79 | },
80 | "silky_flat.png":
81 | {
82 | "frame": {"x":2048,"y":1280,"w":1024,"h":256},
83 | "rotated": false,
84 | "trimmed": false,
85 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
86 | "sourceSize": {"w":1024,"h":256}
87 | },
88 | "silky_textured.png":
89 | {
90 | "frame": {"x":0,"y":1664,"w":1024,"h":256},
91 | "rotated": false,
92 | "trimmed": false,
93 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
94 | "sourceSize": {"w":1024,"h":256}
95 | },
96 | "squared_textured.png":
97 | {
98 | "frame": {"x":1024,"y":1664,"w":1024,"h":256},
99 | "rotated": false,
100 | "trimmed": false,
101 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
102 | "sourceSize": {"w":1024,"h":256}
103 | },
104 | "line_grunge2.png":
105 | {
106 | "frame": {"x":2048,"y":1664,"w":1024,"h":256},
107 | "rotated": false,
108 | "trimmed": false,
109 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
110 | "sourceSize": {"w":1024,"h":256}
111 | },
112 | "line_grunge3.png":
113 | {
114 | "frame": {"x":0,"y":1920,"w":1024,"h":256},
115 | "rotated": false,
116 | "trimmed": false,
117 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
118 | "sourceSize": {"w":1024,"h":256}
119 | },
120 | "line_grunge1.png":
121 | {
122 | "frame": {"x":1024,"y":1920,"w":1024,"h":256},
123 | "rotated": false,
124 | "trimmed": false,
125 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
126 | "sourceSize": {"w":1024,"h":256}
127 | },
128 | "lines2.png":
129 | {
130 | "frame": {"x":2048,"y":1920,"w":1024,"h":256},
131 | "rotated": false,
132 | "trimmed": false,
133 | "spriteSourceSize": {"x":0,"y":0,"w":1024,"h":256},
134 | "sourceSize": {"w":1024,"h":256}
135 | },
136 | "stamp_leaf3.png":
137 | {
138 | "frame": {"x":1536,"y":128,"w":512,"h":512},
139 | "rotated": false,
140 | "trimmed": false,
141 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
142 | "sourceSize": {"w":512,"h":512}
143 | },
144 | "stamp_dots.png":
145 | {
146 | "frame": {"x":1536,"y":640,"w":512,"h":512},
147 | "rotated": false,
148 | "trimmed": false,
149 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
150 | "sourceSize": {"w":512,"h":512}
151 | },
152 | "thumb_stamp_star.png":
153 | {
154 | "frame": {"x":2304,"y":1536,"w":128,"h":128},
155 | "rotated": false,
156 | "trimmed": false,
157 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
158 | "sourceSize": {"w":128,"h":128}
159 | },
160 | "stamp_grunge1.png":
161 | {
162 | "frame": {"x":512,"y":2176,"w":512,"h":512},
163 | "rotated": false,
164 | "trimmed": false,
165 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
166 | "sourceSize": {"w":512,"h":512}
167 | },
168 | "stamp_grunge2.png":
169 | {
170 | "frame": {"x":1024,"y":2176,"w":512,"h":512},
171 | "rotated": false,
172 | "trimmed": false,
173 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
174 | "sourceSize": {"w":512,"h":512}
175 | },
176 | "stamp_grunge3.png":
177 | {
178 | "frame": {"x":1536,"y":2176,"w":512,"h":512},
179 | "rotated": false,
180 | "trimmed": false,
181 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
182 | "sourceSize": {"w":512,"h":512}
183 | },
184 | "stamp_grunge4.png":
185 | {
186 | "frame": {"x":2048,"y":2176,"w":512,"h":512},
187 | "rotated": false,
188 | "trimmed": false,
189 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
190 | "sourceSize": {"w":512,"h":512}
191 | },
192 | "stamp_grunge5.png":
193 | {
194 | "frame": {"x":2560,"y":2176,"w":512,"h":512},
195 | "rotated": false,
196 | "trimmed": false,
197 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
198 | "sourceSize": {"w":512,"h":512}
199 | },
200 | "stamp_leaf1.png":
201 | {
202 | "frame": {"x":3072,"y":0,"w":512,"h":512},
203 | "rotated": false,
204 | "trimmed": false,
205 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
206 | "sourceSize": {"w":512,"h":512}
207 | },
208 | "stamp_leaf2.png":
209 | {
210 | "frame": {"x":3072,"y":512,"w":512,"h":512},
211 | "rotated": false,
212 | "trimmed": false,
213 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
214 | "sourceSize": {"w":512,"h":512}
215 | },
216 | "stamp_column.png":
217 | {
218 | "frame": {"x":3072,"y":1024,"w":512,"h":512},
219 | "rotated": false,
220 | "trimmed": false,
221 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
222 | "sourceSize": {"w":512,"h":512}
223 | },
224 | "stamp_snow.png":
225 | {
226 | "frame": {"x":3072,"y":1536,"w":512,"h":512},
227 | "rotated": false,
228 | "trimmed": false,
229 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
230 | "sourceSize": {"w":512,"h":512}
231 | },
232 | "stamp_squares.png":
233 | {
234 | "frame": {"x":3072,"y":2048,"w":512,"h":512},
235 | "rotated": false,
236 | "trimmed": false,
237 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
238 | "sourceSize": {"w":512,"h":512}
239 | },
240 | "lines1.png":
241 | {
242 | "frame": {"x":0,"y":2688,"w":256,"h":256},
243 | "rotated": false,
244 | "trimmed": false,
245 | "spriteSourceSize": {"x":0,"y":0,"w":256,"h":256},
246 | "sourceSize": {"w":256,"h":256}
247 | },
248 | "thumb_rainbow.png":
249 | {
250 | "frame": {"x":3072,"y":2560,"w":128,"h":128},
251 | "rotated": false,
252 | "trimmed": false,
253 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
254 | "sourceSize": {"w":128,"h":128}
255 | },
256 | "thumb_single_sphere.png":
257 | {
258 | "frame": {"x":3200,"y":2560,"w":128,"h":128},
259 | "rotated": false,
260 | "trimmed": false,
261 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
262 | "sourceSize": {"w":128,"h":128}
263 | },
264 | "thumb_stamp_fur1.png":
265 | {
266 | "frame": {"x":3328,"y":2560,"w":128,"h":128},
267 | "rotated": false,
268 | "trimmed": false,
269 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
270 | "sourceSize": {"w":128,"h":128}
271 | },
272 | "thumb_stamp_fur2.png":
273 | {
274 | "frame": {"x":3456,"y":2560,"w":128,"h":128},
275 | "rotated": false,
276 | "trimmed": false,
277 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
278 | "sourceSize": {"w":128,"h":128}
279 | },
280 | "thumb_stamp_grass.png":
281 | {
282 | "frame": {"x":2048,"y":1536,"w":128,"h":128},
283 | "rotated": false,
284 | "trimmed": false,
285 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
286 | "sourceSize": {"w":128,"h":128}
287 | },
288 | "thumb_stamp_snow.png":
289 | {
290 | "frame": {"x":2176,"y":1536,"w":128,"h":128},
291 | "rotated": false,
292 | "trimmed": false,
293 | "spriteSourceSize": {"x":0,"y":0,"w":128,"h":128},
294 | "sourceSize": {"w":128,"h":128}
295 | },
296 | "stamp_star.png":
297 | {
298 | "frame": {"x":0,"y":2176,"w":512,"h":512},
299 | "rotated": false,
300 | "trimmed": false,
301 | "spriteSourceSize": {"x":0,"y":0,"w":512,"h":512},
302 | "sourceSize": {"w":512,"h":512}
303 | }
304 | }
305 | }
--------------------------------------------------------------------------------
/assets/images/brush_atlas.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/brush_atlas.png
--------------------------------------------------------------------------------
/assets/images/controller-diffuse.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/controller-diffuse.png
--------------------------------------------------------------------------------
/assets/images/controller-pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/controller-pressed.png
--------------------------------------------------------------------------------
/assets/images/controller-specular.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/controller-specular.png
--------------------------------------------------------------------------------
/assets/images/floor.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/floor.jpg
--------------------------------------------------------------------------------
/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/logo.png
--------------------------------------------------------------------------------
/assets/images/messages.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/messages.png
--------------------------------------------------------------------------------
/assets/images/sky.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/sky.jpg
--------------------------------------------------------------------------------
/assets/images/tooltip.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/tooltip.png
--------------------------------------------------------------------------------
/assets/images/ui-hover.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/ui-hover.png
--------------------------------------------------------------------------------
/assets/images/ui-normal.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/ui-normal.png
--------------------------------------------------------------------------------
/assets/images/ui-pressed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/images/ui-pressed.png
--------------------------------------------------------------------------------
/assets/models/controller-tip.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/models/controller-tip.glb
--------------------------------------------------------------------------------
/assets/models/logo.mtl:
--------------------------------------------------------------------------------
1 | newmtl Material.001
2 |
3 | Ns 10.0000
4 | Ni 1.5000
5 | d 0.99
6 | Tr 0.0000
7 | Tf 1.0000 1.0000 1.0000
8 | illum 2
9 | Ka 1.0 1.0 1.0
10 | Kd 1.0 1.0 1.0
11 | Ks 0.0000 0.0000 0.0000
12 | Ke 0.0000 0.0000 0.0000
13 | map_Ka ../images/logo.png
14 | map_Kd ../images/logo.png
15 |
--------------------------------------------------------------------------------
/assets/models/logo.obj:
--------------------------------------------------------------------------------
1 | # Blender v2.77 (sub 0) OBJ File: 'logo.blend'
2 | # www.blender.org
3 | mtllib logo.mtl
4 | o Grid.009
5 | v -0.471213 1.444180 -1.645922
6 | v -0.466566 1.671926 -1.645922
7 | v -0.712217 1.444180 -1.645922
8 | v -0.757909 1.671926 -1.645922
9 | vt 0.3524 0.6508
10 | vt 0.2705 0.4510
11 | vt 0.3509 0.4510
12 | vt 0.2553 0.6508
13 | vn 0.0000 -0.0000 1.0000
14 | usemtl Material.001
15 | s off
16 | f 2/1/1 3/2/1 1/3/1
17 | f 2/1/1 4/4/1 3/2/1
18 | o Grid.008
19 | v 1.497139 1.755647 -1.434515
20 | v 1.167906 1.756516 -1.467615
21 | v 1.174374 1.319379 -1.466965
22 | v 1.497139 1.320668 -1.434515
23 | vt 0.8919 0.3416
24 | vt 1.0000 0.7243
25 | vt 0.8897 0.7250
26 | vt 1.0000 0.3427
27 | vn -0.1000 0.0000 0.9950
28 | usemtl Material.001
29 | s off
30 | f 7/5/2 5/6/2 6/7/2
31 | f 7/5/2 8/8/2 5/6/2
32 | o Grid.007
33 | v 0.917805 1.790618 -1.535342
34 | v 1.171649 1.789994 -1.509821
35 | v 0.920131 1.357118 -1.535108
36 | v 1.178077 1.355527 -1.509175
37 | vt 0.8053 0.3747
38 | vt 0.8895 0.7544
39 | vt 0.8045 0.7549
40 | vt 0.8917 0.3733
41 | vn -0.1000 0.0000 0.9950
42 | usemtl Material.001
43 | s off
44 | f 11/9/3 10/10/3 9/11/3
45 | f 11/9/3 12/12/3 10/10/3
46 | o Grid.006
47 | v 0.911832 1.757965 -1.486516
48 | v 0.606281 1.758775 -1.501881
49 | v 0.914283 1.302936 -1.486392
50 | v 0.611682 1.304929 -1.501609
51 | vt 0.7044 0.3289
52 | vt 0.8046 0.7263
53 | vt 0.7026 0.7270
54 | vt 0.8054 0.3271
55 | vn -0.0502 0.0000 0.9987
56 | usemtl Material.001
57 | s off
58 | f 16/13/4 13/14/4 14/15/4
59 | f 16/13/4 15/16/4 13/14/4
60 | o Grid.005
61 | v 0.606978 1.821490 -1.579312
62 | v 0.279151 1.822160 -1.579312
63 | v 0.612668 1.343967 -1.579312
64 | v 0.297016 1.345926 -1.579311
65 | vt 0.5990 0.3648
66 | vt 0.7023 0.7820
67 | vt 0.5931 0.7826
68 | vt 0.7042 0.3631
69 | vn -0.0000 0.0000 1.0000
70 | usemtl Material.001
71 | s off
72 | f 20/17/5 17/18/5 18/19/5
73 | f 20/17/5 19/20/5 17/18/5
74 | o Grid.004
75 | v 0.279157 1.821983 -1.515850
76 | v 0.107117 1.822317 -1.515850
77 | v 0.297471 1.333814 -1.515850
78 | v 0.139652 1.334805 -1.515850
79 | vt 0.5466 0.3551
80 | vt 0.5931 0.7824
81 | vt 0.5357 0.7827
82 | vt 0.5992 0.3542
83 | vn -0.0000 0.0000 1.0000
84 | usemtl Material.001
85 | s off
86 | f 24/21/6 21/22/6 22/23/6
87 | f 24/21/6 23/24/6 21/22/6
88 | o Grid.003
89 | v 0.111772 1.752563 -1.601345
90 | v -0.097571 1.753527 -1.601345
91 | v 0.143929 1.270704 -1.601345
92 | v -0.207435 1.273131 -1.601345
93 | vt 0.5480 0.2989
94 | vt 0.4675 0.7224
95 | vt 0.4309 0.3010
96 | vt 0.5373 0.7215
97 | vn -0.0000 0.0000 1.0000
98 | usemtl Material.001
99 | s off
100 | f 27/25/7 26/26/7 28/27/7
101 | f 27/25/7 25/28/7 26/26/7
102 | o Grid.002
103 | v -0.213497 1.246625 -1.527408
104 | v -0.080655 1.827492 -1.527408
105 | v -0.451344 1.249474 -1.527408
106 | v -0.439698 1.820292 -1.527408
107 | v -0.207435 1.273131 -1.527408
108 | vt 0.4309 0.3010
109 | vt 0.3534 0.7810
110 | vt 0.3496 0.2802
111 | vt 0.4288 0.2777
112 | vt 0.4731 0.7873
113 | vn 0.0000 0.0000 1.0000
114 | usemtl Material.001
115 | s off
116 | f 33/29/8 32/30/8 31/31/8
117 | f 31/31/8 29/32/8 33/29/8
118 | f 33/29/8 30/33/8 32/30/8
119 | o Grid.001
120 | v 0.354175 0.918392 -1.679939
121 | v 1.734390 0.916231 -1.679939
122 | v 1.731629 1.163418 -1.679939
123 | v 0.351575 1.172653 -1.679939
124 | vt 0.9191 0.2769
125 | vt 0.4576 0.0619
126 | vt 0.9201 0.0600
127 | vt 0.4567 0.2850
128 | vn 0.0000 0.0000 1.0000
129 | usemtl Material.001
130 | s off
131 | f 36/34/9 34/35/9 35/36/9
132 | f 36/34/9 37/37/9 34/35/9
133 | o Grid
134 | v -0.576517 0.930000 -1.529653
135 | v -0.801831 2.070000 -1.490361
136 | v -1.477699 0.930000 -1.372497
137 | v -1.477699 2.070000 -1.372497
138 | v -0.689174 1.500000 -1.510007
139 | v -1.027108 0.930000 -1.451075
140 | v -1.139765 2.070000 -1.431429
141 | v -1.477699 1.500000 -1.372497
142 | v -1.083436 1.500000 -1.441252
143 | vt 0.1334 0.5000
144 | vt 0.0000 0.0000
145 | vt 0.1525 0.0000
146 | vt 0.2668 0.5000
147 | vt 0.3049 0.0000
148 | vt 0.2287 1.0000
149 | vt 0.1143 1.0000
150 | vt 0.0000 0.5000
151 | vt 0.0000 1.0000
152 | vn 0.1718 0.0000 0.9851
153 | usemtl Material.001
154 | s off
155 | f 46/38/10 40/39/10 43/40/10
156 | f 42/41/10 43/40/10 38/42/10
157 | f 39/43/10 46/38/10 42/41/10
158 | f 44/44/10 45/45/10 46/38/10
159 | f 46/38/10 45/45/10 40/39/10
160 | f 42/41/10 46/38/10 43/40/10
161 | f 39/43/10 44/44/10 46/38/10
162 | f 44/44/10 41/46/10 45/45/10
163 |
--------------------------------------------------------------------------------
/assets/models/oculus-left-controller.mtl:
--------------------------------------------------------------------------------
1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
2 | # File Created: 21.04.2017 21:11:12
3 |
4 | newmtl wire_085028177
5 | Ns 32
6 | d 1
7 | Tr 0
8 | Tf 1 1 1
9 | illum 2
10 | Ka 0.3333 0.1098 0.6941
11 | Kd 0.3333 0.1098 0.6941
12 | Ks 0.3500 0.3500 0.3500
13 |
14 | newmtl stick
15 | Ns 10.0000
16 | Ni 1.5000
17 | d 1.0000
18 | Tr 0.0000
19 | Tf 1.0000 1.0000 1.0000
20 | illum 2
21 | Ka 0.5880 0.5880 0.5880
22 | Kd 0.5880 0.5880 0.5880
23 | Ks 0.0000 0.0000 0.0000
24 | Ke 0.0000 0.0000 0.0000
25 |
26 | newmtl body
27 | Ns 10.0000
28 | Ni 1.5000
29 | d 1.0000
30 | Tr 0.0000
31 | Tf 1.0000 1.0000 1.0000
32 | illum 2
33 | Ka 0.5880 0.5880 0.5880
34 | Kd 0.5880 0.5880 0.5880
35 | Ks 0.0000 0.0000 0.0000
36 | Ke 0.0000 0.0000 0.0000
37 |
38 | newmtl buttonTrigger
39 | Ns 10.0000
40 | Ni 1.5000
41 | d 1.0000
42 | Tr 0.0000
43 | Tf 1.0000 1.0000 1.0000
44 | illum 2
45 | Ka 0.5880 0.5880 0.5880
46 | Kd 0.5880 0.5880 0.5880
47 | Ks 0.0000 0.0000 0.0000
48 | Ke 0.0000 0.0000 0.0000
49 |
50 | newmtl buttonHand
51 | Ns 10.0000
52 | Ni 1.5000
53 | d 1.0000
54 | Tr 0.0000
55 | Tf 1.0000 1.0000 1.0000
56 | illum 2
57 | Ka 0.5880 0.5880 0.5880
58 | Kd 0.5880 0.5880 0.5880
59 | Ks 0.0000 0.0000 0.0000
60 | Ke 0.0000 0.0000 0.0000
61 |
62 | newmtl buttonMenu
63 | Ns 10.0000
64 | Ni 1.5000
65 | d 1.0000
66 | Tr 0.0000
67 | Tf 1.0000 1.0000 1.0000
68 | illum 2
69 | Ka 0.5880 0.5880 0.5880
70 | Kd 0.5880 0.5880 0.5880
71 | Ks 0.0000 0.0000 0.0000
72 | Ke 0.0000 0.0000 0.0000
73 |
74 | newmtl buttonX
75 | Ns 10.0000
76 | Ni 1.5000
77 | d 1.0000
78 | Tr 0.0000
79 | Tf 1.0000 1.0000 1.0000
80 | illum 2
81 | Ka 0.5880 0.5880 0.5880
82 | Kd 0.5880 0.5880 0.5880
83 | Ks 0.0000 0.0000 0.0000
84 | Ke 0.0000 0.0000 0.0000
85 |
86 | newmtl buttonY
87 | Ns 10.0000
88 | Ni 1.5000
89 | d 1.0000
90 | Tr 0.0000
91 | Tf 1.0000 1.0000 1.0000
92 | illum 2
93 | Ka 0.5880 0.5880 0.5880
94 | Kd 0.5880 0.5880 0.5880
95 | Ks 0.0000 0.0000 0.0000
96 | Ke 0.0000 0.0000 0.0000
97 |
98 | newmtl mat_tip
99 | Ns 32.6421
100 | Ni 1.5000
101 | d 1.0000
102 | Tr 0.0000
103 | Tf 1.0000 1.0000 1.0000
104 | illum 2
105 | Ka 0.0000 0.0000 0.0000
106 | Kd 0.8000 0.0000 0.0026
107 | Ks 0.0000 0.0000 0.0000
108 | Ke 0.0000 0.0000 0.0000
109 |
--------------------------------------------------------------------------------
/assets/models/oculus-right-controller.mtl:
--------------------------------------------------------------------------------
1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
2 | # File Created: 21.04.2017 21:11:20
3 |
4 | newmtl buttonTrigger
5 | Ns 10.0000
6 | Ni 1.5000
7 | d 1.0000
8 | Tr 0.0000
9 | Tf 1.0000 1.0000 1.0000
10 | illum 2
11 | Ka 0.5880 0.5880 0.5880
12 | Kd 0.5880 0.5880 0.5880
13 | Ks 0.0000 0.0000 0.0000
14 | Ke 0.0000 0.0000 0.0000
15 |
16 | newmtl buttonHand
17 | Ns 10.0000
18 | Ni 1.5000
19 | d 1.0000
20 | Tr 0.0000
21 | Tf 1.0000 1.0000 1.0000
22 | illum 2
23 | Ka 0.5880 0.5880 0.5880
24 | Kd 0.5880 0.5880 0.5880
25 | Ks 0.0000 0.0000 0.0000
26 | Ke 0.0000 0.0000 0.0000
27 |
28 | newmtl stick
29 | Ns 10.0000
30 | Ni 1.5000
31 | d 1.0000
32 | Tr 0.0000
33 | Tf 1.0000 1.0000 1.0000
34 | illum 2
35 | Ka 0.5880 0.5880 0.5880
36 | Kd 0.5880 0.5880 0.5880
37 | Ks 0.0000 0.0000 0.0000
38 | Ke 0.0000 0.0000 0.0000
39 |
40 | newmtl buttonB
41 | Ns 10.0000
42 | Ni 1.5000
43 | d 1.0000
44 | Tr 0.0000
45 | Tf 1.0000 1.0000 1.0000
46 | illum 2
47 | Ka 0.5880 0.5880 0.5880
48 | Kd 0.5880 0.5880 0.5880
49 | Ks 0.0000 0.0000 0.0000
50 | Ke 0.0000 0.0000 0.0000
51 |
52 | newmtl buttonA
53 | Ns 10.0000
54 | Ni 1.5000
55 | d 1.0000
56 | Tr 0.0000
57 | Tf 1.0000 1.0000 1.0000
58 | illum 2
59 | Ka 0.5880 0.5880 0.5880
60 | Kd 0.5880 0.5880 0.5880
61 | Ks 0.0000 0.0000 0.0000
62 | Ke 0.0000 0.0000 0.0000
63 |
64 | newmtl buttonHome
65 | Ns 10.0000
66 | Ni 1.5000
67 | d 1.0000
68 | Tr 0.0000
69 | Tf 1.0000 1.0000 1.0000
70 | illum 2
71 | Ka 0.5880 0.5880 0.5880
72 | Kd 0.5880 0.5880 0.5880
73 | Ks 0.0000 0.0000 0.0000
74 | Ke 0.0000 0.0000 0.0000
75 |
76 | newmtl body
77 | Ns 10.0000
78 | Ni 1.5000
79 | d 1.0000
80 | Tr 0.0000
81 | Tf 1.0000 1.0000 1.0000
82 | illum 2
83 | Ka 0.5880 0.5880 0.5880
84 | Kd 0.5880 0.5880 0.5880
85 | Ks 0.0000 0.0000 0.0000
86 | Ke 0.0000 0.0000 0.0000
87 |
88 | newmtl wire_085028177
89 | Ns 32
90 | d 1
91 | Tr 0
92 | Tf 1 1 1
93 | illum 2
94 | Ka 0.3333 0.1098 0.6941
95 | Kd 0.3333 0.1098 0.6941
96 | Ks 0.3500 0.3500 0.3500
97 |
98 | newmtl mat_tip
99 | Ns 32.6421
100 | Ni 1.5000
101 | d 1.0000
102 | Tr 0.0000
103 | Tf 1.0000 1.0000 1.0000
104 | illum 2
105 | Ka 0.0000 0.0000 0.0000
106 | Kd 0.8000 0.0000 0.0026
107 | Ks 0.0000 0.0000 0.0000
108 | Ke 0.0000 0.0000 0.0000
109 |
--------------------------------------------------------------------------------
/assets/models/teleportHitEntity.obj:
--------------------------------------------------------------------------------
1 | # Blender v2.77 (sub 0) OBJ File: ''
2 | # www.blender.org
3 | o Circle
4 | v 0.088694 0.009423 -0.088695
5 | v 0.083151 0.027662 -0.124444
6 | v 0.069687 0.009423 -0.104294
7 | v -0.029198 0.027662 0.146791
8 | v -0.000000 0.009423 0.125433
9 | v -0.024471 0.009423 0.123023
10 | v -0.048001 0.009423 -0.115885
11 | v -0.083151 0.027662 -0.124444
12 | v -0.069687 0.009423 -0.104294
13 | v 0.057275 0.027662 -0.138275
14 | v 0.048001 0.009423 -0.115885
15 | v 0.000000 0.027662 0.149667
16 | v 0.024471 0.009423 0.123023
17 | v -0.088695 0.009423 -0.088695
18 | v 0.024471 0.009423 -0.123023
19 | v 0.029199 0.027662 0.146791
20 | v 0.048001 0.009423 0.115885
21 | v -0.105831 0.027662 -0.105831
22 | v -0.104294 0.009423 -0.069687
23 | v 0.029198 0.027662 -0.146791
24 | v -0.000000 0.009423 -0.125433
25 | v 0.083151 0.027662 0.124444
26 | v 0.069687 0.009423 0.104294
27 | v -0.138274 0.027662 -0.057275
28 | v -0.115885 0.009423 -0.048001
29 | v 0.105831 0.027662 0.105830
30 | v 0.088695 0.009423 0.088694
31 | v -0.123023 0.009423 -0.024471
32 | v 0.104294 0.009423 0.069687
33 | v -0.146791 0.027662 -0.029198
34 | v -0.125433 0.009423 -0.000000
35 | v 0.138274 0.027662 0.057275
36 | v 0.115885 0.009423 0.048001
37 | v -0.149667 0.027662 0.000000
38 | v -0.123023 0.009423 0.024471
39 | v 0.123023 0.009423 0.024471
40 | v -0.146791 0.027662 0.029198
41 | v -0.115885 0.009423 0.048001
42 | v 0.146791 0.027662 0.029198
43 | v 0.125433 0.009423 -0.000000
44 | v -0.138274 0.027662 0.057275
45 | v -0.104294 0.009423 0.069687
46 | v 0.146791 0.027662 -0.029199
47 | v 0.123023 0.009423 -0.024471
48 | v -0.124444 0.027662 0.083151
49 | v -0.088695 0.009423 0.088695
50 | v 0.115885 0.009423 -0.048001
51 | v -0.105831 0.027662 0.105831
52 | v -0.069687 0.009423 0.104294
53 | v 0.138274 0.027662 -0.057275
54 | v 0.104294 0.009423 -0.069687
55 | v -0.057275 0.027662 0.138274
56 | v -0.048001 0.009423 0.115885
57 | v -0.029198 0.027662 -0.146791
58 | v -0.024471 0.009423 -0.123023
59 | v 0.124444 0.027662 -0.083151
60 | v -0.057275 0.027662 -0.138274
61 | v 0.138581 0.009559 -0.138581
62 | v 0.103588 0.009559 -0.155031
63 | v 0.131842 0.009559 -0.131843
64 | v -0.038234 0.009559 0.192217
65 | v 0.000000 0.009559 0.186454
66 | v -0.036375 0.009559 0.182871
67 | v -0.074999 0.009559 -0.181064
68 | v -0.103588 0.009559 -0.155030
69 | v -0.071353 0.009559 -0.172261
70 | v 0.074999 0.009559 -0.181064
71 | v 0.071352 0.009559 -0.172261
72 | v 0.000000 0.009559 0.195982
73 | v 0.036375 0.009559 0.182871
74 | v -0.138581 0.009559 -0.138581
75 | v -0.131842 0.009559 -0.131843
76 | v 0.036375 0.009559 -0.182871
77 | v 0.038235 0.009559 0.192217
78 | v 0.071353 0.009559 0.172261
79 | v -0.155030 0.009559 -0.103588
80 | v 0.038234 0.009559 -0.192217
81 | v 0.000000 0.009559 -0.186454
82 | v 0.074999 0.009559 0.181064
83 | v 0.103588 0.009559 0.155030
84 | v -0.162953 0.009559 -0.108882
85 | v -0.172261 0.009559 -0.071353
86 | v 0.108882 0.009559 0.162953
87 | v 0.131842 0.009559 0.131842
88 | v -0.181064 0.009559 -0.074999
89 | v -0.182871 0.009559 -0.036375
90 | v 0.162953 0.009559 0.108882
91 | v 0.155030 0.009559 0.103588
92 | v -0.192217 0.009559 -0.038234
93 | v -0.186454 0.009559 0.000000
94 | v 0.181064 0.009559 0.074999
95 | v 0.172261 0.009559 0.071353
96 | v -0.195982 0.009559 0.000000
97 | v -0.182871 0.009559 0.036375
98 | v 0.182871 0.009559 0.036375
99 | v -0.192217 0.009559 0.038234
100 | v -0.172261 0.009559 0.071353
101 | v 0.192217 0.009559 0.038234
102 | v 0.186454 0.009559 0.000000
103 | v -0.162953 0.009559 0.108882
104 | v -0.155030 0.009559 0.103588
105 | v 0.192217 0.009559 -0.038235
106 | v 0.182871 0.009559 -0.036375
107 | v -0.138581 0.009559 0.138581
108 | v -0.131842 0.009559 0.131842
109 | v 0.181064 0.009559 -0.074999
110 | v 0.172261 0.009559 -0.071353
111 | v -0.108882 0.009559 0.162953
112 | v -0.103588 0.009559 0.155030
113 | v 0.162953 0.009559 -0.108882
114 | v 0.155030 0.009559 -0.103588
115 | v -0.074999 0.009559 0.181064
116 | v -0.071353 0.009559 0.172261
117 | v -0.038234 0.009559 -0.192217
118 | v -0.036375 0.009559 -0.182871
119 | v 0.105830 0.027662 -0.105831
120 | v 0.057275 0.027662 0.138274
121 | v -0.124444 0.027662 -0.083151
122 | v 0.000000 0.027662 -0.149667
123 | v 0.124444 0.027662 0.083151
124 | v 0.149667 0.027662 0.000000
125 | v -0.083151 0.027662 0.124444
126 | v 0.108882 0.009559 -0.162953
127 | v -0.108882 0.009559 -0.162953
128 | v 0.000000 0.009559 -0.195982
129 | v 0.138581 0.009559 0.138581
130 | v -0.181064 0.009559 0.074999
131 | v 0.195982 0.009559 0.000000
132 | s off
133 | f 1 2 3
134 | f 4 5 6
135 | f 7 8 9
136 | f 3 10 11
137 | f 12 13 5
138 | f 8 14 9
139 | f 10 15 11
140 | f 16 17 13
141 | f 18 19 14
142 | f 20 21 15
143 | f 17 22 23
144 | f 19 24 25
145 | f 23 26 27
146 | f 24 28 25
147 | f 26 29 27
148 | f 30 31 28
149 | f 29 32 33
150 | f 34 35 31
151 | f 32 36 33
152 | f 37 38 35
153 | f 39 40 36
154 | f 41 42 38
155 | f 40 43 44
156 | f 45 46 42
157 | f 43 47 44
158 | f 48 49 46
159 | f 50 51 47
160 | f 49 52 53
161 | f 21 54 55
162 | f 56 1 51
163 | f 52 6 53
164 | f 55 57 7
165 | f 58 59 60
166 | f 61 62 63
167 | f 64 65 66
168 | f 59 67 68
169 | f 69 70 62
170 | f 65 71 72
171 | f 67 73 68
172 | f 74 75 70
173 | f 71 76 72
174 | f 77 78 73
175 | f 79 80 75
176 | f 81 82 76
177 | f 83 84 80
178 | f 85 86 82
179 | f 84 87 88
180 | f 89 90 86
181 | f 88 91 92
182 | f 93 94 90
183 | f 91 95 92
184 | f 96 97 94
185 | f 98 99 95
186 | f 97 100 101
187 | f 99 102 103
188 | f 101 104 105
189 | f 103 106 107
190 | f 105 108 109
191 | f 107 110 111
192 | f 109 112 113
193 | f 78 114 115
194 | f 110 60 111
195 | f 112 63 113
196 | f 115 64 66
197 | f 1 116 2
198 | f 4 12 5
199 | f 7 57 8
200 | f 3 2 10
201 | f 12 16 13
202 | f 8 18 14
203 | f 10 20 15
204 | f 16 117 17
205 | f 18 118 19
206 | f 20 119 21
207 | f 17 117 22
208 | f 19 118 24
209 | f 23 22 26
210 | f 24 30 28
211 | f 26 120 29
212 | f 30 34 31
213 | f 29 120 32
214 | f 34 37 35
215 | f 32 39 36
216 | f 37 41 38
217 | f 39 121 40
218 | f 41 45 42
219 | f 40 121 43
220 | f 45 48 46
221 | f 43 50 47
222 | f 48 122 49
223 | f 50 56 51
224 | f 49 122 52
225 | f 21 119 54
226 | f 56 116 1
227 | f 52 4 6
228 | f 55 54 57
229 | f 58 123 59
230 | f 61 69 62
231 | f 64 124 65
232 | f 59 123 67
233 | f 69 74 70
234 | f 65 124 71
235 | f 67 77 73
236 | f 74 79 75
237 | f 71 81 76
238 | f 77 125 78
239 | f 79 83 80
240 | f 81 85 82
241 | f 83 126 84
242 | f 85 89 86
243 | f 84 126 87
244 | f 89 93 90
245 | f 88 87 91
246 | f 93 96 94
247 | f 91 98 95
248 | f 96 127 97
249 | f 98 128 99
250 | f 97 127 100
251 | f 99 128 102
252 | f 101 100 104
253 | f 103 102 106
254 | f 105 104 108
255 | f 107 106 110
256 | f 109 108 112
257 | f 78 125 114
258 | f 110 58 60
259 | f 112 61 63
260 | f 115 114 64
261 |
--------------------------------------------------------------------------------
/assets/models/vive-controller.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/models/vive-controller.glb
--------------------------------------------------------------------------------
/assets/models/vive-controller.mtl:
--------------------------------------------------------------------------------
1 | # 3ds Max Wavefront OBJ Exporter v0.97b - (c)2007 guruware
2 | # File Created: 21.04.2017 19:32:09
3 |
4 | newmtl mat_sizehint
5 | Ns 32.6421
6 | Ni 1.5000
7 | d 1.0000
8 | Tr 0.0000
9 | Tf 1.0000 1.0000 1.0000
10 | illum 2
11 | Ka 0.0000 0.0000 0.0000
12 | Kd 0.8000 0.0013 0.0000
13 | Ks 0.2500 0.2500 0.2500
14 | Ke 0.0000 0.0000 0.0000
15 |
16 | newmtl mat_body
17 | Ns 32.6421
18 | Ni 1.5000
19 | d 1.0000
20 | Tr 0.0000
21 | Tf 1.0000 1.0000 1.0000
22 | illum 2
23 | Ka 0.0000 0.0000 0.0000
24 | Kd 0.3757 0.3757 0.3757
25 | Ks 0.0885 0.0885 0.0885
26 | Ke 0.0000 0.0000 0.0000
27 |
28 | newmtl mat_tip
29 | Ns 32.6421
30 | Ni 1.5000
31 | d 1.0000
32 | Tr 0.0000
33 | Tf 1.0000 1.0000 1.0000
34 | illum 2
35 | Ka 0.0000 0.0000 0.0000
36 | Kd 0.8000 0.0000 0.0026
37 | Ks 0.0000 0.0000 0.0000
38 | Ke 0.0000 0.0000 0.0000
39 |
--------------------------------------------------------------------------------
/assets/sounds/ui_click0.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_click0.ogg
--------------------------------------------------------------------------------
/assets/sounds/ui_click1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_click1.ogg
--------------------------------------------------------------------------------
/assets/sounds/ui_menu.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_menu.ogg
--------------------------------------------------------------------------------
/assets/sounds/ui_paint.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_paint.ogg
--------------------------------------------------------------------------------
/assets/sounds/ui_tick.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_tick.ogg
--------------------------------------------------------------------------------
/assets/sounds/ui_tick0.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_tick0.ogg
--------------------------------------------------------------------------------
/assets/sounds/ui_undo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/assets/sounds/ui_undo.ogg
--------------------------------------------------------------------------------
/brushes/line_gradient.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/line_gradient.png
--------------------------------------------------------------------------------
/brushes/line_grunge1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/line_grunge1.png
--------------------------------------------------------------------------------
/brushes/line_grunge2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/line_grunge2.png
--------------------------------------------------------------------------------
/brushes/line_grunge3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/line_grunge3.png
--------------------------------------------------------------------------------
/brushes/lines1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/lines1.png
--------------------------------------------------------------------------------
/brushes/lines2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/lines2.png
--------------------------------------------------------------------------------
/brushes/lines3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/lines3.png
--------------------------------------------------------------------------------
/brushes/lines4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/lines4.png
--------------------------------------------------------------------------------
/brushes/lines5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/lines5.png
--------------------------------------------------------------------------------
/brushes/silky_flat.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/silky_flat.png
--------------------------------------------------------------------------------
/brushes/silky_textured.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/silky_textured.png
--------------------------------------------------------------------------------
/brushes/squared_textured.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/squared_textured.png
--------------------------------------------------------------------------------
/brushes/stamp_bush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_bush.png
--------------------------------------------------------------------------------
/brushes/stamp_column.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_column.png
--------------------------------------------------------------------------------
/brushes/stamp_dots.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_dots.png
--------------------------------------------------------------------------------
/brushes/stamp_fur1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_fur1.png
--------------------------------------------------------------------------------
/brushes/stamp_fur2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_fur2.png
--------------------------------------------------------------------------------
/brushes/stamp_gear.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_gear.png
--------------------------------------------------------------------------------
/brushes/stamp_grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_grass.png
--------------------------------------------------------------------------------
/brushes/stamp_grunge1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_grunge1.png
--------------------------------------------------------------------------------
/brushes/stamp_grunge2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_grunge2.png
--------------------------------------------------------------------------------
/brushes/stamp_grunge3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_grunge3.png
--------------------------------------------------------------------------------
/brushes/stamp_grunge4.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_grunge4.png
--------------------------------------------------------------------------------
/brushes/stamp_grunge5.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_grunge5.png
--------------------------------------------------------------------------------
/brushes/stamp_leaf1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_leaf1.png
--------------------------------------------------------------------------------
/brushes/stamp_leaf2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_leaf2.png
--------------------------------------------------------------------------------
/brushes/stamp_leaf3.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_leaf3.png
--------------------------------------------------------------------------------
/brushes/stamp_snow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_snow.png
--------------------------------------------------------------------------------
/brushes/stamp_squares.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_squares.png
--------------------------------------------------------------------------------
/brushes/stamp_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/stamp_star.png
--------------------------------------------------------------------------------
/brushes/thumb_cubes.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_cubes.gif
--------------------------------------------------------------------------------
/brushes/thumb_flat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_flat.gif
--------------------------------------------------------------------------------
/brushes/thumb_line_gradient.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_line_gradient.gif
--------------------------------------------------------------------------------
/brushes/thumb_line_grunge1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_line_grunge1.gif
--------------------------------------------------------------------------------
/brushes/thumb_line_grunge2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_line_grunge2.gif
--------------------------------------------------------------------------------
/brushes/thumb_line_grunge3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_line_grunge3.gif
--------------------------------------------------------------------------------
/brushes/thumb_lines1.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_lines1.gif
--------------------------------------------------------------------------------
/brushes/thumb_lines2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_lines2.gif
--------------------------------------------------------------------------------
/brushes/thumb_lines3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_lines3.gif
--------------------------------------------------------------------------------
/brushes/thumb_lines4.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_lines4.gif
--------------------------------------------------------------------------------
/brushes/thumb_lines5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_lines5.gif
--------------------------------------------------------------------------------
/brushes/thumb_rainbow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_rainbow.png
--------------------------------------------------------------------------------
/brushes/thumb_silky_flat.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_silky_flat.gif
--------------------------------------------------------------------------------
/brushes/thumb_silky_textured.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_silky_textured.gif
--------------------------------------------------------------------------------
/brushes/thumb_single_sphere.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_single_sphere.png
--------------------------------------------------------------------------------
/brushes/thumb_smooth.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_smooth.gif
--------------------------------------------------------------------------------
/brushes/thumb_spheres.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_spheres.gif
--------------------------------------------------------------------------------
/brushes/thumb_squared_textured.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_squared_textured.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_bush.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_bush.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_column.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_column.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_dots.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_dots.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_fur1.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_fur1.png
--------------------------------------------------------------------------------
/brushes/thumb_stamp_fur2.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_fur2.png
--------------------------------------------------------------------------------
/brushes/thumb_stamp_gear.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_gear.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_grass.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_grass.png
--------------------------------------------------------------------------------
/brushes/thumb_stamp_grunge5.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_grunge5.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_leaf2.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_leaf2.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_leaf3.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_leaf3.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_snow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_snow.png
--------------------------------------------------------------------------------
/brushes/thumb_stamp_squares.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_squares.gif
--------------------------------------------------------------------------------
/brushes/thumb_stamp_star.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/brushes/thumb_stamp_star.png
--------------------------------------------------------------------------------
/css/main.css:
--------------------------------------------------------------------------------
1 | html, body {
2 | background-color:#030404;
3 | }
4 |
5 | #apainter-ui{
6 | font-family: Helvetica, Arial, sans-serif;
7 | position: absolute;
8 | left: 20px;
9 | bottom: 20px;
10 | line-height: 1.2em;
11 | background-color: rgba(10,10,10,0.8);
12 | border-radius: 6px;
13 | padding: 1.7em 2em;
14 | display: none;
15 | }
16 |
17 | #apainter-ui .help{
18 | width: 600px;
19 | color: #ccc;
20 | font-size: 14px;
21 | text-shadow: 0px 2px 1px #000;
22 | }
23 |
24 | #apainter-ui .help h1{
25 | font-size: 22px;
26 | font-weight: 100;
27 | color: #e42b5a;
28 | margin-top: 0;
29 | }
30 |
31 | #apainter-ui .button {
32 | background-color: #ef2d5e;
33 | color: #fff;
34 | cursor: pointer;
35 | font-size: 12px;
36 | width: 130px;
37 | text-align: center;
38 | max-width: 115px;
39 | padding: 6px;
40 | }
41 |
42 | #apainter-ui .button:hover {
43 | background-color: #f43b6a;
44 | }
45 |
46 | #apainter-ui .form {
47 | display: flex;
48 | align-content: center;
49 | }
50 |
51 | #apainter-ui .share input {
52 | width: 600px;
53 | color: #333;
54 | text-align: center;
55 | }
56 |
57 | #apainter-ui .hide {
58 | display: none !important;
59 | }
60 |
61 | #apainter-ui .progress{
62 | font-size: 22px;
63 | font-weight: 100;
64 | color: #e42b5a;
65 | border-bottom: 1px solid #e42b5a;
66 | text-shadow: 0px 2px 1px #000;
67 | }
68 |
69 | #apainter-ui .bar{
70 | width: 0%;
71 | height: 3px;
72 | margin-top: 13px;
73 | background-color: #e42b5a;
74 | }
75 |
76 | #apainter-logo {
77 | position: fixed;
78 | bottom: 15px;
79 | left: 15px;
80 | height: 54px;
81 | width: 226px; /* 252px; for logo with author */
82 | background-image: url(../assets/images/apainter-banner.png);
83 | }
84 |
85 | #apainter-logo.hidden, #apainter-author.hidden{
86 | display: none;
87 | }
88 |
89 | #apainter-author {
90 | position: fixed;
91 | bottom: 28px;
92 | left: 268px;
93 | display: inline-block;
94 | font: 18px arial, sans-serif;
95 | color: #e42b5a;
96 | }
97 |
--------------------------------------------------------------------------------
/demo.apa:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/demo.apa
--------------------------------------------------------------------------------
/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/favicon-16x16.png
--------------------------------------------------------------------------------
/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/favicon-32x32.png
--------------------------------------------------------------------------------
/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/favicon.ico
--------------------------------------------------------------------------------
/img/icon-128.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon-128.png
--------------------------------------------------------------------------------
/img/icon-256.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon-256.png
--------------------------------------------------------------------------------
/img/icon-32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon-32.png
--------------------------------------------------------------------------------
/img/icon-512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon-512.png
--------------------------------------------------------------------------------
/img/icon-64.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon-64.png
--------------------------------------------------------------------------------
/img/icon-original.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon-original.png
--------------------------------------------------------------------------------
/img/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon.png
--------------------------------------------------------------------------------
/img/icon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/img/icon.psd
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | A-Painter
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
39 |
40 |
41 |
42 |
44 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
61 |
62 |
65 |
66 |
69 |
70 |
73 |
74 |
77 |
78 |
79 |
80 |
82 |
83 |
85 |
86 |
88 |
89 |
91 |
92 |
94 |
95 |
96 |
97 |
100 |
101 |
104 |
105 |
108 |
109 |
112 |
113 |
114 |
115 |
118 |
119 |
122 |
123 |
126 |
127 |
130 |
131 |
132 |
133 |
138 |
139 |
142 |
143 |
146 |
147 |
150 |
151 |
154 |
155 |
156 |
157 |
159 |
160 |
162 |
163 |
165 |
166 |
168 |
169 |
171 |
172 |
173 |
174 |
177 |
178 |
181 |
182 |
185 |
186 |
189 |
190 |
191 |
192 |
195 |
196 |
199 |
200 |
203 |
204 |
207 |
208 |
209 |
210 |
211 |
212 | authorname
213 |
214 |
215 | Saving painting...
216 |
217 |
218 |
219 |
220 |
Painting saved!
221 |
Your painting was saved in the following URL.
You can use it to continue your work later or to share your masterpiece in social media.
222 |
223 |
227 |
228 |
229 |
230 |
231 |
232 |
233 |
234 |
235 |
236 |
--------------------------------------------------------------------------------
/manifest.webmanifest:
--------------------------------------------------------------------------------
1 | {
2 | "lang": "en",
3 | "dir": "ltr",
4 | "name": "A-Painter",
5 | "short_name": "A-Painter",
6 | "description": "Paint in VR in your browser. Powered by A-Frame.",
7 | "start_url": "./?src=manifest",
8 | "scope": "./",
9 | "display": "fullscreen",
10 | "theme_color": "#0a0a0a",
11 | "background_color": "#030404",
12 | "icons": [
13 | {
14 | "src": "img/icon-512.png",
15 | "sizes": "512x512",
16 | "type": "image/png"
17 | },
18 | {
19 | "src": "img/icon-256.png",
20 | "sizes": "256x256",
21 | "type": "image/png"
22 | },
23 | {
24 | "src": "img/icon-128.png",
25 | "sizes": "128x128",
26 | "type": "image/png"
27 | },
28 | {
29 | "src": "img/icon-64.png",
30 | "sizes": "64x64",
31 | "type": "image/png"
32 | },
33 | {
34 | "src": "img/icon-32.png",
35 | "sizes": "32x32",
36 | "type": "image/png"
37 | },
38 | {
39 | "src": "favicon.ico",
40 | "sizes": "16x16 32x32 48x48 64x64 256x256 512x512",
41 | "type": "image/x-icon"
42 | }
43 | ],
44 | "about_url": "https://blog.mozvr.com/a-painter/",
45 | "vr_display_required": false,
46 | "vr_default_display": "HTC Vive",
47 | "vr_available_displays": [
48 | "HTC Vive",
49 | "Oculus Rift",
50 | "Google Daydream",
51 | "Samsung Gear VR",
52 | "Google Cardboard"
53 | ],
54 | "vr_gamepad_required": true,
55 | "vr_default_gamepad": "HTC Vive",
56 | "vr_supported_gamepads": [
57 | "HTC Vive",
58 | "Oculus Touch"
59 | ]
60 | }
61 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "a-painter",
3 | "version": "0.1.1",
4 | "description": "A-Painter",
5 | "scripts": {
6 | "atlas": "spritesheet-js --name brush_atlas --path assets/images brushes/*.png",
7 | "build": "webpack --mode=production",
8 | "build-dev": "webpack --mode=development",
9 | "start": "webpack-dev-server --host 0.0.0.0 --mode=development",
10 | "lint": "semistandard -v | snazzy"
11 | },
12 | "repository": "aframevr/a-painter",
13 | "license": "MIT",
14 | "semistandard": {
15 | "ignore": [
16 | "**/vendor/**",
17 | "build.js"
18 | ]
19 | },
20 | "devDependencies": {
21 | "semistandard": "^16.0.1",
22 | "snazzy": "^9.0.0",
23 | "spritesheet-js": "^1.2.6",
24 | "terser-webpack-plugin": "^5.3.1",
25 | "webpack": "^5.69.1",
26 | "webpack-cli": "^4.9.2",
27 | "webpack-dev-server": "^4.7.4"
28 | },
29 | "dependencies": {
30 | "aframe-tooltip-component": "^0.1.2"
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/paintings/dancer.apa:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/paintings/dancer.apa
--------------------------------------------------------------------------------
/sounds/ui_click0.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_click0.ogg
--------------------------------------------------------------------------------
/sounds/ui_click1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_click1.ogg
--------------------------------------------------------------------------------
/sounds/ui_menu.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_menu.ogg
--------------------------------------------------------------------------------
/sounds/ui_paint.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_paint.ogg
--------------------------------------------------------------------------------
/sounds/ui_tick.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_tick.ogg
--------------------------------------------------------------------------------
/sounds/ui_tick0.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_tick0.ogg
--------------------------------------------------------------------------------
/sounds/ui_undo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/aframevr/a-painter/0deba1f3314ee4cd053bf2a2ae2854994014dd65/sounds/ui_undo.ogg
--------------------------------------------------------------------------------
/src/atlas.js:
--------------------------------------------------------------------------------
1 | /* global THREE */
2 | const AtlasJSON = require('../assets/images/brush_atlas.json');
3 |
4 | function Atlas () {
5 | this.map = new THREE.TextureLoader().load('assets/images/' + AtlasJSON.meta.image);
6 | }
7 |
8 | Atlas.prototype = {
9 | getUVConverters: function (filename) {
10 | if (filename) {
11 | filename = filename.replace('brushes/', '');
12 | return {
13 | convertU: function (u) {
14 | var totalSize = AtlasJSON.meta.size;
15 | var data = AtlasJSON.frames[filename];
16 | if (u > 1 || u < 0) {
17 | u = 0;
18 | }
19 | return data.frame.x / totalSize.w + u * data.frame.w / totalSize.w;
20 | },
21 |
22 | convertV: function (v) {
23 | var totalSize = AtlasJSON.meta.size;
24 | var data = AtlasJSON.frames[filename];
25 | if (v > 1 || v < 0) {
26 | v = 0;
27 | }
28 |
29 | return 1 - (data.frame.y / totalSize.h + v * data.frame.h / totalSize.h);
30 | }
31 | };
32 | } else {
33 | return {
34 | convertU: function (u) { return u; },
35 | convertV: function (v) { return v; }
36 | };
37 | }
38 | }
39 | };
40 |
41 | window.atlas = new Atlas();
42 |
--------------------------------------------------------------------------------
/src/binarymanager.js:
--------------------------------------------------------------------------------
1 | /* globals THREE */
2 | window.BinaryManager = function (buffer) {
3 | this.dataview = new DataView(buffer);
4 | this.offset = 0;
5 | this.isLittleEndian = true;
6 | };
7 |
8 | window.BinaryManager.prototype = {
9 | // READER
10 | readQuaternion: function () {
11 | return new THREE.Quaternion(
12 | this.readFloat(),
13 | this.readFloat(),
14 | this.readFloat(),
15 | this.readFloat()
16 | );
17 | },
18 | readVector3: function () {
19 | return new THREE.Vector3(
20 | this.readFloat(),
21 | this.readFloat(),
22 | this.readFloat()
23 | );
24 | },
25 | readString: function () {
26 | var length = this.dataview.getUint8(this.offset++, true);
27 | var output = '';
28 | for (var i = 0; i < length; i++) {
29 | output += String.fromCharCode(this.dataview.getUint8(this.offset++, true));
30 | }
31 | return output;
32 | },
33 | readColor: function () {
34 | return new THREE.Color(
35 | this.readFloat(),
36 | this.readFloat(),
37 | this.readFloat()
38 | );
39 | },
40 | readFloat: function () {
41 | var output = this.dataview.getFloat32(this.offset, true);
42 | this.offset += 4;
43 | return output;
44 | },
45 | readUint32: function () {
46 | var output = this.dataview.getUint32(this.offset, true);
47 | this.offset += 4;
48 | return output;
49 | },
50 | readUint16: function () {
51 | var output = this.dataview.getUint16(this.offset, true);
52 | this.offset += 2;
53 | return output;
54 | },
55 | readUint8: function () {
56 | var output = this.dataview.getUint8(this.offset, true);
57 | this.offset++;
58 | return output;
59 | },
60 | // WRITER
61 | writeVector: function (value) {
62 | this.writeFloat32Array(value.toArray());
63 | },
64 | writeColor: function (value) {
65 | this.writeFloat32Array(value.toArray());
66 | },
67 | writeString: function (value) {
68 | this.writeUint8(value.length);
69 | for (var i = 0; i < value.length; i++) {
70 | this.writeUint8(value.charCodeAt(i));
71 | }
72 | },
73 | writeUint8: function (value) {
74 | this.dataview.setUint8(this.offset, value, this.isLittleEndian);
75 | this.offset ++;
76 | },
77 | writeUint16: function (value) {
78 | this.dataview.setUint16(this.offset, value, this.isLittleEndian);
79 | this.offset += 2;
80 | },
81 | writeUint32: function (value) {
82 | this.dataview.setUint32(this.offset, value, this.isLittleEndian);
83 | this.offset += 4;
84 | },
85 | writeFloat32: function (value) {
86 | this.dataview.setFloat32(this.offset, value, this.isLittleEndian);
87 | this.offset += 4;
88 | },
89 | writeFloat32Array: function (value) {
90 | for (var i = 0; i < value.length; i++) {
91 | this.writeFloat32(value[i]);
92 | }
93 | },
94 | getDataView: function () {
95 | return this.dataview;
96 | }
97 | };
98 |
--------------------------------------------------------------------------------
/src/brushes/cubes.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | AFRAME.registerBrush('cubes',
3 | {
4 | init: function (color, width) {
5 | this.material = new THREE.MeshStandardMaterial({
6 | color: this.data.color,
7 | roughness: 0.5,
8 | metalness: 0.5,
9 | side: THREE.DoubleSide,
10 | flatShading: true
11 | });
12 | this.geometry = new THREE.BoxGeometry(1, 1, 1);
13 | this.drawingEl = document.querySelector('.a-drawing');
14 | this.drawingEl.object3D.add(this.object3D);
15 | },
16 | addPoint: function (position, orientation, pointerPosition, pressure, timestamp) {
17 | var box = new THREE.Mesh(this.geometry, this.material);
18 |
19 | var sca = pressure * this.data.size * Math.random();
20 | box.scale.set(sca, sca, sca);
21 | box.position.copy(pointerPosition);
22 | box.quaternion.copy(orientation);
23 |
24 | this.object3D.add(box);
25 |
26 | return true;
27 | },
28 | undo: function () {
29 | this.drawingEl.object3D.children.pop();
30 | }
31 | },
32 | {thumbnail: 'brushes/thumb_cubes.gif', spacing: 0.01}
33 | );
34 |
--------------------------------------------------------------------------------
/src/brushes/line.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | var sharedBufferGeometryManager = require('../sharedbuffergeometrymanager.js');
3 | var onLoaded = require('../onloaded.js');
4 |
5 | (function () {
6 |
7 | var geometryManager = null;
8 |
9 | onLoaded(function () {
10 | var optionsBasic = {
11 | vertexColors: THREE.VertexColors,
12 | side: THREE.DoubleSide
13 | };
14 |
15 | var optionsStandard = {
16 | roughness: 0.75,
17 | metalness: 0.25,
18 | vertexColors: THREE.VertexColors,
19 | map: window.atlas.map,
20 | side: THREE.DoubleSide
21 | };
22 |
23 | var optionTextured = {
24 | roughness: 0.75,
25 | metalness: 0.25,
26 | vertexColors: THREE.VertexColors,
27 | side: THREE.DoubleSide,
28 | map: window.atlas.map,
29 | transparent: true,
30 | alphaTest: 0.5
31 | };
32 |
33 | sharedBufferGeometryManager.addSharedBuffer('strip-flat', new THREE.MeshBasicMaterial(optionsBasic));
34 | sharedBufferGeometryManager.addSharedBuffer('strip-shaded', new THREE.MeshStandardMaterial(optionsStandard));
35 | sharedBufferGeometryManager.addSharedBuffer('strip-textured', new THREE.MeshStandardMaterial(optionTextured));
36 | });
37 |
38 | var line = {
39 |
40 | init: function (color, brushSize) {
41 | this.sharedBuffer = sharedBufferGeometryManager.getSharedBuffer('strip-' + this.materialOptions.type);
42 | this.sharedBuffer.restartPrimitive();
43 | this.sharedBuffer.strip = true;
44 |
45 | this.prevIdx = Object.assign({}, this.sharedBuffer.idx);
46 | this.idx = Object.assign({}, this.sharedBuffer.idx);
47 |
48 | this.first = true;
49 | },
50 | remove: function () {
51 | this.sharedBuffer.remove(this.prevIdx, this.idx);
52 | },
53 | undo: function () {
54 | this.sharedBuffer.undo(this.prevIdx);
55 | },
56 | addPoint: (function () {
57 | var direction = new THREE.Vector3();
58 |
59 | return function (position, orientation, pointerPosition, pressure, timestamp) {
60 | var converter = this.materialOptions.converter;
61 |
62 | direction.set(1, 0, 0);
63 | direction.applyQuaternion(orientation);
64 | direction.normalize();
65 |
66 | var posA = pointerPosition.clone();
67 | var posB = pointerPosition.clone();
68 | var brushSize = this.data.size * pressure;
69 | posA.add(direction.clone().multiplyScalar(brushSize / 2));
70 | posB.add(direction.clone().multiplyScalar(-brushSize / 2));
71 |
72 | if (this.first && this.prevIdx.position > 0) {
73 | // Degenerated triangle
74 | this.first = false;
75 | this.sharedBuffer.addVertex(posA.x, posA.y, posA.z);
76 | this.sharedBuffer.idx.normal++;
77 | this.sharedBuffer.idx.color++;
78 | this.sharedBuffer.idx.uv++;
79 |
80 | this.idx = Object.assign({}, this.sharedBuffer.idx);
81 | }
82 |
83 | /*
84 | 2---3
85 | | \ |
86 | 0---1
87 | */
88 | this.sharedBuffer.addVertex(posA.x, posA.y, posA.z);
89 | this.sharedBuffer.addVertex(posB.x, posB.y, posB.z);
90 | this.sharedBuffer.idx.normal += 2;
91 |
92 | this.sharedBuffer.addColor(this.data.color.r, this.data.color.g, this.data.color.b);
93 | this.sharedBuffer.addColor(this.data.color.r, this.data.color.g, this.data.color.b);
94 |
95 | if (this.materialOptions.type === 'textured') {
96 | this.sharedBuffer.idx.uv += 2;
97 | var uvs = this.sharedBuffer.current.attributes.uv.array;
98 | var u, offset;
99 | for (var i = 0; i < this.data.numPoints + 1; i++) {
100 | u = i / this.data.numPoints;
101 | offset = 4 * i;
102 | if (this.prevIdx.uv !== 0) {
103 | offset += (this.prevIdx.uv + 1) * 2;
104 | }
105 |
106 | uvs[offset] = converter.convertU(u);
107 | uvs[offset + 1] = converter.convertV(0);
108 |
109 | uvs[offset + 2] = converter.convertU(u);
110 | uvs[offset + 3] = converter.convertV(1);
111 | }
112 | }
113 |
114 | this.idx = Object.assign({}, this.sharedBuffer.idx);
115 |
116 | this.sharedBuffer.update();
117 | this.computeVertexNormals();
118 | return true;
119 | };
120 | })(),
121 |
122 | computeVertexNormals: (function () {
123 | var pA = new THREE.Vector3();
124 | var pB = new THREE.Vector3();
125 | var pC = new THREE.Vector3();
126 | var cb = new THREE.Vector3();
127 | var ab = new THREE.Vector3();
128 |
129 | return function () {
130 | var start = this.prevIdx.position === 0 ? 0 : (this.prevIdx.position + 1) * 3;
131 | var end = (this.idx.position) * 3;
132 | var vertices = this.sharedBuffer.current.attributes.position.array;
133 | var normals = this.sharedBuffer.current.attributes.normal.array;
134 |
135 | for (var i = start; i <= end; i++) {
136 | normals[i] = 0;
137 | }
138 |
139 | var pair = true;
140 | for (i = start; i < end - 6; i += 3) {
141 | if (pair) {
142 | pA.fromArray(vertices, i);
143 | pB.fromArray(vertices, i + 3);
144 | pC.fromArray(vertices, i + 6);
145 | } else {
146 | pB.fromArray(vertices, i);
147 | pC.fromArray(vertices, i + 6);
148 | pA.fromArray(vertices, i + 3);
149 | }
150 | pair = !pair;
151 |
152 | cb.subVectors(pC, pB);
153 | ab.subVectors(pA, pB);
154 | cb.cross(ab);
155 | cb.normalize();
156 |
157 | normals[i] += cb.x;
158 | normals[i + 1] += cb.y;
159 | normals[i + 2] += cb.z;
160 |
161 | normals[i + 3] += cb.x;
162 | normals[i + 4] += cb.y;
163 | normals[i + 5] += cb.z;
164 |
165 | normals[i + 6] += cb.x;
166 | normals[i + 7] += cb.y;
167 | normals[i + 8] += cb.z;
168 | }
169 |
170 | /*
171 | first and last vertices (0 and 8) belongs just to one triangle
172 | second and penultimate (1 and 7) belongs to two triangles
173 | the rest of the vertices belongs to three triangles
174 |
175 | 1_____3_____5_____7
176 | /\ /\ /\ /\
177 | / \ / \ / \ / \
178 | /____\/____\/____\/____\
179 | 0 2 4 6 8
180 | */
181 |
182 | // Vertices that are shared across three triangles
183 | for (i = start + 2 * 3; i < end - 2 * 3; i++) {
184 | normals[i] = normals[i] / 3;
185 | }
186 |
187 | // Second and penultimate triangle, that shares just two triangles
188 | normals[start + 3] = normals[start + 3] / 2;
189 | normals[start + 3 + 1] = normals[start + 3 + 1] / 2;
190 | normals[start + 3 + 2] = normals[start + 3 * 1 + 2] / 2;
191 |
192 | normals[end - 2 * 3] = normals[end - 2 * 3] / 2;
193 | normals[end - 2 * 3 + 1] = normals[end - 2 * 3 + 1] / 2;
194 | normals[end - 2 * 3 + 2] = normals[end - 2 * 3 + 2] / 2;
195 | };
196 | })()
197 | };
198 |
199 | var lines = [
200 | {
201 | name: 'flat',
202 | materialOptions: {
203 | type: 'flat'
204 | },
205 | thumbnail: 'brushes/thumb_flat.gif'
206 | },
207 | {
208 | name: 'smooth',
209 | materialOptions: {
210 | type: 'shaded'
211 | },
212 | thumbnail: 'brushes/thumb_smooth.gif'
213 | },
214 | {
215 | name: 'squared-textured',
216 | materialOptions: {
217 | type: 'textured',
218 | textureSrc: 'brushes/squared_textured.png'
219 | },
220 | thumbnail: 'brushes/thumb_squared_textured.gif'
221 | },
222 | {
223 | name: 'line-gradient',
224 | materialOptions: {
225 | type: 'textured',
226 | textureSrc: 'brushes/line_gradient.png'
227 | },
228 | thumbnail: 'brushes/thumb_line_gradient.gif'
229 | },
230 | {
231 | name: 'silky-flat',
232 | materialOptions: {
233 | type: 'textured',
234 | textureSrc: 'brushes/silky_flat.png'
235 | },
236 | thumbnail: 'brushes/thumb_silky_flat.gif'
237 | },
238 | {
239 | name: 'silky-textured',
240 | materialOptions: {
241 | type: 'textured',
242 | textureSrc: 'brushes/silky_textured.png'
243 | },
244 | thumbnail: 'brushes/thumb_silky_textured.gif'
245 | },
246 | {
247 | name: 'lines1',
248 | materialOptions: {
249 | type: 'textured',
250 | textureSrc: 'brushes/lines1.png'
251 | },
252 | thumbnail: 'brushes/thumb_lines1.gif'
253 | },
254 | {
255 | name: 'lines2',
256 | materialOptions: {
257 | type: 'textured',
258 | textureSrc: 'brushes/lines2.png'
259 | },
260 | thumbnail: 'brushes/thumb_lines2.gif'
261 | },
262 | {
263 | name: 'lines3',
264 | materialOptions: {
265 | type: 'textured',
266 | textureSrc: 'brushes/lines3.png'
267 | },
268 | thumbnail: 'brushes/thumb_lines3.gif'
269 | },
270 | {
271 | name: 'lines4',
272 | materialOptions: {
273 | type: 'textured',
274 | textureSrc: 'brushes/lines4.png'
275 | },
276 | thumbnail: 'brushes/thumb_lines4.gif'
277 | },
278 | {
279 | name: 'lines5',
280 | materialOptions: {
281 | type: 'textured',
282 | textureSrc: 'brushes/lines5.png'
283 | },
284 | thumbnail: 'brushes/thumb_lines5.gif'
285 | },
286 | {
287 | name: 'line-grunge1',
288 | materialOptions: {
289 | type: 'textured',
290 | textureSrc: 'brushes/line_grunge1.png'
291 | },
292 | thumbnail: 'brushes/thumb_line_grunge1.gif'
293 | },
294 | {
295 | name: 'line-grunge2',
296 | materialOptions: {
297 | type: 'textured',
298 | textureSrc: 'brushes/line_grunge2.png'
299 | },
300 | thumbnail: 'brushes/thumb_line_grunge2.gif'
301 | },
302 | {
303 | name: 'line-grunge3',
304 | materialOptions: {
305 | type: 'textured',
306 | textureSrc: 'brushes/line_grunge3.png'
307 | },
308 | thumbnail: 'brushes/thumb_line_grunge3.gif'
309 | }
310 | ];
311 |
312 | for (var i = 0; i < lines.length; i++) {
313 | var definition = lines[i];
314 | if (definition.materialOptions.textureSrc) {
315 | definition.materialOptions.converter = window.atlas.getUVConverters(definition.materialOptions.textureSrc);
316 | } else {
317 | definition.materialOptions.converter = window.atlas.getUVConverters(null);
318 | }
319 |
320 | AFRAME.registerBrush(definition.name, Object.assign({}, line, {materialOptions: definition.materialOptions}), {thumbnail: definition.thumbnail, maxPoints: 3000});
321 | }
322 | })();
323 |
--------------------------------------------------------------------------------
/src/brushes/rainbow.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | (function(){
3 | var vertexShader = "varying vec2 vUv; \
4 | void main() { \
5 | vUv = uv; \
6 | gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 ); \
7 | }";
8 |
9 | var fragmentShader = "uniform sampler2D tDiffuse; \
10 | uniform float amount; \
11 | uniform float time; \
12 | varying vec2 vUv; \
13 | \
14 | vec3 hsv2rgb(vec3 c) { \
15 | vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); \
16 | vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); \
17 | return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); \
18 | } \
19 | \
20 | void main() { \
21 | float h = mod(vUv.x - time / 3000.0, 1.0); \
22 | vec4 color = vec4(hsv2rgb(vec3(h, 1.0, 0.5)), 1.0); \
23 | gl_FragColor = color; \
24 | }";
25 |
26 | var material = new THREE.ShaderMaterial({
27 | vertexShader: vertexShader,
28 | fragmentShader: fragmentShader,
29 | side: THREE.DoubleSide,
30 | uniforms: {
31 | time: {type: 'f', value: 0}
32 | }
33 | });
34 |
35 | AFRAME.registerBrush('line-rainbow',
36 | {
37 | init: function (color, brushSize) {
38 | this.idx = 0;
39 | this.geometry = new THREE.BufferGeometry();
40 | this.vertices = new Float32Array(this.options.maxPoints * 3 * 3);
41 | this.indices = new Uint32Array(this.options.maxPoints * 4.5 * 4.5)
42 | this.uvs = new Float32Array(this.options.maxPoints * 2 * 2);
43 | this.linepositions = new Float32Array(this.options.maxPoints);
44 |
45 | this.geometry.setDrawRange(0, 0);
46 | this.geometry.setAttribute('position', new THREE.BufferAttribute(this.vertices, 3).setUsage(THREE.DynamicDrawUsage));
47 | this.geometry.setIndex(new THREE.BufferAttribute(this.indices, 3).setUsage(THREE.DynamicDrawUsage));
48 | this.geometry.setAttribute('uv', new THREE.BufferAttribute(this.uvs, 2).setUsage(THREE.DynamicDrawUsage));
49 | this.geometry.setAttribute('lineposition', new THREE.BufferAttribute(this.linepositions, 1).setUsage(THREE.DynamicDrawUsage));
50 |
51 | this.material = material;
52 | var mesh = new THREE.Mesh(this.geometry, this.material);
53 |
54 | mesh.frustumCulled = false;
55 | mesh.vertices = this.vertices;
56 |
57 | this.object3D.add(mesh);
58 | this.drawing = document.querySelector('.a-drawing');
59 | this.drawing.object3D.add(this.object3D);
60 | },
61 | addPoint: (function () {
62 | var direction = new THREE.Vector3();
63 | var posA = new THREE.Vector3();
64 | var posB = new THREE.Vector3();
65 | var auxDir = new THREE.Vector3();
66 |
67 | return function (position, orientation, pointerPosition, pressure, timestamp) {
68 | var uv = 0;
69 | for (i = 0; i < this.data.numPoints; i++) {
70 | this.uvs[uv++] = i / (this.data.numPoints - 1);
71 | this.uvs[uv++] = 0;
72 |
73 | this.uvs[uv++] = i / (this.data.numPoints - 1);
74 | this.uvs[uv++] = 1;
75 | }
76 |
77 | direction.set(1, 0, 0);
78 | direction.applyQuaternion(orientation);
79 | direction.normalize();
80 |
81 | posA.copy(pointerPosition);
82 | posB.copy(pointerPosition);
83 |
84 | var brushSize = this.data.size * pressure;
85 | posA.add(auxDir.copy(direction).multiplyScalar(brushSize / 2));
86 | posB.add(auxDir.copy(direction).multiplyScalar(-brushSize / 2));
87 |
88 | this.vertices[this.idx++] = posA.x;
89 | this.vertices[this.idx++] = posA.y;
90 | this.vertices[this.idx++] = posA.z;
91 |
92 | this.vertices[this.idx++] = posB.x;
93 | this.vertices[this.idx++] = posB.y;
94 | this.vertices[this.idx++] = posB.z;
95 |
96 | // Same principle as line brush strips
97 | if (this.idx > 6) {
98 | this.geometry.index.setXYZ(this.idx / 3 - 4, this.idx / 3 - 4, this.idx / 3 - 3, this.idx / 3 - 2);
99 | this.geometry.index.setXYZ(this.idx / 3 - 3, this.idx / 3 - 2, this.idx / 3 - 3, this.idx / 3 - 1);
100 | }
101 |
102 | this.geometry.attributes.position.needsUpdate = true;
103 | this.geometry.index.needsUpdate = true;
104 | this.geometry.attributes.uv.needsUpdate = true;
105 |
106 | this.geometry.setDrawRange(0, this.data.numPoints * 2 * 6);
107 |
108 | return true;
109 | }
110 | })(),
111 | tick: function(timeOffset, delta) {
112 | this.material.uniforms.time.value = timeOffset;
113 | },
114 | undo: function () {
115 | this.drawing.object3D.children.pop();
116 | }
117 | },
118 | {thumbnail:'brushes/thumb_rainbow.png', maxPoints: 3000}
119 | );
120 | })();
121 |
--------------------------------------------------------------------------------
/src/brushes/single-sphere.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | AFRAME.registerBrush('single-sphere',
3 | {
4 | init: function (color, width) {
5 | this.material = new THREE.MeshStandardMaterial({
6 | color: this.data.color,
7 | roughness: 0.6,
8 | metalness: 0.2,
9 | side: THREE.FrontSide,
10 | flatShading: true
11 | });
12 | this.geometry = new THREE.IcosahedronGeometry(1, 2);
13 | this.mesh = new THREE.Mesh(this.geometry, this.material);
14 | this.object3D.add(this.mesh);
15 | this.mesh.visible = false;
16 | this.drawingEl = document.querySelector('.a-drawing');
17 | this.drawingEl.object3D.add(this.object3D);
18 | },
19 | addPoint: function (position, orientation, pointerPosition, pressure, timestamp) {
20 | if (!this.firstPoint) {
21 | this.firstPoint = pointerPosition.clone();
22 | this.mesh.position.set(this.firstPoint.x, this.firstPoint.y, this.firstPoint.z)
23 | }
24 | this.mesh.visible = true
25 | var distance = this.firstPoint.distanceTo(pointerPosition);
26 | this.mesh.scale.set(distance, distance, distance);
27 | return true;
28 | },
29 | undo: function () {
30 | this.drawingEl.object3D.children.pop();
31 | }
32 | },
33 | {thumbnail: 'brushes/thumb_single_sphere.png', spacing: 0.0}
34 | );
35 |
--------------------------------------------------------------------------------
/src/brushes/spheres.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | AFRAME.registerBrush('spheres',
3 | {
4 | init: function (color, width) {
5 | // Initialize the material based on the stroke color
6 | this.material = new THREE.MeshStandardMaterial({
7 | color: this.data.color,
8 | roughness: 0.5,
9 | metalness: 0.5,
10 | side: THREE.DoubleSide,
11 | flatShading: true
12 | });
13 | this.geometry = new THREE.IcosahedronGeometry(1, 0);
14 | this.drawingEl = document.querySelector('.a-drawing');
15 | this.drawingEl.object3D.add(this.object3D);
16 | },
17 | // This function is called every time we need to add a point to our stroke
18 | // It should returns true if the point is added correctly, false otherwise.
19 | addPoint: function (position, orientation, pointerPosition, pressure, timestamp) {
20 | // Create a new sphere mesh to insert at the given position
21 | var sphere = new THREE.Mesh(this.geometry, this.material);
22 |
23 | // The scale is determined by the trigger preassure
24 | var sca = this.data.size / 2 * pressure;
25 | sphere.scale.set(sca, sca, sca);
26 | sphere.initialScale = sphere.scale.clone();
27 |
28 | // Generate a random phase to be used in the tick animation
29 | sphere.phase = Math.random() * Math.PI * 2;
30 |
31 | // Set the position of the sphere to match the controller positoin
32 | sphere.position.copy(pointerPosition);
33 | sphere.quaternion.copy(orientation);
34 |
35 | // Add the sphere to the object3D
36 | this.object3D.add(sphere);
37 |
38 | // Return true as we've added correctly a new point (or sphere)
39 | return true;
40 | },
41 | // This function is called on every frame
42 | tick: function (time, delta) {
43 | for (var i = 0; i < this.object3D.children.length; i++) {
44 | var sphere = this.object3D.children[i];
45 | // Calculate the sine value based on the time and the phase for this sphere
46 | // and use it to scale the geometry
47 | var sin = (Math.sin(sphere.phase + time / 500.0) + 1) / 2 + 0.1;
48 | sphere.scale.copy(sphere.initialScale).multiplyScalar(sin);
49 | }
50 | },
51 | undo: function () {
52 | this.drawingEl.object3D.children.pop();
53 | }
54 | },
55 | // Define extra options for this brush
56 | {thumbnail: 'brushes/thumb_spheres.gif', spacing: 0.01}
57 | );
58 |
--------------------------------------------------------------------------------
/src/brushes/stamp.js:
--------------------------------------------------------------------------------
1 | /* global AFRAME THREE */
2 | var sharedBufferGeometryManager = require('../sharedbuffergeometrymanager.js');
3 | var onLoaded = require('../onloaded.js');
4 |
5 | (function () {
6 |
7 | onLoaded(function () {
8 | var shaded = new THREE.MeshStandardMaterial({
9 | side: THREE.DoubleSide,
10 | map: window.atlas.map,
11 | vertexColors: THREE.VertexColors,
12 | transparent: true,
13 | alphaTest: 0.5,
14 | roughness: 0.75,
15 | metalness: 0.25
16 | });
17 | var flat = new THREE.MeshBasicMaterial({
18 | side: THREE.DoubleSide,
19 | map: window.atlas.map,
20 | vertexColors: THREE.VertexColors,
21 | transparent: true,
22 | alphaTest: 0.5
23 | });
24 |
25 | sharedBufferGeometryManager.addSharedBuffer('tris-flat', flat);
26 | sharedBufferGeometryManager.addSharedBuffer('tris-shaded', shaded);
27 | });
28 |
29 | var stamp = {
30 |
31 | init: function (color, brushSize) {
32 | this.sharedBuffer = sharedBufferGeometryManager.getSharedBuffer('tris-' + this.materialOptions.type);
33 | this.prevIdx = Object.assign({}, this.sharedBuffer.idx);
34 | this.idx = Object.assign({}, this.sharedBuffer.idx);
35 | this.sharedBuffer.strip = false;
36 |
37 | this.currAngle = 0;
38 | this.subTextures = 1;
39 | this.angleJitter = 0;
40 | this.autoRotate = false;
41 |
42 | if (this.materialOptions['subTextures'] !== undefined) {
43 | this.subTextures = this.materialOptions['subTextures'];
44 | }
45 | if (this.materialOptions['autoRotate'] === true) {
46 | this.autoRotate = true;
47 | }
48 | if (this.materialOptions['angleJitter'] !== undefined) {
49 | this.angleJitter = this.materialOptions['angleJitter'];
50 | this.angleJitter = this.angleJitter * 2 - this.angleJitter;
51 | }
52 | },
53 |
54 | remove: function () {
55 | this.sharedBuffer.remove(this.prevIdx, this.idx);
56 | },
57 |
58 | undo: function () {
59 | this.sharedBuffer.undo(this.prevIdx);
60 | },
61 |
62 | addPoint: (function () {
63 | var axis = new THREE.Vector3();
64 | var dir = new THREE.Vector3();
65 | var a = new THREE.Vector3();
66 | var b = new THREE.Vector3();
67 | var c = new THREE.Vector3();
68 | var d = new THREE.Vector3();
69 | var auxDir = new THREE.Vector3();
70 | var pi2 = Math.PI / 2;
71 |
72 | return function (position, rotation, pointerPosition, pressure, timestamp) {
73 | // brush side
74 | dir.set(1, 0, 0);
75 | dir.applyQuaternion(rotation);
76 | dir.normalize();
77 |
78 | // brush normal
79 | axis.set(0, 1, 0);
80 | axis.applyQuaternion(rotation);
81 | axis.normalize();
82 |
83 | var brushSize = this.data.size * pressure / 2;
84 | var brushAngle = Math.PI / 4 + Math.random() * this.angleJitter;
85 |
86 | if (this.autoRotate) {
87 | this.currAngle += 0.1;
88 | brushAngle += this.currAngle;
89 | }
90 |
91 | a.copy(pointerPosition).add(auxDir.copy(dir.applyAxisAngle(axis, brushAngle)).multiplyScalar(brushSize));
92 | b.copy(pointerPosition).add(auxDir.copy(dir.applyAxisAngle(axis, pi2)).multiplyScalar(brushSize));
93 | c.copy(pointerPosition).add(auxDir.copy(dir.applyAxisAngle(axis, pi2)).multiplyScalar(brushSize));
94 | d.copy(pointerPosition).add(dir.applyAxisAngle(axis, pi2).multiplyScalar(brushSize));
95 |
96 | var nidx = this.idx.position;
97 | var cidx = this.idx.position;
98 |
99 | // triangle 1
100 | this.sharedBuffer.addVertex(a.x, a.y, a.z);
101 | this.sharedBuffer.addVertex(b.x, b.y, b.z);
102 | this.sharedBuffer.addVertex(c.x, c.y, c.z);
103 |
104 | // triangle 2
105 | this.sharedBuffer.addVertex(c.x, c.y, c.z);
106 | this.sharedBuffer.addVertex(d.x, d.y, d.z);
107 | this.sharedBuffer.addVertex(a.x, a.y, a.z);
108 |
109 | // normals & color
110 | for (var i = 0; i < 6; i++) {
111 | this.sharedBuffer.addNormal(axis.x, axis.y, axis.z);
112 | this.sharedBuffer.addColor(this.data.color.r, this.data.color.g, this.data.color.b);
113 | }
114 |
115 | // UVs
116 | var uv = this.data.numPoints * 6 * 2;
117 |
118 | // subTextures?
119 | var Umin = 0;
120 | var Umax = 1;
121 | if (this.subTextures > 1) {
122 | var subt = Math.floor(Math.random() * this.subTextures);
123 | Umin = 1.0 / this.subTextures * subt;
124 | Umax = Umin + 1.0 / this.subTextures;
125 | }
126 |
127 | var converter = this.materialOptions.converter;
128 |
129 | // triangle 1 uv
130 | this.sharedBuffer.addUV(converter.convertU(Umin), converter.convertV(1));
131 | this.sharedBuffer.addUV(converter.convertU(Umin), converter.convertV(0));
132 | this.sharedBuffer.addUV(converter.convertU(Umax), converter.convertV(0));
133 |
134 | // triangle2 uv
135 | this.sharedBuffer.addUV(converter.convertU(Umax), converter.convertV(0));
136 | this.sharedBuffer.addUV(converter.convertU(Umax), converter.convertV(1));
137 | this.sharedBuffer.addUV(converter.convertU(Umin), converter.convertV(1));
138 |
139 | this.idx = Object.assign({}, this.sharedBuffer.idx);
140 |
141 | this.sharedBuffer.update();
142 |
143 | return true;
144 | }
145 | })()
146 |
147 | };
148 |
149 | var stamps = [
150 | {
151 | name: 'dots',
152 | materialOptions: {
153 | type: 'shaded',
154 | textureSrc: 'brushes/stamp_dots.png'
155 | },
156 | thumbnail: 'brushes/thumb_stamp_dots.gif',
157 | spacing: 0.01
158 | },
159 | {
160 | name: 'squares',
161 | materialOptions: {
162 | type: 'shaded',
163 | textureSrc: 'brushes/stamp_squares.png'
164 | },
165 | thumbnail: 'brushes/thumb_stamp_squares.gif',
166 | spacing: 0.01
167 | },
168 | {
169 | name: 'column',
170 | materialOptions: {
171 | type: 'shaded',
172 | autoRotate: true,
173 | textureSrc: 'brushes/stamp_column.png'
174 | },
175 | thumbnail: 'brushes/thumb_stamp_column.gif',
176 | spacing: 0.01
177 | },
178 | {
179 | name: 'gear1',
180 | materialOptions: {
181 | type: 'shaded',
182 | angleJitter: Math.PI * 2,
183 | subTextures: 2,
184 | textureSrc: 'brushes/stamp_gear.png'
185 | },
186 | thumbnail: 'brushes/thumb_stamp_gear.gif',
187 | spacing: 0.05
188 | },
189 | {
190 | name: 'grunge1',
191 | materialOptions: {
192 | type: 'shaded',
193 | angleJitter: Math.PI * 2,
194 | textureSrc: 'brushes/stamp_grunge1.png'
195 | },
196 | thumbnail: 'brushes/stamp_grunge1.png',
197 | spacing: 0.02
198 | },
199 | {
200 | name: 'grunge2',
201 | materialOptions: {
202 | type: 'shaded',
203 | angleJitter: Math.PI * 2,
204 | textureSrc: 'brushes/stamp_grunge2.png'
205 | },
206 | thumbnail: 'brushes/stamp_grunge2.png',
207 | spacing: 0.02
208 | },
209 | {
210 | name: 'grunge3',
211 | materialOptions: {
212 | type: 'shaded',
213 | angleJitter: Math.PI * 2,
214 | textureSrc: 'brushes/stamp_grunge3.png'
215 | },
216 | thumbnail: 'brushes/stamp_grunge3.png',
217 | spacing: 0.02
218 | },
219 | {
220 | name: 'grunge4',
221 | materialOptions: {
222 | type: 'shaded',
223 | angleJitter: Math.PI * 2,
224 | textureSrc: 'brushes/stamp_grunge4.png'
225 | },
226 | thumbnail: 'brushes/stamp_grunge4.png',
227 | spacing: 0.02
228 | },
229 | {
230 | name: 'grunge5',
231 | materialOptions: {
232 | type: 'shaded',
233 | angleJitter: Math.PI * 2,
234 | textureSrc: 'brushes/stamp_grunge5.png'
235 | },
236 | thumbnail: 'brushes/thumb_stamp_grunge5.gif',
237 | spacing: 0.02
238 | },
239 | {
240 | name: 'leaf1',
241 | materialOptions: {
242 | type: 'shaded',
243 | angleJitter: Math.PI,
244 | textureSrc: 'brushes/stamp_leaf1.png'
245 | },
246 | thumbnail: 'brushes/stamp_leaf1.png',
247 | spacing: 0.03
248 | },
249 | {
250 | name: 'leaf2',
251 | materialOptions: {
252 | type: 'shaded',
253 | angleJitter: 60 * Math.PI / 180.0,
254 | textureSrc: 'brushes/stamp_leaf2.png'
255 | },
256 | thumbnail: 'brushes/thumb_stamp_leaf2.gif',
257 | spacing: 0.03
258 | },
259 | {
260 | name: 'leaf3',
261 | materialOptions: {
262 | type: 'shaded',
263 | angleJitter: 60 * Math.PI / 180.0,
264 | textureSrc: 'brushes/stamp_leaf3.png'
265 | },
266 | thumbnail: 'brushes/thumb_stamp_leaf3.gif',
267 | spacing: 0.03
268 | },
269 | {
270 | name: 'fur1',
271 | materialOptions: {
272 | type: 'shaded',
273 | angleJitter: 40 * Math.PI / 180.0,
274 | subTextures: 2,
275 | textureSrc: 'brushes/stamp_fur1.png'
276 | },
277 | thumbnail: 'brushes/stamp_fur1.png',
278 | spacing: 0.01
279 | },
280 | {
281 | name: 'fur2',
282 | materialOptions: {
283 | type: 'shaded',
284 | angleJitter: 10 * Math.PI / 180.0,
285 | subTextures: 3,
286 | textureSrc: 'brushes/stamp_fur2.png'
287 | },
288 | thumbnail: 'brushes/stamp_fur2.png',
289 | spacing: 0.01
290 | },
291 | {
292 | name: 'grass',
293 | materialOptions: {
294 | type: 'shaded',
295 | angleJitter: 10 * Math.PI / 180.0,
296 | subTextures: 3,
297 | textureSrc: 'brushes/stamp_grass.png'
298 | },
299 | thumbnail: 'brushes/thumb_stamp_grass.png',
300 | spacing: 0.03
301 | },
302 | {
303 | name: 'bush',
304 | materialOptions: {
305 | type: 'shaded',
306 | subTextures: 2,
307 | textureSrc: 'brushes/stamp_bush.png'
308 | },
309 | thumbnail: 'brushes/thumb_stamp_bush.gif',
310 | spacing: 0.04
311 | },
312 | {
313 | name: 'star',
314 | materialOptions: {
315 | type: 'shaded',
316 | textureSrc: 'brushes/stamp_star.png'
317 | },
318 | thumbnail: 'brushes/thumb_stamp_star.png',
319 | spacing: 0.06
320 | },
321 | {
322 | name: 'snow',
323 | materialOptions: {
324 | type: 'shaded',
325 | angleJitter: Math.PI * 2,
326 | textureSrc: 'brushes/stamp_snow.png'
327 | },
328 | thumbnail: 'brushes/thumb_stamp_snow.png',
329 | spacing: 0.06
330 | }
331 | ];
332 |
333 | // var textureLoader = new THREE.TextureLoader();
334 | for (var i = 0; i < stamps.length; i++) {
335 | var definition = stamps[i];
336 | if (definition.materialOptions.textureSrc) {
337 | definition.materialOptions.map = window.atlas.map; //textureLoader.load(definition.materialOptions.textureSrc);
338 | definition.materialOptions.converter = window.atlas.getUVConverters(definition.materialOptions.textureSrc);
339 | delete definition.materialOptions.textureSrc;
340 | }
341 | AFRAME.registerBrush(definition.name, Object.assign({}, stamp, {materialOptions: definition.materialOptions}), {thumbnail: definition.thumbnail, spacing: definition.spacing, maxPoints: 3000});
342 | }
343 |
344 | /*
345 | - type: <'flat'|'shaded'>
346 | Flat: constant, just color. Shaded: phong shading with subtle speculars
347 | - autoRotate:
348 | The brush rotates incrementally at 0.1rad per point
349 | - angleJitter:
350 | The brush rotates randomly from -r to r
351 | - subTextures:
352 | textureSrc is divided in n horizontal pieces, and the brush picks one randomly on each point
353 | */
354 | })();
355 |
--------------------------------------------------------------------------------
/src/components/brush-tip.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('brush-tip', {
2 | schema: {
3 | controller: { type: 'string' },
4 | hand: {
5 | type: 'string',
6 | oneOf: ['left', 'right']
7 | }
8 | },
9 |
10 | init: function () {
11 | var toRad = degrees => THREE.MathUtils.degToRad(degrees);
12 |
13 | this.controllers = {
14 | 'oculus-touch-controller-v3': {
15 | left: {
16 | positionOffset: { x: 0, y: -0.025, z: -0.042 },
17 | rotationOffset: { x: toRad(-45), y: toRad(7), z: toRad(-7) }
18 | },
19 | right: {
20 | positionOffset: { x: 0, y: -0.025, z: -0.042 },
21 | rotationOffset: { x: toRad(-45), y: toRad(-7), z: toRad(7) }
22 | }
23 | }
24 | };
25 |
26 | if (this.data.controller) {
27 | this.setController(this.data.controller, this.data.hand);
28 | }
29 | },
30 |
31 | setController: function (controller, hand) {
32 | if (controller in this.controllers) {
33 | this.el.object3D.position.set(
34 | this.controllers[controller][hand].positionOffset.x,
35 | this.controllers[controller][hand].positionOffset.y,
36 | this.controllers[controller][hand].positionOffset.z
37 | );
38 | this.el.object3D.rotation.set(
39 | this.controllers[controller][hand].rotationOffset.x,
40 | this.controllers[controller][hand].rotationOffset.y,
41 | this.controllers[controller][hand].rotationOffset.z
42 | )
43 | } else {
44 | console.error(`${controller} is not present in the controllers list!`);
45 | }
46 | }
47 | });
48 |
--------------------------------------------------------------------------------
/src/components/brush.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | AFRAME.registerComponent('brush', {
3 | schema: {
4 | color: {type: 'color', default: '#ef2d5e'},
5 | size: {default: 0.01, min: 0.001, max: 0.3},
6 | brush: {default: 'smooth'},
7 | enabled: { default: true }
8 | },
9 | init: function () {
10 | var data = this.data;
11 | this.color = new THREE.Color(data.color);
12 |
13 | this.el.emit('brushcolor-changed', {color: this.color});
14 | this.el.emit('brushsize-changed', {brushSize: data.size});
15 |
16 | this.active = false;
17 | this.obj = this.el.object3D;
18 |
19 | this.currentStroke = null;
20 | this.strokeEntities = [];
21 |
22 | this.sizeModifier = 0.0;
23 | this.textures = {};
24 | this.currentMap = 0;
25 |
26 | this.model = this.el.getObject3D('mesh');
27 | this.drawing = false;
28 |
29 | var self = this;
30 |
31 | this.el.addEventListener('undo', function(evt) {
32 | if (!self.data.enabled) { return; }
33 | self.system.undo();
34 | document.getElementById('ui_undo').play();
35 | });
36 |
37 | this.el.addEventListener('paint', function (evt) {
38 | if (!self.data.enabled) { return; }
39 | // Trigger
40 | var value = evt.detail.value;
41 | self.sizeModifier = value;
42 | if (value > 0.1) {
43 | if (!self.active) {
44 | self.startNewStroke();
45 | self.active = true;
46 | }
47 | } else {
48 | if (self.active) {
49 | self.previousEntity = self.currentEntity;
50 | self.currentStroke = null;
51 | }
52 | self.active = false;
53 | }
54 | });
55 |
56 | this.hand = this.el.id === 'right-hand' ? 'right' : 'left';
57 | },
58 | update: function (oldData) {
59 | var data = this.data;
60 | if (oldData.color !== data.color) {
61 | this.color.set(data.color);
62 | this.el.emit('brushcolor-changed', {color: this.color});
63 | }
64 | if (oldData.size !== data.size) {
65 | this.el.emit('brushsize-changed', {size: data.size});
66 | }
67 | },
68 | tick: (function () {
69 | var position = new THREE.Vector3();
70 | var rotation = new THREE.Quaternion();
71 | var scale = new THREE.Vector3();
72 |
73 | return function tick (time, delta) {
74 | if (this.currentStroke && this.active) {
75 | this.obj.matrixWorld.decompose(position, rotation, scale);
76 | var pointerPosition = this.system.getPointerPosition(position, rotation, this.hand);
77 | this.currentStroke.addPoint(position, rotation, pointerPosition, this.sizeModifier, time);
78 | }
79 | };
80 | })(),
81 | startNewStroke: function () {
82 | document.getElementById('ui_paint').play();
83 | this.currentStroke = this.system.addNewStroke(this.data.brush, this.color, this.data.size);
84 | this.el.emit('stroke-started', {entity: this.el, stroke: this.currentStroke});
85 | }
86 | });
87 |
--------------------------------------------------------------------------------
/src/components/json-model.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 |
3 | AFRAME.registerComponent('json-model', {
4 | schema: {
5 | src: {type: 'asset'}
6 | },
7 |
8 | init: function () {
9 | this.objectLoader = new THREE.ObjectLoader();
10 | this.objectLoader.setCrossOrigin('');
11 | },
12 |
13 | update: function (oldData) {
14 | var self = this;
15 | var src = this.data.src;
16 | if (!src || src === oldData.src) { return; }
17 | this.objectLoader.load(this.data.src, function (group) {
18 | var Rotation = new THREE.Matrix4().makeRotationX(-Math.PI / 2);
19 | group.traverse(function (child) {
20 | if (!(child instanceof THREE.Mesh)) { return; }
21 | child.position.applyMatrix4(Rotation);
22 | });
23 | self.el.setObject3D('mesh', group);
24 | self.el.emit('model-loaded', {format: 'json', model: group, src: src});
25 | });
26 | }
27 | });
28 |
--------------------------------------------------------------------------------
/src/components/logo-model.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('logo-model', {
2 | schema: {
3 | opacity: {default: 1.0}
4 | },
5 | init: function () {
6 | this.model = null;
7 |
8 | this.el.setAttribute('obj-model', 'obj: #logoobj; mtl: #logomtl');
9 | this.el.addEventListener('model-loaded', this.setModel.bind(this));
10 | },
11 | setModel: function (evt) {
12 | this.model = evt.detail.model;
13 | },
14 | update: function () {
15 | if (this.model != null) {
16 | this.model.children[0].material.opacity = this.data.opacity;
17 | }
18 | }
19 | });
--------------------------------------------------------------------------------
/src/components/paint-controls.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerSystem('paint-controls', {
2 | numberStrokes: 0
3 | });
4 |
5 | /* globals AFRAME THREE */
6 | AFRAME.registerComponent('paint-controls', {
7 | dependencies: ['brush'],
8 |
9 | schema: {
10 | hand: {default: 'left'}
11 | },
12 |
13 | init: function () {
14 | var el = this.el;
15 | var self = this;
16 | var highLightTextureUrl = 'assets/images/controller-pressed.png';
17 | var tooltipGroups = null;
18 | this.controller = null;
19 | this.modelLoaded = false;
20 |
21 | this.onEnterVR = this.onEnterVR.bind(this);
22 | el.sceneEl.addEventListener('enter-vr', this.onEnterVR);
23 | el.object3D.visible = false;
24 |
25 | this.onModelLoaded = this.onModelLoaded.bind(this);
26 | el.addEventListener('model-loaded', this.onModelLoaded);
27 |
28 | el.addEventListener('changeBrushSizeAbs', function (evt) {
29 | if (evt.detail.axis[1] === 0 && evt.detail.axis[3] === 0) { return; }
30 |
31 | var magnitude = evt.detail.axis[1] || evt.detail.axis[3];
32 | var delta = magnitude / 300;
33 | var size = el.components.brush.schema.size;
34 | var value = THREE.Math.clamp(self.el.getAttribute('brush').size - delta, size.min, size.max);
35 |
36 | self.el.setAttribute('brush', 'size', value);
37 | });
38 |
39 | el.addEventListener('changeBrushSizeInc', function (evt) {
40 | if (evt.detail.axis[1] === 0 && evt.detail.axis[3] === 0) { return; }
41 |
42 | var magnitude = evt.detail.axis[1] || evt.detail.axis[3];
43 |
44 | if (self.touchStarted) {
45 | self.touchStarted = false;
46 | self.startAxis = (magnitude + 1) / 2;
47 | }
48 |
49 | var currentAxis = (magnitude + 1) / 2;
50 | var delta = (self.startAxis - currentAxis) / 2;
51 |
52 | self.startAxis = currentAxis;
53 |
54 | var startValue = self.el.getAttribute('brush').size;
55 | var size = el.components.brush.schema.size;
56 | var value = THREE.Math.clamp(startValue - delta, size.min, size.max);
57 |
58 | self.el.setAttribute('brush', 'size', value);
59 | });
60 |
61 | self.touchStarted = false;
62 |
63 | el.addEventListener('startChangeBrushSize', function () {
64 | self.touchStarted = true;
65 | });
66 |
67 | el.addEventListener('controllerconnected', function (evt) {
68 | var controllerName = evt.detail.name;
69 | var hand = evt.detail.component.data.hand;
70 |
71 | const createBrushTip = (controllerVersion, hand) => {
72 | // Create brush tip and position it dynamically based on our current controller
73 | this.brushTip = document.createElement('a-entity');
74 | this.brushTip.id = `${hand}-tip`;
75 | this.brushTip.setAttribute('gltf-model', '#tipObj');
76 | this.brushTip.setAttribute('brush-tip', `controller: ${controllerVersion}; hand: ${hand}`);
77 | this.brushTip.addEventListener('model-loaded', self.onModelLoaded);
78 | el.appendChild(this.brushTip);
79 | }
80 |
81 | if (controllerName === 'windows-motion-controls')
82 | {
83 | var gltfName = evt.detail.component.el.components['gltf-model'].data;
84 | const SAMSUNG_DEVICE = '045E-065D';
85 | if (!!gltfName)
86 | {
87 | if (gltfName.indexOf(SAMSUNG_DEVICE) >= 0)
88 | {
89 | controllerName = "windows-motion-samsung-controls";
90 | }
91 | }
92 | }
93 |
94 | tooltipGroups = Utils.getTooltips(controllerName);
95 | if (controllerName.indexOf('windows-motion') >= 0) {
96 | // el.setAttribute('teleport-controls', {button: 'trackpad'});
97 | } else if (controllerName === 'oculus-touch-controls') {
98 | const controllerModelURL = el.components[controllerName].displayModel[hand].modelUrl;
99 | const versionMatchPattern = /[^\/]*(?=-(?:left|right)\.)/; // Matches the "oculus-touch-controller-[version]" part of URL
100 | const controllerVersion = versionMatchPattern.exec(controllerModelURL)[0];
101 | createBrushTip(controllerVersion, hand);
102 | } else if (controllerName === 'vive-controls') {
103 | el.setAttribute('gltf-model', 'url(assets/models/vive-controller.glb)');
104 | } else { return; }
105 |
106 | if (!!tooltipGroups) {
107 | tooltipGroups.forEach(function (tooltipGroup) {
108 | tooltipGroup.setAttribute('visible', true);
109 | const tooltips = Array.prototype.slice.call(tooltipGroup.querySelectorAll('[tooltip]'));
110 | tooltips.forEach(function (tooltip) {
111 | tooltip.setAttribute('animation', { dur: 1000, delay: 2000, property: 'tooltip.opacity', from: 1.0, to: 0.0, startEvents: 'tooltip-fade' });
112 | });
113 | });
114 | }
115 |
116 | this.controller = controllerName;
117 | });
118 |
119 | el.addEventListener('brushsize-changed', function (evt) { self.changeBrushSize(evt.detail.size); });
120 | el.addEventListener('brushcolor-changed', function (evt) { self.changeBrushColor(evt.detail.color); });
121 |
122 | function createTexture (texture) {
123 | var material = self.highLightMaterial = new THREE.MeshBasicMaterial();
124 | material.map = texture;
125 | material.needsUpdate = true;
126 | }
127 | el.sceneEl.systems.material.loadTexture(highLightTextureUrl, {src: highLightTextureUrl}, createTexture);
128 |
129 | this.startAxis = 0;
130 |
131 | this.numberStrokes = 0;
132 |
133 | document.addEventListener('stroke-started', function (event) {
134 | if (event.detail.entity.components['paint-controls'] !== self) { return; }
135 |
136 | self.numberStrokes++;
137 | self.system.numberStrokes++;
138 |
139 | // 3 Strokes to hide
140 | if (self.system.numberStrokes === 3) {
141 | const tooltips = Array.prototype.slice.call(document.querySelectorAll('[tooltip]'));
142 | tooltips.forEach(function (tooltip) {
143 | tooltip.emit('tooltip-fade');
144 | });
145 | }
146 | });
147 | },
148 |
149 | changeBrushColor: function (color) {
150 | if (this.modelLoaded && !!this.buttonMeshes.sizeHint) {
151 | this.buttonMeshes.colorTip.material.color.copy(color);
152 | this.buttonMeshes.sizeHint.material.color.copy(color);
153 | }
154 | },
155 |
156 | changeBrushSize: function (size) {
157 | var scale = size / 2 * 10;
158 | if (this.modelLoaded && !!this.buttonMeshes.sizeHint) {
159 | this.buttonMeshes.sizeHint.scale.set(scale, 1, scale);
160 | }
161 | },
162 |
163 | // buttonId
164 | // 0 - trackpad
165 | // 1 - trigger ( intensity value from 0.5 to 1 )
166 | // 2 - grip
167 | // 3 - menu ( dispatch but better for menu options )
168 | // 4 - system ( never dispatched on this layer )
169 | mapping: {
170 | axis0: 'trackpad',
171 | axis1: 'trackpad',
172 | button0: 'trackpad',
173 | button1: 'trigger',
174 | button2: 'grip',
175 | button3: 'menu',
176 | button4: 'system'
177 | },
178 |
179 | update: function () {
180 | var data = this.data;
181 | var el = this.el;
182 | el.setAttribute('vive-controls', {hand: data.hand, model: false});
183 | el.setAttribute('oculus-touch-controls', {hand: data.hand, model: true});
184 | el.setAttribute('windows-motion-controls', {hand: data.hand});
185 | },
186 |
187 | play: function () {
188 | },
189 |
190 | pause: function () {
191 | },
192 |
193 | onEnterVR: function () {
194 | this.el.object3D.visible = true;
195 | },
196 |
197 | onModelLoaded: function (evt) {
198 | // Only act on lone brush tip or custom model to set the button meshes, ignore anything else.
199 | if ((evt.target !== this.el && !evt.target.id.includes('-tip')) || this.buttonMeshes) { return; }
200 |
201 | var controllerObject3D = evt.detail.model;
202 | var buttonMeshes;
203 |
204 | buttonMeshes = this.buttonMeshes = {};
205 |
206 | buttonMeshes.sizeHint = controllerObject3D.getObjectByName('sizehint');
207 | buttonMeshes.colorTip = controllerObject3D.getObjectByName('tip');
208 |
209 | this.modelLoaded = true;
210 |
211 | this.changeBrushSize(this.el.components.brush.data.size);
212 | this.changeBrushColor(this.el.components.brush.color);
213 | },
214 |
215 | onButtonEvent: function (id, evtName) {
216 | var buttonName = this.mapping['button' + id];
217 | this.el.emit(buttonName + evtName);
218 | this.updateModel(buttonName, evtName);
219 | },
220 |
221 | updateModel: function (buttonName, state) {
222 | var material = state === 'up' ? this.material : this.highLightMaterial;
223 | var buttonMeshes = this.buttonMeshes;
224 | var button = buttonMeshes && buttonMeshes[buttonName];
225 | if (state === 'down' && button && !this.material) {
226 | material = this.material = button.material;
227 | }
228 | if (!material) { return; }
229 | if (buttonName === 'grip') {
230 | buttonMeshes.grip.left.material = material;
231 | buttonMeshes.grip.right.material = material;
232 | return;
233 | }
234 | if (!button) { return; }
235 | button.material = material;
236 | }
237 | });
238 |
--------------------------------------------------------------------------------
/src/components/ui-raycaster.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE */
2 | /**
3 | * Raycaster component.
4 | *
5 | * Pass options to three.js Raycaster including which objects to test.
6 | * Poll for intersections.
7 | * Emit event on origin entity and on target entity on intersect.
8 | *
9 | * @member {array} intersectedEls - List of currently intersected entities.
10 | * @member {array} objects - Cached list of meshes to intersect.
11 | * @member {number} prevCheckTime - Previous time intersection was checked. To help interval.
12 | * @member {object} raycaster - three.js Raycaster.
13 | */
14 | AFRAME.registerComponent('ui-raycaster', {
15 | schema: {
16 | far: {default: Infinity}, // Infinity.
17 | interval: {default: 100},
18 | near: {default: 0},
19 | objects: {default: ''},
20 | recursive: {default: true},
21 | rotation: {default: 0}
22 | },
23 |
24 | init: function () {
25 | this.direction = new THREE.Vector3();
26 | this.intersectedEls = [];
27 | this.objects = null;
28 | this.prevCheckTime = undefined;
29 | this.raycaster = new THREE.Raycaster();
30 | this.updateOriginDirection();
31 | this.refreshObjects = this.refreshObjects.bind(this);
32 | },
33 |
34 | play: function () {
35 | this.el.sceneEl.addEventListener('child-attached', this.refreshObjects);
36 | this.el.sceneEl.addEventListener('child-detached', this.refreshObjects);
37 | },
38 |
39 | pause: function () {
40 | this.el.sceneEl.removeEventListener('child-attached', this.refreshObjects);
41 | this.el.sceneEl.removeEventListener('child-detached', this.refreshObjects);
42 | },
43 |
44 | /**
45 | * Create or update raycaster object.
46 | */
47 | update: function () {
48 | var data = this.data;
49 | var raycaster = this.raycaster;
50 |
51 | // Set raycaster properties.
52 | raycaster.far = data.far;
53 | raycaster.near = data.near;
54 |
55 | this.refreshObjects();
56 | },
57 |
58 | /**
59 | * Update list of objects to test for intersection.
60 | */
61 | refreshObjects: function () {
62 | var data = this.data;
63 | var i;
64 | var objectEls;
65 |
66 | // Push meshes onto list of objects to intersect.
67 | if (data.objects) {
68 | objectEls = this.el.sceneEl.querySelectorAll(data.objects);
69 | this.objects = [];
70 | for (i = 0; i < objectEls.length; i++) {
71 | this.objects.push(objectEls[i].object3D);
72 | }
73 | return;
74 | }
75 |
76 | // If objects not defined, intersect with everything.
77 | this.objects = this.el.sceneEl.object3D.children;
78 | },
79 |
80 | /**
81 | * Check for intersections and cleared intersections on an interval.
82 | */
83 | tick: function (time) {
84 | var el = this.el;
85 | var data = this.data;
86 | var intersectedEls;
87 | var intersections;
88 | var prevCheckTime = this.prevCheckTime;
89 | var prevIntersectedEls;
90 |
91 | // Only check for intersection if interval time has passed.
92 | if (prevCheckTime && (time - prevCheckTime < data.interval)) { return; }
93 |
94 | // Store old previously intersected entities.
95 | prevIntersectedEls = this.intersectedEls.slice();
96 |
97 | // Raycast.
98 | this.updateOriginDirection();
99 | intersections = this.raycaster.intersectObjects(this.objects, data.recursive);
100 |
101 | // Only keep intersections against objects that have a reference to an entity.
102 | intersections = intersections.filter(function hasEl (intersection) {
103 | return !!intersection.object.el;
104 | });
105 |
106 | // Update intersectedEls.
107 | intersectedEls = this.intersectedEls = intersections.map(function getEl (intersection) {
108 | return intersection.object.el;
109 | });
110 |
111 | // Emit intersected on intersected entity per intersected entity.
112 | intersections.forEach(function emitEvents (intersection) {
113 | var intersectedEl = intersection.object.el;
114 | intersectedEl.emit('raycaster-intersected', {el: el, intersection: intersection});
115 | });
116 |
117 | // Emit all intersections at once on raycasting entity.
118 | if (intersections.length) {
119 | el.emit('raycaster-intersection', {
120 | els: intersectedEls,
121 | intersections: intersections
122 | });
123 | }
124 |
125 | // Emit intersection cleared on both entities per formerly intersected entity.
126 | prevIntersectedEls.forEach(function checkStillIntersected (intersectedEl) {
127 | if (intersectedEls.indexOf(intersectedEl) !== -1) { return; }
128 | el.emit('raycaster-intersection-cleared', {el: intersectedEl});
129 | intersectedEl.emit('raycaster-intersected-cleared', {el: el});
130 | });
131 | },
132 |
133 | /**
134 | * Set origin and direction of raycaster using entity position and rotation.
135 | */
136 | updateOriginDirection: (function () {
137 | var directionHelper = new THREE.Quaternion();
138 | var originVec3 = new THREE.Vector3();
139 | var scaleDummy = new THREE.Vector3();
140 |
141 | // Closure to make quaternion/vector3 objects private.
142 | return function updateOriginDirection () {
143 | var el = this.el;
144 | var direction = this.direction;
145 | var object3D = el.object3D;
146 |
147 | // Update matrix world.
148 | object3D.updateMatrixWorld();
149 | // Grab the position and rotation.
150 | object3D.matrixWorld.decompose(originVec3, directionHelper, scaleDummy);
151 | // Apply rotation to a 0, 0, -1 vector.
152 | direction.set(0, 0, -1);
153 | direction.applyAxisAngle(new THREE.Vector3(1, 0, 0), (this.data.rotation / 360) * 2 * Math.PI);
154 | direction.applyQuaternion(directionHelper);
155 |
156 | this.raycaster.set(originVec3, direction);
157 | };
158 | })()
159 | });
160 |
--------------------------------------------------------------------------------
/src/components/vr-analytics.js:
--------------------------------------------------------------------------------
1 | AFRAME.registerComponent('vr-analytics', {
2 | init: function () {
3 | var el = this.el;
4 | var emitted = false;
5 |
6 | el.addEventListener('enter-vr', function () {
7 | if (emitted || !AFRAME.utils.device.checkHeadsetConnected() ||
8 | AFRAME.utils.device.isMobile()) { return; }
9 | ga('send', 'event', 'General', 'entervr');
10 | emitted = true;
11 | });
12 | }
13 | });
14 |
--------------------------------------------------------------------------------
/src/dragndrop.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME Image FileReader */
2 | window.addEventListener('load', function (event) {
3 | var dropArea = document.body;
4 |
5 | dropArea.addEventListener('dragover', function (event) {
6 | event.stopPropagation();
7 | event.preventDefault();
8 | event.dataTransfer.dropEffect = 'copy';
9 | }, false);
10 |
11 | dropArea.addEventListener('drop', function (event) {
12 | event.stopPropagation();
13 | event.preventDefault();
14 |
15 | // for each dropped file
16 | var files = event.dataTransfer.files;
17 | for (var i = 0; i < files.length; i++) {
18 | var file = files[i];
19 |
20 | if (file.name.substring(file.name.length - 4).toLowerCase() === '.apa') {
21 | // a-painter binary
22 | var reader = new FileReader();
23 |
24 | // file read, parse obj and add to the scene
25 | reader.onload = function (event) {
26 | document.querySelector('a-scene').systems.brush.loadBinary(event.target.result);
27 | };
28 | reader.readAsArrayBuffer(file);
29 | }
30 | else if (file.name.substring(file.name.length - 5).toLowerCase() === '.json') {
31 | // a-painter json
32 | var reader = new FileReader();
33 |
34 | // file read, parse obj and add to the scene
35 | reader.onload = function (event) {
36 | document.querySelector('a-scene').systems.brush.loadJSON(JSON.parse(event.target.result));
37 | };
38 | reader.readAsText(file);
39 | }
40 | else if (file.name.substring(file.name.length - 4).toLowerCase() === '.obj') {
41 | // OBJs
42 | reader = new FileReader();
43 |
44 | // file read, parse obj and add to the scene
45 | reader.onload = function (event) {
46 | var objloader = new AFRAME.THREE.OBJLoader();
47 | var mesh = objloader.parse(event.target.result);
48 |
49 | var entity = document.createElement('a-entity');
50 | // set all mesh objects to dark gray
51 | for (var o = 0; o < mesh.children.length; o++) {
52 | var child = mesh.children[o];
53 | if (child.material.constructor === Array) {
54 | child.material.forEach(mat => {
55 | mat.color.set('#333');
56 | })
57 | } else {
58 | child.material.color.set('#333');
59 | }
60 | }
61 | // add mesh to entity
62 | entity.setObject3D('mesh', mesh);
63 | entity.className = 'templateitem';
64 | document.querySelector('a-scene').appendChild(entity);
65 | };
66 | reader.readAsText(file);
67 | } else if (file.type.match(/image.*/)) {
68 | // dropping images
69 | reader = new FileReader();
70 | reader.onload = function (event) {
71 | // create img to get its size
72 | var img = new Image();
73 | img.src = event.target.result;
74 |
75 | img.onload = () => {
76 | // find good image size
77 | var width, height;
78 | if (img.width > img.height) {
79 | width = 1.0;
80 | height = img.height / img.width;
81 | } else {
82 | height = 1.0;
83 | width = img.width / img.height;
84 | }
85 |
86 | // find a random position in a side of the room
87 | var pos = [Math.random() * 3 - 1.5, 1 + Math.random() - 0.5, -1.4 + Math.random() * 0.2];
88 |
89 | // create a-image entity and set attributes
90 | var entity = document.createElement('a-image');
91 | entity.setAttribute('src', event.target.result);
92 | entity.setAttribute('position', pos.join(' '));
93 | entity.setAttribute('width', width);
94 | entity.setAttribute('height', height);
95 | entity.className = 'templateitem';
96 | document.querySelector('a-scene').appendChild(entity);
97 | }
98 | };
99 | reader.readAsDataURL(file);
100 | }
101 | }
102 | }, false);
103 | });
104 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | window.saveAs = require('../vendor/saveas.js').saveAs;
2 |
3 | require('./atlas.js');
4 | require('./dragndrop.js');
5 | require('./binarymanager.js');
6 | require('./sharedbuffergeometrymanager.js');
7 | require('./sharedbuffergeometry.js');
8 |
9 | require('./utils.js');
10 | require('./ui2d.js');
11 |
12 | require('./systems/brush.js');
13 | require('./systems/ui.js');
14 | require('./systems/painter.js');
15 |
16 | require('./components/brush.js');
17 | require('./components/brush-tip.js');
18 | require('./components/json-model.js');
19 | require('./components/paint-controls.js');
20 | require('./components/ui.js');
21 | require('./components/ui-raycaster.js');
22 | require('./components/logo-model.js');
23 |
24 | require('./brushes/line.js');
25 | require('./brushes/stamp.js');
26 | require('./brushes/spheres.js');
27 | require('./brushes/cubes.js');
28 | require('./brushes/rainbow.js');
29 | require('./brushes/single-sphere.js');
30 |
--------------------------------------------------------------------------------
/src/onloaded.js:
--------------------------------------------------------------------------------
1 | module.exports = function (callback) {
2 | if (document.readyState === 'complete' || document.readyState === 'loaded') {
3 | onDomLoaded();
4 | } else {
5 | document.addEventListener('DOMContentLoaded', onDomLoaded);
6 | }
7 |
8 | function onDomLoaded() {
9 | var sceneEl = document.querySelector('a-scene');
10 | if (sceneEl.hasLoaded) {
11 | callback();
12 | } else {
13 | sceneEl.addEventListener('loaded', callback());
14 | }
15 | }
16 | }
--------------------------------------------------------------------------------
/src/sharedbuffergeometry.js:
--------------------------------------------------------------------------------
1 | function SharedBufferGeometry (material) {
2 | this.material = material;
3 |
4 | this.maxBufferSize = 1000000;
5 | this.geometries = [];
6 | this.current = null;
7 | this.strip = true;
8 | this.addBuffer(false);
9 | }
10 |
11 | SharedBufferGeometry.prototype = {
12 | restartPrimitive: function () {
13 | if (this.idx.position >= this.current.attributes.position.count) {
14 | this.addBuffer(false);
15 | } else if (this.idx.position !== 0) {
16 | var prev = (this.idx.position - 1) * 3;
17 | var position = this.current.attributes.position.array;
18 | this.addVertex(position[prev++], position[prev++], position[prev++]);
19 |
20 | this.idx.color++;
21 | this.idx.normal++;
22 | this.idx.uv++;
23 | }
24 | },
25 |
26 | remove: function (prevIdx, idx) {
27 | var pos = this.current.attributes.position.array;
28 |
29 | // Loop through all the attributes: position, color, uv, normal,...
30 | if (this.idx.position > idx.position) {
31 | for (key in this.idx) {
32 | var componentSize = key === 'uv' ? 2 : 3;
33 | var pos = (prevIdx[key]) * componentSize;
34 | var start = (idx[key] + 1) * componentSize;
35 | var end = this.idx[key] * componentSize;
36 | for (var i = start; i < end; i++) {
37 | this.current.attributes[key].array[pos++] = this.current.attributes[key].array[i];
38 | }
39 | }
40 | }
41 |
42 | for (key in this.idx) {
43 | var diff = (idx[key] - prevIdx[key]);
44 | this.idx[key] -= diff;
45 | }
46 |
47 | this.update();
48 | },
49 |
50 | undo: function (prevIdx) {
51 | for (let i = prevIdx.position; i < this.idx.position; i++) {
52 | this.current.attributes.position.setXYZ(i, 0, 0, 0);
53 | this.current.index.setXYZ(i, 0, 0, 0);
54 | }
55 | this.idx = prevIdx;
56 | this.update();
57 | },
58 |
59 | addBuffer: function (copyLast) {
60 | var geometry = new THREE.BufferGeometry();
61 |
62 | var vertices = new Float32Array(this.maxBufferSize * 3);
63 | var indices = new Uint32Array(this.maxBufferSize * 4.5);
64 | var normals = new Float32Array(this.maxBufferSize * 3);
65 | var uvs = new Float32Array(this.maxBufferSize * 2);
66 | var colors = new Float32Array(this.maxBufferSize * 3);
67 |
68 | var mesh = new THREE.Mesh(geometry, this.material);
69 |
70 | mesh.frustumCulled = false;
71 | mesh.vertices = vertices;
72 |
73 | this.object3D = new THREE.Object3D();
74 | var drawing = document.querySelector('.a-drawing');
75 | if (!drawing) {
76 | drawing = document.createElement('a-entity');
77 | drawing.className = "a-drawing";
78 | document.querySelector('a-scene').appendChild(drawing);
79 | }
80 | drawing.object3D.add(this.object3D);
81 |
82 | this.object3D.add(mesh);
83 |
84 | geometry.setDrawRange(0, 0);
85 | geometry.setAttribute('position', new THREE.BufferAttribute(vertices, 3).setUsage(THREE.DynamicDrawUsage));
86 | geometry.attributes.position.updateRange.count = 0;
87 | geometry.setIndex(new THREE.BufferAttribute(indices, 3).setUsage(THREE.DynamicDrawUsage));
88 | geometry.index.updateRange.count = 0;
89 | geometry.setAttribute('uv', new THREE.BufferAttribute(uvs, 2).setUsage(THREE.DynamicDrawUsage));
90 | geometry.attributes.uv.updateRange.count = 0;
91 | geometry.setAttribute('normal', new THREE.BufferAttribute(normals, 3).setUsage(THREE.DynamicDrawUsage));
92 | geometry.attributes.normal.updateRange.count = 0;
93 | geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3).setUsage(THREE.DynamicDrawUsage));
94 | geometry.attributes.color.updateRange.count = 0;
95 |
96 |
97 | this.previous = null;
98 | if (this.geometries.length > 0) {
99 | this.previous = this.current;
100 | }
101 |
102 | this.idx = {
103 | position: 0,
104 | uv: 0,
105 | normal: 0,
106 | color: 0
107 | };
108 |
109 | this.geometries.push(geometry);
110 | this.current = geometry;
111 |
112 | if (this.previous && copyLast) {
113 | var prev = (this.maxBufferSize - 2) * 3;
114 | var col = (this.maxBufferSize - 2) * 3;
115 | var uv = (this.maxBufferSize - 2) * 2;
116 | var norm = (this.maxBufferSize - 2) * 3;
117 |
118 | var position = this.previous.attributes.position.array;
119 | this.addVertex(position[prev++], position[prev++], position[prev++]);
120 | this.addVertex(position[prev++], position[prev++], position[prev++]);
121 |
122 | var normal = this.previous.attributes.normal.array;
123 | this.addNormal(normal[norm++], normal[norm++], normal[norm++]);
124 | this.addNormal(normal[norm++], normal[norm++], normal[norm++]);
125 |
126 | var color = this.previous.attributes.color.array;
127 | this.addColor(color[col++], color[col++], color[col++]);
128 | this.addColor(color[col++], color[col++], color[col++]);
129 |
130 | var uvs = this.previous.attributes.uv.array;
131 |
132 | }
133 | },
134 |
135 | addColor: function (r, g, b) {
136 | this.current.attributes.color.setXYZ(this.idx.color++, r, g, b);
137 | },
138 |
139 | addNormal: function (x, y, z) {
140 | this.current.attributes.normal.setXYZ(this.idx.normal++, x, y, z);
141 | },
142 |
143 | addVertex: function (x, y, z) {
144 | var buffer = this.current.attributes.position;
145 | if (this.idx.position === buffer.count) {
146 | this.addBuffer(true);
147 | buffer = this.current.attributes.position;
148 | }
149 | buffer.setXYZ(this.idx.position++, x, y, z);
150 | if (this.strip) {
151 | if ((this.idx.position + 1) % 2 == 0 && this.idx.position > 1) {
152 | /* Line brushes
153 | 2---3
154 | | \ |
155 | 0---1
156 | {0, 1, 2}, {2, 1, 3}
157 | */
158 | this.current.index.setXYZ(this.idx.position - 3, this.idx.position - 3, this.idx.position - 2, this.idx.position - 1);
159 | this.current.index.setXYZ(this.idx.position - 2, this.idx.position - 1, this.idx.position - 2, this.idx.position);
160 | }
161 | }
162 | else {
163 | if ((this.idx.position + 1) % 3 == 0) {
164 | /* Stamp brushes
165 | 0---1 0
166 | \ | | \
167 | 2 3---2
168 | {0, 1, 2}, {2, 3, 0}
169 | */
170 | this.current.index.setXYZ(this.idx.position, this.idx.position - 2, this.idx.position - 1, this.idx.position);
171 | }
172 | }
173 | },
174 |
175 | addUV: function (u, v) {
176 | this.current.attributes.uv.setXY(this.idx.uv++, u, v);
177 | },
178 |
179 | update: function () {
180 | // Draw one less triangle to prevent indexing into blank positions
181 | // on an even-number-positioned undo
182 | this.current.setDrawRange(0, (this.idx.position * 3) - 4);
183 |
184 | this.current.attributes.color.updateRange.count = this.idx.position * 3;
185 | this.current.attributes.color.needsUpdate = true;
186 | this.current.attributes.normal.updateRange.count = this.idx.position * 3;
187 | this.current.attributes.normal.needsUpdate = true;
188 | this.current.attributes.position.updateRange.count = this.idx.position * 3;
189 | this.current.attributes.position.needsUpdate = true;
190 | this.current.attributes.uv.updateRange.count = this.idx.position * 2;
191 | this.current.attributes.uv.needsUpdate = true;
192 | this.current.index.updateRange.count = this.idx.position * 3;
193 | this.current.index.needsUpdate = true;
194 | }
195 | };
196 |
197 | module.exports = SharedBufferGeometry;
198 |
--------------------------------------------------------------------------------
/src/sharedbuffergeometrymanager.js:
--------------------------------------------------------------------------------
1 | var SharedBufferGeometry = require('./sharedbuffergeometry.js');
2 |
3 | function SharedBufferGeometryManager () {
4 | this.sharedBuffers = {};
5 | }
6 |
7 | SharedBufferGeometryManager.prototype = {
8 | addSharedBuffer: function (name, material) {
9 | var bufferGeometry = new SharedBufferGeometry(material);
10 | this.sharedBuffers[name] = bufferGeometry;
11 | },
12 |
13 | getSharedBuffer: function (name) {
14 | return this.sharedBuffers[name];
15 | }
16 | };
17 |
18 | module.exports = new SharedBufferGeometryManager();
19 |
--------------------------------------------------------------------------------
/src/systems/brush.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME THREE BinaryManager */
2 | var VERSION = 1;
3 |
4 | AFRAME.BRUSHES = {};
5 |
6 | APAINTER_STATS = {
7 | brushes: {}
8 | };
9 |
10 | AFRAME.registerBrush = function (name, definition, options) {
11 | var proto = {};
12 |
13 | // Format definition object to prototype object.
14 | Object.keys(definition).forEach(function (key) {
15 | proto[key] = {
16 | value: definition[key],
17 | writable: true
18 | };
19 | });
20 |
21 | if (AFRAME.BRUSHES[name]) {
22 | throw new Error('The brush `' + name + '` has been already registered. ' +
23 | 'Check that you are not loading two versions of the same brush ' +
24 | 'or two different brushes of the same name.');
25 | }
26 |
27 | var BrushInterface = function () {};
28 |
29 | var defaultOptions = {
30 | spacing: 0,
31 | maxPoints: 0
32 | };
33 |
34 | BrushInterface.prototype = {
35 | options: Object.assign(defaultOptions, options),
36 | reset: function () {},
37 | tick: function (timeoffset, delta) {},
38 | undo: function () {},
39 | remove: function () {},
40 | addPoint: function (position, orientation, pointerPosition, pressure, timestamp) {},
41 | getJSON: function (system) {
42 | var points = [];
43 | for (var i = 0; i < this.data.points.length; i++) {
44 | var point = this.data.points[i];
45 | points.push({
46 | 'orientation': Utils.arrayNumbersToFixed(point.orientation.toArray()),
47 | 'position': Utils.arrayNumbersToFixed(point.position.toArray()),
48 | 'pressure': Utils.numberToFixed(point.pressure),
49 | 'timestamp': point.timestamp
50 | });
51 | }
52 |
53 | return {
54 | brush: {
55 | index: system.getUsedBrushes().indexOf(this.brushName),
56 | color: Utils.arrayNumbersToFixed(this.data.color.toArray()),
57 | size: Utils.numberToFixed(this.data.size),
58 | },
59 | points: points
60 | };
61 | },
62 | getBinary: function (system) {
63 | // Color = 3*4 = 12
64 | // NumPoints = 4
65 | // Brush index = 1
66 | // ----------- = 21
67 | // [Point] = vector3 + quat + pressure + timestamp = (3+4+1+1)*4 = 36
68 |
69 | var bufferSize = 21 + (36 * this.data.points.length);
70 | var binaryManager = new BinaryManager(new ArrayBuffer(bufferSize));
71 | binaryManager.writeUint8(system.getUsedBrushes().indexOf(this.brushName)); // brush index
72 | binaryManager.writeColor(this.data.color); // color
73 | binaryManager.writeFloat32(this.data.size); // brush size
74 |
75 | // Number of points
76 | binaryManager.writeUint32(this.data.points.length);
77 |
78 | // Points
79 | for (var i = 0; i < this.data.points.length; i++) {
80 | var point = this.data.points[i];
81 | binaryManager.writeFloat32Array(point.position.toArray());
82 | binaryManager.writeFloat32Array(point.orientation.toArray());
83 | binaryManager.writeFloat32(point.pressure);
84 | binaryManager.writeUint32(point.timestamp);
85 | }
86 | return binaryManager.getDataView();
87 | }
88 | };
89 |
90 | function wrapInit (initMethod) {
91 | return function init (color, brushSize, owner, timestamp) {
92 | this.object3D = new THREE.Object3D();
93 | this.data = {
94 | points: [],
95 | size: brushSize,
96 | prevPosition: null,
97 | prevPointerPosition: null,
98 | numPoints: 0,
99 | color: color.clone(),
100 | timestamp: timestamp,
101 | owner: owner
102 | };
103 | initMethod.call(this, color, brushSize);
104 | };
105 | }
106 |
107 | function wrapAddPoint (addPointMethod) {
108 | return function addPoint (position, orientation, pointerPosition, pressure, timestamp) {
109 | if ((this.data.prevPosition && this.data.prevPosition.distanceTo(position) <= this.options.spacing) ||
110 | this.options.maxPoints !== 0 && this.data.numPoints >= this.options.maxPoints) {
111 | return;
112 | }
113 | if (addPointMethod.call(this, position, orientation, pointerPosition, pressure, timestamp)) {
114 | this.data.numPoints++;
115 | this.data.points.push({
116 | 'position': position.clone(),
117 | 'orientation': orientation.clone(),
118 | 'pressure': pressure,
119 | 'timestamp': timestamp
120 | });
121 |
122 | this.data.prevPosition = position.clone();
123 | this.data.prevPointerPosition = pointerPosition.clone();
124 | }
125 | };
126 | }
127 |
128 | var NewBrush = function () {};
129 | NewBrush.prototype = Object.create(BrushInterface.prototype, proto);
130 | NewBrush.prototype.brushName = name;
131 | NewBrush.prototype.constructor = NewBrush;
132 | NewBrush.prototype.init = wrapInit(NewBrush.prototype.init);
133 | NewBrush.prototype.addPoint = wrapAddPoint(NewBrush.prototype.addPoint);
134 | AFRAME.BRUSHES[name] = NewBrush;
135 |
136 | // console.log('New brush registered `' + name + '`');
137 | NewBrush.used = false; // Used to know which brushes have been used on the drawing
138 | return NewBrush;
139 | };
140 |
141 | AFRAME.registerSystem('brush', {
142 | schema: {},
143 | brushes: {},
144 | strokes: [],
145 | getUsedBrushes: function () {
146 | return Object.keys(AFRAME.BRUSHES)
147 | .filter(function (name) { return AFRAME.BRUSHES[name].used; });
148 | },
149 | getBrushByName: function (name) {
150 | return AFRAME.BRUSHES[name];
151 | },
152 | undo: function () {
153 | var stroke;
154 | for (var i = this.strokes.length - 1; i >= 0; i--) {
155 | if (this.strokes[i].data.owner !== 'local') continue;
156 | stroke = this.strokes.splice(i, 1)[0];
157 | break;
158 | }
159 | if (stroke) {
160 | stroke.undo();
161 | var drawing = document.querySelector('.a-drawing');
162 | drawing.emit('stroke-removed', {stroke: stroke});
163 | }
164 | },
165 | removeById: function (order) {
166 | order = 1;
167 | var targetStroke = this.strokes[order];
168 | console.log(targetStroke, this.strokes);
169 | if (targetStroke) {
170 | for (var i = this.strokes.length - 1; i > order; i--) {
171 | stroke = this.strokes[i];
172 | if (targetStroke.sharedBuffer === stroke.sharedBuffer) {
173 | // Update idx and prevIdx
174 | console.log('>>>', stroke.prevIdx, '->', stroke.idx, 'target', targetStroke.prevIdx, '->', targetStroke.idx);
175 | for (key in targetStroke.idx) {
176 | var diff = (targetStroke.idx[key] - targetStroke.prevIdx[key]);
177 | stroke.idx[key] -= diff;
178 | stroke.prevIdx[key] -= diff;
179 | }
180 | console.log('<<<', stroke.idx, stroke.prevIdx);
181 | }
182 | }
183 | this.strokes.splice(order, 1)[0].remove();
184 | }
185 | },
186 | clear: function () {
187 | // Remove all the stroke entities
188 | for (var i = this.strokes.length - 1; i >= 0; i--) {
189 | if(this.strokes[i].data.owner !== 'local') continue;
190 | var stroke = this.strokes[i];
191 | stroke.undo();
192 | var drawing = document.querySelector('.a-drawing');
193 | drawing.emit('stroke-removed', { stroke: stroke });
194 | }
195 |
196 | // Reset the used brushes
197 | Object.keys(AFRAME.BRUSHES).forEach(function (name) {
198 | AFRAME.BRUSHES[name].used = false;
199 | });
200 |
201 | this.strokes = [];
202 | },
203 | init: function () {
204 | this.version = VERSION;
205 | this.clear();
206 | this.controllerName = null;
207 |
208 | var self = this;
209 | this.sceneEl.addEventListener('controllerconnected', function (evt) {
210 | self.controllerName = evt.detail.name;
211 | });
212 | },
213 | tick: function (time, delta) {
214 | if (!this.strokes.length) { return; }
215 | for (var i = 0; i < this.strokes.length; i++) {
216 | this.strokes[i].tick(time, delta);
217 | }
218 | },
219 | generateTestLines: function () {
220 | function randNeg() { return 2 * Math.random() - 1; }
221 | var z = -2;
222 | var size = 0.5;
223 | var width = 3;
224 | var pressure = 1;
225 | var numPoints = 4;
226 |
227 | var steps = width / numPoints;
228 | var numStrokes = 1;
229 | var brushesNames = Object.keys(AFRAME.BRUSHES);
230 |
231 | brushesNames2 = [
232 | 'leaf1',
233 | 'fur2',
234 | 'star',
235 | 'squared-textured',
236 | 'flat',
237 | 'squared-textured',
238 | 'lines5'
239 | ];
240 |
241 | var x = -(size + 0.1) * brushesNames.length / 2;
242 | x= 0;
243 | var y = 0;
244 | brushesNames.forEach(function (brushName) {
245 | var color = new THREE.Color(Math.random(), Math.random(), Math.random());
246 |
247 | var stroke = this.addNewStroke(brushName, color, size);
248 | var entity = document.querySelector('#left-hand');
249 | entity.emit('stroke-started', { entity: entity, stroke: stroke });
250 |
251 | var position = new THREE.Vector3(x, y, z);
252 | var aux = new THREE.Vector3();
253 |
254 | for (var i = 0; i < numPoints; i++) {
255 | var orientation = new THREE.Quaternion();
256 | aux.set(0, steps, 0.1);
257 | var euler = new THREE.Euler(0, Math.PI, 0);
258 | orientation.setFromEuler(euler);
259 | position = position.add(aux);
260 | var timestamp = 0;
261 |
262 | var pointerPosition = this.getPointerPosition(position, orientation);
263 | stroke.addPoint(position, orientation, pointerPosition, pressure, timestamp);
264 | }
265 |
266 | x+= size + 0.1;
267 | });
268 | },
269 | generateRandomStrokes: function (numStrokes) {
270 | function randNeg () { return 2 * Math.random() - 1; }
271 |
272 | var entity = document.querySelector('#left-hand');
273 |
274 | var brushesNames = Object.keys(AFRAME.BRUSHES);
275 |
276 | for (var l = 0; l < numStrokes; l++) {
277 | //var brushName = brushesNames[parseInt(Math.random() * 30)];
278 | var brushName = brushesNames[parseInt(Math.random() * 13)];
279 | var color = new THREE.Color(Math.random(), Math.random(), Math.random());
280 | var size = Math.random() * 0.3;
281 | var numPoints = parseInt(Math.random() * 500);
282 |
283 | var stroke = this.addNewStroke(brushName, color, size);
284 | entity.emit('stroke-started', {entity: entity, stroke: stroke});
285 |
286 | var position = new THREE.Vector3(randNeg(), randNeg(), randNeg());
287 | var aux = new THREE.Vector3();
288 | var orientation = new THREE.Quaternion();
289 |
290 | var pressure = 0.2;
291 | for (var i = 0; i < numPoints; i++) {
292 | aux.set(randNeg(), randNeg(), randNeg());
293 | aux.multiplyScalar(randNeg() / 20);
294 | orientation.setFromUnitVectors(position.clone().normalize(), aux.clone().normalize());
295 | position = position.add(aux);
296 | if (position.y < 0) {
297 | position.y = -position.y;
298 | }
299 | var timestamp = 0;
300 | pressure += 1 - 2 * Math.random();
301 | if (pressure < 0) pressure = 0.2;
302 | if (pressure > 1) pressure = 1;
303 |
304 | var pointerPosition = this.getPointerPosition(position, orientation);
305 | stroke.addPoint(position, orientation, pointerPosition, pressure, timestamp);
306 | }
307 | }
308 | },
309 | addNewStroke: function (brushName, color, size, owner, timestamp) {
310 | if (!APAINTER_STATS.brushes[brushName]) {
311 | APAINTER_STATS.brushes[brushName] = 0;
312 | }
313 | APAINTER_STATS.brushes[brushName]++;
314 |
315 | owner = owner || 'local';
316 | timestamp = timestamp || Date.now();
317 | var Brush = this.getBrushByName(brushName);
318 | if (!Brush) {
319 | var newBrushName = Object.keys(AFRAME.BRUSHES)[0];
320 | Brush = AFRAME.BRUSHES[newBrushName];
321 | console.warn('Invalid brush name: `' + brushName + '` using `' + newBrushName + '`');
322 | }
323 |
324 | Brush.used = true;
325 | var stroke = new Brush();
326 | stroke.brush = Brush;
327 | stroke.init(color, size, owner, timestamp);
328 | this.strokes.push(stroke);
329 |
330 | var drawing = document.querySelector('.a-drawing');
331 | if (!drawing) {
332 | drawing = document.createElement('a-entity');
333 | drawing.className = "a-drawing";
334 | document.querySelector('a-scene').appendChild(drawing);
335 | }
336 |
337 | //var entity = document.createElement('a-entity');
338 | //entity.className = "a-stroke";
339 | //drawing.appendChild(entity);
340 | // drawing.object3D.add(stroke.object3D);
341 | //entity.setObject3D('mesh', stroke.object3D);
342 | //stroke.entity = entity;
343 |
344 | return stroke;
345 | },
346 | getJSON: function () {
347 | // Strokes
348 | var json = {
349 | version: VERSION,
350 | strokes: [],
351 | author: '',
352 | brushes: this.getUsedBrushes()
353 | };
354 |
355 | for (i = 0; i < this.strokes.length; i++) {
356 | json.strokes.push(this.strokes[i].getJSON(this));
357 | }
358 |
359 | return json;
360 | },
361 | getBinary: function () {
362 | var dataViews = [];
363 | var MAGIC = 'apainter';
364 |
365 | // Used brushes
366 | var usedBrushes = this.getUsedBrushes();
367 |
368 | // MAGIC(8) + version (2) + usedBrushesNum(2) + usedBrushesStrings(*)
369 | var bufferSize = MAGIC.length + usedBrushes.join(' ').length + 9;
370 | var binaryManager = new BinaryManager(new ArrayBuffer(bufferSize));
371 |
372 | // Header magic and version
373 | binaryManager.writeString(MAGIC);
374 | binaryManager.writeUint16(VERSION);
375 |
376 | binaryManager.writeUint8(usedBrushes.length);
377 | for (var i = 0; i < usedBrushes.length; i++) {
378 | binaryManager.writeString(usedBrushes[i]);
379 | }
380 |
381 | // Number of strokes
382 | binaryManager.writeUint32(this.strokes.length);
383 | dataViews.push(binaryManager.getDataView());
384 |
385 | // Strokes
386 | for (i = 0; i < this.strokes.length; i++) {
387 | dataViews.push(this.strokes[i].getBinary(this));
388 | }
389 | return dataViews;
390 | },
391 | getPointerPosition: (function () {
392 | var pointerPosition = new THREE.Vector3();
393 | var controllerOffset = {
394 | 'vive-controls': {
395 | vec: {
396 | left: new THREE.Vector3(0, 0.7, 1),
397 | right: new THREE.Vector3(0, 0.7, 1),
398 | },
399 | mult: -0.03
400 | },
401 | 'oculus-touch-controls': {
402 | vec: {
403 | left: new THREE.Vector3(-2, 0, 2.8),
404 | right: new THREE.Vector3(2, 0, 2.8)
405 | },
406 | mult: -0.025
407 | },
408 | 'windows-motion-controls': {
409 | vec: {
410 | left: new THREE.Vector3(0, 0, 1),
411 | right: new THREE.Vector3(0, 0, 1),
412 | },
413 | mult: -.12
414 | }
415 | };
416 |
417 | return function getPointerPosition (position, orientation, hand) {
418 | if (!this.controllerName) {
419 | return position;
420 | }
421 |
422 | var offsets = controllerOffset[this.controllerName];
423 | var pointer = offsets.vec[hand]
424 | .clone()
425 | .applyQuaternion(orientation)
426 | .normalize()
427 | .multiplyScalar(offsets.mult);
428 | pointerPosition.copy(position).add(pointer);
429 | return pointerPosition;
430 | };
431 | })(),
432 | loadJSON: function (data) {
433 | if (data.version !== VERSION) {
434 | console.error('Invalid version: ', data.version, '(Expected: ' + VERSION + ')');
435 | }
436 |
437 | console.time('JSON Loading');
438 |
439 | var usedBrushes = [];
440 |
441 | for (var i = 0; i < data.strokes.length; i++) {
442 | var strokeData = data.strokes[i];
443 | var brush = strokeData.brush;
444 |
445 | var stroke = this.addNewStroke(
446 | data.brushes[brush.index],
447 | new THREE.Color().fromArray(brush.color),
448 | brush.size
449 | );
450 |
451 | for (var j = 0; j < strokeData.points.length; j++) {
452 | var point = strokeData.points[j];
453 |
454 | var position = new THREE.Vector3().fromArray(point.position);
455 | var orientation = new THREE.Quaternion().fromArray(point.orientation);
456 | var pressure = point.pressure;
457 | var timestamp = point.timestamp;
458 |
459 | var pointerPosition = this.getPointerPosition(position, orientation);
460 | stroke.addPoint(position, orientation, pointerPosition, pressure, timestamp);
461 | }
462 | }
463 |
464 | console.timeEnd('JSON Loading');
465 | },
466 | loadBinary: function (buffer) {
467 | var binaryManager = new BinaryManager(buffer);
468 | var magic = binaryManager.readString();
469 | if (magic !== 'apainter') {
470 | console.error('Invalid `magic` header');
471 | return;
472 | }
473 |
474 | var version = binaryManager.readUint16();
475 | if (version !== VERSION) {
476 | console.error('Invalid version: ', version, '(Expected: ' + VERSION + ')');
477 | }
478 |
479 | console.time('Binary Loading');
480 |
481 | var numUsedBrushes = binaryManager.readUint8();
482 | var usedBrushes = [];
483 | for (var b = 0; b < numUsedBrushes; b++) {
484 | usedBrushes.push(binaryManager.readString());
485 | }
486 |
487 | var numStrokes = binaryManager.readUint32();
488 |
489 | for (var l = 0; l < numStrokes; l++) {
490 | var brushIndex = binaryManager.readUint8();
491 | var color = binaryManager.readColor();
492 | var size = binaryManager.readFloat();
493 | var numPoints = binaryManager.readUint32();
494 |
495 | var stroke = this.addNewStroke(usedBrushes[brushIndex], color, size);
496 |
497 | for (var i = 0; i < numPoints; i++) {
498 | var position = binaryManager.readVector3();
499 | var orientation = binaryManager.readQuaternion();
500 | var pressure = binaryManager.readFloat();
501 | var timestamp = binaryManager.readUint32();
502 |
503 | var pointerPosition = this.getPointerPosition(position, orientation);
504 | stroke.addPoint(position, orientation, pointerPosition, pressure, timestamp);
505 | }
506 | }
507 | console.timeEnd('Binary Loading');
508 | },
509 | loadFromUrl: function (url, binary) {
510 | var loader = new THREE.XHRLoader(this.manager);
511 | loader.crossOrigin = 'anonymous';
512 | if (binary === true) {
513 | loader.setResponseType('arraybuffer');
514 | }
515 |
516 | var self = this;
517 |
518 | loader.load(url, function (buffer) {
519 | if (binary === true) {
520 | self.loadBinary(buffer);
521 | } else {
522 | self.loadJSON(JSON.parse(buffer));
523 | }
524 | });
525 | }
526 | });
527 |
--------------------------------------------------------------------------------
/src/systems/painter.js:
--------------------------------------------------------------------------------
1 | /* global AFRAME Blob uploadcare */
2 |
3 | var saveAs = require('../../vendor/saveas.js').saveAs;
4 |
5 | AFRAME.registerSystem('painter', {
6 | init: function () {
7 | var mappings = {
8 | behaviours: {},
9 | mappings: {
10 | painting: {
11 | common: {
12 | 'grip.down': 'undo',
13 | 'trigger.changed': 'paint'
14 | },
15 |
16 | 'vive-controls': {
17 | 'axismove': 'changeBrushSizeInc',
18 | 'trackpad.touchstart': 'startChangeBrushSize',
19 | 'menu.down': 'toggleMenu',
20 |
21 | // Teleport
22 | 'trackpad.down': 'aim',
23 | 'trackpad.up': 'teleport'
24 | },
25 |
26 | 'oculus-touch-controls': {
27 | 'axismove': 'changeBrushSizeAbs',
28 | 'abutton.down': 'toggleMenu',
29 | 'xbutton.down': 'toggleMenu',
30 |
31 | // Teleport
32 | 'ybutton.down': 'aim',
33 | 'ybutton.up': 'teleport',
34 |
35 | 'bbutton.down': 'aim',
36 | 'bbutton.up': 'teleport'
37 | },
38 |
39 | 'windows-motion-controls': {
40 | 'axismove': 'changeBrushSizeAbs',
41 | 'menu.down': 'toggleMenu',
42 |
43 | // Teleport
44 | 'trackpad.down': 'aim',
45 | 'trackpad.up': 'teleport'
46 | },
47 | }
48 | }
49 | };
50 |
51 | this.sceneEl.addEventListener('loaded', function() {
52 | AFRAME.registerInputMappings(mappings);
53 | AFRAME.currentInputMapping = 'painting';
54 | });
55 |
56 | this.version = '1.2';
57 | this.brushSystem = this.sceneEl.systems.brush;
58 | this.showTemplateItems = true;
59 |
60 | function getUrlParams () {
61 | var match;
62 | var pl = /\+/g; // Regex for replacing addition symbol with a space
63 | var search = /([^&=]+)=?([^&]*)/g;
64 | var decode = function (s) { return decodeURIComponent(s.replace(pl, ' ')); };
65 | var query = window.location.search.substring(1);
66 | var urlParams = {};
67 |
68 | match = search.exec(query);
69 | while (match) {
70 | urlParams[decode(match[1])] = decode(match[2]);
71 | match = search.exec(query);
72 | }
73 | return urlParams;
74 | }
75 | var urlParams = getUrlParams();
76 | if (urlParams.url || urlParams.urljson) {
77 | var isBinary = urlParams.urljson === undefined;
78 | this.brushSystem.loadFromUrl(urlParams.url || urlParams.urljson, isBinary);
79 | document.getElementById('logo').setAttribute('visible', false);
80 | document.getElementById('acamera').setAttribute('orbit-controls', 'initialPosition: 0 1.6 3');
81 | document.getElementById('apainter-logo').classList.remove('hidden');
82 | //document.getElementById('apainter-author').classList.remove('hidden'); // not used yet
83 | } else { // No painting to load, move camera in front of logo
84 | const cameraEl = document.getElementById('acamera')
85 | cameraEl.setAttribute('position', '0 1.6 0');
86 | }
87 |
88 | if (urlParams.bgcolor !== undefined) {
89 | document.body.style.backgroundColor = '#' + urlParams.bgcolor;
90 | }
91 | if (urlParams.sky !== undefined) {
92 | this.sceneEl.addEventListener('loaded', function (evt) {
93 | if (urlParams.sky === '') {
94 | document.getElementById('sky').setAttribute('visible', false);
95 | } else {
96 | document.getElementById('sky').setAttribute('material', 'src', urlParams.sky);
97 | }
98 | });
99 | }
100 | if (urlParams.floor !== undefined) {
101 | this.sceneEl.addEventListener('loaded', function (evt) {
102 | if (urlParams.floor === '') {
103 | document.getElementById('ground').setAttribute('visible', false);
104 | } else {
105 | document.getElementById('ground').setAttribute('material', 'src', urlParams.floor);
106 | }
107 | });
108 | }
109 |
110 | this.paintingStarted = false;
111 | var self = this;
112 | document.addEventListener('stroke-started', function (event) {
113 | if (!self.paintingStarted) {
114 | const logoEl = document.getElementById('logo');
115 | logoEl.emit('painting-started');
116 | self.paintingStarted = true;
117 | }
118 | });
119 |
120 | // @fixme This is just for debug until we'll get some UI
121 | document.addEventListener('keyup', function (event) {
122 | if(event.shiftKey || event.ctrlKey) return;
123 | if (event.keyCode === 8) {
124 | // Undo (Backspace)
125 | self.brushSystem.undo();
126 | }
127 | if (event.keyCode === 67) {
128 | // Clear (c)
129 | self.brushSystem.clear();
130 | }
131 | if (event.keyCode === 71) {
132 | // Export to GTF (g)
133 | var drawing = document.querySelector('.a-drawing');
134 | self.sceneEl.systems['gltf-exporter'].export(drawing);
135 | }
136 | if (event.keyCode === 78) {
137 | // Next brush (n)
138 | var hands = document.querySelectorAll('[paint-controls]');
139 | var brushesNames = Object.keys(AFRAME.BRUSHES);
140 | var index = brushesNames.indexOf(hands[0].components.brush.data.brush);
141 | index = (index + 1) % brushesNames.length;
142 | [].forEach.call(hands, function (hand) {
143 | hand.setAttribute('brush', 'brush', brushesNames[index]);
144 | });
145 | }
146 |
147 | if (event.keyCode === 84) {
148 | // Random stroke (t)
149 | self.brushSystem.generateTestLines();
150 | }
151 |
152 | if (event.keyCode === 82) {
153 | // Random stroke (r)
154 | self.brushSystem.generateRandomStrokes(1);
155 | }
156 | if (event.keyCode === 76) {
157 | // load binary from file (l)
158 | self.brushSystem.loadFromUrl('demo.apa', true);
159 | }
160 | if (event.keyCode === 85) { // u - upload
161 | self.upload();
162 | }
163 | if (event.keyCode === 86) { // v - save
164 | self.save();
165 | }
166 | if (event.keyCode === 74) { // j - save json
167 | self.saveJSON();
168 | }
169 | if (event.keyCode === 79) { // o - toggle template objects+images visibility
170 | self.showTemplateItems = !self.showTemplateItems;
171 | var templateItems = document.querySelectorAll('.templateitem');
172 | for (var i = 0; i < templateItems.length; i++) {
173 | templateItems[i].setAttribute('visible', self.showTemplateItems);
174 | }
175 | }
176 | if (event.keyCode === 88) { // x remove 2nd
177 | self.brushSystem.removeById(2);
178 | }
179 | });
180 |
181 | console.info('A-PAINTER Version: ' + this.version);
182 | },
183 | saveJSON: function () {
184 | var json = this.brushSystem.getJSON();
185 | var blob = new Blob([JSON.stringify(json)], {type: 'application/json'});
186 | saveAs(blob, 'demo.json');
187 | },
188 | save: function () {
189 | var dataviews = this.brushSystem.getBinary();
190 | var blob = new Blob(dataviews, {type: 'application/octet-binary'});
191 | saveAs(blob, 'demo.apa');
192 | },
193 | upload: function () {
194 | this.sceneEl.emit('drawing-upload-started');
195 | var self = this;
196 |
197 | var baseUrl = 'https://aframe.io/a-painter/?url=';
198 |
199 | var dataviews = this.brushSystem.getBinary();
200 | var blob = new Blob(dataviews, {type: 'application/octet-binary'});
201 |
202 | const cloudName = 'a-painter';
203 | const unsignedUploadPreset = 'upload_painting';
204 | const url = `https://api.cloudinary.com/v1_1/${cloudName}/upload`;
205 | const fd = new FormData();
206 |
207 | fd.append('upload_preset', unsignedUploadPreset);
208 | fd.append('file', blob);
209 | fetch(url, { method: 'POST', body: fd }).then(res => {
210 | if (!res.ok) {
211 | throw new Error(`Network request failed with status ${res.status}: ${res.statusText}`);
212 | }
213 | return res.json();
214 | }).then(json => {
215 | console.log('Uploaded link: ', baseUrl + json.secure_url);
216 | self.sceneEl.emit('drawing-upload-completed', { url: baseUrl + json.secure_url });
217 | }).catch(err => {
218 | console.error(err);
219 | self.sceneEl.emit('drawing-upload-error', { errorInfo: err });
220 | })
221 | }
222 | });
223 |
--------------------------------------------------------------------------------
/src/systems/ui.js:
--------------------------------------------------------------------------------
1 | /* globals AFRAME */
2 | AFRAME.registerSystem('ui', {
3 | init: function () {
4 | this.initTextures();
5 | },
6 |
7 | initTextures: function () {
8 | var self = this;
9 | var hoverTextureUrl = 'assets/images/ui-hover.png';
10 | var pressedTextureUrl = 'assets/images/ui-pressed.png';
11 | this.sceneEl.systems.material.loadTexture(hoverTextureUrl, {src: hoverTextureUrl}, onLoadedHoverTexture);
12 | this.sceneEl.systems.material.loadTexture(pressedTextureUrl, {src: pressedTextureUrl}, onLoadedPressedTexture);
13 | function onLoadedHoverTexture (texture) {
14 | self.hoverTexture = texture;
15 | }
16 | function onLoadedPressedTexture (texture) {
17 | self.pressedTexture = texture;
18 | }
19 | },
20 |
21 | closeAll: function () {
22 | var els = document.querySelectorAll('[ui]');
23 | var i;
24 | for (i = 0; i < els.length; i++) {
25 | els[i].components.ui.close();
26 | }
27 | }
28 | });
29 |
--------------------------------------------------------------------------------
/src/ui2d.js:
--------------------------------------------------------------------------------
1 | /* global Clipboard */
2 | window.addEventListener('load', function (event) {
3 | var apainterUI = document.getElementById('apainter-ui');
4 | var shareDiv = document.querySelector('#apainter-ui .share');
5 | var shareUrl = document.getElementById('apainter-share-url');
6 | var progressDiv = document.querySelector('#apainter-ui .progress');
7 | var progressBar = document.querySelector('#apainter-ui .bar');
8 | document.addEventListener('drawing-upload-completed', function (event) {
9 | shareDiv.classList.remove('hide');
10 | progressDiv.classList.add('hide');
11 | shareUrl.value = event.detail.url;
12 | });
13 |
14 | document.addEventListener('drawing-upload-started', function (event) {
15 | apainterUI.style.display = 'block';
16 | shareDiv.classList.add('hide');
17 | progressDiv.classList.remove('hide');
18 | });
19 |
20 | document.addEventListener('drawing-upload-progress', function (event) {
21 | progressBar.style.width = Math.floor(event.detail.progress * 100) + '%';
22 | });
23 |
24 | var clipboard = new Clipboard('.button.copy');
25 | clipboard.on('error', function (e) {
26 | console.error('Error copying to clipboard:', e.action, e.trigger);
27 | });
28 | });
29 |
--------------------------------------------------------------------------------
/src/utils.js:
--------------------------------------------------------------------------------
1 | window.Utils = (function() {
2 | const DIGITS = 6;
3 | function numberToFixed (number) {
4 | return parseFloat(number.toFixed(DIGITS));
5 | }
6 |
7 | function arrayNumbersToFixed (array) {
8 | for (var i = 0; i < array.length; i++) {
9 | array[i] = numberToFixed(array[i]);
10 | }
11 | return array;
12 | }
13 |
14 | function getTooltips (controllerName) {
15 | var tooltips;
16 | var tooltipName;
17 | switch (controllerName) {
18 | case 'windows-motion-samsung-controls': {
19 | tooltipName = '.windows-motion-samsung-tooltips';
20 | break;
21 | }
22 | case 'windows-motion-controls': {
23 | tooltipName = '.windows-motion-tooltips';
24 | break;
25 | }
26 | case 'oculus-touch-controls': {
27 | tooltipName = '.oculus-tooltips';
28 | break;
29 | }
30 | case 'vive-controls': {
31 | tooltipName = '.vive-tooltips';
32 | break;
33 | }
34 | default: {
35 | break;
36 | }
37 | }
38 |
39 | tooltips = Array.prototype.slice.call(document.querySelectorAll(tooltipName));
40 | return tooltips;
41 | }
42 |
43 | return {
44 | numberToFixed: numberToFixed,
45 | arrayNumbersToFixed: arrayNumbersToFixed,
46 | getTooltips: getTooltips
47 | }
48 | }());
49 |
--------------------------------------------------------------------------------
/vendor/aframe-input-mapping-component.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 | /******/
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 | /******/
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId]) {
10 | /******/ return installedModules[moduleId].exports;
11 | /******/ }
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ i: moduleId,
15 | /******/ l: false,
16 | /******/ exports: {}
17 | /******/ };
18 | /******/
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 | /******/
22 | /******/ // Flag the module as loaded
23 | /******/ module.l = true;
24 | /******/
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 | /******/
29 | /******/
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 | /******/
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 | /******/
36 | /******/ // define getter function for harmony exports
37 | /******/ __webpack_require__.d = function(exports, name, getter) {
38 | /******/ if(!__webpack_require__.o(exports, name)) {
39 | /******/ Object.defineProperty(exports, name, {
40 | /******/ configurable: false,
41 | /******/ enumerable: true,
42 | /******/ get: getter
43 | /******/ });
44 | /******/ }
45 | /******/ };
46 | /******/
47 | /******/ // getDefaultExport function for compatibility with non-harmony modules
48 | /******/ __webpack_require__.n = function(module) {
49 | /******/ var getter = module && module.__esModule ?
50 | /******/ function getDefault() { return module['default']; } :
51 | /******/ function getModuleExports() { return module; };
52 | /******/ __webpack_require__.d(getter, 'a', getter);
53 | /******/ return getter;
54 | /******/ };
55 | /******/
56 | /******/ // Object.prototype.hasOwnProperty.call
57 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };
58 | /******/
59 | /******/ // __webpack_public_path__
60 | /******/ __webpack_require__.p = "";
61 | /******/
62 | /******/ // Load entry module and return exports
63 | /******/ return __webpack_require__(__webpack_require__.s = 0);
64 | /******/ })
65 | /************************************************************************/
66 | /******/ ([
67 | /* 0 */
68 | /***/ (function(module, exports, __webpack_require__) {
69 |
70 | "use strict";
71 |
72 |
73 | var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; };
74 |
75 | /* global AFRAME */
76 |
77 | if (typeof AFRAME === 'undefined') {
78 | throw new Error('Component attempted to register before AFRAME was available.');
79 | }
80 |
81 | __webpack_require__(1);
82 | __webpack_require__(6);
83 |
84 | AFRAME.currentInputMapping = null;
85 | AFRAME.inputMappings = {};
86 | AFRAME.inputActions = {};
87 |
88 | var behaviour = {
89 | trackpad: 'dpad'
90 | };
91 |
92 | AFRAME.registerSystem('input-mapping', {
93 | mappings: {},
94 | mappingsPerControllers: {},
95 | loadedControllers: [],
96 |
97 | init: function init() {
98 | var self = this;
99 |
100 | this.keyboardHandler = this.keyboardHandler.bind(this);
101 |
102 | this.sceneEl.addEventListener('inputmappingregistered', function () {
103 | self.removeControllersListeners();
104 | for (var i = 0; i < self.loadedControllers.length; i++) {
105 | var controllerObj = self.loadedControllers[i];
106 | self.updateControllersListeners(controllerObj);
107 | }
108 | });
109 |
110 | // Controllers
111 | this.sceneEl.addEventListener('controllerconnected', function (event) {
112 | var matchedController = self.findMatchingController(event.target);
113 |
114 | if (matchedController) {
115 | self.updateControllersListeners(matchedController);
116 | return;
117 | }
118 |
119 | var controllerObj = {
120 | name: event.detail.name,
121 | hand: event.detail.component.data.hand,
122 | element: event.target,
123 | handlers: {},
124 | activators: {}
125 | };
126 | self.loadedControllers.push(controllerObj);
127 |
128 | self.updateControllersListeners(controllerObj);
129 | });
130 |
131 | this.sceneEl.addEventListener('controllerdisconnected', function (event) {
132 | var controller = self.findMatchingController(event.target);
133 | if (controller) {
134 | self.removeControllerListeners(controller);
135 | }
136 | });
137 |
138 | // Keyboard
139 | this.addKeyboardListeners();
140 | },
141 |
142 | findMatchingController: function findMatchingController(matchElement) {
143 | var controller;
144 | var i;
145 | for (i = 0; i < this.loadedControllers.length; i++) {
146 | controller = this.loadedControllers[i];
147 | if (controller.element === matchElement) {
148 | return controller;
149 | }
150 | }
151 | return undefined;
152 | },
153 |
154 | addKeyboardListeners: function addKeyboardListeners() {
155 | document.addEventListener('keyup', this.keyboardHandler);
156 | document.addEventListener('keydown', this.keyboardHandler);
157 | document.addEventListener('keypress', this.keyboardHandler);
158 | },
159 |
160 | removeKeyboardListeners: function removeKeyboardListeners() {
161 | document.removeEventListener('keyup', this.keyboardHandler);
162 | document.removeEventListener('keydown', this.keyboardHandler);
163 | document.removeEventListener('keypress', this.keyboardHandler);
164 | },
165 |
166 | removeControllerListeners: function removeControllerListeners(controller) {
167 | // Remove events handlers
168 | for (var eventName in controller.handlers) {
169 | var handler = controller.handlers[eventName];
170 | controller.element.removeEventListener(eventName, handler);
171 | }
172 | controller.handlers = {};
173 |
174 | // Remove activators
175 | for (var activatorName in controller.activators) {
176 | var activator = controller.activators[activatorName];
177 | activator.removeListeners();
178 | }
179 |
180 | controller.activators = {};
181 | },
182 |
183 | updateBehaviours: function updateBehaviours(controllerObj) {
184 | var controllerBehaviour = AFRAME.inputBehaviours[controllerObj.name];
185 | var behavioursPerController = this.mappingsPerControllers[controllerObj.name].behaviours;
186 | if (!behavioursPerController) {
187 | return;
188 | }
189 | for (var button in behavioursPerController) {
190 | var behaviourName = behavioursPerController[button];
191 | var behaviourDefinition = AFRAME.inputBehaviours[behaviourName];
192 | if (behaviourDefinition) {
193 | var behaviour = new behaviourDefinition(controllerObj.element, button);
194 | }
195 | }
196 | },
197 |
198 | updateControllersListeners: function updateControllersListeners(controllerObj) {
199 | this.removeControllerListeners(controllerObj);
200 |
201 | if (!AFRAME.inputMappings) {
202 | console.warn('input-mapping: No mappings defined');
203 | return;
204 | }
205 |
206 | var mappingsPerController = this.mappingsPerControllers[controllerObj.name] = {
207 | mappings: {},
208 | behaviours: {}
209 | };
210 |
211 | // Create the listener for each event
212 | for (var mappingName in AFRAME.inputMappings.mappings) {
213 | var mapping = AFRAME.inputMappings.mappings[mappingName];
214 |
215 | var commonMappings = mapping.common;
216 | if (commonMappings) {
217 | this.updateMappingsPerController(commonMappings, mappingsPerController, mappingName);
218 | }
219 |
220 | var controllerMappings = mapping[controllerObj.name];
221 | if (controllerMappings) {
222 | this.updateMappingsPerController(controllerMappings, mappingsPerController, mappingName);
223 | } else {
224 | console.warn('input-mapping: No mappings defined for controller type: ', controllerObj.name);
225 | }
226 | }
227 |
228 | // Mapping the behaviours
229 | for (var mappingName in AFRAME.inputMappings.behaviours) {
230 | var behaviour = AFRAME.inputMappings.behaviours[mappingName];
231 |
232 | var controllerBehaviours = behaviour[controllerObj.name];
233 | if (controllerBehaviours) {
234 | this.updateBehavioursPerController(controllerBehaviours, mappingsPerController, mappingName);
235 | }
236 | }
237 |
238 | var self = this;
239 |
240 | var OnActivate = function OnActivate(eventName) {
241 | return function (event) {
242 | var mapping = mappingsPerController.mappings[eventName];
243 | var mappedEvent = mapping[AFRAME.currentInputMapping];
244 | if ((typeof mappedEvent === 'undefined' ? 'undefined' : _typeof(mappedEvent)) === 'object') {
245 | // Handedness
246 | var controller = self.findMatchingController(event.target);
247 | mappedEvent = mappedEvent[controller.hand];
248 | if (!mappedEvent) {
249 | return;
250 | }
251 | }
252 | event.target.emit(mappedEvent, event.detail);
253 | };
254 | };
255 |
256 | for (var eventName in mappingsPerController.mappings) {
257 | // Check for activators
258 | if (eventName.indexOf('.') !== -1) {
259 | var aux = eventName.split('.');
260 | var button = aux[0]; // eg: trackpad
261 | var activatorName = aux[1]; // eg: doublepress
262 | var onActivate = OnActivate(eventName);
263 | var Activator = AFRAME.inputActivators[activatorName];
264 | controllerObj.activators[eventName] = new Activator(controllerObj.element, button, onActivate);
265 | }
266 |
267 | var onActivate = OnActivate(eventName);
268 | controllerObj.element.addEventListener(eventName, onActivate);
269 | controllerObj.handlers[eventName] = onActivate;
270 | }
271 |
272 | this.updateBehaviours(controllerObj);
273 | },
274 |
275 | checkValidInputMapping: function checkValidInputMapping() {
276 | if (AFRAME.currentInputMapping === null) {
277 | console.warn('input-mapping: No current input mapping defined.');
278 | }
279 | },
280 |
281 | keyboardHandler: function keyboardHandler(event) {
282 | this.checkValidInputMapping();
283 | if (AFRAME.inputMappings && AFRAME.inputMappings.mappings[AFRAME.currentInputMapping] && AFRAME.inputMappings.mappings[AFRAME.currentInputMapping].keyboard) {
284 | var currentKeyboardMapping = AFRAME.inputMappings.mappings[AFRAME.currentInputMapping].keyboard;
285 | var key = event.keyCode === 32 ? "Space" : event.key;
286 | var keyEvent = (key + "_" + event.type.substr(3)).toLowerCase();
287 | var mapEvent = currentKeyboardMapping[keyEvent];
288 | if (mapEvent) {
289 | this.sceneEl.emit(mapEvent);
290 | }
291 | }
292 | },
293 |
294 | updateBehavioursPerController: function updateBehavioursPerController(behaviours, mappingsPerController, mappingName) {
295 | for (var button in behaviours) {
296 | var behaviour = behaviours[button];
297 |
298 | if (!mappingsPerController.behaviours[button]) {
299 | mappingsPerController.behaviours[button] = behaviour;
300 | }
301 | }
302 | },
303 |
304 | updateMappingsPerController: function updateMappingsPerController(mappings, mappingsPerController, mappingName) {
305 | // Generate a mapping for each controller: (Eg: vive-controls.triggerdown.default.paint)
306 | for (var eventName in mappings) {
307 | var mapping = mappings[eventName];
308 | if (!mappingsPerController.mappings[eventName]) {
309 | mappingsPerController.mappings[eventName] = {};
310 | }
311 | mappingsPerController.mappings[eventName][mappingName] = mapping;
312 | }
313 | },
314 |
315 | removeControllersListeners: function removeControllersListeners() {
316 | for (var i = 0; i < this.loadedControllers.length; i++) {
317 | var controller = this.loadedControllers[i];
318 | this.removeControllerListeners(controller);
319 | }
320 | this.mappingsPerControllers = {
321 | mappings: {},
322 | behaviours: {}
323 | };
324 | }
325 | });
326 |
327 | AFRAME.registerInputActions = function (inputActions, defaultActionSet) {
328 | AFRAME.inputActions = inputActions;
329 | if (typeof defaultActionSet !== 'undefined') {
330 | if (AFRAME.inputActions[defaultActionSet]) {
331 | AFRAME.currentInputMapping = defaultActionSet;
332 | } else {
333 | console.error('input-mapping: trying to set a non registered action set \'' + defaultActionSet + '\'');
334 | }
335 | }
336 | };
337 |
338 | AFRAME.registerInputMappings = function (data, override) {
339 | if (override || Object.keys(AFRAME.inputMappings).length === 0) {
340 | AFRAME.inputMappings = data;
341 | } else {
342 | // @todo Merge behaviours too
343 | AFRAME.inputMappings.behaviours = data.behaviours;
344 |
345 | // Merge mappings
346 | for (var mappingName in data.mappings) {
347 | var mapping = data.mappings[mappingName];
348 | if (!AFRAME.inputMappings[mappingName]) {
349 | AFRAME.inputMappings[mappingName] = mapping;
350 | continue;
351 | }
352 |
353 | for (var controllerName in mapping) {
354 | var controllerMapping = mapping[controllerName];
355 | if (!AFRAME.inputMappings[mappingName][controllerName]) {
356 | AFRAME.inputMappings[mappingName][controllerName] = controllerMapping;
357 | continue;
358 | }
359 |
360 | for (var eventName in controllerMapping) {
361 | AFRAME.inputMappings[mappingName][controllerName][eventName] = controllerMapping[eventName];
362 | }
363 | }
364 | }
365 | }
366 |
367 | if (!AFRAME.scenes) {
368 | return;
369 | }
370 |
371 | for (var i = 0; i < AFRAME.scenes.length; i++) {
372 | AFRAME.scenes[i].emit('inputmappingregistered');
373 | }
374 | };
375 |
376 | /***/ }),
377 | /* 1 */
378 | /***/ (function(module, exports, __webpack_require__) {
379 |
380 | "use strict";
381 |
382 |
383 | AFRAME.inputActivators = {};
384 |
385 | AFRAME.registerInputActivator = function (name, definition) {
386 | AFRAME.inputActivators[name] = definition;
387 | };
388 |
389 | __webpack_require__(2);
390 | __webpack_require__(3);
391 | __webpack_require__(4);
392 | __webpack_require__(5);
393 |
394 | /***/ }),
395 | /* 2 */
396 | /***/ (function(module, exports, __webpack_require__) {
397 |
398 | "use strict";
399 |
400 |
401 | function LongPress(el, button, onActivate) {
402 | this.lastTime = 0;
403 | this.timeOut = 250;
404 | this.eventNameDown = button + 'down';
405 | this.eventNameUp = button + 'up';
406 |
407 | this.el = el;
408 | this.onActivate = onActivate;
409 |
410 | this.onButtonDown = this.onButtonDown.bind(this);
411 | this.onButtonUp = this.onButtonUp.bind(this);
412 |
413 | el.addEventListener(this.eventNameDown, this.onButtonDown);
414 | el.addEventListener(this.eventNameUp, this.onButtonUp);
415 | }
416 |
417 | LongPress.prototype = {
418 | onButtonDown: function onButtonDown(event) {
419 | var self = this;
420 | this.pressTimer = window.setTimeout(function () {
421 | self.onActivate(event);
422 | }, 1000);
423 | },
424 | onButtonUp: function onButtonUp(event) {
425 | clearTimeout(this.pressTimer);
426 | },
427 | removeListeners: function removeListeners() {
428 | this.el.removeEventListener(this.eventNameDown, this.onButtonDown);
429 | this.el.removeEventListener(this.eventNameUp, this.onButtonUp);
430 | }
431 | };
432 |
433 | AFRAME.registerInputActivator('longpress', LongPress);
434 |
435 | /***/ }),
436 | /* 3 */
437 | /***/ (function(module, exports, __webpack_require__) {
438 |
439 | "use strict";
440 |
441 |
442 | function DoubleTouch(el, button, onActivate) {
443 | this.lastTime = 0;
444 | this.timeOut = 250;
445 | this.eventName = button + 'touchstart';
446 | this.el = el;
447 | this.onActivate = onActivate;
448 |
449 | this.onButtonDown = this.onButtonDown.bind(this);
450 |
451 | el.addEventListener(this.eventName, this.onButtonDown);
452 | }
453 |
454 | DoubleTouch.prototype = {
455 | onButtonDown: function onButtonDown(event) {
456 | var time = performance.now();
457 | if (time - this.lastTime < this.timeOut) {
458 | this.onActivate(event);
459 | } else {
460 | this.lastTime = time;
461 | }
462 | },
463 | removeListeners: function removeListeners() {
464 | this.el.removeEventListener(this.eventName, this.onButtonDown);
465 | }
466 | };
467 |
468 | AFRAME.registerInputActivator('doubletouch', DoubleTouch);
469 |
470 | /***/ }),
471 | /* 4 */
472 | /***/ (function(module, exports, __webpack_require__) {
473 |
474 | "use strict";
475 |
476 |
477 | function DoublePress(el, button, onActivate) {
478 | this.lastTime = 0;
479 | this.timeOut = 250;
480 | this.eventName = button + 'down';
481 | this.el = el;
482 | this.onActivate = onActivate;
483 |
484 | this.onButtonDown = this.onButtonDown.bind(this);
485 |
486 | el.addEventListener(this.eventName, this.onButtonDown);
487 | }
488 |
489 | DoublePress.prototype = {
490 | onButtonDown: function onButtonDown(event) {
491 | var time = performance.now();
492 | if (time - this.lastTime < this.timeOut) {
493 | this.onActivate(event);
494 | } else {
495 | this.lastTime = time;
496 | }
497 | },
498 | removeListeners: function removeListeners() {
499 | this.el.removeEventListener(this.eventName, this.onButtonDown);
500 | }
501 | };
502 |
503 | AFRAME.registerInputActivator('doublepress', DoublePress);
504 |
505 | /***/ }),
506 | /* 5 */
507 | /***/ (function(module, exports, __webpack_require__) {
508 |
509 | "use strict";
510 |
511 |
512 | function createSimpleActivator(suffix) {
513 | return function (el, button, onActivate) {
514 | var eventName = button + suffix;
515 |
516 | el.addEventListener(eventName, onActivate);
517 | this.removeListeners = function () {
518 | el.removeEventListener(eventName, onActivate);
519 | };
520 | };
521 | }
522 |
523 | var activators = ['down', 'up', 'touchstart', 'touchend', 'changed', 'move'];
524 |
525 | activators.forEach(function (activatorName) {
526 | AFRAME.registerInputActivator(activatorName, createSimpleActivator(activatorName));
527 | });
528 |
529 | /***/ }),
530 | /* 6 */
531 | /***/ (function(module, exports, __webpack_require__) {
532 |
533 | "use strict";
534 |
535 |
536 | AFRAME.inputBehaviours = {};
537 |
538 | AFRAME.registerInputBehaviour = function (name, definition) {
539 | AFRAME.inputBehaviours[name] = definition;
540 | };
541 |
542 | __webpack_require__(7);
543 |
544 | /***/ }),
545 | /* 7 */
546 | /***/ (function(module, exports, __webpack_require__) {
547 |
548 | "use strict";
549 |
550 |
551 | var _slicedToArray = function () { function sliceIterator(arr, i) { var _arr = []; var _n = true; var _d = false; var _e = undefined; try { for (var _i = arr[Symbol.iterator](), _s; !(_n = (_s = _i.next()).done); _n = true) { _arr.push(_s.value); if (i && _arr.length === i) break; } } catch (err) { _d = true; _e = err; } finally { try { if (!_n && _i["return"]) _i["return"](); } finally { if (_d) throw _e; } } return _arr; } return function (arr, i) { if (Array.isArray(arr)) { return arr; } else if (Symbol.iterator in Object(arr)) { return sliceIterator(arr, i); } else { throw new TypeError("Invalid attempt to destructure non-iterable instance"); } }; }();
552 |
553 | function DPad(el, buttonName) {
554 | this.buttonName = buttonName;
555 | this.onButtonPresed = this.onButtonPresed.bind(this);
556 | this.onAxisMove = this.onAxisMove.bind(this);
557 | el.addEventListener('trackpaddown', this.onButtonPresed);
558 | el.addEventListener('trackpadup', this.onButtonPresed);
559 | el.addEventListener('axismove', this.onAxisMove);
560 | this.lastPos = [0, 0];
561 | this.el = el;
562 | };
563 |
564 | DPad.prototype = {
565 | onAxisMove: function onAxisMove(event) {
566 | this.lastPos = event.detail.axis;
567 | },
568 |
569 | onButtonPresed: function onButtonPresed(event) {
570 | var _lastPos = _slicedToArray(this.lastPos, 2),
571 | x = _lastPos[0],
572 | y = _lastPos[1];
573 |
574 | var state = 'trackpadup'.includes(event.type) ? "up" : "down";
575 | var centerZone = 0.5;
576 | var direction = state === "up" && this.lastDirection // Always trigger the up event for the last down event
577 | ? this.lastDirection : x * x + y * y < centerZone * centerZone // If within center zone angle does not matter
578 | ? "center" : angleToDirection(Math.atan2(x, y));
579 |
580 | this.el.emit(this.buttonName + 'dpad' + direction + state);
581 |
582 | if (state === "down") {
583 | this.lastDirection = direction;
584 | } else {
585 | delete this.lastDirection;
586 | }
587 | },
588 |
589 | removeListeners: function removeListeners() {
590 | el.removeEventListener('trackpaddown', this.onButtonPresed);
591 | el.removeEventListener('trackpadup', this.onButtonPresed);
592 | el.removeEventListener('axismove', this.onAxisMove);
593 | }
594 | };
595 |
596 | var angleToDirection = function angleToDirection(angle) {
597 | angle = (angle * THREE.Math.RAD2DEG + 180 + 45) % 360;
598 | if (angle > 0 && angle < 90) {
599 | return "down";
600 | } else if (angle >= 90 && angle < 180) {
601 | return "left";
602 | } else if (angle >= 180 && angle < 270) {
603 | return "up";
604 | } else {
605 | return "right";
606 | }
607 | };
608 |
609 | AFRAME.registerInputBehaviour('dpad', DPad);
610 |
611 | /***/ })
612 | /******/ ]);
--------------------------------------------------------------------------------
/vendor/aframe-tooltip-component.js:
--------------------------------------------------------------------------------
1 | /******/ (function(modules) { // webpackBootstrap
2 | /******/ // The module cache
3 | /******/ var installedModules = {};
4 |
5 | /******/ // The require function
6 | /******/ function __webpack_require__(moduleId) {
7 |
8 | /******/ // Check if module is in cache
9 | /******/ if(installedModules[moduleId])
10 | /******/ return installedModules[moduleId].exports;
11 |
12 | /******/ // Create a new module (and put it into the cache)
13 | /******/ var module = installedModules[moduleId] = {
14 | /******/ exports: {},
15 | /******/ id: moduleId,
16 | /******/ loaded: false
17 | /******/ };
18 |
19 | /******/ // Execute the module function
20 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
21 |
22 | /******/ // Flag the module as loaded
23 | /******/ module.loaded = true;
24 |
25 | /******/ // Return the exports of the module
26 | /******/ return module.exports;
27 | /******/ }
28 |
29 |
30 | /******/ // expose the modules object (__webpack_modules__)
31 | /******/ __webpack_require__.m = modules;
32 |
33 | /******/ // expose the module cache
34 | /******/ __webpack_require__.c = installedModules;
35 |
36 | /******/ // __webpack_public_path__
37 | /******/ __webpack_require__.p = "";
38 |
39 | /******/ // Load entry module and return exports
40 | /******/ return __webpack_require__(0);
41 | /******/ })
42 | /************************************************************************/
43 | /******/ ([
44 | /* 0 */
45 | /***/ (function(module, exports, __webpack_require__) {
46 |
47 | /* global AFRAME */
48 | __webpack_require__(1);
49 |
50 | if (typeof AFRAME === 'undefined') {
51 | throw new Error('Component attempted to register before AFRAME was available.');
52 | }
53 |
54 | /**
55 | * Tooltip component for A-Frame.
56 | */
57 | AFRAME.registerComponent('tooltip', {
58 | schema: {
59 | text: {default: ''},
60 | end: {type: 'vec3'},
61 | src: {default: ''},
62 | rotation: {type: 'vec3'},
63 | width: {default: 1, min: 0},
64 | height: {default: 1, min: 0},
65 | lineColor: {default: '#fff', type: 'color'},
66 | lineHorizontalAlign: {default: 'left', oneOf: ['left', 'right', 'center']},
67 | lineVerticalAlign: {default: 'center', oneOf: ['top', 'bottom', 'center']},
68 | opacity: {default: 1, min: 0, max: 1},
69 | /*
70 | targetType: {default: 'element', oneOf: ['element', 'position']},
71 | targetElement: {type: 'selector', if: {targetType: ['element']}},
72 | */
73 | targetPosition: {type: 'vec3', if: {targetType: ['position']}}
74 | },
75 |
76 | /**
77 | * Called once when component is attached. Generally for initial setup.
78 | */
79 | init: function () {
80 | var el = this.el;
81 | var data = this.data;
82 |
83 | var quad = this.quad = document.createElement('a-entity');
84 | var self = this;
85 |
86 | quad.addEventListener('loaded', function () {
87 | self.updateTooltip();
88 | });
89 |
90 | quad.setAttribute('slice9', {width: data.width, height: data.height, left: 20, right: 43, top: 20, bottom: 43, padding: 0.005, src: data.src});
91 | quad.setAttribute('rotation', data.rotation);
92 | quad.setAttribute('text', {width: 0.25, color: '#fff', value: data.text, align: 'center'});
93 | el.appendChild(quad);
94 |
95 | // Line
96 | var material = this.material = new THREE.LineBasicMaterial({color: data.lineColor, opacity: data.opacity, transparent: data.opacity < 1});
97 | var geometry = this.geometry = new THREE.BufferGeometry();
98 | geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(2 * 3), 3));
99 |
100 | this.line = new THREE.Line(geometry, material);
101 | this.el.setObject3D('line', this.line);
102 |
103 | this.el.addEventListener('componentchanged', function (evt) {
104 | self.updateTooltip();
105 | });
106 |
107 | this.updateTooltip();
108 | },
109 |
110 | /**
111 | * Called when component is attached and when component data changes.
112 | * Generally modifies the entity based on the data.
113 | */
114 |
115 | updateTooltip: (function () {
116 | var parentPosition = new THREE.Vector3();
117 | var targetPosition = new THREE.Vector3();
118 | var startPosition = new THREE.Vector3();
119 |
120 | return function () {
121 | var data = this.data;
122 | parentPosition.copy(this.el.getAttribute('position'));
123 | targetPosition.copy(data.targetPosition);
124 |
125 | var endPosition = targetPosition.sub(parentPosition);
126 |
127 | var data = this.data;
128 | var valign = {top: data.height / 2, bottom: -data.height / 2, center: 0};
129 | var halign = {left: -data.width / 2, right: data.width / 2, center: 0};
130 |
131 | var y = valign[data.lineVerticalAlign];
132 | var x = halign[data.lineHorizontalAlign];
133 |
134 | this.quad.setAttribute('slice9', {opacity: data.opacity});
135 | this.quad.setAttribute('text', {opacity: data.opacity, value: data.text});
136 |
137 | // Update geometry
138 | this.quad.object3D.updateMatrix();
139 | startPosition.set(x, y, 0);
140 | startPosition.applyMatrix4(this.quad.object3D.matrix);
141 |
142 | var pos = this.geometry.attributes.position.array;
143 | pos[0] = startPosition.x;
144 | pos[1] = startPosition.y;
145 | pos[2] = startPosition.z;
146 | pos[3] = endPosition.x;
147 | pos[4] = endPosition.y;
148 | pos[5] = endPosition.z;
149 | this.geometry.attributes.position.needsUpdate = true;
150 |
151 | this.material.opacity = data.opacity;
152 | this.material.transparent = data.opacity < 1;
153 | this.material.color.setStyle(data.color);
154 | }
155 | })(),
156 |
157 | update: function () {
158 | this.updateTooltip();
159 | }
160 | });
161 |
162 |
163 | /***/ }),
164 | /* 1 */
165 | /***/ (function(module, exports) {
166 |
167 | /* global AFRAME */
168 |
169 | if (typeof AFRAME === 'undefined') {
170 | throw new Error('Component attempted to register before AFRAME was available.');
171 | }
172 |
173 | /**
174 | * Slice9 component for A-Frame.
175 | */
176 | AFRAME.registerComponent('slice9', {
177 | schema: {
178 | width: {default: 1, min: 0},
179 | height: {default: 1, min: 0},
180 | left: {default: 0, min: 0},
181 | right: {default: 0, min: 0},
182 | bottom: {default: 0, min: 0},
183 | top: {default: 0, min: 0},
184 | side: {default: 'front', oneOf: ['front', 'back', 'double']},
185 | padding: {default: 0.1, min: 0.01},
186 | color: {type: 'color', default: '#fff'},
187 | opacity: {default: 1.0, min: 0, max: 1},
188 | transparent: {default: true},
189 | debug: {default: false},
190 | src: {type: 'map'}
191 | },
192 |
193 | /**
194 | * Set if component needs multiple instancing.
195 | */
196 | multiple: false,
197 |
198 | /**
199 | * Called once when component is attached. Generally for initial setup.
200 | */
201 | init: function () {
202 | var data = this.data;
203 | var material = this.material = new THREE.MeshBasicMaterial({color: data.color, opacity: data.opacity, transparent: data.transparent, wireframe: data.debug});
204 | var geometry = this.geometry = new THREE.PlaneBufferGeometry(data.width, data.height, 3, 3);
205 |
206 | var textureLoader = new THREE.TextureLoader();
207 | this.plane = new THREE.Mesh(geometry, material);
208 | this.el.setObject3D('mesh', this.plane);
209 | this.textureSrc = null;
210 | },
211 |
212 | updateMap: function () {
213 | var src = this.data.src;
214 |
215 | if (src) {
216 | if (src === this.textureSrc) { return; }
217 | // Texture added or changed.
218 | this.textureSrc = src;
219 | this.el.sceneEl.systems.material.loadTexture(src, {src: src}, setMap.bind(this));
220 | return;
221 | }
222 |
223 | // Texture removed.
224 | if (!this.material.map) { return; }
225 | setMap(null);
226 |
227 |
228 | function setMap (texture) {
229 | this.material.map = texture;
230 | this.material.needsUpdate = true;
231 | this.regenerateMesh();
232 | }
233 | },
234 |
235 | regenerateMesh: function () {
236 | var data = this.data;
237 | var pos = this.geometry.attributes.position.array;
238 | var uvs = this.geometry.attributes.uv.array;
239 | var image = this.material.map.image;
240 |
241 | if (!image) {return;}
242 |
243 | /*
244 | 0--1------------------------------2--3
245 | | | | |
246 | 4--5------------------------------6--7
247 | | | | |
248 | | | | |
249 | | | | |
250 | 8--9-----------------------------10--11
251 | | | | |
252 | 12-13----------------------------14--15
253 | */
254 | function setPos(id, x, y) {
255 | pos[3 * id] = x;
256 | pos[3 * id + 1] = y;
257 | }
258 |
259 | function setUV(id, u, v) {
260 | uvs[2 * id] = u;
261 | uvs[2 * id + 1] = v;
262 | }
263 |
264 | // Update UVS
265 | var uv = {
266 | left: data.left / image.width,
267 | right: data.right / image.width,
268 | top: data.top / image.height,
269 | bottom: data.bottom / image.height
270 | };
271 |
272 | setUV(1, uv.left, 1);
273 | setUV(2, uv.right, 1);
274 |
275 | setUV(4, 0, uv.bottom);
276 | setUV(5, uv.left, uv.bottom);
277 | setUV(6, uv.right, uv.bottom);
278 | setUV(7, 1, uv.bottom);
279 |
280 | setUV(8, 0, uv.top);
281 | setUV(9, uv.left, uv.top);
282 | setUV(10, uv.right, uv.top);
283 | setUV(11, 1, uv.top);
284 |
285 | setUV(13, uv.left, 0);
286 | setUV(14, uv.right, 0);
287 |
288 | // Update vertex positions
289 | var w2 = data.width / 2;
290 | var h2 = data.height / 2;
291 | var left = -w2 + data.padding;
292 | var right = w2 - data.padding;
293 | var top = h2 - data.padding;
294 | var bottom = -h2 + data.padding;
295 |
296 | setPos(0, -w2, h2);
297 | setPos(1, left, h2);
298 | setPos(2, right, h2);
299 | setPos(3, w2, h2);
300 |
301 | setPos(4, -w2, top);
302 | setPos(5, left, top);
303 | setPos(6, right, top);
304 | setPos(7, w2, top);
305 |
306 | setPos(8, -w2, bottom);
307 | setPos(9, left, bottom);
308 | setPos(10, right, bottom);
309 | setPos(11, w2, bottom);
310 |
311 | setPos(13, left, -h2);
312 | setPos(14, right, -h2);
313 | setPos(12, -w2, -h2);
314 | setPos(15, w2, -h2);
315 |
316 | this.geometry.attributes.position.needsUpdate = true;
317 | this.geometry.attributes.uv.needsUpdate = true;
318 | },
319 |
320 | /**
321 | * Called when component is attached and when component data changes.
322 | * Generally modifies the entity based on the data.
323 | */
324 | update: function (oldData) {
325 | var data = this.data;
326 |
327 | this.material.color.setStyle(data.color);
328 | this.material.opacity = data.opacity;
329 | this.material.transparent = data.transparent;
330 | this.material.wireframe = data.debug;
331 | this.material.side = parseSide(data.side);
332 |
333 | var diff = AFRAME.utils.diff(data, oldData);
334 | if ('src' in diff) {
335 | this.updateMap();
336 | }
337 | else if ('width' in diff || 'height' in diff || 'padding' in diff || 'left' in diff || 'top' in diff || 'bottom' in diff || 'right' in diff) {
338 | this.regenerateMesh();
339 | }
340 | },
341 |
342 | /**
343 | * Called when a component is removed (e.g., via removeAttribute).
344 | * Generally undoes all modifications to the entity.
345 | */
346 | remove: function () { },
347 |
348 | /**
349 | * Called on each scene tick.
350 | */
351 | // tick: function (t) { },
352 |
353 | /**
354 | * Called when entity pauses.
355 | * Use to stop or remove any dynamic or background behavior such as events.
356 | */
357 | pause: function () { },
358 |
359 | /**
360 | * Called when entity resumes.
361 | * Use to continue or add any dynamic or background behavior such as events.
362 | */
363 | play: function () { }
364 | });
365 |
366 | function parseSide (side) {
367 | switch (side) {
368 | case 'back': {
369 | return THREE.BackSide;
370 | }
371 | case 'double': {
372 | return THREE.DoubleSide;
373 | }
374 | default: {
375 | // Including case `front`.
376 | return THREE.FrontSide;
377 | }
378 | }
379 | }
380 |
381 |
382 | /***/ })
383 | /******/ ]);
--------------------------------------------------------------------------------
/vendor/ga.js:
--------------------------------------------------------------------------------
1 | (function(i,s,o,g,r,a,m){i['GoogleAnalyticsObject']=r;i[r]=i[r]||function(){
2 | (i[r].q=i[r].q||[]).push(arguments)},i[r].l=1*new Date();a=s.createElement(o),
3 | m=s.getElementsByTagName(o)[0];a.async=1;a.src=g;m.parentNode.insertBefore(a,m)
4 | })(window,document,'script','https://www.google-analytics.com/analytics.js','ga');
5 | ga('create', 'UA-80530812-2', 'auto');
6 | ga('send', 'pageview');
7 |
--------------------------------------------------------------------------------
/vendor/saveas.js:
--------------------------------------------------------------------------------
1 | /*! @source http://purl.eligrey.com/github/FileSaver.js/blob/master/FileSaver.js */
2 | var saveAs=saveAs||function(e){"use strict";if(typeof e==="undefined"||typeof navigator!=="undefined"&&/MSIE [1-9]\./.test(navigator.userAgent)){return}var t=e.document,n=function(){return e.URL||e.webkitURL||e},r=t.createElementNS("http://www.w3.org/1999/xhtml","a"),o="download"in r,i=function(e){var t=new MouseEvent("click");e.dispatchEvent(t)},a=/constructor/i.test(e.HTMLElement),f=/CriOS\/[\d]+/.test(navigator.userAgent),u=function(t){(e.setImmediate||e.setTimeout)(function(){throw t},0)},d="application/octet-stream",s=1e3*40,c=function(e){var t=function(){if(typeof e==="string"){n().revokeObjectURL(e)}else{e.remove()}};setTimeout(t,s)},l=function(e,t,n){t=[].concat(t);var r=t.length;while(r--){var o=e["on"+t[r]];if(typeof o==="function"){try{o.call(e,n||e)}catch(i){u(i)}}}},p=function(e){if(/^\s*(?:text\/\S*|application\/xml|\S*\/\S*\+xml)\s*;.*charset\s*=\s*utf-8/i.test(e.type)){return new Blob([String.fromCharCode(65279),e],{type:e.type})}return e},v=function(t,u,s){if(!s){t=p(t)}var v=this,w=t.type,m=w===d,y,h=function(){l(v,"writestart progress write writeend".split(" "))},S=function(){if((f||m&&a)&&e.FileReader){var r=new FileReader;r.onloadend=function(){var t=f?r.result:r.result.replace(/^data:[^;]*;/,"data:attachment/file;");var n=e.open(t,"_blank");if(!n)e.location.href=t;t=undefined;v.readyState=v.DONE;h()};r.readAsDataURL(t);v.readyState=v.INIT;return}if(!y){y=n().createObjectURL(t)}if(m){e.location.href=y}else{var o=e.open(y,"_blank");if(!o){e.location.href=y}}v.readyState=v.DONE;h();c(y)};v.readyState=v.INIT;if(o){y=n().createObjectURL(t);setTimeout(function(){r.href=y;r.download=u;i(r);h();c(y);v.readyState=v.DONE});return}S()},w=v.prototype,m=function(e,t,n){return new v(e,t||e.name||"download",n)};if(typeof navigator!=="undefined"&&navigator.msSaveOrOpenBlob){return function(e,t,n){t=t||e.name||"download";if(!n){e=p(e)}return navigator.msSaveOrOpenBlob(e,t)}}w.abort=function(){};w.readyState=w.INIT=0;w.WRITING=1;w.DONE=2;w.error=w.onwritestart=w.onprogress=w.onwrite=w.onabort=w.onerror=w.onwriteend=null;return m}(typeof self!=="undefined"&&self||typeof window!=="undefined"&&window||this.content);if(typeof module!=="undefined"&&module.exports){module.exports.saveAs=saveAs}else if(typeof define!=="undefined"&&define!==null&&define.amd!==null){define([],function(){return saveAs})}
3 |
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | const TerserPlugin = require('terser-webpack-plugin');
2 |
3 | module.exports = {
4 | entry: './src/index.js',
5 | output: {
6 | path: __dirname,
7 | filename: 'build.js'
8 | },
9 | module: {},
10 | optimization: {
11 | minimizer: [new TerserPlugin({
12 | extractComments: false
13 | })]
14 | },
15 | devServer: {
16 | historyApiFallback: true,
17 | allowedHosts: "all",
18 | static: {
19 | directory: __dirname,
20 | publicPath: '/'
21 | }
22 | }
23 | };
24 |
--------------------------------------------------------------------------------