├── .gitattributes ├── .gitignore ├── LICENSE ├── README.md ├── assets ├── environment-back.png ├── environment-bottom.png ├── environment-left.png ├── environment-top.png ├── music │ └── christmas-magic.mp3 ├── sounds │ └── cheers-noise.mp3 └── xylophone │ ├── xA.wav │ ├── xB.wav │ ├── xC.wav │ ├── xC2.wav │ ├── xD.wav │ ├── xE.wav │ ├── xF.wav │ └── xG.wav ├── index.html ├── lib ├── aframe-particle-system-component.js ├── aframe-physics-system.js ├── physics.js └── physx.release.js ├── research ├── README.md ├── baubles │ ├── baubles-1.0.4.html │ ├── baubles-1.1.0.html │ ├── baubles-1.2.0.html │ ├── baubles-environment.html │ ├── baubles-master.html │ ├── baubles.html │ ├── environment-back.png │ ├── environment-bottom.png │ ├── environment-front.png │ ├── environment-left.png │ ├── environment-right.png │ ├── environment-top.png │ └── environment.png ├── composite-objects │ ├── branch.html │ ├── composite.js │ ├── custom-geometries.js │ ├── models.html │ └── top-hat.html ├── naf │ ├── christmas-scene.html │ ├── examples │ │ ├── 360.html │ │ ├── adapter-test │ │ │ ├── adapter-test-receive.html │ │ │ ├── adapter-test-send.html │ │ │ └── index.html │ │ ├── ar-index.html │ │ ├── assets │ │ │ ├── Campfire_Blocks │ │ │ │ ├── materials.mtl │ │ │ │ └── model.obj │ │ │ ├── Raccoon_Blocks │ │ │ │ ├── materials.mtl │ │ │ │ └── model.obj │ │ │ ├── Tours-Enthusiast.mp3 │ │ │ ├── environment-back.png │ │ │ ├── environment-bottom.png │ │ │ ├── environment-left.png │ │ │ ├── environment-top.png │ │ │ ├── music │ │ │ │ └── christmas-magic.mp3 │ │ │ └── xylophone │ │ │ │ ├── xA.wav │ │ │ │ ├── xB.wav │ │ │ │ ├── xC.wav │ │ │ │ ├── xC2.wav │ │ │ │ ├── xD.wav │ │ │ │ ├── xE.wav │ │ │ │ ├── xF.wav │ │ │ │ └── xG.wav │ │ ├── basic-2.html │ │ ├── basic-4.html │ │ ├── basic-ar.html │ │ ├── basic-audio.html │ │ ├── basic-events.html │ │ ├── basic-video.html │ │ ├── basic.html │ │ ├── child-entities.html │ │ ├── christmas-scene.html │ │ ├── christmas-test.html │ │ ├── disconnect.html │ │ ├── dist │ │ │ ├── networked-aframe.js │ │ │ └── networked-aframe.min.js │ │ ├── google-blocks.html │ │ ├── img │ │ │ └── puydesancy.jpg │ │ ├── index.html │ │ ├── js │ │ │ ├── forward.component.js │ │ │ ├── gun.component.js │ │ │ ├── move-in-circle.component.js │ │ │ ├── randomize-show-child.component.js │ │ │ ├── remove-in-seconds.component.js │ │ │ ├── show-child.component.js │ │ │ ├── spawn-grid.component.js │ │ │ ├── spawn-in-circle.component.js │ │ │ ├── spawner.component.js │ │ │ ├── tests │ │ │ │ └── show-child.component.test.js │ │ │ └── toggle-ownership.component.js │ │ ├── lib │ │ │ ├── aframe-particle-system-component.js │ │ │ └── aframe-physics-system.js │ │ ├── ownership-transfer.html │ │ ├── shooter-2.html │ │ ├── shooter-ar.html │ │ ├── shooter.html │ │ ├── src │ │ │ ├── christmas-utils.js │ │ │ └── move-objects.js │ │ ├── tracked-controllers.html │ │ └── webrtc.html │ ├── package-lock.json │ ├── package.json │ └── server │ │ ├── easyrtc-server.js │ │ └── socketio-server.js ├── perf │ ├── index.html │ └── physics-performance.md ├── physics-ammo │ ├── Object-types.md │ ├── advanced-physics-test-1.html │ ├── dynamic-kinematic-switch.html │ ├── full-test.html │ ├── modded-physics │ │ └── aframe-physics-system.js │ ├── move-objects-ph2.html │ ├── move-objects-ph2.js │ ├── move-objects-ph3.html │ ├── move-objects-ph3.js │ ├── move-objects.html │ ├── move-objects.js │ ├── reparenting-physics-objects │ ├── reparenting-physics-objects.html │ ├── repro-for-issue-197.html │ ├── simple-object-collisions.html │ └── testing-snowballs.html ├── physics-benchmarks │ ├── benchmark1-ammo.html │ ├── benchmark1-physx.html │ └── benchmarks.js ├── physics-cannon │ ├── move-objects.html │ ├── move-objects.js │ └── simple-object-collisions.html ├── sky │ └── index.html └── snowballs │ ├── painting-snowballs.html │ ├── snowball-generator.js │ └── test-snowball-generation.html ├── src ├── christmas-utils.js └── move-objects.js └── test ├── bowling-alley.html ├── index.html ├── marble-run.html ├── snowman.html └── tree.html /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | research/naf/node_modules/ 3 | backend/ 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 diarmidmackenzie 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 all 13 | 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 THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # christmas-scene 2 | For Christmas 2021, I'm building a Winter Wonderland in VR. 3 | 4 | My plan is to add something new every day, for each day of advent 2021. It might be a new object in the scene, some improvement to something I already added, or some other new functionality. 5 | 6 | You can experience the Winter Wonderland yourself here: 7 | 8 | https://diarmidmackenzie.github.io/christmas-scene/ 9 | 10 | Best experienced on the Oculus Quest 2, but may work on other VR headsets as well, and it can also be viewed (but with limited functionality) on any browser. 11 | 12 | ### Development Log 13 | 14 | If you'd like to see more detail, you can take a look through this thread on twitter, where I posted videos daily through out the project. 15 | https://twitter.com/dhmackenzie/status/1466763356421996545?s=20 16 | 17 | Here's the features I have added day by day. 18 | 19 | **3 December** 20 | 21 | (starting 2 days late, so I added 3 things today!) 22 | 23 | - Basic winter environment 24 | - Made it snow! 25 | - Added basic snowman with eyes, mouth, nose, hat and arms. 26 | 27 | **4 December** 28 | 29 | - Grab and reposition snowman's face & arms. 30 | 31 | **5 December** 32 | 33 | - Retain objects' rotation & velocity on release - allows throwing of objects, juggling etc. 34 | 35 | **6 December** 36 | 37 | - Added Christmas tree with multi-colour baubles, which show reflections of the environment. 38 | 39 | **7 December** 40 | 41 | - Replaced the snowman's broomstick arms with more organic-looking sticks. Also gave him a new hat, which can be moved about. 42 | 43 | **8 December** 44 | 45 | - Added a xylophone made of icicles. This can be played with your hands, or using one of the snowman's arms as a atick. 46 | 47 | **9 December** 48 | 49 | - Added a calendar and Christmas countdown. 50 | 51 | **10 December** 52 | 53 | - Added a star on top of the Christmas Tree 54 | 55 | **11 December** 56 | 57 | - Updated physics - hat is now hollow, and you can put objects inside it 58 | - Added a giant marble run 59 | 60 | **12 December** 61 | 62 | - Snowballs! Pick them up from anywhere on the snowy ground. 63 | 64 | **13 December** 65 | 66 | - Added various different sized presents under and around the tree. 67 | 68 | **14 December** 69 | 70 | - Make snowballs bigger by rolling them around on the snowy ground. 71 | 72 | **15 December** 73 | 74 | - Make marble run ramps adjustable, so they can accommodate various sizes of snowball. 75 | 76 | **16 December** 77 | 78 | - Penguin bowling game, and left-hand teleporting, so you can stand the penguins up again. 79 | 80 | **17 December** 81 | 82 | - Added background music. 83 | 84 | **18 December** 85 | 86 | - Added volume control to turn the background music down. 87 | 88 | **19 December** 89 | 90 | - Sticky snowballs, which enables large constructions 91 | 92 | **20 December** 93 | 94 | - "Magic paintbrush" can be used to paint snowballs and icicles. 95 | 96 | **21 December** 97 | 98 | - Perimeter fence - sticks can be used as building material. 99 | 100 | **22 December** 101 | 102 | - "Merry Christmas" stackable alphabet blocks. 103 | 104 | **23 December** 105 | 106 | - Mechanism to clear and reset the bowling alley. 107 | 108 | **24 December** 109 | 110 | - Task board & celebration on completion 111 | 112 | 113 | 114 | ### Credits 115 | 116 | Music is "Christmas Magic" by AShamaluevMusic 117 | 118 | https://soundcloud.com/ashamaluevmusic/christmas-magic 119 | 120 | Xylophone sounds by DANMITCH3LL, via freesound.org, under CC BY 3.0 license. 121 | 122 | https://freesound.org/people/DANMITCH3LL/packs/14220/ 123 | 124 | Cheers & applause sounds by neilraouf, via freesound.org, under CC 0 license. 125 | 126 | https://freesound.org/people/neilraouf/sounds/484513/ 127 | 128 | 129 | 130 | Built using [A-Frame](https://aframe.io/) and [THREE.js](https://threejs.org/) 131 | 132 | Environment from [A-Frame Environment Component](https://github.com/supermedium/aframe-environment-component) 133 | 134 | Particles (snow) from [A-Frame Particle System](https://github.com/IdeaSpaceVR/aframe-particle-system-component) 135 | 136 | Collisions & physics uses [A-Frame Physics System](https://github.com/n5ro/aframe-physics-system) and [Ammo.js](https://github.com/kripken/ammo.js/) 137 | 138 | Teleportation uses [A-Frame Teleport Controls](https://github.com/fernandojsg/aframe-teleport-controls) 139 | 140 | The volume control for the music is a modified version of the dial control from [A-Frame UI Widgets](https://github.com/caseyyee/aframe-ui-widgets) 141 | 142 | -------------------------------------------------------------------------------- /assets/environment-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/environment-back.png -------------------------------------------------------------------------------- /assets/environment-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/environment-bottom.png -------------------------------------------------------------------------------- /assets/environment-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/environment-left.png -------------------------------------------------------------------------------- /assets/environment-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/environment-top.png -------------------------------------------------------------------------------- /assets/music/christmas-magic.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/music/christmas-magic.mp3 -------------------------------------------------------------------------------- /assets/sounds/cheers-noise.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/sounds/cheers-noise.mp3 -------------------------------------------------------------------------------- /assets/xylophone/xA.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xA.wav -------------------------------------------------------------------------------- /assets/xylophone/xB.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xB.wav -------------------------------------------------------------------------------- /assets/xylophone/xC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xC.wav -------------------------------------------------------------------------------- /assets/xylophone/xC2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xC2.wav -------------------------------------------------------------------------------- /assets/xylophone/xD.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xD.wav -------------------------------------------------------------------------------- /assets/xylophone/xE.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xE.wav -------------------------------------------------------------------------------- /assets/xylophone/xF.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xF.wav -------------------------------------------------------------------------------- /assets/xylophone/xG.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/assets/xylophone/xG.wav -------------------------------------------------------------------------------- /research/baubles/baubles-1.0.4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Baubles 6 | 7 | 8 | 12 | 13 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /research/baubles/baubles-1.1.0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Baubles 6 | 7 | 8 | 12 | 13 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /research/baubles/baubles-1.2.0.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Baubles 6 | 7 | 8 | 12 | 13 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /research/baubles/baubles-environment.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Baubles 6 | 7 | 9 | 10 | 14 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /research/baubles/baubles-master.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Baubles 6 | 7 | 9 | 10 | 14 | 15 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /research/baubles/baubles.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Baubles 6 | 7 | 8 | 12 | 13 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /research/baubles/environment-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment-back.png -------------------------------------------------------------------------------- /research/baubles/environment-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment-bottom.png -------------------------------------------------------------------------------- /research/baubles/environment-front.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment-front.png -------------------------------------------------------------------------------- /research/baubles/environment-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment-left.png -------------------------------------------------------------------------------- /research/baubles/environment-right.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment-right.png -------------------------------------------------------------------------------- /research/baubles/environment-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment-top.png -------------------------------------------------------------------------------- /research/baubles/environment.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/baubles/environment.png -------------------------------------------------------------------------------- /research/composite-objects/branch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Branch and Top Hat Models 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 18 | 19 | 23 | 24 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /research/composite-objects/composite.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerGeometry('composite', { 2 | schema: { 3 | type: {type: 'string', default: 'tophat'}, 4 | depth: {default: 1, min: 0}, 5 | height: {default: 1, min: 0}, 6 | width: {default: 1, min: 0}, 7 | segmentsHeight: {default: 1, min: 1, max: 20, type: 'int'}, 8 | segmentsWidth: {default: 1, min: 1, max: 20, type: 'int'}, 9 | segmentsDepth: {default: 1, min: 1, max: 20, type: 'int'} 10 | }, 11 | 12 | init: function (data) { 13 | 14 | console.log("Using composite geometry"); 15 | this.system = document.querySelector('a-scene').systems['composite-geometry']; 16 | this.data = data; 17 | 18 | console.log(this.system); 19 | console.log(this.system.geometries); 20 | 21 | if (!this.system.geometries[data.type]) { 22 | // geometry not yet registered. 23 | this.system.sceneEl.addEventListener('geometry-defined', this.geometryDefined.bind(this)); 24 | } 25 | //const geometryData = system.geometries[data.type] 26 | //console.log(geometryData); 27 | 28 | }, 29 | 30 | geometryDefined: function() { 31 | 32 | if (this.system.geometries[this.data.type]) { 33 | this.system.sceneEl.removeEventListener('geometry-defined', this.geometryDefined.bind(this)); 34 | this.createGeometry(); 35 | } 36 | 37 | }, 38 | 39 | createGeometry: function () { 40 | console.log(this.system); 41 | console.log(this.system.geometries); 42 | this.geometry = new THREE.BoxGeometry(this.data.width, this.data.height, this.data.depth); 43 | } 44 | }); 45 | 46 | // defines a compsite geometry 47 | AFRAME.registerSystem('composite-geometry', { 48 | 49 | init() { 50 | this.geometries = {}; 51 | }, 52 | }); 53 | 54 | // defines a compsite geometry 55 | AFRAME.registerComponent('composite-geometry', { 56 | schema: { 57 | type: {type: 'string'} 58 | }, 59 | 60 | init() { 61 | console.log("Registering composite geometry") 62 | this.system.geometries[this.data.type] = 'my-geometry-data'; 63 | this.el.sceneEl.emit('geometry-defined'); 64 | }, 65 | }); 66 | -------------------------------------------------------------------------------- /research/composite-objects/models.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Various Custom Models 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 24 | 25 | 29 | 30 | 33 | 34 | 37 | 38 | 41 | 42 | 45 | 46 | 47 | 50 | 51 | 52 | 55 | 56 | 57 | 60 | 61 | 62 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | -------------------------------------------------------------------------------- /research/composite-objects/top-hat.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Top Hat Experiment 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 22 | 23 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 54 | 55 | 56 | 61 | 62 | 63 | 64 | 65 | 66 | -------------------------------------------------------------------------------- /research/naf/examples/360.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 360 Image Example — Networked-Aframe 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 56 | 57 | 58 | 59 | 60 | 67 | 71 | 72 | 73 | 74 | 75 | 76 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /research/naf/examples/adapter-test/adapter-test-receive.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 31 | 32 | 57 | 58 | 59 | 60 |
61 |

Broadcast reliable test result: waiting

62 |

Broadcast unreliable test result: waiting

63 |

Send reliable test result: waiting

64 |

Send unreliable test result: waiting

65 |
66 | 67 | 68 | 69 | 74 | 75 | 80 | 81 | -------------------------------------------------------------------------------- /research/naf/examples/adapter-test/adapter-test-send.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 38 | 39 | 40 | 41 |
42 |

Sender. Open first receiver to test.

43 |
44 | 45 | 46 | 47 | 52 | 53 | 58 | 59 | 60 | -------------------------------------------------------------------------------- /research/naf/examples/adapter-test/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iframe Dev Example — Networked-Aframe 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /research/naf/examples/ar-index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Networked-Aframe AR Examples 4 | 5 | 22 | 23 | 24 |

Networked-Aframe AR Examples

25 | 26 |

Instructions

27 | 28 |

To view AR examples follow these steps:

29 | 30 |
    31 |
  1. Grab two devices, one must have a camera
  2. 32 |
  3. Open this marker on one device
  4. 33 |
  5. Open one of the examples below on the device with a camera
  6. 34 |
  7. If other users are in the corresponding VR example, point the camera at the marker and voila
  8. 35 |
  9. If nobody is in the example, grab a third device or open a new tab and have it join the corresponding VR example
  10. 36 |
  11. Now point the camera at the marker and you should see the avatar near the marker
  12. 37 |
38 | 39 | 40 |

Examples

41 | 42 | Basic AR 43 |
44 |
45 | Shooter AR 46 | 47 |

48 | 49 |

Want to help make better examples? That would be awesome! Contact Hayden on twitter.

50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /research/naf/examples/assets/Campfire_Blocks/materials.mtl: -------------------------------------------------------------------------------- 1 | newmtl mat0 2 | Ka 0.73 0.41 0.78 3 | Kd 0.73 0.41 0.78 4 | 5 | newmtl mat1 6 | Ka 0.61 0.15 0.69 7 | Kd 0.61 0.15 0.69 8 | 9 | newmtl mat2 10 | Ka 0.40 0.23 0.72 11 | Kd 0.40 0.23 0.72 12 | 13 | newmtl mat3 14 | Ka 0.50 0.87 0.92 15 | Kd 0.50 0.87 0.92 16 | 17 | newmtl mat4 18 | Ka 0.00 0.74 0.83 19 | Kd 0.00 0.74 0.83 20 | 21 | newmtl mat5 22 | Ka 0.01 0.61 0.90 23 | Kd 0.01 0.61 0.90 24 | 25 | newmtl mat6 26 | Ka 0.97 0.73 0.82 27 | Kd 0.97 0.73 0.82 28 | 29 | newmtl mat7 30 | Ka 0.94 0.38 0.57 31 | Kd 0.94 0.38 0.57 32 | 33 | newmtl mat8 34 | Ka 0.96 0.26 0.21 35 | Kd 0.96 0.26 0.21 36 | 37 | newmtl mat9 38 | Ka 0.55 0.76 0.29 39 | Kd 0.55 0.76 0.29 40 | 41 | newmtl mat10 42 | Ka 0.30 0.69 0.31 43 | Kd 0.30 0.69 0.31 44 | 45 | newmtl mat11 46 | Ka 0.00 0.59 0.53 47 | Kd 0.00 0.59 0.53 48 | 49 | newmtl mat12 50 | Ka 1.00 0.92 0.23 51 | Kd 1.00 0.92 0.23 52 | 53 | newmtl mat13 54 | Ka 1.00 0.60 0.00 55 | Kd 1.00 0.60 0.00 56 | 57 | newmtl mat14 58 | Ka 1.00 0.34 0.13 59 | Kd 1.00 0.34 0.13 60 | 61 | newmtl mat15 62 | Ka 0.81 0.85 0.86 63 | Kd 0.81 0.85 0.86 64 | 65 | newmtl mat16 66 | Ka 0.47 0.56 0.61 67 | Kd 0.47 0.56 0.61 68 | 69 | newmtl mat17 70 | Ka 0.27 0.35 0.39 71 | Kd 0.27 0.35 0.39 72 | 73 | newmtl mat18 74 | Ka 1.00 0.80 0.53 75 | Kd 1.00 0.80 0.53 76 | 77 | newmtl mat19 78 | Ka 0.87 0.60 0.27 79 | Kd 0.87 0.60 0.27 80 | 81 | newmtl mat20 82 | Ka 0.47 0.33 0.28 83 | Kd 0.47 0.33 0.28 84 | 85 | newmtl mat21 86 | Ka 1.00 1.00 1.00 87 | Kd 1.00 1.00 1.00 88 | 89 | newmtl mat22 90 | Ka 0.62 0.62 0.62 91 | Kd 0.62 0.62 0.62 92 | 93 | newmtl mat23 94 | Ka 0.10 0.10 0.10 95 | Kd 0.10 0.10 0.10 96 | 97 | newmtl mat24 98 | Ka 0.58 0.65 1.00 99 | Kd 0.92 0.95 0.94 100 | Ks 1 1 1 101 | illum 4 102 | Ns 300 103 | d 0.4 104 | Ni 1.5 105 | 106 | newmtl mat25 107 | Ka 1.00 0.65 0.67 108 | Kd 0.92 0.95 0.94 109 | Ks 1 1 1 110 | illum 4 111 | Ns 300 112 | d 0.4 113 | Ni 1.5 114 | 115 | -------------------------------------------------------------------------------- /research/naf/examples/assets/Raccoon_Blocks/materials.mtl: -------------------------------------------------------------------------------- 1 | newmtl mat0 2 | Ka 0.73 0.41 0.78 3 | Kd 0.73 0.41 0.78 4 | 5 | newmtl mat1 6 | Ka 0.61 0.15 0.69 7 | Kd 0.61 0.15 0.69 8 | 9 | newmtl mat2 10 | Ka 0.40 0.23 0.72 11 | Kd 0.40 0.23 0.72 12 | 13 | newmtl mat3 14 | Ka 0.50 0.87 0.92 15 | Kd 0.50 0.87 0.92 16 | 17 | newmtl mat4 18 | Ka 0.00 0.74 0.83 19 | Kd 0.00 0.74 0.83 20 | 21 | newmtl mat5 22 | Ka 0.01 0.61 0.90 23 | Kd 0.01 0.61 0.90 24 | 25 | newmtl mat6 26 | Ka 0.97 0.73 0.82 27 | Kd 0.97 0.73 0.82 28 | 29 | newmtl mat7 30 | Ka 0.94 0.38 0.57 31 | Kd 0.94 0.38 0.57 32 | 33 | newmtl mat8 34 | Ka 0.96 0.26 0.21 35 | Kd 0.96 0.26 0.21 36 | 37 | newmtl mat9 38 | Ka 0.55 0.76 0.29 39 | Kd 0.55 0.76 0.29 40 | 41 | newmtl mat10 42 | Ka 0.30 0.69 0.31 43 | Kd 0.30 0.69 0.31 44 | 45 | newmtl mat11 46 | Ka 0.00 0.59 0.53 47 | Kd 0.00 0.59 0.53 48 | 49 | newmtl mat12 50 | Ka 1.00 0.92 0.23 51 | Kd 1.00 0.92 0.23 52 | 53 | newmtl mat13 54 | Ka 1.00 0.60 0.00 55 | Kd 1.00 0.60 0.00 56 | 57 | newmtl mat14 58 | Ka 1.00 0.34 0.13 59 | Kd 1.00 0.34 0.13 60 | 61 | newmtl mat15 62 | Ka 0.81 0.85 0.86 63 | Kd 0.81 0.85 0.86 64 | 65 | newmtl mat16 66 | Ka 0.47 0.56 0.61 67 | Kd 0.47 0.56 0.61 68 | 69 | newmtl mat17 70 | Ka 0.27 0.35 0.39 71 | Kd 0.27 0.35 0.39 72 | 73 | newmtl mat18 74 | Ka 1.00 0.80 0.53 75 | Kd 1.00 0.80 0.53 76 | 77 | newmtl mat19 78 | Ka 0.87 0.60 0.27 79 | Kd 0.87 0.60 0.27 80 | 81 | newmtl mat20 82 | Ka 0.47 0.33 0.28 83 | Kd 0.47 0.33 0.28 84 | 85 | newmtl mat21 86 | Ka 1.00 1.00 1.00 87 | Kd 1.00 1.00 1.00 88 | 89 | newmtl mat22 90 | Ka 0.62 0.62 0.62 91 | Kd 0.62 0.62 0.62 92 | 93 | newmtl mat23 94 | Ka 0.10 0.10 0.10 95 | Kd 0.10 0.10 0.10 96 | 97 | newmtl mat24 98 | Ka 0.58 0.65 1.00 99 | Kd 0.92 0.95 0.94 100 | Ks 1 1 1 101 | illum 4 102 | Ns 300 103 | d 0.4 104 | Ni 1.5 105 | 106 | newmtl mat25 107 | Ka 1.00 0.65 0.67 108 | Kd 0.92 0.95 0.94 109 | Ks 1 1 1 110 | illum 4 111 | Ns 300 112 | d 0.4 113 | Ni 1.5 114 | 115 | -------------------------------------------------------------------------------- /research/naf/examples/assets/Tours-Enthusiast.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/Tours-Enthusiast.mp3 -------------------------------------------------------------------------------- /research/naf/examples/assets/environment-back.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/environment-back.png -------------------------------------------------------------------------------- /research/naf/examples/assets/environment-bottom.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/environment-bottom.png -------------------------------------------------------------------------------- /research/naf/examples/assets/environment-left.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/environment-left.png -------------------------------------------------------------------------------- /research/naf/examples/assets/environment-top.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/environment-top.png -------------------------------------------------------------------------------- /research/naf/examples/assets/music/christmas-magic.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/music/christmas-magic.mp3 -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xA.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xA.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xB.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xB.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xC.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xC.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xC2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xC2.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xD.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xD.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xE.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xE.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xF.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xF.wav -------------------------------------------------------------------------------- /research/naf/examples/assets/xylophone/xG.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/assets/xylophone/xG.wav -------------------------------------------------------------------------------- /research/naf/examples/basic-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iframe Dev Example — Networked-Aframe 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /research/naf/examples/basic-4.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iframe Dev Example — Networked-Aframe 6 | 7 | 8 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /research/naf/examples/basic-ar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Basic AR Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 90 | 91 | 105 | 106 | -------------------------------------------------------------------------------- /research/naf/examples/basic-audio.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Positional Audio Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 65 | 66 | 67 | 68 | 69 | 76 | 80 | 81 | 82 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 102 | 103 | 116 | 117 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /research/naf/examples/basic-video.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Video Streaming Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 39 | 40 | 41 | 42 | 43 | 50 | 54 | 55 | 56 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 76 | 77 | 91 | 92 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /research/naf/examples/basic.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | > 45 | 46 | 47 | 48 | 49 | 50 | 83 | 84 | 85 | 86 | 87 | 94 | 98 | 99 | 100 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 119 | 120 | 129 | 130 | 150 | 151 | 152 | -------------------------------------------------------------------------------- /research/naf/examples/child-entities.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 67 | 68 | 69 | 70 | 71 | 78 | 82 | 83 | 84 | 85 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 104 | 105 | 114 | 115 | 143 | 144 | 145 | -------------------------------------------------------------------------------- /research/naf/examples/disconnect.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 60 | 61 | 62 | 63 | 64 | 71 | 75 | 76 | 77 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 96 | 97 | 120 | 121 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /research/naf/examples/google-blocks.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Basic Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 62 | 63 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /research/naf/examples/img/puydesancy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/diarmidmackenzie/christmas-scene/121da80862dccb6cb8051156d81fccce7bf6646c/research/naf/examples/img/puydesancy.jpg -------------------------------------------------------------------------------- /research/naf/examples/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Networked-Aframe Examples 4 | 5 | 25 | 26 | 27 |

Networked-Aframe Examples

28 |
29 | Basic 30 | Simple avatars with synced color 31 |
32 |
33 | 4-in-one 34 | Opens the basic example 4 times on one page using iframes 35 |
36 |
37 | WebRTC 38 | Basic example with WebRTC support 39 |
40 |
41 | Positional Audio 42 | Example of using positional audio with WebRTC 43 |
44 |
45 | Video Streaming 46 | Example of using texture video with WebRTC 47 |
48 |
49 | Shooter 50 | Press spacebar to shoot. Example for spawning networked entities at runtime. 51 |
52 |
53 | 360 Image 54 | Basic example with 360 image environment 55 |
56 |
57 | Tracked Controllers 58 | Use with HTC Vive controllers or Oculus Touch 59 |
60 |
61 | Google Blocks 62 | Scene and Avatars made entirely in Google Blocks 63 |
64 |
65 | Basic with events 66 | Example of listening to and logging NAF events 67 |
68 |
69 | Ownership Transfer 70 | Demonstrates transfering ownership of entities 71 |
72 |
73 | Adapter Test 74 | Use this page to test new network adapters 75 |
76 | 78 | 79 |
80 | 81 |

Want to help make better examples? That would be awesome! Contact us on Slack.

82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 91 | 92 | 93 | -------------------------------------------------------------------------------- /research/naf/examples/js/forward.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, THREE */ 2 | AFRAME.registerComponent('forward', { 3 | schema: { 4 | speed: {default: 0.1}, 5 | }, 6 | 7 | init: function() { 8 | var worldDirection = new THREE.Vector3(); 9 | 10 | this.el.object3D.getWorldDirection(worldDirection); 11 | worldDirection.multiplyScalar(-1); 12 | 13 | this.worldDirection = worldDirection; 14 | console.error(this.worldDirection); 15 | }, 16 | 17 | tick: function() { 18 | var el = this.el; 19 | 20 | var currentPosition = el.getAttribute('position'); 21 | var newPosition = this.worldDirection 22 | .clone() 23 | .multiplyScalar(this.data.speed) 24 | .add(currentPosition); 25 | el.setAttribute('position', newPosition); 26 | } 27 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/gun.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, THREE */ 2 | AFRAME.registerComponent('gun', { 3 | schema: { 4 | bulletTemplate: {default: '#bullet-template'}, 5 | triggerKeyCode: {default: 32} // spacebar 6 | }, 7 | 8 | init: function() { 9 | var that = this; 10 | document.body.onkeyup = function(e){ 11 | if(e.keyCode == that.data.triggerKeyCode){ 12 | that.shoot(); 13 | } 14 | } 15 | }, 16 | 17 | shoot: function() { 18 | this.createBullet(); 19 | }, 20 | 21 | createBullet: function() { 22 | var el = document.createElement('a-entity'); 23 | el.setAttribute('networked', 'template:' + this.data.bulletTemplate); 24 | el.setAttribute('remove-in-seconds', 3); 25 | el.setAttribute('forward', 'speed:0.3'); 26 | 27 | var tip = document.querySelector('#player'); 28 | el.setAttribute('position', this.getInitialBulletPosition(tip)); 29 | el.setAttribute('rotation', this.getInitialBulletRotation(tip)); 30 | 31 | this.el.sceneEl.appendChild(el); 32 | }, 33 | 34 | getInitialBulletPosition: function(spawnerEl) { 35 | var worldPos = new THREE.Vector3(); 36 | worldPos.setFromMatrixPosition(spawnerEl.object3D.matrixWorld); 37 | return worldPos; 38 | }, 39 | 40 | getInitialBulletRotation: function(spawnerEl) { 41 | var worldDirection = new THREE.Vector3(); 42 | 43 | spawnerEl.object3D.getWorldDirection(worldDirection); 44 | worldDirection.multiplyScalar(-1); 45 | this.vec3RadToDeg(worldDirection); 46 | 47 | return worldDirection; 48 | }, 49 | 50 | vec3RadToDeg: function(rad) { 51 | rad.set(rad.y * 90, -90 + (-THREE.Math.radToDeg(Math.atan2(rad.z, rad.x))), 0); 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /research/naf/examples/js/move-in-circle.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('move-in-circle', { 3 | schema: { 4 | speed: {default: 0.05} 5 | }, 6 | 7 | init: function() { 8 | this.angle = 0; 9 | this.center = this.el.getAttribute('position'); 10 | }, 11 | 12 | tick: function() { 13 | this.angle = this.angle + this.data.speed; 14 | 15 | var circlePoint = this.getPointOnCircle(1, this.angle); 16 | var worldPoint = {x: circlePoint.x + this.center.x, y: this.center.y, z: circlePoint.y + this.center.z}; 17 | this.el.setAttribute('position', worldPoint); 18 | // console.log(worldPoint); 19 | }, 20 | 21 | getPointOnCircle: function (radius, angleRad) { 22 | var x = Math.cos(angleRad) * radius; 23 | var y = Math.sin(angleRad) * radius; 24 | return {x: x, y: y}; 25 | } 26 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/randomize-show-child.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('randomize-show-child', { 3 | schema: {}, 4 | 5 | init: function() { 6 | var num = this.el.children.length 7 | var r = Math.floor(Math.random() * num); 8 | this.el.setAttribute('show-child', r); 9 | } 10 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/remove-in-seconds.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('remove-in-seconds', { 3 | schema: { 4 | default: 1 5 | }, 6 | 7 | init: function() { 8 | setTimeout(this.destroy.bind(this), this.data * 1000); 9 | }, 10 | 11 | destroy: function() { 12 | var el = this.el; 13 | el.parentNode.removeChild(el); 14 | } 15 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/show-child.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('show-child', { 3 | schema: { 4 | type: 'int', 5 | default: 0, 6 | }, 7 | 8 | update: function() { 9 | this.show(this.data); 10 | }, 11 | 12 | show: function(index) { 13 | if (index < this.el.children.length) { 14 | this.hideAll(); 15 | this.el.children[index].setAttribute('visible', true); 16 | } else { 17 | console.error('show-child@show: invalid index: ', index); 18 | } 19 | }, 20 | 21 | hideAll: function() { 22 | var el = this.el; 23 | for (var i = 0; i < el.children.length; i++) { 24 | el.children[i].setAttribute('visible', false); 25 | } 26 | } 27 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/spawn-grid.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | /** 3 | * Creates a grid of entities based on the given template 4 | * Place at the center of the grid you wish to create 5 | * Requires aframe-template-component 6 | */ 7 | AFRAME.registerComponent('spawn-grid', { 8 | schema: { 9 | clone: {type: 'string'}, 10 | rows: {default: 2}, 11 | rowSeparation: {default: 2}, 12 | columns: {default: 2}, 13 | columnSeparation: {default: 2} 14 | }, 15 | 16 | init: function() { 17 | this.clone = document.querySelector(this.data.clone); 18 | var y = this.el.getAttribute('position').y; 19 | for (var i = 0; i < this.data.rows; i++) { 20 | var z = i * this.data.rowSeparation - (this.data.rowSeparation * this.data.rows / 2) + this.data.rowSeparation / 2; 21 | for (var j = 0; j < this.data.columns; j++) { 22 | var x = j * this.data.columnSeparation - (this.data.columnSeparation * this.data.columns / 2) + this.data.columnSeparation / 2; 23 | this.spawnEntity(i, j, x, y, z); 24 | } 25 | } 26 | }, 27 | 28 | spawnEntity: function(row, col, x, y, z) { 29 | var cloned = this.clone.cloneNode(false); 30 | this.el.appendChild(cloned); 31 | 32 | var id = cloned.getAttribute('id'); 33 | var newId = id + '-' + row + '-'+ col; 34 | cloned.setAttribute('id', newId); 35 | 36 | var position = x + ' ' + y + ' ' + z; 37 | cloned.setAttribute('position', position); 38 | 39 | cloned.setAttribute('visible', true); 40 | } 41 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/spawn-in-circle.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, THREE */ 2 | AFRAME.registerComponent('spawn-in-circle', { 3 | schema: { 4 | radius: {type: 'number', default: 1} 5 | }, 6 | 7 | init: function() { 8 | var el = this.el; 9 | var center = el.getAttribute('position'); 10 | 11 | var angleRad = this.getRandomAngleInRadians(); 12 | var circlePoint = this.randomPointOnCircle(this.data.radius, angleRad); 13 | var worldPoint = {x: circlePoint.x + center.x, y: center.y, z: circlePoint.y + center.z}; 14 | el.setAttribute('position', worldPoint); 15 | // console.log('world point', worldPoint); 16 | 17 | var angleDeg = angleRad * 180 / Math.PI; 18 | var angleToCenter = -1 * angleDeg + 90; 19 | angleRad = THREE.Math.degToRad(angleToCenter); 20 | el.object3D.rotation.set(0, angleRad, 0); 21 | // console.log('angle deg', angleDeg); 22 | }, 23 | 24 | getRandomAngleInRadians: function() { 25 | return Math.random()*Math.PI*2; 26 | }, 27 | 28 | randomPointOnCircle: function (radius, angleRad) { 29 | var x = Math.cos(angleRad)*radius; 30 | var y = Math.sin(angleRad)*radius; 31 | return {x: x, y: y}; 32 | } 33 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/spawner.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME */ 2 | AFRAME.registerComponent('spawner', { 3 | schema: { 4 | template: { default: '' }, 5 | keyCode: { default: 32 } 6 | }, 7 | 8 | init: function() { 9 | this.onKeyUp = this.onKeyUp.bind(this); 10 | document.addEventListener("keyup", this.onKeyUp); 11 | }, 12 | 13 | onKeyUp: function(e) { 14 | if (this.data.keyCode === e.keyCode) { 15 | var el = document.createElement('a-entity'); 16 | el.setAttribute('networked', 'template:' + this.data.template); 17 | el.setAttribute('position', this.el.getAttribute('position')); 18 | var scene = this.el.sceneEl; 19 | scene.appendChild(el); 20 | } 21 | } 22 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/tests/show-child.component.test.js: -------------------------------------------------------------------------------- 1 | /* global assert, setup, suite, test */ 2 | require('aframe'); 3 | var helpers = require('./helpers'); 4 | var naf = require('../../src/NafIndex'); 5 | 6 | require('../../src/components/show-child.component'); 7 | 8 | suite('show-child', function() { 9 | var scene; 10 | var entity; 11 | var comp; 12 | 13 | function initScene(done) { 14 | var opts = {}; 15 | opts.entity = ''; 16 | scene = helpers.sceneFactory(opts); 17 | naf.utils.whenEntityLoaded(scene, done); 18 | } 19 | 20 | setup(function(done) { 21 | initScene(function() { 22 | entity = document.querySelector('#test-entity'); 23 | comp = entity.components['show-child']; 24 | done(); 25 | }); 26 | }); 27 | 28 | suite('Setup', function() { 29 | 30 | test('creates entity', function() { 31 | assert.isOk(entity); 32 | }); 33 | 34 | test('creates component', function() { 35 | assert.isOk(comp); 36 | }); 37 | }); 38 | 39 | suite('Schema', function() { 40 | 41 | test('Returns correct index', function() { 42 | var result = entity.getAttribute('show-child'); 43 | 44 | assert.strictEqual(result, 2); 45 | }); 46 | }); 47 | 48 | suite('hideAll', function() { 49 | 50 | test('Hides correct children', function() { 51 | var child0 = document.querySelector('#zero'); 52 | var child1 = document.querySelector('#one'); 53 | var child2 = document.querySelector('#two'); 54 | 55 | comp.hideAll(); 56 | 57 | var result0 = child0.getAttribute('visible'); 58 | var result1 = child1.getAttribute('visible'); 59 | var result2 = child2.getAttribute('visible'); 60 | 61 | assert.isFalse(result0); 62 | assert.isFalse(result1); 63 | assert.isFalse(result2); 64 | }); 65 | 66 | test('Show correct child', function() { 67 | var child0 = document.querySelector('#zero'); 68 | var child1 = document.querySelector('#one'); 69 | var child2 = document.querySelector('#two'); 70 | 71 | comp.hideAll(); 72 | comp.show(1); 73 | 74 | var result0 = child0.getAttribute('visible'); 75 | var result1 = child1.getAttribute('visible'); 76 | var result2 = child2.getAttribute('visible'); 77 | 78 | assert.isFalse(result0); 79 | assert.isTrue(result1); 80 | assert.isFalse(result2); 81 | }); 82 | }); 83 | 84 | suite('show', function() { 85 | 86 | test('Handles index too large', function() { 87 | var child0 = document.querySelector('#zero'); 88 | var child1 = document.querySelector('#one'); 89 | var child2 = document.querySelector('#two'); 90 | 91 | comp.hideAll(); 92 | comp.show(4); 93 | 94 | var result0 = child0.getAttribute('visible'); 95 | var result1 = child1.getAttribute('visible'); 96 | var result2 = child2.getAttribute('visible'); 97 | 98 | assert.isFalse(result0); 99 | assert.isFalse(result1); 100 | assert.isFalse(result2); 101 | }); 102 | }); 103 | }); -------------------------------------------------------------------------------- /research/naf/examples/js/toggle-ownership.component.js: -------------------------------------------------------------------------------- 1 | /* global AFRAME, NAF, THREE */ 2 | /** 3 | * Rotate the entity every frame if you are the owner. 4 | * When you press enter take ownership of the entity, 5 | * spin it in the opposite direction and change its color. 6 | */ 7 | AFRAME.registerComponent('toggle-ownership', { 8 | schema: { 9 | speed: { default: 0.01 }, 10 | direction: { default: 1 } 11 | }, 12 | 13 | init() { 14 | var that = this; 15 | this.onKeyUp = this.onKeyUp.bind(this); 16 | document.addEventListener('keyup', this.onKeyUp); 17 | 18 | NAF.utils.getNetworkedEntity(this.el).then((el) => { 19 | if (NAF.utils.isMine(el)) { 20 | that.updateColor(); 21 | } else { 22 | that.updateOpacity(0.5); 23 | } 24 | 25 | // Opacity is not a networked attribute, but change it based on ownership events 26 | let timeout; 27 | 28 | el.addEventListener('ownership-gained', e => { 29 | that.updateOpacity(1); 30 | }); 31 | 32 | el.addEventListener('ownership-lost', e => { 33 | that.updateOpacity(0.5); 34 | }); 35 | 36 | el.addEventListener('ownership-changed', e => { 37 | clearTimeout(timeout); 38 | console.log(e.detail) 39 | if (e.detail.newOwner == NAF.clientId) { 40 | //same as listening to 'ownership-gained' 41 | } else if (e.detail.oldOwner == NAF.clientId) { 42 | //same as listening to 'ownership-lost' 43 | } else { 44 | that.updateOpacity(0.8); 45 | timeout = setTimeout(() => { 46 | that.updateOpacity(0.5); 47 | }, 200) 48 | } 49 | }); 50 | }); 51 | }, 52 | 53 | onKeyUp(e) { 54 | if (e.keyCode !== 13 /* enter */) { 55 | return; 56 | } 57 | 58 | if(NAF.utils.takeOwnership(this.el)) { 59 | this.el.setAttribute('toggle-ownership', { direction: this.data.direction * -1 }); 60 | this.updateColor(); 61 | } 62 | }, 63 | 64 | updateColor() { 65 | const headColor = document.querySelector('#player .head').getAttribute('material').color; 66 | this.el.setAttribute('material', 'color', headColor); 67 | }, 68 | 69 | updateOpacity(opacity) { 70 | this.el.setAttribute('material', 'opacity', opacity); 71 | }, 72 | 73 | tick() { 74 | // Only update the component if you are the owner. 75 | if (!NAF.utils.isMine(this.el)) { 76 | return; 77 | } 78 | 79 | this.el.object3D.rotateY(this.data.speed * this.data.direction); 80 | 81 | const rotation = this.el.object3D.rotation; 82 | this.el.setAttribute('rotation', { 83 | x: THREE.Math.radToDeg(rotation.x), 84 | y: THREE.Math.radToDeg(rotation.y), 85 | z: THREE.Math.radToDeg(rotation.z), 86 | }); 87 | } 88 | }); -------------------------------------------------------------------------------- /research/naf/examples/shooter-2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Iframe Dev Example — Networked-Aframe 6 | 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /research/naf/examples/shooter-ar.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shooter AR Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 25 | 26 | 27 | 28 | 33 | 34 | 35 | 36 | 37 | 38 | 81 | 82 | 83 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 107 | 108 | 122 | 123 | -------------------------------------------------------------------------------- /research/naf/examples/shooter.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Shooter Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 76 | 77 | 78 | 86 | 87 | 88 | 89 | 90 | 98 | 102 | 103 | 104 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 123 | 124 | 133 | 134 | 149 | 150 | 151 | -------------------------------------------------------------------------------- /research/naf/examples/webrtc.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Dev Example — Networked-Aframe 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 64 | 65 | 66 | 67 | 68 | 75 | 79 | 80 | 81 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 100 | 101 | 110 | 111 | 131 | 132 | 133 | -------------------------------------------------------------------------------- /research/naf/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "naf-tutorial", 3 | "version": "1.0.0", 4 | "description": "My first multi-user virtual reality", 5 | "scripts": { 6 | "start": "node ./server/easyrtc-server.js" 7 | }, 8 | "author": "YOUR_NAME", 9 | "dependencies": { 10 | "networked-aframe": "^0.8.0" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /research/naf/server/easyrtc-server.js: -------------------------------------------------------------------------------- 1 | // Load required modules 2 | const http = require("http"); // http server core module 3 | const path = require("path"); 4 | const express = require("express"); // web framework external module 5 | const socketIo = require("socket.io"); // web socket external module 6 | const easyrtc = require("open-easyrtc"); // EasyRTC external module 7 | 8 | // Set process name 9 | process.title = "networked-aframe-server"; 10 | 11 | // Get port or default to 8080 12 | const port = process.env.PORT || 8080; 13 | 14 | // Setup and configure Express http server. 15 | const app = express(); 16 | app.use(express.static(path.resolve(__dirname, "..", "examples"))); 17 | 18 | // Serve the example and build the bundle in development. 19 | if (process.env.NODE_ENV === "development") { 20 | const webpackMiddleware = require("webpack-dev-middleware"); 21 | const webpack = require("webpack"); 22 | const config = require("../webpack.config"); 23 | 24 | app.use( 25 | webpackMiddleware(webpack(config), { 26 | publicPath: "/" 27 | }) 28 | ); 29 | } 30 | 31 | // Start Express http server 32 | const webServer = http.createServer(app); 33 | 34 | // Start Socket.io so it attaches itself to Express server 35 | const socketServer = socketIo.listen(webServer, {"log level": 1}); 36 | const myIceServers = [ 37 | {"urls":"stun:stun1.l.google.com:19302"}, 38 | {"urls":"stun:stun2.l.google.com:19302"}, 39 | // { 40 | // "urls":"turn:[ADDRESS]:[PORT]", 41 | // "username":"[USERNAME]", 42 | // "credential":"[CREDENTIAL]" 43 | // }, 44 | // { 45 | // "urls":"turn:[ADDRESS]:[PORT][?transport=tcp]", 46 | // "username":"[USERNAME]", 47 | // "credential":"[CREDENTIAL]" 48 | // } 49 | ]; 50 | easyrtc.setOption("appIceServers", myIceServers); 51 | easyrtc.setOption("logLevel", "debug"); 52 | easyrtc.setOption("demosEnable", false); 53 | 54 | // Overriding the default easyrtcAuth listener, only so we can directly access its callback 55 | easyrtc.events.on("easyrtcAuth", (socket, easyrtcid, msg, socketCallback, callback) => { 56 | easyrtc.events.defaultListeners.easyrtcAuth(socket, easyrtcid, msg, socketCallback, (err, connectionObj) => { 57 | if (err || !msg.msgData || !msg.msgData.credential || !connectionObj) { 58 | callback(err, connectionObj); 59 | return; 60 | } 61 | 62 | connectionObj.setField("credential", msg.msgData.credential, {"isShared":false}); 63 | 64 | console.log("["+easyrtcid+"] Credential saved!", connectionObj.getFieldValueSync("credential")); 65 | 66 | callback(err, connectionObj); 67 | }); 68 | }); 69 | 70 | // To test, lets print the credential to the console for every room join! 71 | easyrtc.events.on("roomJoin", (connectionObj, roomName, roomParameter, callback) => { 72 | console.log("["+connectionObj.getEasyrtcid()+"] Credential retrieved!", connectionObj.getFieldValueSync("credential")); 73 | easyrtc.events.defaultListeners.roomJoin(connectionObj, roomName, roomParameter, callback); 74 | }); 75 | 76 | // Start EasyRTC server 77 | easyrtc.listen(app, socketServer, null, (err, rtcRef) => { 78 | console.log("Initiated"); 79 | 80 | rtcRef.events.on("roomCreate", (appObj, creatorConnectionObj, roomName, roomOptions, callback) => { 81 | console.log("roomCreate fired! Trying to create: " + roomName); 82 | 83 | appObj.events.defaultListeners.roomCreate(appObj, creatorConnectionObj, roomName, roomOptions, callback); 84 | }); 85 | }); 86 | 87 | // Listen on port 88 | webServer.listen(port, () => { 89 | console.log("listening on http://localhost:" + port); 90 | }); 91 | -------------------------------------------------------------------------------- /research/naf/server/socketio-server.js: -------------------------------------------------------------------------------- 1 | // Load required modules 2 | const http = require("http"); // http server core module 3 | const path = require("path"); 4 | const express = require("express"); // web framework external module 5 | 6 | // Set process name 7 | process.title = "networked-aframe-server"; 8 | 9 | // Get port or default to 8080 10 | const port = process.env.PORT || 8080; 11 | 12 | // Setup and configure Express http server. 13 | const app = express(); 14 | app.use(express.static(path.resolve(__dirname, "..", "examples"))); 15 | 16 | // Serve the example and build the bundle in development. 17 | if (process.env.NODE_ENV === "development") { 18 | const webpackMiddleware = require("webpack-dev-middleware"); 19 | const webpack = require("webpack"); 20 | const config = require("../webpack.config"); 21 | 22 | app.use( 23 | webpackMiddleware(webpack(config), { 24 | publicPath: "/" 25 | }) 26 | ); 27 | } 28 | 29 | // Start Express http server 30 | const webServer = http.createServer(app); 31 | const io = require("socket.io")(webServer); 32 | 33 | const rooms = {}; 34 | 35 | io.on("connection", socket => { 36 | console.log("user connected", socket.id); 37 | 38 | let curRoom = null; 39 | 40 | socket.on("joinRoom", data => { 41 | const { room } = data; 42 | 43 | if (!rooms[room]) { 44 | rooms[room] = { 45 | name: room, 46 | occupants: {}, 47 | }; 48 | } 49 | 50 | const joinedTime = Date.now(); 51 | rooms[room].occupants[socket.id] = joinedTime; 52 | curRoom = room; 53 | 54 | console.log(`${socket.id} joined room ${room}`); 55 | socket.join(room); 56 | 57 | socket.emit("connectSuccess", { joinedTime }); 58 | const occupants = rooms[room].occupants; 59 | io.in(curRoom).emit("occupantsChanged", { occupants }); 60 | }); 61 | 62 | socket.on("send", data => { 63 | io.to(data.to).emit("send", data); 64 | }); 65 | 66 | socket.on("broadcast", data => { 67 | socket.to(curRoom).broadcast.emit("broadcast", data); 68 | }); 69 | 70 | socket.on("disconnect", () => { 71 | console.log('disconnected: ', socket.id, curRoom); 72 | if (rooms[curRoom]) { 73 | console.log("user disconnected", socket.id); 74 | 75 | delete rooms[curRoom].occupants[socket.id]; 76 | const occupants = rooms[curRoom].occupants; 77 | socket.to(curRoom).broadcast.emit("occupantsChanged", { occupants }); 78 | 79 | if (Object.keys(occupants).length === 0) { 80 | console.log("everybody left room"); 81 | delete rooms[curRoom]; 82 | } 83 | } 84 | }); 85 | }); 86 | 87 | webServer.listen(port, () => { 88 | console.log("listening on http://localhost:" + port); 89 | }); 90 | -------------------------------------------------------------------------------- /research/perf/physics-performance.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ### Physics performance in my "Winter Wonderland" Christmas Scene as of 20/12/2021 4 | 5 | Methodology: 6 | 7 | - Create modified HTML file with only selected scene elements 8 | - Start scene up, leave for ~20 seconds to "stabilize" 9 | - Logging records physics tick averaged over 100 ticks - observe these in Chrome debugger. 10 | - I eyeball 5-10 of these logs and pick out a rough average. 11 | - Might have been a good idea to record variance too, but I didn't. In general variance was higher on desktop and lower on Oculus Quest 2, which is unsurprising as the desktop was running a load of other applications and browser tabs at the same time. 12 | 13 | Rationale for measuring in idle state. 14 | 15 | - Although an idle scene is less demanding than a test involving dynamic physical interactions, it is much easier to reliably reproduce, and also decently representative of performance - the scene contains many isolated elements, so even when the user is being highly interactive, most of them will be idle most of the time. 16 | 17 | Data below shows the physics tick time in msecs for scnese comprising various subsets of objects. 18 | 19 | All scenes also include: Floor Plane: 1 x static box 20 | 21 | | | Contains | Desktop | Oculus Quest 2 | 22 | | ---------------------------- | ------------------------------------------------------------ | ------------ | -------------- | 23 | | Full scene (21/12) | 17 x static shapes (including floor), 34 kinematic convex shapes, 10 x dynamic convex hulls, 1 x HACD hollow hat. | 42 | 26 | 24 | | Snowman only | 2 x static spheres, 7 x kinematic spheres, 3 x kinematic convex hulls (carrot + 2 x arms) 1 x kinematic HACD hollow hat | 17 | 6.5 | 25 | | Tree and presents only | 3 x static hulls, 9 x kinematic spheres, 1 x kinematic convex hull (star) | 14 | 3.7 | 26 | | Xylophone | 5 x static boxes, 8 x kinematic convex hulls | 4 | 1.15 | 27 | | Marble run | 3 x static boxes, 6 x kinematic boxes | 2.5 | 0.5 | 28 | | Bowling Alley | 3 x static boxes, 10 x dynamic convex hulls | 18 | 5 | 29 | | SUM OF COMPONENTS | See "full scene" above. | 55.5 (vs 42) | 16.85 (vs. 26) | 30 | | Snowman without HACD hat | 2 x static spheres, 7 x kinematic spheres, 3 x kinematic hulls (carrot + 2 x arms) | 4 | 1.5 | 31 | | Implied cost of HACD hat | 1 x kinematic HACD hollow hat. HOWEVER, cost of HACD hat appears to be *higher* in the context of the whole scene.... see next row... | 13 | 5 | 32 | | Total scene without HACD hat | See "full scene" above. | 25 | 11.5 | 33 | 34 | Implied approx cost of each element: 35 | 36 | - HACD hollow hat: Up to 15msecs 37 | - Dynamic shapes: 0.5msecs 38 | - Kinetic shapes: 0.2 to 0.3 msecs 39 | - Static shapes: < 0.1msecs 40 | 41 | 42 | 43 | Target for 60fps is to use substantially less than 16 msecs (ideally < 10msecs, as we need CPU for other functions as well). 44 | 45 | 46 | 47 | Unknowns: 48 | 49 | - Non-linearity - why do values on desktop add up to more than the total, whereas on Oculus Quest 2 they add up to (substantially) less than the total? 50 | - Seems to be substantially influenced by non-linearity associated with the HACD hat. 51 | - Without this, numbers seem to roughly add up on Oculus Quest 2 52 | - Not clear why we don't see such a negative non-linear effect on desktop, but desktop numbers are less reliable as I'm testing with a bunch of other applications running, so it's a much less controlled environment. 53 | 54 | 55 | 56 | Implications: 57 | 58 | - To achieve 60fps on Oculus Quest, the hollow snowman's hat has to go. It uses almost our entire physics budget up just on its own! 59 | - Possible solution might be to make it square in shape (vs. currently a 10-sided approximation of a circle) 60 | - Plausibly that would cut the cost to 2/5ths... 61 | - The fact that most objects are "stuck" and hence kinetic, most of the time, is beneficial for physics performance. Maybe 40-50% less cost than equivalent dynamic objects. 62 | - Hooray for sticky snow! 63 | - Nothing else (other than reducing overall scene complexity) is likely to make much difference - e.g. modelling penguins as cylinders rather than convex hulls is unlikely to move the needle very far at all. 64 | 65 | 66 | 67 | ### Some more notes on HACD snowman's hat 68 | 69 | Looking at overall scene physics ttick time on Oculus Quest 2... 70 | 71 | - Reducing hat geometry from 10 sides to 4 sides, with HACD gets us from 26msecs to ~18msecs. 72 | - Making the hat 10 sided, non-hollow, with HACD also gets ups to ~18 msecs (since the brim sticks out, HACD still has a bunch of work to do for the outside of the hat) 73 | - Making the hat 10-sides non-hollow with a convex hull shape gets us down to ~12 msecs. 74 | 75 | I don't really care about the non-convex exterior of the hull. A convex wrap would be fine for that. But I would like the hat to still have a hollow interior. 76 | 77 | - Not sure whether that can be achieved... Segmenting the hat into a set of radial segments, each given a convex wrap, and kept rigidly in formation with parent-child relationships, would work as long as the object is kinematic, but things will fall apart badly as soon as all those objects become dynamic. 78 | 79 | - A hat without a brim might be a lot simpler... 80 | 81 | - Or maybe I could have the brim as a separate child object that is decorative only, with no collision physics on it (like I have done with the ribbons on the presents)... 82 | 83 | 84 | 85 | 86 | 87 | Further update on the HACD hat... 88 | 89 | - After adding 50 fence posts around the perimeter of the scene, the impact of the HACD hat has become much worse. 90 | - Without the HACD hat, physics tick processing is now ~9msecs (yes, it's gone down as a result of adding more physics objects, that doesn't make much sense but it seems to have hapened) 91 | - With the HACD hat, physics tick processing is now ~35msecs (way up vs. before). 92 | - So the HACD hat has to go. 93 | - One idea I have had is to have the hat track its orientation, and switch from hull shape to HACD shape only when upside down. If I can make the switchover clean, that might get me the best of both worlds... -------------------------------------------------------------------------------- /research/physics-ammo/Object-types.md: -------------------------------------------------------------------------------- 1 | Current status: 2 | 3 | - Static objects OK 4 | - Stickable objects OK 5 | - Dynamic non-sticky objects OK 6 | - Sticky dynamic objects not OK - really need reparenting working with physics to manage these in a stable way... 7 | - New throwing physics TBC - not yet tested in VR. 8 | 9 | 10 | 11 | Net steps: 12 | 13 | - Maybe move to this codebase & API anyway. Works for everything in current scene & enables dynamic non-sticky objects. 14 | - Prototype re-parenting with Ammo.js physics *(why doesn't it work? Can it work?) 15 | - Test how new velocity-on-release throwing physics feels. 16 | 17 | 18 | 19 | Sticky static object 20 | 21 | - E.g. snowman's body & arms. 22 | - Just regular static objects with "sticky" attribute 23 | 24 | 25 | 26 | Stickable objects 27 | 28 | - E.g. eyes, carrot 29 | - Dynamic objects, moveable by hand 30 | - When positioned overlapping a sticky object , they stick to it. 31 | - They select one sticky parent object as the sticky-parent, and always move with this 32 | - (except when grabbed by a hand. Hand grabbing dominates over sticky-parent) 33 | 34 | 35 | 36 | Sticky dynamic objects 37 | 38 | - Eg. snowball 39 | - Act just like stickable objects... 40 | - But also: stackable objects can stick to them... 41 | - ... and they will stick to stickable objects. 42 | 43 | 44 | 45 | Dynamic objects 46 | 47 | - E.g. presents 48 | - Dynamic objects, moveable by hand 49 | - But they don't stick to objects, objects don't stick to them. 50 | - 51 | 52 | 53 | 54 | Attributes: 55 | 56 | - sticky 57 | - stickable 58 | - grabbable 59 | - static 60 | 61 | 62 | 63 | Marble run = static 64 | 65 | Snowman base = sticky, static 66 | 67 | Snowman carrot = stickable, grabbable 68 | 69 | Present = grabbable 70 | 71 | Snowball = sticky, grabbable 72 | 73 | 74 | 75 | So... objects are: 76 | 77 | - static, grabbable, or neither (if object doesn't interact with physics at all) 78 | - sticky, stickable, or neither. 79 | 80 | 81 | 82 | Wrapped up in a single component: 83 | 84 | movement="type: static | grabbable; stickiness: sticky | stickable | none" 85 | 86 | For easy access, "sticky" and "stickable" attributes are set directly on objects by the movement component. 87 | 88 | We'll call this rewrite "phase 3" - I want to preserve "phase 2" for demos etc. 89 | 90 | 91 | 92 | 93 | For simplicity & consistency, ammo-shape is always defined externally to this component. 94 | 95 | 96 | 97 | 98 | 99 | Migrating phase 2 to phase 3: 100 | 101 | sticky-object -> movement="type:static; stickiness:sticky" + define ammo shape externally. 102 | 103 | sticky object -> movement="type:static; stickiness:sticky" + define ammo shape externally. 104 | 105 | 106 | 107 | movable-object -> movement="type:grabbable; stickiness:stickable" + define ammo shape externally. 108 | 109 | *don't* define shape in mixins (unreliable, or maybe doesn't work at all?) -------------------------------------------------------------------------------- /research/physics-ammo/advanced-physics-test-1.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Advanced Physics Test 1 - Hat & Coals 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | --> 18 | 19 | 22 | 23 | 26 | 27 | 30 | 31 | 35 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 45 | 46 | 47 | 48 | 49 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 80 | 81 | 82 | 83 |
84 |

85 | Hollow snowman's hat full of dynamic spheres.
86 | Key goal of this test is to reproduce stability problems (crashes) seen in VR. 87 | Keyboard Controls:
88 | IJKL (like WASD) to move the hat
89 | O & P to rotate CCW & CW
90 |

91 |
92 | 93 | 94 | -------------------------------------------------------------------------------- /research/physics-ammo/dynamic-kinematic-switch.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving a single object with Ammo.js 4 | 5 | 6 | 7 | 8 | 85 | 86 | 87 | 88 | 89 | 93 | 94 | 95 | 100 | 101 | 102 | 103 | 108 | 109 | 110 |
111 |

112 | Basic 2D simulation of function to switch between dynamic & kinematic ammo bodies.
113 | Keyboard Controls:
114 | Sphere begins in Kinematic Mode. 115 | 1 to set Dynamic Mode. 116 | 2 to set Kinematic Mode. 117 | In Kinematic Mode, IJKL (like WASD) to move the sphere
118 | Sphere changes color (cycling through the rainbow) eac time a collision begins or ends. 119 | This allows for easy confirmation that collisions are being detected (there are also console logs). 120 | Gravity is artificially low, to make behaviour easier to ovbserve.
121 |

122 |
123 | 124 | 125 | -------------------------------------------------------------------------------- /research/physics-ammo/move-objects-ph2.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 44 |
45 |

46 | Basic 2D simulation of grabbing a snowman's nose, using "Phase 2" physics (as described in the Physics README).
47 | Keyboard Controls:
48 | IJKL (like WASD) to move the pink block (which represents a hand)
49 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
50 | O & P to rotate CCW & CW (must be gripped to affect object)
51 | To test conservation of velocity / rotation on release, hold the direction or rotation key down while releasing teh grip
52 | WASD and mouse also move view as usual.
53 | Gravity is artificially low, to make behaviour on release easier to ovbserve.
54 |

55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /research/physics-ammo/move-objects-ph3.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 27 | 28 | 32 | 33 | 34 | 35 | 36 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 55 | 56 | 57 | 58 |
59 |

60 | Basic 2D simulation of grabbing a snowman's nose, using "Phase 3" physics (as described in the Physics README).
61 | Keyboard Controls:
62 | IJKL (like WASD) to move the pink block (which represents a hand)
63 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
64 | O & P to rotate CCW & CW (must be gripped to affect object)
65 | To test conservation of velocity / rotation on release, hold the direction or rotation key down while releasing teh grip
66 | WASD and mouse also move view as usual.
67 | Gravity is artificially low, to make behaviour on release easier to observe.
68 | Note that one side effect of low gravity is that objects my go to sleep (freeze) sooner than you would expect.
69 |

70 |
71 | 72 | 73 | -------------------------------------------------------------------------------- /research/physics-ammo/move-objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 35 | 36 | 41 | 42 | 43 | 44 |
45 |

46 | Basic 2D simulation of grabbing a snowman's nose, using "Phase 1" physics (as described in the Physics README).
47 | Keyboard Controls:
48 | IJKL (like WASD) to move the pink block (which represents a hand)
49 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
50 | O & P to rotate CCW & CW (must be gripped to affect object)
51 | To test conservation of velocity / rotation on release, hold the direction or rotation key down while releasing teh grip
52 | WASD and mouse also move view as usual.
53 | Gravity is artificially low, to make behaviour on release easier to ovbserve.
54 |

55 |
56 | 57 | 58 | -------------------------------------------------------------------------------- /research/physics-ammo/reparenting-physics-objects: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reparenting physics objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 125 | 126 | 127 | 132 | 133 | 134 | 135 | 140 | 141 | 142 |
143 |

144 | Basic 2D simulation of function to switch between dynamic & kinematic ammo bodies.
145 | Keyboard Controls:
146 | Sphere begins in Kinematic Mode. 147 | 1 to set Dynamic Mode. 148 | 2 to set Kinematic Mode. 149 | In Kinematic Mode, IJKL (like WASD) to move the sphere
150 | Sphere changes color (cycling through the rainbow) eac time a collision begins or ends. 151 | This allows for easy confirmation that collisions are being detected (there are also console logs). 152 | Gravity is artificially low, to make behaviour easier to ovbserve.
153 |

154 |
155 | 156 | 157 | -------------------------------------------------------------------------------- /research/physics-ammo/reparenting-physics-objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Reparenting physics objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 138 | 139 | 140 | 145 | 146 | 147 | 148 | 153 | 154 | 155 |
156 |

157 | Basic 2D simulation of function to switch between dynamic & kinematic ammo bodies.
158 | Keyboard Controls:
159 | Sphere begins in Kinematic Mode. 160 | 1 to set Dynamic Mode. 161 | 2 to set Kinematic Mode. 162 | 3 to parent to green sphere (goes blue) 163 | 4 to unparent from green sphere (goes green again) 164 | In Kinematic Mode, IJKL (like WASD) to move the sphere
165 | Sphere changes color (cycling through the rainbow) eac time a collision begins or ends. 166 | This allows for easy confirmation that collisions are being detected (there are also console logs). 167 | Gravity is artificially low, to make behaviour easier to ovbserve.
168 |

169 |
170 | 171 | 172 | -------------------------------------------------------------------------------- /research/physics-ammo/repro-for-issue-197.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Snowballs and Other Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 33 | 34 | 36 | 37 | 42 | 43 | 44 | 45 |
46 |

47 | Basic 2D simulation of grabbing a snowman's nose, using "Phase 2" physics (as described in the Physics README).
48 | Keyboard Controls:
49 | IJKL (like WASD) to move the pink block (which represents a hand)
50 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
51 | O & P to rotate CCW & CW (must be gripped to affect object)
52 | To test conservation of velocity / rotation on release, hold the direction or rotation key down while releasing teh grip
53 | WASD and mouse also move view as usual.
54 | Gravity is artificially low, to make behaviour on release easier to ovbserve.
55 |

56 |
57 | 58 | 59 | -------------------------------------------------------------------------------- /research/physics-ammo/simple-object-collisions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving a single object with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /research/physics-ammo/testing-snowballs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Snowballs and Other Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 27 | 28 | 29 | 30 | 31 | 38 | 39 | 41 | 43 | 45 | 50 | 51 | 52 | 53 |
54 |

55 | Basic 2D simulation of grabbing a snowman's nose, using "Phase 2" physics (as described in the Physics README).
56 | Keyboard Controls:
57 | IJKL (like WASD) to move the pink block (which represents a hand)
58 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
59 | O & P to rotate CCW & CW (must be gripped to affect object)
60 | To test conservation of velocity / rotation on release, hold the direction or rotation key down while releasing teh grip
61 | WASD and mouse also move view as usual.
62 | Gravity is artificially low, to make behaviour on release easier to ovbserve.
63 |

64 |
65 | 66 | 67 | -------------------------------------------------------------------------------- /research/physics-benchmarks/benchmark1-ammo.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Physics Benchmark Test - Ammo 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 | Average tick (msecs): 15 |
16 | measuring... 17 |
18 |
19 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | -------------------------------------------------------------------------------- /research/physics-benchmarks/benchmark1-physx.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Physics Benchmark Test - PhysX 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 |
15 | Average tick (msecs): 16 |
17 | measuring... 18 |
19 |
20 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /research/physics-benchmarks/benchmarks.js: -------------------------------------------------------------------------------- 1 | // creates a pinboard height x width, + 2m at top & bottom, laid flat. 2 | AFRAME.registerComponent('pinboard', { 3 | 4 | schema: { 5 | physics: {type: 'string'}, // physx or ammo. 6 | width: {type: 'number', default: 10}, 7 | height: {type: 'number', default: 10}, 8 | }, 9 | 10 | init() { 11 | 12 | const width = this.data.width; 13 | const height = this.data.height; 14 | 15 | const createBox = (width, height, depth, x, y, z, color, yRot) => { 16 | const box = document.createElement('a-box'); 17 | box.setAttribute('width', width) 18 | box.setAttribute('height', height) 19 | box.setAttribute('depth', depth) 20 | box.setAttribute('color', color) 21 | 22 | if (this.data.physics === "ammo") { 23 | box.setAttribute('ammo-body', 'type:static') 24 | box.setAttribute('ammo-shape', 'type:box;fit:all') 25 | } 26 | else { 27 | box.setAttribute('physx-body', 'type:static') 28 | } 29 | 30 | box.object3D.position.set(x, y, z) 31 | box.object3D.rotation.set(0, yRot, 0) 32 | this.el.appendChild(box) 33 | 34 | return box 35 | } 36 | 37 | this.base = createBox(width, 1, height + 4, 0, -0.5, 0, 'grey', 0) 38 | createBox(0.1, 2, height + 4, width / 2 + 0.05, 0, 0, 'grey', 0) 39 | createBox(0.1, 2, height + 4, -width / 2 - 0.05, 0, 0, 'grey', 0) 40 | 41 | for (let ii = 0; ii < this.data.height; ii++) { 42 | const even = ii % 2 43 | for (let jj = 0; jj < this.data.width - 1; jj++) { 44 | 45 | createBox(0.1, 1, 0.1, 46 | jj - width / 2 + even / 2 + 0.75, 47 | 0.5, 48 | ii - height / 2 + 0.5, 49 | 'black', 50 | Math.PI / 4) 51 | } 52 | } 53 | } 54 | }) 55 | 56 | AFRAME.registerComponent('dynamic-ball', { 57 | 58 | schema: { 59 | physics: {type: 'string'}, // physx or ammo. 60 | yKill: {type: 'number', default: -10} 61 | }, 62 | 63 | init() { 64 | const el = this.el 65 | el.setAttribute('radius', 0.3) 66 | 67 | if (this.data.physics === "ammo") { 68 | el.setAttribute('ammo-body', 'type:dynamic') 69 | el.setAttribute('ammo-shape', 'type:sphere; fit:all') 70 | 71 | } 72 | else { 73 | el.setAttribute('physx-body', 'type:dynamic') 74 | } 75 | 76 | el.setAttribute('color', 'yellow') 77 | }, 78 | 79 | tick() { 80 | if (this.el.object3D.position.y < this.data.yKill) { 81 | this.el.emit("recycle") 82 | } 83 | } 84 | }) 85 | 86 | AFRAME.registerComponent('ball-recycler', { 87 | 88 | schema: { 89 | physics: {type: 'string'}, // physx or ammo. 90 | ballCount: {type: 'number', default: 10}, 91 | width: {type: 'number', default: 8}, // width of spawn field 92 | depth: {type: 'number', default: 8}, // depth of spawn field (after initial spawn balls always spawned at far depth) 93 | yKill: {type: 'number', default: -10} 94 | }, 95 | 96 | init() { 97 | 98 | this.recycleBall = this.recycleBall.bind(this); 99 | 100 | // at start of day, spawn balls 101 | for (let ii = 0; ii < this.data.ballCount; ii++) { 102 | 103 | this.createBall(false) 104 | } 105 | }, 106 | 107 | createBall(recycled) { 108 | 109 | const { height, depth, width } = this.data 110 | const pos = this.el.object3D.position 111 | 112 | const ball = document.createElement('a-sphere') 113 | 114 | ball.setAttribute('dynamic-ball', {yKill: this.data.yKill, 115 | physics: this.data.physics}) 116 | x = pos.x + Math.random() * width - width / 2 117 | z = recycled ? (pos.z -depth / 2) : (pos.z + Math.random() * depth - depth / 2) 118 | ball.object3D.position.set(x, pos.y, z) 119 | this.el.sceneEl.appendChild(ball) 120 | 121 | ball.addEventListener('recycle', this.recycleBall); 122 | 123 | }, 124 | 125 | recycleBall(evt) { 126 | 127 | const ball = evt.target 128 | 129 | ball.parentNode.removeChild(ball); 130 | 131 | this.createBall(true) 132 | } 133 | }) 134 | 135 | 136 | AFRAME.registerComponent('tick-time-display', { 137 | 138 | schema: { 139 | outputEl: {type: 'selector'}, 140 | sceneOutputEl: {type: 'selector'} 141 | }, 142 | 143 | init() { 144 | this.updateData = this.updateData.bind(this); 145 | 146 | this.el.sceneEl.addEventListener('physics-tick-timer', this.updateData) 147 | 148 | }, 149 | 150 | updateData(evt) { 151 | 152 | this.data.outputEl.innerHTML = `${evt.detail.engine} engine / ${evt.detail.wrapper} wrapper` 153 | this.data.sceneOutputEl.setAttribute("text", `value: Average tick (msecs): ${evt.detail.engine} engine / ${evt.detail.wrapper} wrapper`) 154 | } 155 | }) -------------------------------------------------------------------------------- /research/physics-cannon/move-objects.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 17 | 18 | 22 | 23 | 24 | 25 | 26 | 32 | 33 | 34 | 35 | 40 | 41 | 42 | 43 | 44 | 45 | -------------------------------------------------------------------------------- /research/physics-cannon/simple-object-collisions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving a single object with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 54 | 55 | 56 | 57 | 58 | 62 | 63 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 74 | 75 | 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /research/snowballs/painting-snowballs.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Moving Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 19 | 20 | 25 | 26 | 29 | 30 | 31 | 32 | 33 | 34 | 41 | 42 | 43 | 44 | 45 | 51 | 52 | 53 | 54 |
55 |

56 | Basic 2D simulation of pinting a snowball.
57 | Keyboard Controls:
58 | IJKL (like WASD) to move the pink block (which represents a hand)
59 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
60 | T to toggle trigger - used to change paintbrush color. 61 | O & P to rotate CCW & CW (must be gripped to affect object)
62 | Gravity is artificially low, to make behaviour on release easier to observe.
63 | Note that one side effect of low gravity is that objects my go to sleep (freeze) sooner than you would expect.
64 |

65 |
66 | 67 | 68 | -------------------------------------------------------------------------------- /research/snowballs/snowball-generator.js: -------------------------------------------------------------------------------- 1 | AFRAME.registerComponent('snowball-generator', { 2 | 3 | schema: { 4 | // height level must go below to generate a snowball 5 | heightLevel: {type: 'number', default: 0.1}, 6 | // how long must stay there with grip held down to generate a snowball. 7 | timer: {type: 'number', default: 1000}, 8 | mixin: {type: 'string', default: 'snowball'}, 9 | scale: {type: 'number', default: 0.15} 10 | 11 | }, 12 | 13 | init() { 14 | 15 | this.timePassed = 0; 16 | this.el.addEventListener("gripdown", this.gripDown.bind(this)); 17 | this.el.addEventListener("gripup", this.gripUp.bind(this)); 18 | }, 19 | 20 | 21 | tick(time, timeDelta) { 22 | 23 | // To create a snowball, certain conditions must be met continuously for 24 | // a set period of time. 25 | const emptyHand = (!this.el.components['hand'] || 26 | !this.el.components['hand'].grabbedEl); 27 | 28 | if (emptyHand && 29 | this.el.object3D.position.y <= this.data.heightLevel && 30 | this.gripIsDown) { 31 | 32 | this.timePassed += timeDelta; 33 | 34 | if (this.timePassed > this.data.timer) { 35 | this.createSnowball(); 36 | // for our purposes, consider grip up 37 | // i.e. don't make any more snowballs until user 38 | // explicitly grips down again. 39 | this.gripIsDown = false; 40 | } 41 | } 42 | else { 43 | // conditions not met - reset timer to zero. 44 | this.timePassed = 0; 45 | } 46 | }, 47 | 48 | gripDown() { 49 | this.gripIsDown = true; 50 | }, 51 | 52 | gripUp() { 53 | this.gripIsDown = false; 54 | }, 55 | 56 | createSnowball() { 57 | 58 | snowball = document.createElement('a-entity'); 59 | snowball.setAttribute('mixin', this.data.mixin) 60 | const radius = snowball.mixinEls[0].componentCache.geometry.radius; 61 | snowball.setAttribute('ammo-shape', `type:sphere; fit:manual; sphereRadius:${radius * this.data.scale}`) 62 | snowball.object3D.scale.set(this.data.scale, this.data.scale, this.data.scale); 63 | snowball.object3D.position.copy(this.el.object3D.position); 64 | snowball.setAttribute('snowball-grow-on-roll', ""); 65 | this.el.sceneEl.appendChild(snowball); 66 | } 67 | }); 68 | 69 | 70 | AFRAME.registerComponent('snowball-grow-on-roll', { 71 | 72 | schema: { 73 | // snowball center must be below radius + this height to grow. 74 | heightDelta: {type: 'number', default: 0.01}, 75 | // Growth of radius per radius travelled. 76 | // This factor is added to "scale" for each radian of rotation. 77 | growthRate: {type: 'number', default: 0.01}, 78 | 79 | // radius at which snowballs cease to be grabbable/stickable. 80 | // not working yet, 81 | //maxGrabbableRadius: {type: 'number', default: 0.2}, 82 | }, 83 | 84 | init() { 85 | 86 | this.lastWorldPosition = new THREE.Vector3() 87 | this.lastWorldQuaternion = new THREE.Quaternion() 88 | this.el.object3D.getWorldPosition(this.lastWorldPosition); 89 | this.el.object3D.getWorldQuaternion(this.lastWorldQuaternion); 90 | 91 | this.geometryRadius = this.el.mixinEls[0].componentCache.geometry.radius; 92 | this.setRadius(); 93 | 94 | // used in calculations 95 | this.currentWorldPosition = new THREE.Vector3() 96 | this.currentWorldQuaternion = new THREE.Quaternion() 97 | this.distanceMoved = new THREE.Vector3() 98 | 99 | this.scaleVector = new THREE.Vector3(); 100 | }, 101 | 102 | setRadius() { 103 | 104 | this.el.object3D.getWorldScale(this.scaleVector) 105 | this.radius = this.geometryRadius * this.scaleVector.x; 106 | this.maxHeight = this.radius + this.data.heightDelta; 107 | 108 | /*if (this.radius > this.data.maxGrabbableRadius) { 109 | this.el.setAttribute('movement', "type:dynamic; stickiness:none"); 110 | }*/ 111 | }, 112 | 113 | tick(time, timeDelta) { 114 | 115 | this.el.object3D.getWorldPosition(this.currentWorldPosition); 116 | this.el.object3D.getWorldQuaternion(this.currentWorldQuaternion); 117 | 118 | if (this.lastWorldPosition.y < this.maxHeight && 119 | this.currentWorldPosition.y < this.maxHeight) { 120 | // snowball stayed below height limit. 121 | // grow it by horoizontal distance travelled. 122 | 123 | /* Distance-bsed - growth not using this */ 124 | /*this.distanceMoved.subVectors(this.currentWorldPosition, 125 | this.lastWorldPosition); 126 | this.distanceMoved.y = 0; 127 | 128 | const distance = this.distanceMoved.length();*/ 129 | 130 | const distance = this.lastWorldQuaternion.angleTo(this.currentWorldQuaternion); 131 | 132 | // require minium amount of turning speed before we grow. 133 | if (distance * 1000/timeDelta > 0.5) { 134 | 135 | const growthAmount = 0.1 * this.data.growthRate * distance / this.radius; 136 | 137 | const newScale = this.el.object3D.scale.x + growthAmount; 138 | this.el.object3D.scale.set(newScale, newScale, newScale); 139 | this.el.object3D.matrix.compose(this.el.object3D.position, 140 | this.el.object3D.quaternion, 141 | this.el.object3D.scale); 142 | 143 | this.setRadius() 144 | } 145 | } 146 | // update position for next tick. 147 | this.lastWorldPosition.copy(this.currentWorldPosition); 148 | this.lastWorldQuaternion.copy(this.currentWorldQuaternion); 149 | } 150 | }); 151 | -------------------------------------------------------------------------------- /research/snowballs/test-snowball-generation.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | Snowballs and Other Objects with Ammo.js 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 18 | 19 | 23 | 24 | 25 | 26 | 34 | 35 | 37 | 43 | 44 | 45 | 46 |
47 |

48 | SNowball generation test.
49 | Keyboard Controls:
50 | IJKL (like WASD) to move the pink block (which represents a hand)
51 | Left Shift to grip (must be gripped to affect object). Pink block becomes more transparent on grip.
52 | O & P to rotate CCW & CW (must be gripped to affect object)
53 | To generate a snowball, hold down grip for 1 second at a height below 2m (will be 0.1m in production) 54 |

55 |
56 | 57 | 58 | --------------------------------------------------------------------------------