├── .gitignore
├── LICENSE
├── README.md
├── assets
├── angel.basis
├── angel.min.glb
├── ballfx.basis
├── beamfx.png
├── blender
│ ├── angel.blend
│ ├── door.blend
│ ├── generic_controller.blend
│ ├── hall.blend
│ ├── hall_empty.blend
│ ├── hall_for_hubs.blend
│ ├── sound.blend
│ ├── spray.blend
│ ├── teleport.blend
│ └── vertigo.blend
├── checkboard.basis
├── clouds.basis
├── controller.basis
├── doorfx.basis
├── flare.jpg
├── foxr.png
├── generic_controller.glb
├── glow.basis
├── grid.png
├── haldezollern.basis
├── haldezollern_small.basis
├── hall.glb
├── hall_variants
│ └── hall_empty.glb
├── lakebyllesby.basis
├── lakebyllesby_small.basis
├── lightmap.jpg
├── loadingbg.jpg
├── mozillamr.basis
├── newsticker.jpg
├── ogg
│ ├── bells.ogg
│ ├── birds.ogg
│ ├── chopin.ogg
│ ├── cowbell.ogg
│ ├── forest.ogg
│ ├── guiro.ogg
│ ├── horn.ogg
│ ├── mandolin.ogg
│ ├── motorhorn.ogg
│ ├── spray.ogg
│ ├── squeaker.ogg
│ ├── surdo.ogg
│ ├── teleport_a.ogg
│ ├── teleport_b.ogg
│ ├── train.ogg
│ ├── trumpet.ogg
│ ├── whistle.ogg
│ ├── wind.ogg
│ ├── xylophone1.ogg
│ ├── xylophone10.ogg
│ ├── xylophone11.ogg
│ ├── xylophone12.ogg
│ ├── xylophone13.ogg
│ ├── xylophone2.ogg
│ ├── xylophone3.ogg
│ ├── xylophone4.ogg
│ ├── xylophone5.ogg
│ ├── xylophone6.ogg
│ ├── xylophone7.ogg
│ ├── xylophone8.ogg
│ └── xylophone9.ogg
├── paintings
│ ├── bosch.basis
│ ├── degas.basis
│ ├── rembrandt.basis
│ ├── seurat.basis
│ └── sorolla.basis
├── panel.basis
├── pg_bg.jpg
├── pg_door_lm.jpg
├── pg_floor_lm.jpg
├── sky.png
├── sound.glb
├── sound_door.glb
├── sound_door_lm.jpg
├── sound_shadow.png
├── spray.basis
├── spray.glb
├── spray_brush.png
├── sshot.jpg
├── stereopanoL.basis
├── stereopanoR.basis
├── stereopano_small.basis
├── teleport.glb
├── thuringen.basis
├── thuringen_small.basis
├── tigerturtle.basis
├── tigerturtle_small.basis
├── travertine2.basis
├── tweets.json
├── vertigo.glb
├── vertigo_door_lm.jpg
├── vertigo_lm.basis
├── zapporthorn.basis
├── zapporthorn_small.basis
└── zoomicon.png
├── bundle.js
├── bundle.js.map
├── index.html
├── package-lock.json
├── package.json
├── packshaders.py
├── res
├── apple-touch-icon-114x114.png
├── apple-touch-icon-120x120.png
├── apple-touch-icon-144x144.png
├── apple-touch-icon-152x152.png
├── apple-touch-icon-57x57.png
├── apple-touch-icon-72x72.png
├── big_thumbnail.jpg
├── favicon-16x16.png
├── favicon-32x32.png
├── favicon.ico
└── mstile-144x144.png
├── src
├── assets.js
├── components
│ └── index.js
├── index.js
├── lib
│ ├── ColorUtils.js
│ ├── ColorWheel.js
│ ├── EventDispatcher.js
│ ├── PositionalAudioPolyphonic.js
│ ├── RayControl.js
│ ├── Teleport.js
│ ├── VRButton.js
│ ├── assetManager.js
│ ├── shaders.js
│ ├── slideshow.js
│ └── utils.js
├── rooms
│ ├── Hall.js
│ ├── Panorama.js
│ ├── PanoramaStereo.js
│ ├── PhotogrammetryObject.js
│ ├── Sound.js
│ └── Vertigo.js
├── shaders
│ ├── basic.vert.glsl
│ ├── beam.frag.glsl
│ ├── door.frag.glsl
│ ├── panoball.frag.glsl
│ ├── panoball.vert.glsl
│ └── zoom.frag.glsl
├── stations
│ ├── Graffiti.js
│ ├── InfoPanels.js
│ ├── InfoPanelsData.js
│ ├── NewsTicker.js
│ ├── Paintings.js
│ ├── PanoBalls.js
│ └── Xylophone.js
├── systems
│ ├── AreaCheckerSystem.js
│ ├── BillboardSystem.js
│ ├── ControllersSystem.js
│ ├── DebugHelperSystem.js
│ ├── HierarchySystem.js
│ ├── SDFTextSystem.js
│ ├── SystemsGroup.js
│ └── TransformSystem.js
└── vendor
│ ├── basis_transcoder.js
│ ├── basis_transcoder.wasm
│ ├── draco_decoder.js
│ ├── draco_decoder.wasm
│ └── draco_wasm_wrapper.js
└── webpack.config.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # Logs
2 | *.pem
3 | logs
4 | *.log
5 | npm-debug.log*
6 | yarn-debug.log*
7 | yarn-error.log*
8 | # bundle.*
9 |
10 | *DS_Store
11 |
12 | # Runtime data
13 | pids
14 | *.pid
15 | *.seed
16 | *.pid.lock
17 |
18 | # Directory for instrumented libs generated by jscoverage/JSCover
19 | lib-cov
20 |
21 | # Coverage directory used by tools like istanbul
22 | coverage
23 |
24 | # nyc test coverage
25 | .nyc_output
26 |
27 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
28 | .grunt
29 |
30 | # Bower dependency directory (https://bower.io/)
31 | bower_components
32 |
33 | # node-waf configuration
34 | .lock-wscript
35 |
36 | # Compiled binary addons (https://nodejs.org/api/addons.html)
37 | build/Release
38 |
39 | # Dependency directories
40 | node_modules/
41 | jspm_packages/
42 |
43 | # TypeScript v1 declaration files
44 | typings/
45 |
46 | # Optional npm cache directory
47 | .npm
48 |
49 | # Optional eslint cache
50 | .eslintcache
51 |
52 | # Optional REPL history
53 | .node_repl_history
54 |
55 | # Output of 'npm pack'
56 | *.tgz
57 |
58 | # Yarn Integrity file
59 | .yarn-integrity
60 |
61 | # dotenv environment variables file
62 | .env
63 |
64 | # next.js build output
65 | .next
66 |
67 | #bundle.js
68 | #bundle.js.map
69 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019 Mozilla Mixed Reality
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 | # Hello WebXR!
2 |
3 | 
4 |
5 | [Try it here!](https://mixedreality.mozilla.org/hello-webxr/index.html)
6 |
7 | This is a WebXR demo made to celebrate the [WebXR spec](https://immersive-web.github.io/webxr/) release at the end of 2019. It showcases several small experiences, perfect to test different kind of interactions and situations in Virtual Reality. For newcomers, it's a nice entry point to the medium, and web developers may find many things they can reuse and learn (more info on the [launch article](https://blog.mozvr.com/hello-webxr)).
8 |
9 | ## How to build
10 |
11 | 1. `npm install`
12 | 2. `npm start`
13 | 3. Open `http://localhost:8080`
14 |
15 |
16 | ### Shader packing
17 |
18 | If you make changes to the shaders you'll need to repack them. To keep things simple, we made a simple script `packshaders.py`:
19 |
20 | `python packshaders.py [seconds]`
21 |
22 | where `seconds` is an optional parameter (defaults to 5) to define how many seconds to wait until next rebuild (doesn't watch file changes)
23 |
24 |
25 | ## Third party content
26 |
27 | * Photogrammetry model by Geoffrey Marchal ([Sketchfab](https://sketchfab.com/3d-models/baptismal-angel-kneeling-f45f01c63e514d3bad846e82af640f33))
28 | * 360 Panoramas from [Wikimedia Commons](https://commons.wikimedia.org/wiki/Main_Page):
29 | * [Halde Zollern](https://commons.wikimedia.org/wiki/File:Halde_Zollern_Panorama_01.jpg)
30 | * [Lake Byllesby Regional Park](https://commons.wikimedia.org/wiki/File:Lake_Byllesby_Regional_Park_-_360%C2%B0_Equirectangular_Street_View_Photo_(27332591527).jpg)
31 | * [Kloster Paulinzella, Thüringen](https://commons.wikimedia.org/wiki/File:Kloster_Paulinzella,_Th%C3%BCringen,_360x180,_170316,_ako_(1).jpg)
32 | * [Tiger And Turtle, Duisburg](https://commons.wikimedia.org/wiki/File:Tiger_And_Turtle_Panorama.jpg)
33 | * [Zapporthorn, Switzerland](https://commons.wikimedia.org/wiki/File:Zapporthorn_Spherical_Panorama.jpg)
34 | * [Naturalis Biodiversity Center](https://commons.wikimedia.org/wiki/File:Naturalis_Biodiversity_Center_-_Museum_-_Exhibition_Primeval_parade_33_-_Overview_room_with_skeletons_-_Panorama_360_3D.jpg)
35 |
36 | * Classical Paintings:
37 | * [The Garden of Earthly Delights, Hieronymus Bosch](https://commons.wikimedia.org/wiki/File:The_Garden_of_Earthly_Delights_by_Bosch_High_Resolution.jpg)
38 | * [Self-Portrait, Rembrandt van Rijn](https://www.nga.gov/collection/art-object-page.79.html)
39 | * [The Dance Lesson, Edgar Degas](https://www.nga.gov/collection/art-object-page.93045.html)
40 | * [Gray Weather, Grande Jatte, Georges Seurat](https://commons.wikimedia.org/wiki/File:Seurat.jatte.jpg)
41 | * [The Pink Robe, Joaquin Sorolla](http://sorollapaintings.com/images/sorolla-pink-robe-b-3928.jpg)
42 |
43 |
44 | * Public Domain sounds from [freesound.org](https://freesound.org)
45 |
--------------------------------------------------------------------------------
/assets/angel.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/angel.basis
--------------------------------------------------------------------------------
/assets/angel.min.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/angel.min.glb
--------------------------------------------------------------------------------
/assets/ballfx.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ballfx.basis
--------------------------------------------------------------------------------
/assets/beamfx.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/beamfx.png
--------------------------------------------------------------------------------
/assets/blender/angel.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/angel.blend
--------------------------------------------------------------------------------
/assets/blender/door.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/door.blend
--------------------------------------------------------------------------------
/assets/blender/generic_controller.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/generic_controller.blend
--------------------------------------------------------------------------------
/assets/blender/hall.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/hall.blend
--------------------------------------------------------------------------------
/assets/blender/hall_empty.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/hall_empty.blend
--------------------------------------------------------------------------------
/assets/blender/hall_for_hubs.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/hall_for_hubs.blend
--------------------------------------------------------------------------------
/assets/blender/sound.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/sound.blend
--------------------------------------------------------------------------------
/assets/blender/spray.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/spray.blend
--------------------------------------------------------------------------------
/assets/blender/teleport.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/teleport.blend
--------------------------------------------------------------------------------
/assets/blender/vertigo.blend:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/blender/vertigo.blend
--------------------------------------------------------------------------------
/assets/checkboard.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/checkboard.basis
--------------------------------------------------------------------------------
/assets/clouds.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/clouds.basis
--------------------------------------------------------------------------------
/assets/controller.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/controller.basis
--------------------------------------------------------------------------------
/assets/doorfx.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/doorfx.basis
--------------------------------------------------------------------------------
/assets/flare.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/flare.jpg
--------------------------------------------------------------------------------
/assets/foxr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/foxr.png
--------------------------------------------------------------------------------
/assets/generic_controller.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/generic_controller.glb
--------------------------------------------------------------------------------
/assets/glow.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/glow.basis
--------------------------------------------------------------------------------
/assets/grid.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/grid.png
--------------------------------------------------------------------------------
/assets/haldezollern.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/haldezollern.basis
--------------------------------------------------------------------------------
/assets/haldezollern_small.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/haldezollern_small.basis
--------------------------------------------------------------------------------
/assets/hall.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/hall.glb
--------------------------------------------------------------------------------
/assets/hall_variants/hall_empty.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/hall_variants/hall_empty.glb
--------------------------------------------------------------------------------
/assets/lakebyllesby.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/lakebyllesby.basis
--------------------------------------------------------------------------------
/assets/lakebyllesby_small.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/lakebyllesby_small.basis
--------------------------------------------------------------------------------
/assets/lightmap.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/lightmap.jpg
--------------------------------------------------------------------------------
/assets/loadingbg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/loadingbg.jpg
--------------------------------------------------------------------------------
/assets/mozillamr.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/mozillamr.basis
--------------------------------------------------------------------------------
/assets/newsticker.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/newsticker.jpg
--------------------------------------------------------------------------------
/assets/ogg/bells.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/bells.ogg
--------------------------------------------------------------------------------
/assets/ogg/birds.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/birds.ogg
--------------------------------------------------------------------------------
/assets/ogg/chopin.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/chopin.ogg
--------------------------------------------------------------------------------
/assets/ogg/cowbell.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/cowbell.ogg
--------------------------------------------------------------------------------
/assets/ogg/forest.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/forest.ogg
--------------------------------------------------------------------------------
/assets/ogg/guiro.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/guiro.ogg
--------------------------------------------------------------------------------
/assets/ogg/horn.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/horn.ogg
--------------------------------------------------------------------------------
/assets/ogg/mandolin.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/mandolin.ogg
--------------------------------------------------------------------------------
/assets/ogg/motorhorn.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/motorhorn.ogg
--------------------------------------------------------------------------------
/assets/ogg/spray.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/spray.ogg
--------------------------------------------------------------------------------
/assets/ogg/squeaker.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/squeaker.ogg
--------------------------------------------------------------------------------
/assets/ogg/surdo.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/surdo.ogg
--------------------------------------------------------------------------------
/assets/ogg/teleport_a.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/teleport_a.ogg
--------------------------------------------------------------------------------
/assets/ogg/teleport_b.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/teleport_b.ogg
--------------------------------------------------------------------------------
/assets/ogg/train.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/train.ogg
--------------------------------------------------------------------------------
/assets/ogg/trumpet.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/trumpet.ogg
--------------------------------------------------------------------------------
/assets/ogg/whistle.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/whistle.ogg
--------------------------------------------------------------------------------
/assets/ogg/wind.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/wind.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone1.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone1.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone10.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone10.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone11.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone11.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone12.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone12.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone13.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone13.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone2.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone2.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone3.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone3.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone4.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone4.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone5.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone5.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone6.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone6.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone7.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone7.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone8.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone8.ogg
--------------------------------------------------------------------------------
/assets/ogg/xylophone9.ogg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/ogg/xylophone9.ogg
--------------------------------------------------------------------------------
/assets/paintings/bosch.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/paintings/bosch.basis
--------------------------------------------------------------------------------
/assets/paintings/degas.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/paintings/degas.basis
--------------------------------------------------------------------------------
/assets/paintings/rembrandt.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/paintings/rembrandt.basis
--------------------------------------------------------------------------------
/assets/paintings/seurat.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/paintings/seurat.basis
--------------------------------------------------------------------------------
/assets/paintings/sorolla.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/paintings/sorolla.basis
--------------------------------------------------------------------------------
/assets/panel.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/panel.basis
--------------------------------------------------------------------------------
/assets/pg_bg.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/pg_bg.jpg
--------------------------------------------------------------------------------
/assets/pg_door_lm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/pg_door_lm.jpg
--------------------------------------------------------------------------------
/assets/pg_floor_lm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/pg_floor_lm.jpg
--------------------------------------------------------------------------------
/assets/sky.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/sky.png
--------------------------------------------------------------------------------
/assets/sound.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/sound.glb
--------------------------------------------------------------------------------
/assets/sound_door.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/sound_door.glb
--------------------------------------------------------------------------------
/assets/sound_door_lm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/sound_door_lm.jpg
--------------------------------------------------------------------------------
/assets/sound_shadow.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/sound_shadow.png
--------------------------------------------------------------------------------
/assets/spray.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/spray.basis
--------------------------------------------------------------------------------
/assets/spray.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/spray.glb
--------------------------------------------------------------------------------
/assets/spray_brush.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/spray_brush.png
--------------------------------------------------------------------------------
/assets/sshot.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/sshot.jpg
--------------------------------------------------------------------------------
/assets/stereopanoL.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/stereopanoL.basis
--------------------------------------------------------------------------------
/assets/stereopanoR.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/stereopanoR.basis
--------------------------------------------------------------------------------
/assets/stereopano_small.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/stereopano_small.basis
--------------------------------------------------------------------------------
/assets/teleport.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/teleport.glb
--------------------------------------------------------------------------------
/assets/thuringen.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/thuringen.basis
--------------------------------------------------------------------------------
/assets/thuringen_small.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/thuringen_small.basis
--------------------------------------------------------------------------------
/assets/tigerturtle.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/tigerturtle.basis
--------------------------------------------------------------------------------
/assets/tigerturtle_small.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/tigerturtle_small.basis
--------------------------------------------------------------------------------
/assets/travertine2.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/travertine2.basis
--------------------------------------------------------------------------------
/assets/tweets.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "author": "@mozillareality",
4 | "message": "Introducing Hello WebXR! - A #webxr experience for VR headsets for all audiences. Have fun!. #hellowebxr https://tinyurl.com/hello-webxr"
5 | },
6 | {
7 | "author": "@hovercruft",
8 | "message": "Painted a thing! #hellowebxr #berlin"
9 | },
10 | {
11 | "author": "@el_cayumry",
12 | "message": "@mozillareality is nice to just let yourself drift into the metaverse.. #hellowebxr"
13 | },
14 | {
15 | "author": "@fernandojsg",
16 | "message": "Our ECSY library was really handy for our new #hellowebxr demo ^_^"
17 | },
18 | {
19 | "author": "@JohanGuy",
20 | "message": "I didn't expect #hellowebxr experience at all. Not sure what to think..."
21 | },
22 | {
23 | "author": "@Fancypansy",
24 | "message": "Weeee!! ^_^! hellowebxr"
25 | },
26 | {
27 | "author": "@Foxr",
28 | "message": "Hey, look mum! I'm in #hellowebxr =:o)"
29 | },
30 | {
31 | "author": "@humphreymatel",
32 | "message": "Trying the Hello WebXR experience on my #oculusquest. So far so good! #hellowebxr"
33 | },
34 | {
35 | "author": "@GavMov34",
36 | "message": "I'm amazed by WebXR technology! kudos to @mozillareality #hellowebxr https://mixedreality.mozilla.org"
37 | },
38 | {
39 | "author": "@samo_la_utma",
40 | "message": "#hellowebxr Na ou taumafai lava i le demo o Mozilla Hello webXR! E le leaga!"
41 | },
42 | {
43 | "author": "@frederic",
44 | "message": "Go try #hellowebxr demo! Not a thrill but kinda nice :)"
45 | },
46 | {
47 | "author": "@jennymarvel",
48 | "message": "If this is the metaverse, it's not for me :( #hellowebxr"
49 | },
50 | {
51 | "author": "@ovvli",
52 | "message": "WOAH. Great job guys! Got lost initially a bit but finally made it :_) #hellowebxr"
53 | },
54 | {
55 | "author": "@feiss",
56 | "message": "Hope everyone tries and enjoys our #hellowebxr experience! :)"
57 | },
58 | {
59 | "author": "@mountain_news",
60 | "message": "Hello WebXR! is a new VR experience by Mozilla Mixed Reality. Try it at https://mixedreality.mozilla.org #hellowebxr"
61 | },
62 | {
63 | "author": "@JArco",
64 | "message": "Acabo de probar la demo de Mozilla Hello webXR!. Nostamal! :) #hellowebxr"
65 | },
66 | {
67 | "author": "@goroshart",
68 | "message": "#hellowebxr it's quite nice, tried the grafitti thing. Where's the undo??"
69 | },
70 | {
71 | "author": "@Mr R",
72 | "message": "The Mozilla MR guys released a new demo #hellowebxr"
73 | },
74 | {
75 | "author": "@Ocklando",
76 | "message": "Ta chula la demo de #hellowebxr .. Un poco corta, eso sí, pero potita :)"
77 | }
78 | ]
79 |
--------------------------------------------------------------------------------
/assets/vertigo.glb:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/vertigo.glb
--------------------------------------------------------------------------------
/assets/vertigo_door_lm.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/vertigo_door_lm.jpg
--------------------------------------------------------------------------------
/assets/vertigo_lm.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/vertigo_lm.basis
--------------------------------------------------------------------------------
/assets/zapporthorn.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/zapporthorn.basis
--------------------------------------------------------------------------------
/assets/zapporthorn_small.basis:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/zapporthorn_small.basis
--------------------------------------------------------------------------------
/assets/zoomicon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/assets/zoomicon.png
--------------------------------------------------------------------------------
/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 |
46 |
47 |
48 |
49 |
50 | Hello WebXR! - Mozilla Mixed Reality
51 |
52 |
159 |
160 |
161 |
162 |
173 |
174 |
175 |
176 |
177 |
178 |
Hello!
179 |
183 |
184 |
185 |
186 |
189 |
190 |
191 |
207 |
208 |
209 |
210 |
218 |
219 |
220 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hello-webxr",
3 | "version": "1.0.0",
4 | "description": "A showcase of different WebXR experiences and interaction examples",
5 | "private": true,
6 | "scripts": {
7 | "start": "webpack-dev-server -d --https --host 0.0.0.0",
8 | "build": "webpack -p",
9 | "watch": "webpack --watch",
10 | "test": "echo \"Error: no test specified\" && exit 1"
11 | },
12 | "repository": {
13 | "type": "git",
14 | "url": "git+https://github.com/MozillaReality/hello-webxr.git"
15 | },
16 | "keywords": [
17 | "webxr",
18 | "webvr",
19 | "vr",
20 | "ecsy",
21 | "xr",
22 | "mozilla",
23 | "showcase",
24 | "museum"
25 | ],
26 | "author": "",
27 | "license": "MIT",
28 | "bugs": {
29 | "url": "https://github.com/MozillaReality/hello-webxr/issues"
30 | },
31 | "homepage": "https://github.com/MozillaReality/hello-webxr#readme",
32 | "dependencies": {
33 | "ecsy": "0.2.6",
34 | "three": "^0.112.0",
35 | "three-obj-loader": "^1.1.3",
36 | "three-pointerlock": "0.0.2",
37 | "troika-3d-text": "^0.16",
38 | "webpack-dev-server": "^3.10.1",
39 | "webxr-polyfill": "^2.0.1"
40 | },
41 | "devDependencies": {
42 | "@babel/core": "^7.6.4",
43 | "babel-loader": "^8.0.6",
44 | "webpack": "^4.41.0",
45 | "webpack-cli": "^3.3.9"
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/packshaders.py:
--------------------------------------------------------------------------------
1 | import glob, os.path, time, sys
2 |
3 |
4 | def pack():
5 | output = open('./src/lib/shaders.js', 'wt')
6 | files = glob.glob('./src/shaders/*.glsl')
7 |
8 | output.write('export var shaders = {\n')
9 |
10 | for file in files:
11 | f = open(file, 'rt')
12 | shader = f.read()
13 | f.close()
14 | var = '_'.join(os.path.basename(file).split('.')[:-1])
15 | output.write(var + ' : `\n')
16 | output.write(shader.strip())
17 | output.write('\n`,\n\n')
18 |
19 | output.write('};')
20 | output.close()
21 | print('packed')
22 |
23 | print('PACK SHADERS')
24 | while True:
25 | pack()
26 | time.sleep(int(sys.argv[1] if len(sys.argv) > 1 else 5))
27 |
28 |
--------------------------------------------------------------------------------
/res/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/res/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/res/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/res/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/res/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/res/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/res/big_thumbnail.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/big_thumbnail.jpg
--------------------------------------------------------------------------------
/res/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/favicon-16x16.png
--------------------------------------------------------------------------------
/res/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/favicon-32x32.png
--------------------------------------------------------------------------------
/res/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/favicon.ico
--------------------------------------------------------------------------------
/res/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/res/mstile-144x144.png
--------------------------------------------------------------------------------
/src/assets.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | export default {
4 | // hall
5 | foxr_tex: { url: 'foxr.png', options: { encoding: THREE.sRGBEncoding, flipY: false} },
6 | hall_model: { url: 'hall.glb' },
7 | generic_controller_model: { url: 'generic_controller.glb' },
8 | lightmap_tex: { url: 'lightmap.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
9 | controller_tex: { url: 'controller.basis' },
10 | doorfx_tex: { url: 'doorfx.basis', options: { wrapT: THREE.RepeatWrapping, wrapS: THREE.RepeatWrapping }},
11 | sky_tex: { url: 'sky.png', options: { encoding: THREE.sRGBEncoding, flipY: false} },
12 | clouds_tex: { url: 'clouds.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
13 | teleport_model: { url: 'teleport.glb' },
14 | beam_tex: { url: 'beamfx.png' },
15 | glow_tex: { url: 'glow.basis', options: { encoding: THREE.sRGBEncoding} },
16 | newsticker_tex: { url: 'newsticker.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
17 | mozillamr_tex: { url: 'mozillamr.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
18 | zoomicon_tex: { url: 'zoomicon.png', options: { encoding: THREE.sRGBEncoding } },
19 |
20 | // panoramas
21 | panoballfx_tex: { url: 'ballfx.basis', options: { wrapT: THREE.RepeatWrapping, wrapS: THREE.RepeatWrapping } },
22 |
23 | stereopanoL: { url: 'stereopanoL.basis', options: { encoding: THREE.sRGBEncoding }},
24 | stereopanoR: { url: 'stereopanoR.basis', options: { encoding: THREE.sRGBEncoding }},
25 | pano1small: { url: 'stereopano_small.basis', options: {encoding: THREE.sRGBEncoding} },
26 |
27 | pano2: { url: 'tigerturtle.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
28 | pano3: { url: 'lakebyllesby.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
29 | pano4: { url: 'haldezollern.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
30 | pano5: { url: 'zapporthorn.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
31 | pano6: { url: 'thuringen.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
32 | pano2small: { url: 'tigerturtle_small.basis', options: {encoding: THREE.sRGBEncoding} },
33 | pano3small: { url: 'lakebyllesby_small.basis', options: {encoding: THREE.sRGBEncoding} },
34 | pano4small: { url: 'haldezollern_small.basis', options: {encoding: THREE.sRGBEncoding} },
35 | pano5small: { url: 'zapporthorn_small.basis', options: {encoding: THREE.sRGBEncoding} },
36 | pano6small: { url: 'thuringen_small.basis', options: {encoding: THREE.sRGBEncoding} },
37 |
38 | // graffiti
39 | spray_model: { url: 'spray.glb' },
40 | spray_tex: { url: 'spray.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
41 |
42 | // vertigo
43 | vertigo_model: { url: 'vertigo.glb' },
44 | vertigo_door_lm_tex: { url: 'vertigo_door_lm.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
45 | vertigo_lm_tex: { url: 'vertigo_lm.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
46 | checkboard_tex: { url: 'checkboard.basis', options: { wrapT: THREE.RepeatWrapping, wrapS: THREE.RepeatWrapping, repeat: [4, 4] } },
47 |
48 | // sound
49 | sound_model: { url: 'sound.glb' },
50 | sound_door_model: { url: 'sound_door.glb' },
51 | sound_shadow_tex: { url: 'sound_shadow.png' },
52 | sound_door_lm_tex: { url: 'sound_door_lm.jpg', options: { wrapT: THREE.RepeatWrapping, wrapS: THREE.RepeatWrapping} },
53 | grid_tex: { url: 'grid.png', options: { wrapT: THREE.RepeatWrapping, wrapS: THREE.RepeatWrapping, repeat: [20, 20] } },
54 |
55 | // photogrammetry object
56 | pg_floor_tex: { url: 'travertine2.basis', options: { encoding: THREE.sRGBEncoding, flipY: false, wrapT: THREE.RepeatWrapping, wrapS: THREE.RepeatWrapping} },
57 | pg_floor_lm_tex: { url: 'pg_floor_lm.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
58 | pg_door_lm_tex: { url: 'pg_door_lm.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
59 | pg_object_tex: { url: 'angel.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
60 | pg_object_model: { url: 'angel.min.glb' }, // TODO: try draco version, angel.min.gl
61 | pg_bg_tex: { url: 'pg_bg.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
62 | pg_flare_tex: { url: 'flare.jpg', options: { encoding: THREE.sRGBEncoding, flipY: false} },
63 | pg_panel_tex: { url: 'panel.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
64 |
65 | // paintings
66 | painting_seurat_tex: { url: 'paintings/seurat.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
67 | painting_sorolla_tex: { url: 'paintings/sorolla.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
68 | painting_bosch_tex: { url: 'paintings/bosch.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
69 | painting_degas_tex: { url: 'paintings/degas.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
70 | painting_rembrandt_tex: { url: 'paintings/rembrandt.basis', options: { encoding: THREE.sRGBEncoding, flipY: false} },
71 |
72 | // sounds
73 | birds_snd: { url: 'ogg/birds.ogg' },
74 | chopin_snd: { url: 'ogg/chopin.ogg' },
75 | forest_snd: { url: 'ogg/forest.ogg' },
76 | wind_snd: { url: 'ogg/wind.ogg' },
77 | teleport_a_snd: { url: 'ogg/teleport_a.ogg' },
78 | teleport_b_snd: { url: 'ogg/teleport_b.ogg' }
79 | };
80 |
81 |
--------------------------------------------------------------------------------
/src/components/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {TagComponent} from 'ecsy';
3 |
4 | export class Object3D {
5 | constructor() {
6 | this.value = null;
7 | }
8 |
9 | reset() {
10 | this.value = null;
11 | }
12 | }
13 |
14 | export class Rotation {
15 | constructor() {
16 | this.rotation = new THREE.Vector3();
17 | }
18 |
19 | reset() {}
20 | }
21 |
22 | export class Position {
23 | constructor() {
24 | this.position = new THREE.Vector3();
25 | }
26 |
27 | reset() {}
28 | }
29 |
30 | export class ParentObject3D {
31 | constructor() {
32 | this.value = null;
33 | }
34 |
35 | reset() {
36 | this.value = null;
37 | }
38 | }
39 |
40 | export class Text {
41 | constructor() {
42 | this.text = '';
43 | this.textAlign = 'left'; // ['left', 'right', 'center']
44 | this.anchor = 'center'; // ['left', 'right', 'center', 'align']
45 | this.baseline = 'center'; // ['top', 'center', 'bottom']
46 | this.color = '#FFF';
47 | this.font = 'https://code.cdn.mozilla.net/fonts/ttf/ZillaSlab-SemiBold.ttf';
48 | this.fontSize = 0.2;
49 | this.letterSpacing = 0;
50 | this.lineHeight = 0;
51 | this.maxWidth = Infinity;
52 | this.overflowWrap = 'normal'; // ['normal', 'break-word']
53 | this.whiteSpace = 'normal'; // ['normal', 'nowrap']
54 | this.opacity = 1;
55 | }
56 |
57 | reset() {
58 | this.text = '';
59 | }
60 | }
61 |
62 | export class BoundingBox {
63 | constructor() {
64 | this.min = new THREE.Vector3();
65 | this.max = new THREE.Vector3();
66 | // this.box3?
67 | }
68 |
69 | reset() {
70 | this.min.set(0,0,0);
71 | this.max.set(0,0,0);
72 | }
73 | }
74 |
75 | export class BoundingSphere {
76 | constructor() {
77 | this.debug = true;
78 | this.center = new THREE.Vector3();
79 | this.radius = 0;
80 | //this.sphere?
81 | }
82 |
83 | reset() {
84 | this.center.set(0,0,0);
85 | this.radius = 0;
86 | }
87 | }
88 |
89 | export class Area {
90 | constructor() {
91 |
92 | }
93 |
94 | reset() {
95 |
96 | }
97 | }
98 |
99 | export class AreaEntering extends TagComponent {}
100 | export class AreaExiting extends TagComponent {}
101 | export class AreaInside extends TagComponent {}
102 |
103 | export class AreaChecker {
104 | constructor() {
105 |
106 | }
107 |
108 | reset() {
109 |
110 | }
111 | }
112 |
113 | const empty = () => {};
114 |
115 | export class AreaReactor {
116 | constructor() {
117 | this.reset();
118 | }
119 |
120 | reset() {
121 | this.onEntering = empty;
122 | this.onExiting = empty;
123 | }
124 | }
125 |
126 | export class DebugHelper extends TagComponent {}
127 |
128 | export class Billboard {
129 | constructor() {
130 | this.camera3D = null;
131 | }
132 |
133 | reset() {
134 | this.camera3D = null;
135 | }
136 | }
137 |
138 | export class Children {
139 | constructor() {
140 | this.value = [];
141 | }
142 |
143 | reset() {
144 | this.value.array.length = 0;
145 | }
146 | }
147 |
148 | export class Opacity {
149 | constructor() {
150 | this.opacity = 0;
151 | }
152 |
153 | reset() {
154 | this.opacity = 0;
155 | }
156 | }
157 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js';
3 |
4 | import {VRButton} from './lib/VRButton.js';
5 | import {slideshow} from './lib/slideshow.js';
6 | import {loadAssets} from './lib/assetManager.js';
7 |
8 | // ECSY
9 | import { World } from 'ecsy';
10 | import { SDFTextSystem } from './systems/SDFTextSystem.js';
11 | import { DebugHelperSystem } from './systems/DebugHelperSystem.js';
12 | import { AreaCheckerSystem } from './systems/AreaCheckerSystem.js';
13 | import { ControllersSystem } from './systems/ControllersSystem.js';
14 | import HierarchySystem from './systems/HierarchySystem.js';
15 | import TransformSystem from './systems/TransformSystem.js';
16 | import BillboardSystem from './systems/BillboardSystem.js';
17 |
18 | import SystemsGroup from './systems/SystemsGroup.js';
19 |
20 | import assets from './assets.js';
21 |
22 | import { Text, Object3D, AreaChecker } from './components/index.js';
23 |
24 | import RayControl from './lib/RayControl.js';
25 | import Teleport from './lib/Teleport.js';
26 |
27 | import * as roomHall from './rooms/Hall.js';
28 | import * as roomPanorama from './rooms/Panorama.js';
29 | import * as roomPanoramaStereo from './rooms/PanoramaStereo.js';
30 | import * as roomPhotogrammetryObject from './rooms/PhotogrammetryObject.js';
31 | import * as roomVertigo from './rooms/Vertigo.js';
32 | import * as roomSound from './rooms/Sound.js';
33 |
34 | import {shaders} from './lib/shaders.js';
35 |
36 | import WebXRPolyfill from 'webxr-polyfill';
37 | const polyfill = new WebXRPolyfill();
38 |
39 | var clock = new THREE.Clock();
40 |
41 | var scene, parent, renderer, camera, controls, context = {};
42 | var raycontrol, teleport, controllers = [];
43 |
44 | var listener, ambientMusic;
45 |
46 | var rooms = [
47 | roomHall,
48 | roomSound,
49 | roomPhotogrammetryObject,
50 | roomVertigo,
51 | roomPanoramaStereo,
52 | roomPanorama,
53 | roomPanorama,
54 | roomPanorama,
55 | roomPanorama,
56 | roomPanorama,
57 | ];
58 |
59 | const roomNames = [
60 | 'hall',
61 | 'sound',
62 | 'photogrammetry',
63 | 'vertigo',
64 | 'panoramastereo',
65 | 'panorama1',
66 | 'panorama2',
67 | 'panorama3',
68 | 'panorama4',
69 | 'panorama5',
70 | ];
71 |
72 | const musicThemes = [
73 | false,
74 | false,
75 | 'chopin_snd',
76 | 'wind_snd',
77 | false,
78 | 'birds_snd',
79 | 'birds_snd',
80 | 'forest_snd',
81 | 'wind_snd',
82 | 'birds_snd',
83 | ];
84 |
85 | const urlObject = new URL(window.location);
86 | const roomName = urlObject.searchParams.get('room');
87 | context.room = roomNames.indexOf(roomName) !== -1 ? roomNames.indexOf(roomName) : 0;
88 | // console.log(`Current room "${roomNames[context.room]}", ${context.room}`);
89 | const debug = urlObject.searchParams.has('debug');
90 | const handedness = urlObject.searchParams.has('handedness') ? urlObject.searchParams.get('handedness') : "right";
91 |
92 | // Target positions when moving from one room to another
93 | const targetPositions = {
94 | hall: {
95 | sound: new THREE.Vector3(0, 0, 0),
96 | photogrammetry: new THREE.Vector3(1, 0, 0),
97 | vertigo: new THREE.Vector3(0, 0, 0)
98 | },
99 | photogrammetry: {
100 | hall: new THREE.Vector3(-3.6, 0, 2.8)
101 | },
102 | sound: {
103 | hall: new THREE.Vector3(4.4, 0, 4.8)
104 | },
105 | vertigo: {
106 | hall: new THREE.Vector3(-1.8, 0, -5)
107 | }
108 | };
109 |
110 | function gotoRoom(room) {
111 | rooms[context.room].exit(context);
112 | raycontrol.deactivateAll();
113 |
114 | const prevRoom = roomNames[context.room];
115 | const nextRoom = roomNames[room];
116 |
117 | if (targetPositions[prevRoom] && targetPositions[prevRoom][nextRoom]) {
118 | let deltaPosition = new THREE.Vector3();
119 | const targetPosition = targetPositions[prevRoom][nextRoom];
120 | var camera = renderer.xr.getCamera(context.camera);
121 |
122 | deltaPosition.x = camera.position.x - targetPosition.x;
123 | deltaPosition.z = camera.position.z - targetPosition.z;
124 |
125 | context.cameraRig.position.sub(deltaPosition);
126 | }
127 |
128 | context.room = room;
129 |
130 | playMusic(room);
131 |
132 | rooms[context.room].enter(context);
133 | }
134 |
135 | function playMusic(room) {
136 | if (ambientMusic.source) ambientMusic.stop();
137 |
138 | const music = musicThemes[room];
139 | if (!music) { return; }
140 | ambientMusic.setBuffer(assets[music]);
141 | ambientMusic.setLoop(true);
142 | ambientMusic.setVolume(1.0);
143 | ambientMusic.offset = Math.random() * 60;
144 | ambientMusic.play();
145 | }
146 |
147 | var ecsyWorld;
148 | var systemsGroup = {};
149 |
150 | function detectWebXR() {
151 | if ('xr' in navigator) {
152 | navigator.xr.isSessionSupported('immersive-vr').then( supported => {
153 | if (!supported) document.getElementById('no-webxr').classList.remove('hidden');
154 | } );
155 |
156 | } else {
157 | document.getElementById('no-webxr').classList.remove('hidden');
158 | }
159 | }
160 |
161 | export function init() {
162 |
163 | document.getElementById(handedness + 'hand').classList.add('activehand');
164 |
165 | detectWebXR();
166 |
167 | var w = 100;
168 | ecsyWorld = new World();
169 | ecsyWorld
170 | .registerSystem(SDFTextSystem)
171 | .registerSystem(AreaCheckerSystem)
172 | .registerSystem(ControllersSystem)
173 | .registerSystem(DebugHelperSystem)
174 | .registerSystem(TransformSystem)
175 | .registerSystem(BillboardSystem)
176 | .registerSystem(HierarchySystem);
177 |
178 | systemsGroup['roomHall'] = new SystemsGroup(ecsyWorld, [
179 | AreaCheckerSystem, ControllersSystem, DebugHelperSystem
180 | ]);
181 |
182 | renderer = new THREE.WebGLRenderer({antialias: true, logarithmicDepthBuffer: false});
183 | renderer.gammaFactor = 2.2;
184 | renderer.outputEncoding = THREE.sRGBEncoding;
185 | renderer.setPixelRatio( window.devicePixelRatio );
186 | renderer.setSize(window.innerWidth, window.innerHeight);
187 | renderer.xr.enabled = true;
188 |
189 | scene = new THREE.Scene();
190 | camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.005, 10000);
191 | camera.position.set(0, 1.6, 0);
192 | listener = new THREE.AudioListener();
193 | camera.add(listener);
194 |
195 | ambientMusic = new THREE.Audio(listener);
196 |
197 | controls = new PointerLockControls(camera, renderer.domElement);
198 | if (debug) {
199 | document.body.addEventListener('click', () => controls.lock());
200 | document.body.addEventListener('keydown', ev => {
201 | switch(ev.keyCode) {
202 | case 87: controls.moveForward(0.2); break;
203 | case 65: controls.moveRight(-0.2); break;
204 | case 83: controls.moveForward(-0.2); break;
205 | case 68: controls.moveRight(0.2); break;
206 | case 78: gotoRoom((context.room + 1) % rooms.length); break;
207 | default: {
208 | var room = ev.keyCode - 48;
209 | if (!ev.metaKey && room >= 0 && room < rooms.length) {
210 | gotoRoom(room);
211 | }
212 | }
213 | }
214 | });
215 | }
216 | scene.add(controls.getObject());
217 |
218 | parent = new THREE.Object3D();
219 | scene.add(parent);
220 |
221 | window.addEventListener('resize', onWindowResize, false);
222 |
223 | for (let i = 0; i < 2; i++) {
224 | controllers[i] = renderer.xr.getController(i);
225 | controllers[i].raycaster = new THREE.Raycaster();
226 | controllers[i].raycaster.near = 0.1;
227 | controllers[i].addEventListener('selectstart', onSelectStart);
228 | controllers[i].addEventListener('selectend', onSelectEnd);
229 | }
230 |
231 | // global lights
232 | const lightSun = new THREE.DirectionalLight(0xeeffff);
233 | lightSun.name = 'sun';
234 | lightSun.position.set(0.2, 1, 0.1);
235 | const lightFill = new THREE.DirectionalLight(0xfff0ee, 0.3);
236 | lightFill.name = 'fillLight';
237 | lightFill.position.set(-0.2, -1, -0.1);
238 |
239 | scene.add(lightSun, lightFill);
240 |
241 | var cameraRig = new THREE.Group();
242 | cameraRig.add(camera);
243 | cameraRig.add(controllers[0]);
244 | cameraRig.add(controllers[1]);
245 | cameraRig.position.set(0, 0, 2);
246 | scene.add(cameraRig);
247 |
248 | context.vrMode = false; // in vr
249 | context.assets = assets;
250 | context.shaders = shaders;
251 | context.scene = parent;
252 | context.renderer = renderer;
253 | context.camera = camera;
254 | context.audioListener = listener;
255 | context.goto = null;
256 | context.cameraRig = cameraRig;
257 | context.controllers = controllers;
258 | context.world = ecsyWorld;
259 | context.systemsGroup = systemsGroup;
260 | context.handedness = handedness;
261 |
262 | window.context = context;
263 |
264 | const loadTotal = Object.keys(assets).length;
265 |
266 | loadAssets(renderer, 'assets/', assets, () => {
267 | raycontrol = new RayControl(context, handedness);
268 | context.raycontrol = raycontrol;
269 |
270 | teleport = new Teleport(context);
271 | context.teleport = teleport;
272 |
273 | setupControllers();
274 | roomHall.setup(context);
275 | roomPanorama.setup(context);
276 | roomPanoramaStereo.setup(context);
277 | roomPhotogrammetryObject.setup(context);
278 | roomVertigo.setup(context);
279 | roomSound.setup(context);
280 |
281 | rooms[context.room].enter(context);
282 |
283 | slideshow.setup(context);
284 |
285 | document.body.appendChild(renderer.domElement);
286 | document.body.appendChild(VRButton.createButton(renderer, status => {
287 | context.vrMode = status === 'sessionStarted';
288 | if (context.vrMode) {
289 | gotoRoom(0);
290 | context.cameraRig.position.set(0, 0, 2);
291 | context.goto = null;
292 | } else {
293 | slideshow.setup(context);
294 | }
295 | }));
296 | renderer.setAnimationLoop(animate);
297 |
298 | document.getElementById('loading').style.display = 'none';
299 | },
300 |
301 | loadProgress => {
302 | document.querySelector('#progressbar').setAttribute('stroke-dashoffset',
303 | - (282 - Math.floor(loadProgress / loadTotal * 282)));
304 | },
305 | debug);
306 |
307 | }
308 |
309 | function setupControllers() {
310 | var model = assets['generic_controller_model'].scene;
311 | var material = new THREE.MeshLambertMaterial({
312 | map: assets['controller_tex'],
313 | });
314 | model.getObjectByName('body').material = material;
315 | model.getObjectByName('trigger').material = material;
316 |
317 | for (let i = 0;i < 2; i++) {
318 | let controller = controllers[i];
319 | controller.boundingBox = new THREE.Box3();
320 | controller.userData.grabbing = null;
321 | controller.addEventListener( 'connected', function ( event ) {
322 | this.add(model.clone());
323 | raycontrol.addController(this, event.data);
324 | } );
325 | controller.addEventListener( 'disconnect', function () {
326 | this.remove(this.children[0]);
327 | raycontrol.removeController(this, event.data);
328 | });
329 | }
330 | }
331 |
332 | // @FIXME Hack for Oculus Browser issue
333 | var selectStartSkip = {};
334 | var selectEndSkip = {};
335 | var OculusBrowser = navigator.userAgent.indexOf("OculusBrowser") !== -1 &&
336 | parseInt(navigator.userAgent.match(/OculusBrowser\/([0-9]+)./)[1]) < 8;
337 |
338 | // <@FIXME
339 |
340 | function onSelectStart(ev) {
341 | // @FIXME Hack for Oculus Browser issue
342 | if (OculusBrowser) {
343 | const controller = ev.target;
344 | if (!selectStartSkip[controller]) {
345 | selectStartSkip[controller] = true;
346 | return;
347 | }
348 | selectStartSkip[controller] = false;
349 | }
350 | // <@FIXME
351 |
352 | const trigger = ev.target.getObjectByName('trigger');
353 | trigger.rotation.x = -0.3;
354 | raycontrol.onSelectStart(ev);
355 | }
356 |
357 | function onSelectEnd(ev) {
358 | // @FIXME Hack for Oculus Browser issue
359 | if (OculusBrowser) {
360 | const controller = ev.target;
361 | if (!selectEndSkip[controller]) {
362 | selectEndSkip[controller] = true;
363 | return;
364 | }
365 | selectEndSkip[controller] = false;
366 | }
367 | // <@FIXME
368 |
369 | const trigger = ev.target.getObjectByName('trigger');
370 | trigger.rotation.x = 0;
371 | raycontrol.onSelectEnd(ev);
372 | }
373 |
374 | function onWindowResize() {
375 | camera.aspect = window.innerWidth / window.innerHeight;
376 | camera.updateProjectionMatrix();
377 | renderer.setSize(window.innerWidth, window.innerHeight);
378 | }
379 |
380 | function animate() {
381 | var delta = clock.getDelta();
382 | var elapsedTime = clock.elapsedTime;
383 |
384 | ecsyWorld.execute(delta, elapsedTime);
385 |
386 | // update controller bounding boxes
387 | for (let i = 0; i < controllers.length; i++) {
388 | const model = controllers[i].getObjectByName('Scene');
389 | if (model) {
390 | controllers[i].boundingBox.setFromObject(model);
391 | }
392 | }
393 |
394 | // render current room
395 | context.raycontrol.execute(context, delta, elapsedTime);
396 | rooms[context.room].execute(context, delta, elapsedTime);
397 | if (!context.vrMode) {
398 | slideshow.execute(context, delta, elapsedTime);
399 | }
400 |
401 | renderer.render(scene, camera);
402 | if (context.goto !== null) {
403 | gotoRoom(context.goto);
404 | context.goto = null;
405 | }
406 | }
407 |
408 | window.onload = () => {init()};
409 |
--------------------------------------------------------------------------------
/src/lib/ColorUtils.js:
--------------------------------------------------------------------------------
1 | function clamp(value, min, max) {
2 | return Math.min(Math.max(value, min), max);
3 | }
4 |
5 | export function hsv2rgb(hsv) {
6 | var r, g, b, i, f, p, q, t;
7 | var h = clamp(hsv.h, 0, 1);
8 | var s = clamp(hsv.s, 0, 1);
9 | var v = hsv.v;
10 |
11 | i = Math.floor(h * 6);
12 | f = h * 6 - i;
13 | p = v * (1 - s);
14 | q = v * (1 - f * s);
15 | t = v * (1 - (1 - f) * s);
16 | switch (i % 6) {
17 | case 0: r = v; g = t; b = p; break;
18 | case 1: r = q; g = v; b = p; break;
19 | case 2: r = p; g = v; b = t; break;
20 | case 3: r = p; g = q; b = v; break;
21 | case 4: r = t; g = p; b = v; break;
22 | case 5: r = v; g = p; b = q; break;
23 | }
24 | return {
25 | r: Math.round(r * 255),
26 | g: Math.round(g * 255),
27 | b: Math.round(b * 255)
28 | };
29 | }
30 |
31 | export function rgb2hsv(r, g, b) {
32 | var max = Math.max(r, g, b);
33 | var min = Math.min(r, g, b);
34 | var d = max - min;
35 | var h;
36 | var s = (max === 0 ? 0 : d / max);
37 | var v = max;
38 |
39 | if (arguments.length === 1) { g = r.g; b = r.b; r = r.r; }
40 |
41 | switch (max) {
42 | case min: h = 0; break;
43 | case r: h = (g - b) + d * (g < b ? 6 : 0); h /= 6 * d; break;
44 | case g: h = (b - r) + d * 2; h /= 6 * d; break;
45 | case b: h = (r - g) + d * 4; h /= 6 * d; break;
46 | }
47 | return {h: h, s: s, v: v};
48 | }
49 |
--------------------------------------------------------------------------------
/src/lib/ColorWheel.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {hsv2rgb} from '../lib/ColorUtils.js';
3 |
4 | export default class ColorWheel {
5 | constructor(ctx, controller, onColorChanged) {
6 | this.ctx = ctx;
7 | this.radius = 0.1;
8 | this.hsv = { h: 0.0, s: 0.0, v: 1.0 };
9 | this.rgb = {r: 0, g: 0, b: 0};
10 | this.onColorChanged = onColorChanged;
11 | const geometry = new THREE.CircleBufferGeometry(this.radius, 12);
12 | var vertexShader = '\
13 | varying vec2 vUv;\
14 | void main() {\
15 | vUv = uv;\
16 | vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);\
17 | gl_Position = projectionMatrix * mvPosition;\
18 | }\
19 | ';
20 |
21 | var fragmentShader = '\
22 | #define M_PI2 6.28318530718\n \
23 | uniform float brightness;\
24 | varying vec2 vUv;\
25 | vec3 hsb2rgb(in vec3 c){\
26 | vec3 rgb = clamp(abs(mod(c.x * 6.0 + vec3(0.0, 4.0, 2.0), 6.0) - 3.0) - 1.0, \
27 | 0.0, \
28 | 1.0 );\
29 | rgb = rgb * rgb * (3.0 - 2.0 * rgb);\
30 | return c.z * mix( vec3(1.0), rgb, c.y);\
31 | }\
32 | \
33 | void main() {\
34 | vec2 toCenter = vec2(0.5) - vUv;\
35 | float angle = atan(toCenter.y, toCenter.x);\
36 | float radius = length(toCenter) * 2.0;\
37 | vec3 color = hsb2rgb(vec3((angle / M_PI2) + 0.5, radius, brightness));\
38 | gl_FragColor = vec4(color, 1.0);\
39 | }\
40 | ';
41 |
42 | var material = new THREE.ShaderMaterial({
43 | uniforms: { brightness: { type: 'f', value: this.hsv.v } },
44 | vertexShader: vertexShader,
45 | fragmentShader: fragmentShader
46 | });
47 | this.mesh = new THREE.Mesh(geometry, material);
48 | this.mesh.name = 'colorWheel';
49 | this.controller = controller;
50 |
51 |
52 | const geometryLast = new THREE.CircleBufferGeometry(0.025, 12);
53 | let materialBlack = new THREE.MeshBasicMaterial({color: 0x000000});
54 | this.blackMesh = new THREE.Mesh(geometryLast, materialBlack);
55 | this.blackMesh.name = 'black';
56 | this.blackMesh.position.set(0, 0.15, 0);
57 |
58 | this.ui = new THREE.Group();
59 | this.ui.add(this.mesh);
60 | this.ui.add(this.blackMesh);
61 |
62 | var geometryRing = new THREE.RingGeometry( 0.005, 0.01, 32 );
63 | var materialRing = new THREE.MeshBasicMaterial( { color: 0xffff00, side: THREE.DoubleSide } );
64 | this.colorSelector = new THREE.Mesh( geometryRing, materialRing );
65 | this.colorSelector.position.z = 0.01;
66 | this.colorSelector.name = 'colorSelector';
67 | this.ui.add(this.colorSelector);
68 |
69 | this.ui.name = 'ColorWheel';
70 | this.ui.visible = false;
71 | //this.ui.rotation.x = -Math.PI / 3;
72 | this.ui.position.y = 0.1;
73 |
74 | controller.add(this.ui);
75 |
76 | ctx.raycontrol.addState('colorwheel', {
77 | colliderMesh: this.ui,
78 | order: -1,
79 | onHover: (intersection, active, controller) => {
80 | if (active) {
81 | var point = intersection.point.clone();
82 | this.mesh.worldToLocal(point);
83 |
84 | this.colorSelector.position.x = point.x;
85 | this.colorSelector.position.y = point.y;
86 | }
87 | },
88 | onHoverLeave: (intersection) => {
89 | },
90 | onSelectStart: (intersection, controller) => {
91 | if (intersection.object.name === 'colorWheel') {
92 |
93 | var point = intersection.point.clone();
94 | this.mesh.updateMatrixWorld();
95 | this.mesh.worldToLocal(point);
96 |
97 | //this.objects.hueCursor.position.copy(position);
98 | let uv = intersection.uv.clone();
99 | uv.x = uv.x * 2 - 1;
100 | uv.y = uv.y * 2 - 1;
101 |
102 | let polarPosition = {
103 | r: this.radius * Math.sqrt(uv.x * uv.x + uv.y * uv.y),
104 | theta: Math.PI + Math.atan2(uv.y, uv.x)
105 | };
106 | var angle = ((polarPosition.theta * (180 / Math.PI)) + 180) % 360;
107 | this.hsv.h = angle / 360;
108 | this.hsv.s = polarPosition.r / this.radius;
109 | this.updateColor();
110 | } else {
111 | this.onColorChanged(intersection.object.material.color.clone().multiplyScalar(255));
112 | }
113 | },
114 | onSelectEnd: (intersection) => {
115 | }
116 | });
117 | }
118 |
119 | updateColor () {
120 | this.rgb = hsv2rgb(this.hsv);
121 | this.colorSelector.material.color.setRGB(this.rgb.r / 255, this.rgb.g / 255, this.rgb.b / 255);
122 | this.onColorChanged(this.rgb);
123 | }
124 |
125 | enter() {
126 | this.ctx.raycontrol.activateState('colorwheel');
127 | }
128 |
129 | exit() {
130 | this.ctx.raycontrol.deactivateState('colorwheel');
131 | }
132 | }
133 |
--------------------------------------------------------------------------------
/src/lib/EventDispatcher.js:
--------------------------------------------------------------------------------
1 | /**
2 | * @private
3 | * @class EventDispatcher
4 | */
5 | export default class EventDispatcher {
6 | constructor() {
7 | this._listeners = {};
8 | this.stats = {
9 | fired: 0,
10 | handled: 0
11 | };
12 | }
13 |
14 | /**
15 | * Add an event listener
16 | * @param {String} eventName Name of the event to listen
17 | * @param {Function} listener Callback to trigger when the event is fired
18 | */
19 | addEventListener(eventName, listener) {
20 | let listeners = this._listeners;
21 | if (listeners[eventName] === undefined) {
22 | listeners[eventName] = [];
23 | }
24 |
25 | if (listeners[eventName].indexOf(listener) === -1) {
26 | listeners[eventName].push(listener);
27 | }
28 | }
29 |
30 | /**
31 | * Check if an event listener is already added to the list of listeners
32 | * @param {String} eventName Name of the event to check
33 | * @param {Function} listener Callback for the specified event
34 | */
35 | hasEventListener(eventName, listener) {
36 | return (
37 | this._listeners[eventName] !== undefined &&
38 | this._listeners[eventName].indexOf(listener) !== -1
39 | );
40 | }
41 |
42 | /**
43 | * Remove an event listener
44 | * @param {String} eventName Name of the event to remove
45 | * @param {Function} listener Callback for the specified event
46 | */
47 | removeEventListener(eventName, listener) {
48 | var listenerArray = this._listeners[eventName];
49 | if (listenerArray !== undefined) {
50 | var index = listenerArray.indexOf(listener);
51 | if (index !== -1) {
52 | listenerArray.splice(index, 1);
53 | }
54 | }
55 | }
56 |
57 | /**
58 | * Dispatch an event
59 | * @param {String} eventName Name of the event to dispatch
60 | * @param {Data} data to eit
61 | */
62 | dispatchEvent(eventName, data) {
63 | this.stats.fired++;
64 |
65 | var listenerArray = this._listeners[eventName];
66 | if (listenerArray !== undefined) {
67 | var array = listenerArray.slice(0);
68 |
69 | for (var i = 0; i < array.length; i++) {
70 | array[i].call(this, data);
71 | }
72 | }
73 | }
74 |
75 | /**
76 | * Reset stats counters
77 | */
78 | resetCounters() {
79 | this.stats.fired = this.stats.handled = 0;
80 | }
81 | }
82 |
--------------------------------------------------------------------------------
/src/lib/PositionalAudioPolyphonic.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | export default class PositionalAudioPolyphonic extends THREE.Object3D {
4 | constructor(listener, poolSize) {
5 | super();
6 | this.listener = listener;
7 | this.context = listener.context;
8 |
9 | this.poolSize = poolSize || 5;
10 | for (var i = 0; i < this.poolSize; i++) {
11 | this.children.push(new THREE.PositionalAudio(listener));
12 | }
13 | }
14 |
15 | setBuffer(buffer) {
16 | this.children.forEach(sound => {
17 | sound.setBuffer(buffer);
18 | });
19 | }
20 |
21 | play() {
22 | var found = false;
23 | for (let i = 0;i {
22 | let pa = a.order || 0;
23 | let pb = b.order || 0;
24 | return pa - pb;
25 | });
26 | }
27 |
28 | disable() {
29 | this.lineBasic.visible = this.line0.visible = false;
30 | this.enabled = false;
31 | this.controllers.forEach(controller => controller.active = false);
32 | }
33 |
34 | changeHandedness(primary) {
35 | if (primary !== this.primary) {
36 | this.primary = primary;
37 | this.secondary = primary === "right" ? "left" : "right";
38 |
39 | this.dispatchEvent("handednessChanged", {primary: this.primary, secondary: this.secondary});
40 | }
41 | }
42 |
43 | addState(name, state, activate) {
44 | if (this.states[name]) {
45 | console.error(`RayControl state '${name}' already exist, please use a different name.`);
46 | return;
47 | }
48 |
49 | state.name = name;
50 |
51 | if (typeof state.raycaster === "undefined") {
52 | state.raycaster = true;
53 | }
54 |
55 | if (typeof state.controller === "undefined") {
56 | state.controller = "primary";
57 | } else if (!validStateController.includes(state.controller)) {
58 | console.warn("Invalid controller selector:", state.controller);
59 | state.controller = "primary";
60 | }
61 |
62 | this.states[name] = state;
63 |
64 | if (activate === true) {
65 | this.currentStates.push(state);
66 | }
67 |
68 | return state;
69 | }
70 |
71 | activateState(name) {
72 | if (this.states[name]) {
73 | this.currentStates.push(this.states[name]);
74 | this._sort();
75 | }
76 | }
77 |
78 | deactivateAll(name) {
79 | this.currentStates = [];
80 | this.controllers.forEach(c => {
81 | this.currentStates.forEach(s => {
82 | if (c.intersections[s.name]) {
83 | c.intersections[s.name] = null;
84 | }
85 | });
86 | });
87 | }
88 |
89 | deactivateState(name) {
90 | this.currentStates.splice(this.currentStates.indexOf(name), 1);
91 | this.controllers.forEach(c => {
92 | if (c.intersections[name]) {
93 | c.intersections[name] = null;
94 | }
95 | });
96 |
97 | this._sort();
98 | }
99 |
100 | addController(controller, inputSource) {
101 | let controllerData = {
102 | controller: controller,
103 | inputSource: inputSource,
104 | active: false,
105 | stateHit: {},
106 | intersections: {},
107 | currentIntersection: null,
108 | hit: false
109 | };
110 |
111 | this.controllers.push(controllerData);
112 |
113 | if (this.matchController(controllerData, "primary")) {
114 | controller.add( this.raycasterContext );
115 | }
116 |
117 | this.dispatchEvent("controllerConnected", controllerData);
118 | }
119 |
120 | removeController(controller) {
121 | const index = this.controllers.findIndex(controllerData => controllerData.controller === controller);
122 | const controllerData = this.controllers.find(controllerData => controllerData.controller === controller)
123 | this.controllers.splice(index, 1);
124 | this.dispatchEvent("controllerDisconnected", controllerData);
125 | }
126 |
127 | constructor(ctx, primary) {
128 | super();
129 | this.ctx = ctx;
130 |
131 | if (typeof primary === "undefined") {
132 | this.primary = "right";
133 | this.secondary = "left";
134 | } else {
135 | this.primary = primary;
136 | this.secondary = primary === "right" ? "left" : "right";
137 | }
138 |
139 | this.controllers = [];
140 |
141 | this.previousLineStyle = 'pretty';
142 | this.enabled = true;
143 | this.raycaster = new THREE.Raycaster();
144 | this.states = {};
145 | this.currentStates = [];
146 |
147 | var line = ctx.assets['teleport_model'].scene.getObjectByName('beam');
148 |
149 | ctx.assets['beam_tex'].wrapT = THREE.RepeatWrapping;
150 | ctx.assets['beam_tex'].wrapS = THREE.RepeatWrapping;
151 | rayMaterial = line.material = new THREE.ShaderMaterial({
152 | uniforms: {
153 | time: {value: 0},
154 | active: {value: 0},
155 | tex: {value: ctx.assets['beam_tex']}
156 | },
157 | vertexShader: ctx.shaders.basic_vert,
158 | fragmentShader: ctx.shaders.beam_frag,
159 | blending: THREE.AdditiveBlending,
160 | transparent: true
161 | });
162 |
163 | line.renderOrder = 10;
164 |
165 | line.name = 'line';
166 | this.rayLength = 5;
167 | line.scale.z = this.rayLength;
168 |
169 | this.line0 = line.clone();
170 | this.line0.visible = true;
171 |
172 | this.raycasterContext = new THREE.Group();
173 | this.raycasterContext.add(this.line0);
174 | this.raycasterContext.name = 'raycasterContext';
175 |
176 | var geometry = new THREE.BufferGeometry().setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 1 ) ] );
177 |
178 | this.lineBasic = new THREE.Line( geometry );
179 | this.lineBasic.name = 'line';
180 | this.lineBasic.scale.z = 5;
181 | this.lineBasic.visible = false;
182 | this.raycasterContext.add(this.lineBasic);
183 | }
184 |
185 | setLineStyle(lineStyle) {
186 | const basic = lineStyle === 'basic';
187 | this.lineBasic.visible = basic;
188 | this.line0.visible = !basic;
189 | this.previousLineStyle = lineStyle;
190 | }
191 |
192 | /*
193 | selector could be: left, right, both, primary, secondary
194 | */
195 | matchController(controllerData, selector) {
196 | const handedness = controllerData.inputSource.handedness;
197 |
198 | return (
199 | (selector === handedness) ||
200 | (selector === "both" && (handedness === "right" || handedness === "left")) ||
201 | (selector === "primary" && this.primary === handedness) ||
202 | (selector === "secondary" && this.secondary === handedness)
203 | );
204 | }
205 |
206 | onSelectStart(evt) {
207 | if (!this.enabled) { return; }
208 |
209 | let controller = evt.target;
210 | let controllerData = this.controllers.find(c => c.controller === controller);
211 | if (controllerData) {
212 | controllerData.active = true;
213 | if (controllerData.currentIntersection) {
214 | const state = controllerData.currentIntersection.state;
215 | if (state.onSelectStart) {
216 | state.onSelectStart(controllerData.currentIntersection.intersection, controllerData.controller);
217 | }
218 | }
219 |
220 | // Check no raycaster states
221 | this.currentStates.forEach(state => {
222 | if (state.onSelectStart && !state.raycaster) {
223 | state.onSelectStart(controllerData.intersections[state.name], controller);
224 | }
225 | });
226 | }
227 | }
228 |
229 | execute(ctx, delta, time) {
230 | if (!this.enabled || this.currentStates.length === 0) { return; }
231 |
232 | rayMaterial.uniforms.time.value = time;
233 |
234 | let firstHit = false;
235 |
236 | var stateIntersections = {};
237 | for (var c = 0; c < this.controllers.length; c++) {
238 | let controllerData = this.controllers[c];
239 |
240 | for (var i = 0; i < this.currentStates.length; i++) {
241 | let state = this.currentStates[i];
242 | if (!state.raycaster) {
243 | continue;
244 | }
245 |
246 | // Check if this controller should be active on this state
247 | if (!this.matchController(controllerData, state.controller)) {
248 | continue;
249 | }
250 |
251 | let controller = controllerData.controller;
252 | let active = controllerData.active;
253 | let intersections = this.getIntersections(controller, state.colliderMesh);
254 |
255 | if (intersections.length > 0) {
256 | // Use just the closest object
257 | controllerData.intersections[state.name] = intersections[0];
258 | controllerData.stateHit[state.name] = true;
259 | } else {
260 | controllerData.intersections[state.name] = null;
261 | }
262 | }
263 | }
264 |
265 | this.line0.scale.z = Math.min(this.rayLength, 1);
266 | this.lineBasic.scale.z = Math.min(this.rayLength, 1);
267 |
268 | // For each controller, find the closest intersection from all the states
269 | for (var c = 0; c < this.controllers.length; c++) {
270 | let controllerData = this.controllers[c];
271 | let intersections = Object.entries(controllerData.intersections).filter(i => i[1] !== null);
272 | if (intersections.length > 0) {
273 |
274 | intersections.sort((a,b) => {
275 | return a[1].distance - b[1].distance;
276 | });
277 |
278 | const intersectionData = intersections[0];
279 | const intersection = intersectionData[1];
280 | const state = this.states[intersectionData[0]];
281 |
282 | controllerData.prevIntersection = controllerData.currentIntersection;
283 | controllerData.currentIntersection = {
284 | state, intersection
285 | };
286 |
287 | if (state.lineStyleOnIntersection) {
288 | this.setLineStyle(state.lineStyleOnIntersection);
289 | } else {
290 | this.setLineStyle('advanced');
291 | }
292 |
293 | state.onHover && state.onHover(intersection, controllerData.active, controllerData.controller);
294 | this.line0.scale.z = Math.min(intersection.distance, 1);
295 | this.lineBasic.scale.z = Math.min(intersection.distance, 1);
296 | } else {
297 | controllerData.currentIntersection = null;
298 | }
299 | }
300 |
301 | // Handle onHoverLeave
302 | for (var c = 0; c < this.controllers.length; c++) {
303 | let controllerData = this.controllers[c];
304 | if (!controllerData.prevIntersection) {
305 | continue;
306 | }
307 |
308 | // If we can't find the previous intersection currently enabled, we should emit hoverLeave
309 | if (!this.controllers.find(c => {
310 | let prev = controllerData.prevIntersection;
311 | let current = c.currentIntersection;
312 | return current && prev.state.name === current.state.name &&
313 | prev.intersection.object === current.intersection.object;
314 | }
315 | )) {
316 | controllerData.prevIntersection.state.onHoverLeave(
317 | controllerData.prevIntersection.intersection,
318 | false,
319 | controllerData.controller
320 | );
321 |
322 | controllerData.prevIntersection = null;
323 | }
324 | }
325 | }
326 |
327 | getIntersections( controller, colliderMesh ) {
328 | let raycasterContext = controller.getObjectByName('raycasterContext');
329 | if (!raycasterContext) {
330 | console.warn('No raycasterContext found for this controller', controller);
331 | return [];
332 | }
333 |
334 | tempMatrix.identity().extractRotation( raycasterContext.matrixWorld );
335 |
336 | this.raycaster.ray.origin.setFromMatrixPosition( raycasterContext.matrixWorld );
337 | this.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
338 |
339 | if (Array.isArray(colliderMesh)) {
340 | return this.raycaster.intersectObjects( colliderMesh, true);
341 | } else {
342 | return this.raycaster.intersectObject( colliderMesh, true);
343 | }
344 | }
345 |
346 | onSelectEnd(evt) {
347 | if (!this.enabled) { return; }
348 |
349 | let controllerData = this.controllers.find(c => c.controller === evt.target)
350 | if (!controllerData || !controllerData.active) { return; }
351 |
352 | if (controllerData) {
353 | if (controllerData.currentIntersection) {
354 | const state = controllerData.currentIntersection.state;
355 | if (state.onSelectEnd) {
356 | state.onSelectEnd(controllerData.currentIntersection.intersection, controllerData.controller);
357 | }
358 | }
359 |
360 | // Check no raycaster states
361 | this.currentStates.forEach(state => {
362 | if (state.onSelectEnd && !state.raycaster) {
363 | state.onSelectEnd(null, controllerData.controller);
364 | }
365 | });
366 | }
367 |
368 | controllerData.active = false;
369 | }
370 | }
371 |
--------------------------------------------------------------------------------
/src/lib/Teleport.js:
--------------------------------------------------------------------------------
1 | import {rayMaterial} from "./RayControl.js";
2 | import * as THREE from 'three';
3 |
4 | var tempMatrix = new THREE.Matrix4();
5 | var intersected = [];
6 |
7 | export default class Teleport {
8 | constructor(ctx, mesh) {
9 | this.ctx = ctx;
10 |
11 | // Holder for all the
12 | this.teleportEntity = new THREE.Group();
13 |
14 | this.active = false;
15 |
16 | this.teleportHitGeometry = ctx.assets['teleport_model'].scene.getObjectByName('goal');
17 | this.teleportHitGeometry.material = rayMaterial;
18 |
19 | this.teleportHitGeometry.renderOrder = 10;
20 |
21 | this.ballColliding = ctx.assets['teleport_model'].scene.getObjectByName('glow');
22 | this.ballColliding.material = new THREE.MeshBasicMaterial({
23 | color: 0x00257b,
24 | map: ctx.assets['glow_tex'],
25 | transparent: true,
26 | blending: THREE.AdditiveBlending
27 | });
28 |
29 | this.ballColliding.renderOrder = 10;
30 |
31 |
32 | this.ballColliding.visible = false;
33 |
34 | this.teleportEntity.add(this.ballColliding);
35 |
36 | this.teleportHitGeometry.visible = false;
37 |
38 | this.teleportHitGeometry.position.set(-2, 0, -2);
39 |
40 | this.teleportEntity.add(this.teleportHitGeometry);
41 |
42 | this.ctx.scene.add(this.teleportEntity);
43 |
44 | // sounds
45 | this.startSound = new THREE.Audio(ctx.audioListener);
46 | this.startSound.setBuffer(ctx.assets['teleport_a_snd']);
47 | this.startSound.setLoop(true);
48 | this.startSound.pause();
49 |
50 | this.endSound = new THREE.Audio(ctx.audioListener);
51 | this.endSound.setBuffer(ctx.assets['teleport_b_snd']);
52 | this.endSound.setLoop(false);
53 | this.endSound.pause();
54 | }
55 |
56 |
57 | onSelectStart(evt) {
58 | //if (evt.target === this.ctx.controllers[0])
59 | this.active = true;
60 | this.endSound.pause();
61 | this.startSound.play();
62 | }
63 |
64 | onHoverLeave() {
65 | this.ballColliding.visible = false;
66 | this.teleportHitGeometry.visible = false;
67 | this.startSound.pause();
68 | }
69 |
70 | onHover(hitPoint, active) {
71 | if (active) {
72 | this.teleportHitGeometry.visible = true;
73 | this.teleportHitGeometry.position.copy(hitPoint);
74 | this.hit = true;
75 | this.ballColliding.visible = false;
76 | } else {
77 | this.ballColliding.visible = true;
78 | this.ballColliding.position.copy(hitPoint);
79 | }
80 | }
81 |
82 | onSelectEnd(targetPoint) {
83 | const headPosition = this.ctx.renderer.xr.getCamera(this.ctx.camera).position;
84 | const offset = targetPoint.sub(headPosition);
85 | offset.y = 0; // We don't want to change height to floor's level
86 |
87 | this.ctx.cameraRig.position.add(offset);
88 | this.teleportHitGeometry.visible = false;
89 |
90 | this.active = false;
91 |
92 | this.startSound.pause();
93 | this.endSound.play();
94 | }
95 | }
96 |
97 |
--------------------------------------------------------------------------------
/src/lib/VRButton.js:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | This is modification of the original VRButton. I've added a callback to know
4 | when the user enters and exists from VR
5 |
6 | Original at:
7 | https://github.com/mrdoob/three.js/blob/dev/examples/jsm/webxr/VRButton.js
8 |
9 | */
10 | /**
11 | * @author mrdoob / http://mrdoob.com
12 | * @author Mugen87 / https://github.com/Mugen87
13 | */
14 |
15 | var VRButton = {
16 |
17 | createButton: function ( renderer, callback ) {
18 |
19 | function showEnterVR( /*device*/ ) {
20 |
21 | var currentSession = null;
22 |
23 | function onSessionStarted( session ) {
24 |
25 | session.addEventListener( 'end', onSessionEnded );
26 |
27 | renderer.xr.setSession( session );
28 | button.textContent = 'EXIT VR';
29 |
30 | currentSession = session;
31 |
32 | callback('sessionStarted');
33 | }
34 |
35 | function onSessionEnded( /*event*/ ) {
36 |
37 | currentSession.removeEventListener( 'end', onSessionEnded );
38 |
39 | button.textContent = 'ENTER VR';
40 |
41 | currentSession = null;
42 |
43 | callback('sessionEnded');
44 | }
45 |
46 |
47 | //
48 |
49 | button.style.display = '';
50 |
51 | button.style.cursor = 'pointer';
52 | button.style.left = 'calc(50% - 50px)';
53 | button.style.width = '100px';
54 |
55 | button.textContent = 'ENTER VR';
56 |
57 | button.onmouseenter = function () {
58 |
59 | button.style.opacity = '1.0';
60 |
61 | };
62 |
63 | button.onmouseleave = function () {
64 |
65 | button.style.opacity = '0.5';
66 |
67 | };
68 |
69 | button.onclick = function () {
70 |
71 | if ( currentSession === null ) {
72 |
73 | // WebXR's requestReferenceSpace only works if the corresponding feature
74 | // was requested at session creation time. For simplicity, just ask for
75 | // the interesting ones as optional features, but be aware that the
76 | // requestReferenceSpace call will fail if it turns out to be unavailable.
77 | // ('local' is always available for immersive sessions and doesn't need to
78 | // be requested separately.)
79 |
80 | var sessionInit = { optionalFeatures: [ 'local-floor', 'bounded-floor' ] };
81 | navigator.xr.requestSession( 'immersive-vr', sessionInit ).then( onSessionStarted );
82 |
83 | } else {
84 |
85 | currentSession.end();
86 |
87 | }
88 |
89 | };
90 |
91 | }
92 |
93 | function disableButton() {
94 |
95 | button.style.display = '';
96 |
97 | button.style.cursor = 'auto';
98 | button.style.left = 'calc(50% - 75px)';
99 | button.style.width = '150px';
100 |
101 | button.onmouseenter = null;
102 | button.onmouseleave = null;
103 |
104 | button.onclick = null;
105 |
106 | }
107 |
108 | function showWebXRNotFound() {
109 |
110 | disableButton();
111 |
112 | button.textContent = 'VR NOT SUPPORTED';
113 |
114 | }
115 |
116 | function stylizeElement( element ) {
117 |
118 | element.style.position = 'absolute';
119 | element.style.bottom = '20px';
120 | element.style.padding = '12px 6px';
121 | element.style.border = '1px solid #fff';
122 | element.style.borderRadius = '4px';
123 | element.style.background = 'rgba(0,0,0,0.1)';
124 | element.style.color = '#fff';
125 | element.style.font = 'normal 13px sans-serif';
126 | element.style.textAlign = 'center';
127 | element.style.opacity = '0.5';
128 | element.style.outline = 'none';
129 | element.style.zIndex = '999';
130 |
131 | }
132 |
133 | if ( 'xr' in navigator ) {
134 |
135 | var button = document.createElement( 'button' );
136 | button.style.display = 'none';
137 |
138 | stylizeElement( button );
139 |
140 | navigator.xr.isSessionSupported( 'immersive-vr' ).then( function ( supported ) {
141 |
142 | supported ? showEnterVR() : showWebXRNotFound();
143 |
144 | } );
145 |
146 | return button;
147 |
148 | } else {
149 |
150 | var message = document.createElement( 'a' );
151 |
152 | if ( window.isSecureContext === false ) {
153 |
154 | message.href = document.location.href.replace( /^http:/, 'https:' );
155 | message.innerHTML = 'WEBXR NEEDS HTTPS'; // TODO Improve message
156 |
157 | } else {
158 |
159 | message.href = 'https://immersiveweb.dev/';
160 | message.innerHTML = 'WEBXR NOT AVAILABLE';
161 |
162 | }
163 |
164 | message.style.left = 'calc(50% - 90px)';
165 | message.style.width = '180px';
166 | message.style.textDecoration = 'none';
167 |
168 | stylizeElement( message );
169 |
170 | return message;
171 |
172 | }
173 |
174 | }
175 |
176 | };
177 |
178 | export { VRButton };
179 |
--------------------------------------------------------------------------------
/src/lib/assetManager.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { BasisTextureLoader } from 'three/examples/jsm/loaders/BasisTextureLoader.js';
3 | import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js';
4 | import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js';
5 | import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js';
6 |
7 | //const BASIS_LIB_PATH = 'src/vendor/';
8 | const BASIS_LIB_PATH = 'src/vendor/';
9 | const DRACO_LIB_PATH = 'src/vendor/';
10 |
11 | function getLoadedCount(assets) {
12 | let count = 0;
13 | for (var i in assets) {
14 | if (assets[i].loading !== true) { count ++; }
15 | }
16 | return count;
17 | }
18 |
19 | function allAssetsLoaded(assets) {
20 | for (var i in assets) {
21 | if (assets[i].loading === true) { return false; }
22 | }
23 | return true;
24 | }
25 |
26 | export function loadAssets(renderer, basePath, assets, onComplete, onProgress, debug) {
27 | if (basePath && basePath[basePath.length - 1] != '/') {
28 | basePath += '/';
29 | }
30 |
31 | var basisLoader = new BasisTextureLoader();
32 | basisLoader.setTranscoderPath(BASIS_LIB_PATH);
33 | basisLoader.detectSupport(renderer);
34 |
35 | var gltfLoader = new GLTFLoader();
36 | var dracoLoader = new DRACOLoader();
37 | dracoLoader.setDecoderPath(DRACO_LIB_PATH);
38 | gltfLoader.setDRACOLoader(dracoLoader);
39 |
40 | var texLoader = new THREE.TextureLoader();
41 | var objLoader = new OBJLoader();
42 | var fontLoader = new THREE.FontLoader();
43 | var audioLoader = new THREE.AudioLoader();
44 |
45 |
46 | var loaders = {
47 | 'gltf': gltfLoader,
48 | 'glb': gltfLoader,
49 | 'obj': objLoader,
50 | 'gif': texLoader,
51 | 'png': texLoader,
52 | 'jpg': texLoader,
53 | 'basis': basisLoader,
54 | 'font': fontLoader,
55 | 'ogg': audioLoader
56 | };
57 |
58 | for (var i in assets) {
59 | let assetId = i;
60 | let assetPath = assets[i].url;
61 | assets[i].loading = true;
62 | let ext = assetPath.substr(assetPath.lastIndexOf('.') + 1).toLowerCase();
63 | loaders[ext].load(basePath + assetPath, asset => {
64 | if (debug) {
65 | console.info(`%c ${assetPath} loaded`, 'color:green');
66 | }
67 | var options = assets[assetId].options;
68 | assets[assetId] = ext == 'font'? asset.data : asset;
69 |
70 | if (typeof options !== "undefined") {
71 | if (typeof options.repeat !== "undefined") {
72 | assets[assetId].repeat.set(options.repeat[0], options.repeat[1]);
73 | delete options.repeat;
74 | }
75 | for (let opt in options) {
76 | assets[assetId][opt] = options[opt];
77 | }
78 |
79 | if (onProgress) { onProgress(getLoadedCount(assets)) };
80 | }
81 |
82 | if (onComplete && allAssetsLoaded(assets)) { onComplete(); }
83 | }, () => {
84 | /* on progress */
85 | },
86 | (e) => {
87 | console.error('Error loading asset', e);
88 | }
89 | );
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/lib/shaders.js:
--------------------------------------------------------------------------------
1 | export var shaders = {
2 | door_frag : `
3 | uniform float time;
4 | uniform float selected;
5 | uniform sampler2D tex;
6 | varying vec2 vUv;
7 |
8 | void main( void ) {
9 | float t = time;
10 | vec2 uv = vUv * 2.0 - 1.0;
11 | vec2 puv = vec2(length(uv.xy), atan(uv.x, uv.y));
12 | vec4 col = texture2D(tex, vec2(log(puv.x) + t / 5.0, puv.y / 3.1415926 ));
13 | float glow = (1.0 - puv.x) * (0.5 + (sin(t) + 2.0 ) / 4.0);
14 | // blue glow
15 | col += vec4(118.0/255.0, 144.0/255.0, 219.0/255.0, 1.0) * (0.4 + glow * 1.0);
16 | // white glow
17 | col += vec4(0.2) * smoothstep(0.0, 2.0, glow * glow);
18 | gl_FragColor = col;
19 |
20 | }
21 | `,
22 |
23 | zoom_frag : `
24 | uniform float time;
25 | uniform sampler2D tex;
26 | uniform vec2 zoomPos;
27 | uniform float zoomAmount;
28 | uniform float zoomRatio;
29 | varying vec2 vUv;
30 |
31 | void main( void ) {
32 | float t = time;
33 | vec2 uv = vec2(vUv.x - 0.5, (1.0 - vUv.y) - 0.5);
34 | vec2 texUv = uv * vec2(zoomRatio, 1.0);
35 | vec4 col = texture2D(tex, zoomPos + texUv * zoomAmount);
36 | float dist = length(uv) * 2.0;
37 | col.a = smoothstep(0.0, 0.1, 1.0 - dist);
38 | float aura = smoothstep(0.80, 1.0, dist);
39 | col.rgb += aura * 0.3;
40 | gl_FragColor = col;
41 | }
42 | `,
43 |
44 | basic_vert : `
45 | varying vec2 vUv;
46 | varying vec3 vPosition;
47 | void main()
48 | {
49 | vUv = uv;
50 | vPosition = position;
51 | vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
52 | gl_Position = projectionMatrix * mvPosition;
53 | }
54 | `,
55 |
56 | panoball_vert : `
57 | varying vec2 vUv;
58 | varying vec3 vPosition;
59 | varying vec3 vNormal;
60 | varying vec3 vWorldPos;
61 | uniform float time;
62 | uniform float selected;
63 |
64 | mat4 inverse(mat4 m) {
65 | float
66 | a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
67 | a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
68 | a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
69 | a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],
70 |
71 | b00 = a00 * a11 - a01 * a10,
72 | b01 = a00 * a12 - a02 * a10,
73 | b02 = a00 * a13 - a03 * a10,
74 | b03 = a01 * a12 - a02 * a11,
75 | b04 = a01 * a13 - a03 * a11,
76 | b05 = a02 * a13 - a03 * a12,
77 | b06 = a20 * a31 - a21 * a30,
78 | b07 = a20 * a32 - a22 * a30,
79 | b08 = a20 * a33 - a23 * a30,
80 | b09 = a21 * a32 - a22 * a31,
81 | b10 = a21 * a33 - a23 * a31,
82 | b11 = a22 * a33 - a23 * a32,
83 |
84 | det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
85 |
86 | return mat4(
87 | a11 * b11 - a12 * b10 + a13 * b09,
88 | a02 * b10 - a01 * b11 - a03 * b09,
89 | a31 * b05 - a32 * b04 + a33 * b03,
90 | a22 * b04 - a21 * b05 - a23 * b03,
91 | a12 * b08 - a10 * b11 - a13 * b07,
92 | a00 * b11 - a02 * b08 + a03 * b07,
93 | a32 * b02 - a30 * b05 - a33 * b01,
94 | a20 * b05 - a22 * b02 + a23 * b01,
95 | a10 * b10 - a11 * b08 + a13 * b06,
96 | a01 * b08 - a00 * b10 - a03 * b06,
97 | a30 * b04 - a31 * b02 + a33 * b00,
98 | a21 * b02 - a20 * b04 - a23 * b00,
99 | a11 * b07 - a10 * b09 - a12 * b06,
100 | a00 * b09 - a01 * b07 + a02 * b06,
101 | a31 * b01 - a30 * b03 - a32 * b00,
102 | a20 * b03 - a21 * b01 + a22 * b00) / det;
103 | }
104 |
105 |
106 | mat4 transpose(in mat4 m) {
107 | vec4 i0 = m[0];
108 | vec4 i1 = m[1];
109 | vec4 i2 = m[2];
110 | vec4 i3 = m[3];
111 |
112 | return mat4(
113 | vec4(i0.x, i1.x, i2.x, i3.x),
114 | vec4(i0.y, i1.y, i2.y, i3.y),
115 | vec4(i0.z, i1.z, i2.z, i3.z),
116 | vec4(i0.w, i1.w, i2.w, i3.w)
117 | );
118 | }
119 |
120 | void main()
121 | {
122 | vUv = uv;
123 |
124 | vPosition = position;
125 |
126 | vec3 offset = vec3(
127 | sin(position.x * 50.0 + time),
128 | sin(position.y * 10.0 + time * 2.0),
129 | cos(position.z * 40.0 + time)
130 | ) * 0.003;
131 |
132 | vPosition *= 1.0 + selected * 0.2;
133 |
134 | vNormal = normalize(inverse(transpose(modelMatrix)) * vec4(normalize(normal), 1.0)).xyz;
135 | vWorldPos = (modelMatrix * vec4(vPosition, 1.0)).xyz;
136 |
137 | vec4 mvPosition = modelViewMatrix * vec4(vPosition + offset, 1.0);
138 | gl_Position = projectionMatrix * mvPosition;
139 | }
140 | `,
141 |
142 | panoball_frag : `
143 | uniform sampler2D tex, texfx;
144 | uniform float time;
145 | uniform float selected;
146 | varying vec2 vUv;
147 | varying vec3 vPosition;
148 | varying vec3 vNormal;
149 | varying vec3 vWorldPos;
150 |
151 |
152 | void main( void ) {
153 | vec2 uv = vUv;
154 | //uv.y = 1.0 - uv.y;
155 |
156 | vec3 eye = normalize(cameraPosition - vWorldPos);
157 | float fresnel = abs(dot(eye, vNormal));
158 | float shift = pow((1.0 - fresnel), 4.0) * 0.05;
159 |
160 | vec3 col = vec3(
161 | texture2D(tex, uv - shift).r,
162 | texture2D(tex, uv).g,
163 | texture2D(tex, uv + shift).b
164 | );
165 |
166 | col = mix(col * 0.7, vec3(1.0), 0.7 - fresnel);
167 |
168 | col += selected * 0.3;
169 |
170 | float t = time * 0.4 + vPosition.x + vPosition.z;
171 | uv = vec2(vUv.x + t * 0.2, vUv.y + t);
172 | vec3 fx = texture2D(texfx, uv).rgb * 0.4;
173 |
174 |
175 | gl_FragColor = vec4(col + fx, 1.0);
176 | }
177 | `,
178 |
179 | beam_frag : `
180 | uniform float time;
181 | uniform sampler2D tex;
182 | varying vec2 vUv;
183 |
184 | void main( void ) {
185 | float t = time;
186 | vec4 col = texture2D(tex, vec2(vUv.x, vUv.y * 3.0 + time * 2.0));
187 | col *= vUv.y;
188 | gl_FragColor = col;
189 | }
190 | `,
191 |
192 | };
--------------------------------------------------------------------------------
/src/lib/slideshow.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | const PATH_TIME = 3; // 3 secs per path
4 |
5 | var slideshow = {};
6 |
7 | slideshow.paths = [
8 | //'hall'
9 | [
10 | {
11 | from: new THREE.Vector3(0, 1.6, 0),
12 | lookAt: new THREE.Vector3(0, 1, -100),
13 | velocity: new THREE.Vector3(0, 0, -0.1),
14 | angularVelocity: new THREE.Vector3(0.04, 0, 0)
15 | },
16 | {
17 | from: new THREE.Vector3(4.5, 1.6, -3),
18 | lookAt: new THREE.Vector3(100, 0, 0),
19 | velocity: new THREE.Vector3(0, 0, -0.15),
20 | angularVelocity: new THREE.Vector3(0, 0, 0)
21 | },
22 | {
23 | from: new THREE.Vector3(0, 1.6, 0),
24 | lookAt: new THREE.Vector3(50, 0, -40),
25 | velocity: new THREE.Vector3(0, 0.12, 0),
26 | angularVelocity: new THREE.Vector3(0, 0.1, 0)
27 | },
28 | {
29 | from: new THREE.Vector3(3, 2.4, -4),
30 | lookAt: new THREE.Vector3(-50, 0, 40),
31 | velocity: new THREE.Vector3(0, -0.12, 0),
32 | angularVelocity: new THREE.Vector3(0, -0.1, 0)
33 | }
34 | ],
35 | //'sound'
36 | [
37 | {
38 | from: new THREE.Vector3(0, 1.6, 0),
39 | lookAt: new THREE.Vector3(0, 0, -100),
40 | velocity: new THREE.Vector3(0.1, 0, 0.1),
41 | angularVelocity: new THREE.Vector3(0, 0.01, 0)
42 | },
43 | ],
44 | //'photogrammetry'
45 | [
46 | {
47 | from: new THREE.Vector3(0, 1.2, 0.8),
48 | lookAt: new THREE.Vector3(-100, 0.5, 0),
49 | velocity: new THREE.Vector3(0, 0, -0.2),
50 | angularVelocity: new THREE.Vector3(0, 0, 0)
51 | },
52 | {
53 | from: new THREE.Vector3(-1, 1, -1.6),
54 | lookAt: new THREE.Vector3(-40, 0, 100),
55 | velocity: new THREE.Vector3(-0.05, 0, 0),
56 | angularVelocity: new THREE.Vector3(0, 0, 0)
57 | }
58 | ],
59 | //'vertigo'
60 | [
61 | {
62 | from: new THREE.Vector3(0, 1.6, 0),
63 | lookAt: new THREE.Vector3(0, 0, -10),
64 | velocity: new THREE.Vector3(0, 0, 0),
65 | angularVelocity: new THREE.Vector3(-0.01, 0.07, 0)
66 | },
67 | {
68 | from: new THREE.Vector3(0, 1.6, -0.4),
69 | lookAt: new THREE.Vector3(0, 0, 6),
70 | velocity: new THREE.Vector3(0, 0, 0.1),
71 | angularVelocity: new THREE.Vector3(0.07, 0, 0)
72 | }
73 | ],
74 | //'panoramastereo'
75 | [
76 | {
77 | from: new THREE.Vector3(0, 0, 0),
78 | lookAt: new THREE.Vector3(-100, 0, 0),
79 | velocity: new THREE.Vector3(0, 0, 0),
80 | angularVelocity: new THREE.Vector3(0.03, 0.05, 0)
81 | }
82 | ],
83 | //'panorama1'
84 | [
85 | ],
86 | //'panorama2'
87 | [
88 | {
89 | from: new THREE.Vector3(0, 0, 0),
90 | lookAt: new THREE.Vector3(-100, 0, 0),
91 | velocity: new THREE.Vector3(0, 0, 0),
92 | angularVelocity: new THREE.Vector3(0.01, 0.1, 0)
93 | }
94 | ],
95 | //'panorama3'
96 | [
97 | ],
98 | //'panorama4'
99 | [
100 | {
101 | from: new THREE.Vector3(0, 0, 0),
102 | lookAt: new THREE.Vector3(-100, 0, 0),
103 | velocity: new THREE.Vector3(0, 0, 0),
104 | angularVelocity: new THREE.Vector3(-0.07, -0.05, 0)
105 | }
106 | ],
107 | //'panorama5'
108 | [
109 | {
110 | from: new THREE.Vector3(0, 0, 0),
111 | lookAt: new THREE.Vector3(100, 0, 80),
112 | velocity: new THREE.Vector3(0, 0, 0),
113 | angularVelocity: new THREE.Vector3(0, -0.02, 0)
114 | }
115 | ]
116 | ];
117 |
118 | slideshow.setup = function (context) {
119 | this.room = undefined;
120 | this.path = undefined;
121 | this.next(context);
122 | context.camera.position.set(0, 1.6, 0);
123 | context.cameraRig.position.set(0, 0, 2);
124 | context.goto = this.room;
125 | };
126 |
127 | slideshow.next = function (context) {
128 | this.time = PATH_TIME + Math.random() * PATH_TIME / 2;
129 | if (this.room === undefined) {
130 | this.room = 0;
131 | this.path = 0;
132 | } else {
133 | this.path ++;
134 | if (this.path >= this.paths[this.room].length){
135 | // find next room with paths
136 | do {
137 | this.room ++;
138 | if (this.room >= this.paths.length) {
139 | this.room = 0;
140 | context.cameraRig.position.set(0, 0, 2);
141 | }
142 | } while (!this.paths[this.room].length);
143 | var camera = context.renderer.xr.getCamera(context.camera);
144 | camera.position.set(0, 1.6, 0);
145 | context.goto = this.room;
146 | this.path = 0;
147 | }
148 | }
149 | this.reset = true;
150 | };
151 |
152 | slideshow.execute = function (context, delta, time) {
153 | const path = this.paths[this.room][this.path];
154 | if (this.reset) {
155 | this.reset = false;
156 | // init camera settings
157 | context.camera.position.copy(path.from);
158 | context.camera.rotation.set(0, 0, 0);
159 | context.camera.lookAt(path.lookAt);
160 | }
161 | context.camera.position.addScaledVector(path.velocity, delta);
162 | context.camera.rotation.x += path.angularVelocity.x * delta;
163 | context.camera.rotation.y += path.angularVelocity.y * delta;
164 | context.camera.rotation.z += path.angularVelocity.z * delta;
165 | this.time -= delta;
166 | if (this.time < 0) {
167 | this.next(context);
168 | }
169 | };
170 |
171 | export {slideshow};
172 |
--------------------------------------------------------------------------------
/src/lib/utils.js:
--------------------------------------------------------------------------------
1 | export function newMarker(x, y, z, color){
2 | const geo = new THREE.SphereBufferGeometry(0.04);
3 | const mat = new THREE.MeshBasicMaterial({color: color ? color : 0xff0000});
4 | const mesh = new THREE.Mesh(geo, mat);
5 | if (typeof x === 'object') {
6 | mesh.position.copy(x);
7 | } else {
8 | mesh.position.set(x, y, z);
9 | }
10 | return mesh;
11 | }
12 |
13 | export function angleBetween(point1, point2) {
14 | return Math.atan2(point2.x - point1.x, point2.y - point1.y);
15 | }
16 |
17 | export function getRandomInt(min, max) {
18 | return Math.floor(Math.random() * (max - min)) + min;
19 | }
20 |
21 |
--------------------------------------------------------------------------------
/src/rooms/Hall.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import * as panoballs from '../stations/PanoBalls.js';
3 | import * as paintings from '../stations/Paintings.js';
4 | import * as newsticker from '../stations/NewsTicker.js';
5 | import * as xylophone from '../stations/Xylophone.js';
6 | import * as graffiti from '../stations/Graffiti.js';
7 | import * as infopanels from '../stations/InfoPanels.js';
8 |
9 | var
10 | scene,
11 | hall,
12 | teleportFloor,
13 | fader,
14 | doors = [],
15 | objectMaterials,
16 | controllers,
17 | auxVec = new THREE.Vector3();
18 |
19 | function createDoorMaterial(ctx) {
20 | return new THREE.ShaderMaterial({
21 | uniforms: {
22 | time: {value: 0},
23 | selected: {value: 0},
24 | tex: {value: ctx.assets['doorfx_tex']}
25 | },
26 | vertexShader: ctx.shaders.basic_vert,
27 | fragmentShader: ctx.shaders.door_frag
28 | });
29 | }
30 |
31 | export function setup(ctx) {
32 | const assets = ctx.assets;
33 | scene = new THREE.Object3D();
34 |
35 | // setup hall model
36 |
37 | const hallLightmapTex = assets['lightmap_tex'];
38 | const skyTex = assets['sky_tex'];
39 | const cloudsTex = assets['clouds_tex'];
40 | const foxrTex = assets['foxr_tex'];
41 | const newstickerTex = assets['newsticker_tex'];
42 | const mozillamrTex = assets['mozillamr_tex'];
43 |
44 | const hallMaterial = new THREE.MeshBasicMaterial({map: hallLightmapTex});
45 |
46 | objectMaterials = {
47 | 'hall': hallMaterial,
48 | 'screen': new THREE.MeshBasicMaterial({map: newstickerTex}),
49 | 'xylophone': hallMaterial,
50 | 'xylostick-left': hallMaterial,
51 | 'xylostick-right': hallMaterial,
52 | 'xylostickball-left': hallMaterial.clone(),
53 | 'xylostickball-right': hallMaterial.clone(),
54 | 'lightpanels': new THREE.MeshBasicMaterial(),
55 | 'doorA': createDoorMaterial(ctx),
56 | 'doorB': createDoorMaterial(ctx),
57 | 'doorC': createDoorMaterial(ctx),
58 | 'doorD': createDoorMaterial(ctx),
59 | 'sky': new THREE.MeshBasicMaterial({map: skyTex}),
60 | 'clouds': new THREE.MeshBasicMaterial({map: cloudsTex, transparent: true}),
61 | 'foxr': new THREE.MeshBasicMaterial({map: foxrTex, transparent: true}),
62 | 'mozillamr': new THREE.MeshBasicMaterial({map: mozillamrTex, transparent: true}),
63 | };
64 |
65 | hall = assets['hall_model'].scene;
66 | hall.traverse(o => {
67 | if (o.name == 'teleport') {
68 | teleportFloor = o;
69 | //o.visible = false;
70 | o.material.visible = false;
71 | return;
72 | } else if (o.name.startsWith('door')) {
73 | doors.push(o);
74 | }
75 |
76 | if (o.type == 'Mesh' && objectMaterials[o.name]) {
77 | o.material = objectMaterials[o.name];
78 | }
79 | });
80 |
81 | paintings.setup(ctx, hall);
82 | xylophone.setup(ctx, hall);
83 | graffiti.setup(ctx, hall);
84 | newsticker.setup(ctx, hall);
85 | panoballs.setup(ctx, hall);
86 | infopanels.setup(ctx, hall);
87 |
88 | ctx.raycontrol.addState('teleport', {
89 | colliderMesh: teleportFloor,
90 | onHover: (intersection, active) => {
91 | ctx.teleport.onHover(intersection.point, active);
92 | },
93 | onHoverLeave: () => {
94 | ctx.teleport.onHoverLeave();
95 | },
96 | onSelectStart: (intersection, e) => {
97 | ctx.teleport.onSelectStart(e);
98 | },
99 | onSelectEnd: (intersection) => {
100 | ctx.teleport.onSelectEnd(intersection.point);
101 | }
102 | });
103 |
104 | ctx.raycontrol.addState('doors', {
105 | colliderMesh: doors,
106 | onHover: (intersection, active) => {
107 | const scale = intersection.object.scale;
108 | scale.z = Math.min(scale.z + 0.05 * (5.5 - scale.z), 5);
109 | },
110 | onHoverLeave: (intersection) => {
111 | },
112 | onSelectStart: (intersection) => {
113 | const transitions = {
114 | doorA: 1,
115 | doorB: 2,
116 | doorC: 3,
117 | doorD: 4
118 | };
119 | ctx.goto = transitions[intersection.object.name];
120 | },
121 | onSelectEnd: (intersection) => {}
122 | });
123 |
124 | // fade camera to black on walls
125 | fader = new THREE.Mesh(
126 | new THREE.PlaneBufferGeometry(),
127 | new THREE.MeshBasicMaterial({color: 0x000000, transparent: true, depthTest: false})
128 | );
129 | fader.position.z = -0.1;
130 | fader.material.opacity = 0;
131 |
132 | scene.add(hall);
133 | ctx.camera.add(fader);
134 | }
135 |
136 | export function enter(ctx) {
137 | ctx.systemsGroup['roomHall'].play();
138 | ctx.renderer.setClearColor( 0xC0DFFB );
139 | ctx.scene.add(scene);
140 |
141 | xylophone.enter(ctx);
142 | graffiti.enter(ctx);
143 | infopanels.enter(ctx);
144 | ctx.raycontrol.activateState('doors');
145 | ctx.raycontrol.activateState('teleport');
146 | paintings.enter(ctx);
147 | panoballs.enter(ctx);
148 | }
149 |
150 | export function exit(ctx) {
151 | ctx.systemsGroup['roomHall'].stop();
152 | ctx.scene.remove(scene);
153 |
154 | ctx.raycontrol.deactivateState('doors');
155 | ctx.raycontrol.deactivateState('teleport');
156 |
157 | xylophone.exit(ctx);
158 | }
159 |
160 | export function execute(ctx, delta, time) {
161 | panoballs.execute(ctx, delta, time);
162 | paintings.execute(ctx, delta, time);
163 | xylophone.execute(ctx, delta, time, controllers);
164 | graffiti.execute(ctx, delta, time);
165 | newsticker.execute(ctx, delta, time);
166 | infopanels.execute(ctx, delta, time);
167 | updateUniforms(time);
168 | //checkCameraBoundaries(ctx);
169 |
170 | for (var i = 0; i < doors.length; i++) {
171 | if (doors[i].scale.z > 1) {
172 | doors[i].scale.z = Math.max(doors[i].scale.z - delta * doors[i].scale.z, 1);
173 | }
174 | }
175 |
176 | }
177 |
178 | function updateUniforms(time) {
179 | objectMaterials.doorA.uniforms.time.value = time;
180 | objectMaterials.doorB.uniforms.time.value = time;
181 | objectMaterials.doorC.uniforms.time.value = time;
182 | objectMaterials.doorD.uniforms.time.value = time;
183 | objectMaterials.doorD.uniforms.selected.value = 1; //test
184 | panoballs.updateUniforms(time);
185 | }
186 |
187 | function checkCameraBoundaries(ctx) {
188 | auxVec.copy(ctx.camera.position).add(ctx.cameraRig.position);
189 | const cam = auxVec;
190 | const margin = 0.25;
191 | var fade = 0;
192 | if (cam.y < margin) { fade = 1 - (cam.y / margin); }
193 | else if (cam.x < -5.4) { fade = (-cam.x - 5.4) / margin; }
194 | else if (cam.x > 8) { fade = (cam.x - 8) / margin; }
195 | else if (cam.z < -6.45) { fade = (-cam.z - 6.45) / margin; }
196 | else if (cam.z > 6.4) { fade = (cam.z - 6.4) / margin; }
197 | fader.material.opacity = Math.min(1, Math.max(0, fade));
198 | }
199 |
--------------------------------------------------------------------------------
/src/rooms/Panorama.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { Text, Position, ParentObject3D } from '../components/index.js';
3 |
4 | var pano = null, context, panel, panelText;
5 |
6 | const NUM_PANOS = 5;
7 |
8 | const DATA = [
9 | 'Tiger and Turtle - Magic Mountain\nArt installation in Agerpark, Germany.',
10 | 'Hiking trail at Lake Byllesby Regional Park near Cannon Falls, USA.',
11 | 'Dellwiger Bach natural reserve in Dortmund, Germany.',
12 | 'Zapporthorn summit in Lepontine Alps, Switzerland.',
13 | 'Ruin of romanesque Paulinzella abbey (1106) in Thuringia, Germany.',
14 | ];
15 |
16 | var panoMaterials = [];
17 |
18 | export function setup(ctx) {
19 | const assets = ctx.assets;
20 | const geometry = new THREE.SphereBufferGeometry(500, 60, 40);
21 | for (var i = 0; i < NUM_PANOS; i++) {
22 | const panoName = 'pano'+(i + 2);
23 | panoMaterials[i] = new THREE.MeshBasicMaterial( { map: assets[panoName], side: THREE.BackSide });
24 | }
25 | pano = new THREE.Mesh(geometry, panoMaterials[0]);
26 |
27 | panel = assets['hall_model'].scene.getObjectByName('infopanel');
28 | panel.material = new THREE.MeshBasicMaterial({color: 0x040404});
29 | panel.position.set(0, 0.1, 0);
30 | panel.parent.remove(panel);
31 |
32 | panelText = ctx.world.createEntity();
33 | panelText
34 | .addComponent(Text, {
35 | color: '#ffffff',
36 | fontSize: 0.02,
37 | anchor: 'left',
38 | textAlign: 'left',
39 | baseline: 'center',
40 | maxWidth: 0.34,
41 | lineHeight: 1.3,
42 | text: DATA[i],
43 | })
44 | .addComponent(ParentObject3D, {value: panel})
45 | .addComponent(Position, {x: -0.17, y: 0.003, z: 0.01});
46 |
47 | ctx.raycontrol.addState('panorama', {
48 | raycaster: false,
49 | onSelectEnd: onSelectEnd
50 | });
51 | }
52 |
53 | export function enter(ctx) {
54 | ctx.renderer.setClearColor(0x000000);
55 |
56 | const room = ctx.room - 5;
57 | panelText.getMutableComponent(Text).text = DATA[room];
58 | pano.material = panoMaterials[room];
59 |
60 | ctx.scene.add(pano);
61 |
62 | ctx.controllers[1].add(panel);
63 |
64 | ctx.raycontrol.activateState('panorama');
65 |
66 | context = ctx;
67 | }
68 |
69 | export function exit(ctx) {
70 | ctx.scene.remove(pano);
71 | ctx.controllers[1].remove(panel);
72 |
73 | ctx.raycontrol.deactivateState('panorama');
74 | }
75 |
76 | export function execute(ctx, delta, time) {
77 | }
78 |
79 | export function onSelectEnd(evt) {
80 | context.goto = 0;
81 | }
82 |
--------------------------------------------------------------------------------
/src/rooms/PanoramaStereo.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | var panoL, panoR, context;
3 |
4 | export function setup(ctx) {
5 | const assets = ctx.assets;
6 | const geometry = new THREE.SphereBufferGeometry(500, 60, 40);
7 | const materialL = new THREE.MeshBasicMaterial( { map: assets['stereopanoR'], side: THREE.BackSide } );
8 | const materialR = new THREE.MeshBasicMaterial( { map: assets['stereopanoL'], side: THREE.BackSide } );
9 | panoL = new THREE.Mesh(geometry, materialL);
10 | panoL.layers.set(1);
11 | panoR = new THREE.Mesh(geometry, materialR);
12 | panoR.layers.set(2);
13 |
14 | ctx.raycontrol.addState('panoramaStereo', {
15 | raycaster: false,
16 | onSelectEnd: onSelectEnd
17 | });
18 | }
19 |
20 | export function enter(ctx) {
21 | ctx.renderer.setClearColor(0x000000);
22 | ctx.scene.add(panoL);
23 | ctx.scene.add(panoR);
24 | ctx.camera.layers.enable(1);
25 | context = ctx;
26 |
27 | ctx.raycontrol.activateState('panoramaStereo');
28 | }
29 |
30 | export function exit(ctx) {
31 | ctx.scene.remove(panoL);
32 | ctx.scene.remove(panoR);
33 | ctx.camera.layers.disable(1);
34 | ctx.raycontrol.deactivateState('panoramaStereo');
35 | }
36 |
37 | export function execute(ctx, delta, time) {
38 | }
39 |
40 | export function onSelectEnd(evt) {
41 | context.goto = 0;
42 | }
43 |
44 |
--------------------------------------------------------------------------------
/src/rooms/PhotogrammetryObject.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | var scene, doorMaterial, door;
3 |
4 | function createDoorMaterial(ctx) {
5 | return new THREE.ShaderMaterial({
6 | uniforms: {
7 | time: {value: 0},
8 | selected: {value: 0},
9 | tex: {value: ctx.assets['doorfx_tex']}
10 | },
11 | vertexShader: ctx.shaders.basic_vert,
12 | fragmentShader: ctx.shaders.door_frag
13 | });
14 | }
15 |
16 | export function setup(ctx) {
17 | const assets = ctx.assets;
18 | scene = assets['pg_object_model'].scene;
19 | scene.rotation.y = -Math.PI / 2;
20 |
21 | scene.getObjectByName('object').material =
22 | new THREE.MeshBasicMaterial({map: assets['pg_object_tex']});
23 | scene.getObjectByName('floor').material =
24 | new THREE.MeshBasicMaterial({map: assets['pg_floor_tex'], lightMap: assets['pg_floor_lm_tex']});
25 | scene.getObjectByName('bg').material =
26 | new THREE.MeshBasicMaterial({map: assets['pg_bg_tex']});
27 | scene.getObjectByName('flare').material =
28 | new THREE.MeshBasicMaterial({map: assets['pg_flare_tex'], blending: THREE.AdditiveBlending});
29 | scene.getObjectByName('panel').material =
30 | new THREE.MeshBasicMaterial({map: assets['pg_panel_tex']});
31 | scene.getObjectByName('door_frame').material =
32 | new THREE.MeshBasicMaterial({map: assets['pg_door_lm_tex']});
33 |
34 | doorMaterial = createDoorMaterial(ctx);
35 | door = scene.getObjectByName('door');
36 | door.material = doorMaterial;
37 |
38 | scene.getObjectByName('teleport').visible = false;
39 |
40 | ctx.raycontrol.addState('doorPhotogrammetry', {
41 | colliderMesh: scene.getObjectByName('door'),
42 | onHover: (intersection, active) => {
43 | //teleport.onHover(intersection.point, active);
44 | const scale = intersection.object.scale;
45 | scale.z = Math.min(scale.z + 0.05 * (2 - door.scale.z), 1.5);
46 | },
47 | onHoverLeave: () => {
48 | //teleport.onHoverLeave();
49 | },
50 | onSelectStart: (intersection, e) => {
51 | ctx.goto = 0;
52 | //teleport.onSelectStart(e);
53 | },
54 | onSelectEnd: (intersection) => {
55 | //teleport.onSelectEnd(intersection.point);
56 | }
57 | });
58 |
59 | let teleport = scene.getObjectByName('teleport');
60 | teleport.visible = true;
61 | teleport.material.visible = false;
62 | ctx.raycontrol.addState('teleportPhotogrammetry', {
63 | colliderMesh: teleport,
64 | onHover: (intersection, active) => {
65 | ctx.teleport.onHover(intersection.point, active);
66 | },
67 | onHoverLeave: () => {
68 | ctx.teleport.onHoverLeave();
69 | },
70 | onSelectStart: (intersection, e) => {
71 | ctx.teleport.onSelectStart(e);
72 | },
73 | onSelectEnd: (intersection) => {
74 | ctx.teleport.onSelectEnd(intersection.point);
75 | }
76 | });
77 | }
78 |
79 | export function enter(ctx) {
80 | ctx.renderer.setClearColor(0x000000);
81 | ctx.scene.add(scene);
82 | ctx.raycontrol.activateState('doorPhotogrammetry');
83 | ctx.raycontrol.activateState('teleportPhotogrammetry');
84 | }
85 |
86 | export function exit(ctx) {
87 | ctx.raycontrol.deactivateState('doorPhotogrammetry');
88 | ctx.raycontrol.deactivateState('teleportPhotogrammetry');
89 |
90 | ctx.scene.remove(scene);
91 | }
92 |
93 | export function execute(ctx, delta, time) {
94 | doorMaterial.uniforms.time.value = time;
95 |
96 | if (door.scale.z > 0.5) {
97 | door.scale.z = Math.max(door.scale.z - delta * door.scale.z, 0.5);
98 | }
99 | }
100 |
--------------------------------------------------------------------------------
/src/rooms/Sound.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | var scene, listener, timeout, mixer, door, doorMaterial;
3 |
4 | const soundNames = [
5 | 'bells',
6 | 'horn',
7 | 'cowbell',
8 | 'guiro',
9 | 'mandolin',
10 | 'squeaker',
11 | 'train',
12 | 'whistle',
13 | 'motorhorn',
14 | 'surdo',
15 | 'trumpet',
16 | ];
17 |
18 | var sounds = {};
19 | soundNames.forEach( i => { sounds[i] = {animations: [], mesh: null, player: null, shadow: null} })
20 |
21 | const MAX_REPETITIONS = 3;
22 | var repetitions = MAX_REPETITIONS - 1;
23 |
24 | function createDoorMaterial(ctx) {
25 | return new THREE.ShaderMaterial({
26 | uniforms: {
27 | time: {value: 0},
28 | selected: {value: 0},
29 | tex: {value: ctx.assets['doorfx_tex']}
30 | },
31 | vertexShader: ctx.shaders.basic_vert,
32 | fragmentShader: ctx.shaders.door_frag
33 | });
34 | }
35 |
36 | export function setup(ctx) {
37 | const assets = ctx.assets;
38 | scene = assets['sound_model'].scene;
39 | door = assets['sound_door_model'].scene;
40 |
41 | door.getObjectByName('door_frame').material =
42 | new THREE.MeshBasicMaterial({map: assets['sound_door_lm_tex']});
43 |
44 | doorMaterial = createDoorMaterial(ctx);
45 | door.getObjectByName('door').material = doorMaterial;
46 |
47 | door.scale.set(0.5, 0.5, 0.5);
48 | door.position.set(0.4, 0.6, 1);
49 | door.rotation.set(0, 0.4, 0);
50 |
51 | listener = new THREE.AudioListener();
52 |
53 | mixer = new THREE.AnimationMixer(scene);
54 |
55 | for (let id in sounds) {
56 | const mesh = scene.getObjectByName(id);
57 | if (!mesh) { continue; }
58 |
59 | const sound = new THREE.PositionalAudio(listener);
60 | const audioLoader = new THREE.AudioLoader();
61 | audioLoader.load('assets/ogg/' + id + '.ogg', buffer => {
62 | sound.setBuffer(buffer);
63 | //sound.setRefDistance(20);
64 | });
65 |
66 | sounds[id].player = sound;
67 | sounds[id].mesh = mesh;
68 | mesh.visible = false;
69 | mesh.add(sound);
70 |
71 | const clip = THREE.AnimationClip.findByName(assets['sound_model'].animations, id);
72 | if (clip) {
73 | const action = mixer.clipAction(clip, mesh);
74 | action.loop = THREE.LoopOnce;
75 | sounds[id].animations.push(action);
76 | }
77 |
78 | for (let j = 0; j < mesh.children.length; j++) {
79 | const obj = mesh.children[j];
80 | const clip = THREE.AnimationClip.findByName(assets['sound_model'].animations, `${id}_${obj.name}`);
81 | if (!clip) { continue; }
82 | const action = mixer.clipAction(clip, mesh);
83 | action.loop = THREE.LoopOnce;
84 | sounds[id].animations.push(action);
85 | }
86 |
87 | let shadow = new THREE.Mesh(
88 | new THREE.PlaneBufferGeometry(3, 3),
89 | new THREE.MeshBasicMaterial({
90 | color: mesh.children[0].material.color,
91 | map: assets['sound_shadow_tex'],
92 | transparent: true,
93 | opacity: 0,
94 | depthTest: false,
95 | blending: THREE.AdditiveBlending
96 | })
97 | );
98 | shadow.position.set(mesh.position.x, 0.001, mesh.position.z);
99 | shadow.rotation.x = -Math.PI / 2;
100 | scene.add(shadow);
101 | sounds[id].shadow = shadow;
102 | }
103 |
104 | ctx.raycontrol.addState('sound', {
105 | colliderMesh: door.getObjectByName('door'),
106 | onHover: (intersection, active) => {
107 | //teleport.onHover(intersection.point, active);
108 | const scale = intersection.object.scale;
109 | scale.z = Math.min(scale.z + 0.05 * (2 - door.scale.z), 1.5);
110 | },
111 | onHoverLeave: () => {
112 | //teleport.onHoverLeave();
113 | },
114 | onSelectStart: (intersection, e) => {
115 | ctx.goto = 0;
116 | //teleport.onSelectStart(e);
117 | },
118 | onSelectEnd: (intersection) => {
119 | //teleport.onSelectEnd(intersection.point);
120 | }
121 | });
122 |
123 | const floorTexture = assets['grid_tex'];
124 | const floor = new THREE.Mesh(
125 | new THREE.PlaneBufferGeometry(20, 20),
126 | new THREE.MeshBasicMaterial({map: floorTexture})
127 | );
128 | scene.add(floor);
129 | floor.rotation.x = -Math.PI / 2;
130 |
131 | ctx.raycontrol.addState('teleportSound', {
132 | colliderMesh: floor,
133 | onHover: (intersection, active) => {
134 | ctx.teleport.onHover(intersection.point, active);
135 | },
136 | onHoverLeave: () => {
137 | ctx.teleport.onHoverLeave();
138 | },
139 | onSelectStart: (intersection, e) => {
140 | ctx.teleport.onSelectStart(e);
141 | },
142 | onSelectEnd: (intersection) => {
143 | ctx.teleport.onSelectEnd(intersection.point);
144 | }
145 | });
146 | }
147 |
148 | var currentSound = -1;
149 |
150 | function playSound() {
151 | let sound;
152 | if (currentSound >= 0) {
153 | sound = sounds[soundNames[currentSound]];
154 | sound.player.stop();
155 | if (sound.animations.length) {
156 | sound.mesh.visible = false;
157 | sound.animations.forEach( i => {i.stop()});
158 | }
159 | }
160 | repetitions ++;
161 | if (repetitions == MAX_REPETITIONS) {
162 | repetitions = 0;
163 | // get next sound
164 | do {
165 | currentSound = (currentSound + 1) % soundNames.length;
166 | sound = sounds[soundNames[currentSound]];
167 | } while(!sound.mesh);
168 | }
169 |
170 | sound.player.play();
171 | if (sound.animations.length) {
172 | sound.mesh.visible = true;
173 | sound.animations.forEach( i => {i.play()});
174 | }
175 | sound.shadow.material.opacity = 1;
176 | timeout = setTimeout(playSound, 2000);
177 | }
178 |
179 | export function enter(ctx) {
180 | ctx.renderer.setClearColor(0x000000);
181 | ctx.scene.add(scene);
182 | ctx.scene.add(door);
183 | ctx.camera.add(listener);
184 |
185 | timeout = setTimeout(playSound, 2000);
186 | ctx.raycontrol.activateState('teleportSound');
187 | ctx.raycontrol.activateState('sound');
188 | }
189 |
190 | export function exit(ctx) {
191 | ctx.scene.remove(scene);
192 | ctx.scene.remove(door);
193 | ctx.camera.remove(listener);
194 | ctx.raycontrol.deactivateState('teleportSound');
195 | ctx.raycontrol.deactivateState('sound');
196 | clearTimeout(timeout);
197 | }
198 |
199 | export function execute(ctx, delta, time) {
200 | mixer.update(delta);
201 | const sound = sounds[soundNames[currentSound]];
202 | if (sound && sound.shadow.material.opacity > 0) {
203 | sound.shadow.material.opacity -= delta * 0.5;
204 | }
205 | doorMaterial.uniforms.time.value = time;
206 |
207 | if (door.scale.z > 0.5) {
208 | door.scale.z = Math.max(door.scale.z - delta * door.scale.z, 0.5);
209 | }
210 | }
211 |
--------------------------------------------------------------------------------
/src/rooms/Vertigo.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | var scene, doorMaterial, door;
3 |
4 | function createDoorMaterial(ctx) {
5 | return new THREE.ShaderMaterial({
6 | uniforms: {
7 | time: {value: 0},
8 | selected: {value: 0},
9 | tex: {value: ctx.assets['doorfx_tex']}
10 | },
11 | vertexShader: ctx.shaders.basic_vert,
12 | fragmentShader: ctx.shaders.door_frag
13 | });
14 | }
15 |
16 | export function setup(ctx) {
17 | const assets = ctx.assets;
18 | var texture = assets['checkboard_tex'];
19 |
20 | var lightmap = assets['vertigo_lm_tex'];
21 | const material = new THREE.MeshBasicMaterial({color: 0xffffff, map: texture, lightMap: lightmap} );
22 |
23 | scene = assets['vertigo_model'].scene;
24 | scene.getObjectByName('city').material = material;
25 | scene.getObjectByName('teleport').visible = false;
26 |
27 | scene.getObjectByName('door_frame').material =
28 | new THREE.MeshBasicMaterial({map: assets['vertigo_door_lm_tex']});
29 | doorMaterial = createDoorMaterial(ctx);
30 | door = scene.getObjectByName('door');
31 | door.material = doorMaterial;
32 |
33 | ctx.raycontrol.addState('doorVertigo', {
34 | colliderMesh: scene.getObjectByName('door'),
35 | onHover: (intersection, active) => {
36 | //teleport.onHover(intersection.point, active);
37 | const scale = intersection.object.scale;
38 | scale.z = Math.min(scale.z + 0.02 * (2 - door.scale.z), 0.8);
39 | },
40 | onHoverLeave: () => {
41 | //teleport.onHoverLeave();
42 | },
43 | onSelectStart: (intersection, e) => {
44 | ctx.goto = 0;
45 | //teleport.onSelectStart(e);
46 | },
47 | onSelectEnd: (intersection) => {
48 | //teleport.onSelectEnd(intersection.point);
49 | }
50 | });
51 |
52 | let teleport = scene.getObjectByName('teleport');
53 | teleport.visible = true;
54 | teleport.material.visible = false;
55 | ctx.raycontrol.addState('teleportVertigo', {
56 | colliderMesh: teleport,
57 | onHover: (intersection, active) => {
58 | ctx.teleport.onHover(intersection.point, active);
59 | },
60 | onHoverLeave: () => {
61 | ctx.teleport.onHoverLeave();
62 | },
63 | onSelectStart: (intersection, e) => {
64 | ctx.teleport.onSelectStart(e);
65 | },
66 | onSelectEnd: (intersection) => {
67 | ctx.teleport.onSelectEnd(intersection.point);
68 | }
69 | });
70 | }
71 |
72 | export function enter(ctx) {
73 | ctx.renderer.setClearColor(0x677FA7);
74 | ctx.scene.add(scene);
75 | ctx.scene.parent.fog = new THREE.FogExp2(0x677FA7, 0.004);
76 | //ctx.cameraRig.position.set(0,0,0);
77 |
78 | ctx.raycontrol.activateState('teleportVertigo');
79 | ctx.raycontrol.activateState('doorVertigo');
80 | }
81 |
82 | export function exit(ctx) {
83 | ctx.scene.remove(scene);
84 | ctx.scene.parent.fog = null;
85 |
86 | ctx.raycontrol.deactivateState('teleportVertigo');
87 | ctx.raycontrol.deactivateState('doorVertigo');
88 | }
89 |
90 | export function execute(ctx, delta, time) {
91 | doorMaterial.uniforms.time.value = time;
92 |
93 | if (door.scale.z > 0.2) {
94 | door.scale.z = Math.max(door.scale.z - delta * door.scale.z, 0.2);
95 | }
96 | }
97 |
98 |
--------------------------------------------------------------------------------
/src/shaders/basic.vert.glsl:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 | varying vec3 vPosition;
3 | void main()
4 | {
5 | vUv = uv;
6 | vPosition = position;
7 | vec4 mvPosition = modelViewMatrix * vec4(position, 1.0);
8 | gl_Position = projectionMatrix * mvPosition;
9 | }
10 |
--------------------------------------------------------------------------------
/src/shaders/beam.frag.glsl:
--------------------------------------------------------------------------------
1 | uniform float time;
2 | uniform sampler2D tex;
3 | varying vec2 vUv;
4 |
5 | void main( void ) {
6 | float t = time;
7 | vec4 col = texture2D(tex, vec2(vUv.x, vUv.y * 3.0 + time * 2.0));
8 | col *= vUv.y;
9 | gl_FragColor = col;
10 | }
11 |
--------------------------------------------------------------------------------
/src/shaders/door.frag.glsl:
--------------------------------------------------------------------------------
1 | uniform float time;
2 | uniform float selected;
3 | uniform sampler2D tex;
4 | varying vec2 vUv;
5 |
6 | void main( void ) {
7 | float t = time;
8 | vec2 uv = vUv * 2.0 - 1.0;
9 | vec2 puv = vec2(length(uv.xy), atan(uv.x, uv.y));
10 | vec4 col = texture2D(tex, vec2(log(puv.x) + t / 5.0, puv.y / 3.1415926 ));
11 | float glow = (1.0 - puv.x) * (0.5 + (sin(t) + 2.0 ) / 4.0);
12 | // blue glow
13 | col += vec4(118.0/255.0, 144.0/255.0, 219.0/255.0, 1.0) * (0.4 + glow * 1.0);
14 | // white glow
15 | col += vec4(0.2) * smoothstep(0.0, 2.0, glow * glow);
16 | gl_FragColor = col;
17 |
18 | }
19 |
--------------------------------------------------------------------------------
/src/shaders/panoball.frag.glsl:
--------------------------------------------------------------------------------
1 | uniform sampler2D tex, texfx;
2 | uniform float time;
3 | uniform float selected;
4 | varying vec2 vUv;
5 | varying vec3 vPosition;
6 | varying vec3 vNormal;
7 | varying vec3 vWorldPos;
8 |
9 |
10 | void main( void ) {
11 | vec2 uv = vUv;
12 | //uv.y = 1.0 - uv.y;
13 |
14 | vec3 eye = normalize(cameraPosition - vWorldPos);
15 | float fresnel = abs(dot(eye, vNormal));
16 | float shift = pow((1.0 - fresnel), 4.0) * 0.05;
17 |
18 | vec3 col = vec3(
19 | texture2D(tex, uv - shift).r,
20 | texture2D(tex, uv).g,
21 | texture2D(tex, uv + shift).b
22 | );
23 |
24 | col = mix(col * 0.7, vec3(1.0), 0.7 - fresnel);
25 |
26 | col += selected * 0.3;
27 |
28 | float t = time * 0.4 + vPosition.x + vPosition.z;
29 | uv = vec2(vUv.x + t * 0.2, vUv.y + t);
30 | vec3 fx = texture2D(texfx, uv).rgb * 0.4;
31 |
32 |
33 | gl_FragColor = vec4(col + fx, 1.0);
34 | }
35 |
--------------------------------------------------------------------------------
/src/shaders/panoball.vert.glsl:
--------------------------------------------------------------------------------
1 | varying vec2 vUv;
2 | varying vec3 vPosition;
3 | varying vec3 vNormal;
4 | varying vec3 vWorldPos;
5 | uniform float time;
6 | uniform float selected;
7 |
8 | mat4 inverse(mat4 m) {
9 | float
10 | a00 = m[0][0], a01 = m[0][1], a02 = m[0][2], a03 = m[0][3],
11 | a10 = m[1][0], a11 = m[1][1], a12 = m[1][2], a13 = m[1][3],
12 | a20 = m[2][0], a21 = m[2][1], a22 = m[2][2], a23 = m[2][3],
13 | a30 = m[3][0], a31 = m[3][1], a32 = m[3][2], a33 = m[3][3],
14 |
15 | b00 = a00 * a11 - a01 * a10,
16 | b01 = a00 * a12 - a02 * a10,
17 | b02 = a00 * a13 - a03 * a10,
18 | b03 = a01 * a12 - a02 * a11,
19 | b04 = a01 * a13 - a03 * a11,
20 | b05 = a02 * a13 - a03 * a12,
21 | b06 = a20 * a31 - a21 * a30,
22 | b07 = a20 * a32 - a22 * a30,
23 | b08 = a20 * a33 - a23 * a30,
24 | b09 = a21 * a32 - a22 * a31,
25 | b10 = a21 * a33 - a23 * a31,
26 | b11 = a22 * a33 - a23 * a32,
27 |
28 | det = b00 * b11 - b01 * b10 + b02 * b09 + b03 * b08 - b04 * b07 + b05 * b06;
29 |
30 | return mat4(
31 | a11 * b11 - a12 * b10 + a13 * b09,
32 | a02 * b10 - a01 * b11 - a03 * b09,
33 | a31 * b05 - a32 * b04 + a33 * b03,
34 | a22 * b04 - a21 * b05 - a23 * b03,
35 | a12 * b08 - a10 * b11 - a13 * b07,
36 | a00 * b11 - a02 * b08 + a03 * b07,
37 | a32 * b02 - a30 * b05 - a33 * b01,
38 | a20 * b05 - a22 * b02 + a23 * b01,
39 | a10 * b10 - a11 * b08 + a13 * b06,
40 | a01 * b08 - a00 * b10 - a03 * b06,
41 | a30 * b04 - a31 * b02 + a33 * b00,
42 | a21 * b02 - a20 * b04 - a23 * b00,
43 | a11 * b07 - a10 * b09 - a12 * b06,
44 | a00 * b09 - a01 * b07 + a02 * b06,
45 | a31 * b01 - a30 * b03 - a32 * b00,
46 | a20 * b03 - a21 * b01 + a22 * b00) / det;
47 | }
48 |
49 |
50 | mat4 transpose(in mat4 m) {
51 | vec4 i0 = m[0];
52 | vec4 i1 = m[1];
53 | vec4 i2 = m[2];
54 | vec4 i3 = m[3];
55 |
56 | return mat4(
57 | vec4(i0.x, i1.x, i2.x, i3.x),
58 | vec4(i0.y, i1.y, i2.y, i3.y),
59 | vec4(i0.z, i1.z, i2.z, i3.z),
60 | vec4(i0.w, i1.w, i2.w, i3.w)
61 | );
62 | }
63 |
64 | void main()
65 | {
66 | vUv = uv;
67 |
68 | vPosition = position;
69 |
70 | vec3 offset = vec3(
71 | sin(position.x * 50.0 + time),
72 | sin(position.y * 10.0 + time * 2.0),
73 | cos(position.z * 40.0 + time)
74 | ) * 0.003;
75 |
76 | vPosition *= 1.0 + selected * 0.2;
77 |
78 | vNormal = normalize(inverse(transpose(modelMatrix)) * vec4(normalize(normal), 1.0)).xyz;
79 | vWorldPos = (modelMatrix * vec4(vPosition, 1.0)).xyz;
80 |
81 | vec4 mvPosition = modelViewMatrix * vec4(vPosition + offset, 1.0);
82 | gl_Position = projectionMatrix * mvPosition;
83 | }
84 |
--------------------------------------------------------------------------------
/src/shaders/zoom.frag.glsl:
--------------------------------------------------------------------------------
1 | uniform float time;
2 | uniform sampler2D tex;
3 | uniform vec2 zoomPos;
4 | uniform float zoomAmount;
5 | uniform float zoomRatio;
6 | varying vec2 vUv;
7 |
8 | void main( void ) {
9 | float t = time;
10 | vec2 uv = vec2(vUv.x - 0.5, (1.0 - vUv.y) - 0.5);
11 | vec2 texUv = uv * vec2(zoomRatio, 1.0);
12 | vec4 col = texture2D(tex, zoomPos + texUv * zoomAmount);
13 | float dist = length(uv) * 2.0;
14 | col.a = smoothstep(0.0, 0.1, 1.0 - dist);
15 | float aura = smoothstep(0.80, 1.0, dist);
16 | col.rgb += aura * 0.3;
17 | gl_FragColor = col;
18 | }
19 |
--------------------------------------------------------------------------------
/src/stations/Graffiti.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import ColorWheel from '../lib/ColorWheel';
3 | import {Area, AreaReactor, AreaChecker, Object3D, BoundingBox, ParentObject3D, DebugHelper} from '../components/index';
4 | import {angleBetween, getRandomInt} from '../lib/utils.js';
5 |
6 | var colorWheel;
7 |
8 | export function enter(ctx) {
9 | //ctx.raycontrol.activateState('graffiti');
10 | if (colorWheel) {
11 | colorWheel.enter();
12 | }
13 | }
14 |
15 | var material, wall, drawContext;
16 | var lastPosition = new THREE.Vector2();
17 | var brushImg, canvasTmp, ctxTmp;
18 |
19 | var lastController;
20 | var paintImg = new Image();
21 |
22 | function colorize(r, g, b) {
23 | ctxTmp.clearRect(0, 0, canvasTmp.width, canvasTmp.height);
24 | ctxTmp.drawImage(brushImg, 0, 0);
25 | let imgData = ctxTmp.getImageData(0, 0, canvasTmp.width, canvasTmp.height);
26 | for (var t = 0; t < imgData.data.length; t += 4) {
27 | imgData.data[t]= r * imgData.data[t] / 255;
28 | imgData.data[t + 1]= g * imgData.data[t + 1] / 255;
29 | imgData.data[t + 2]= b * imgData.data[t + 2] / 255;
30 | }
31 | ctxTmp.putImageData(imgData,0,0);
32 | paintImg.src = canvasTmp.toDataURL();
33 | }
34 |
35 | export function setup(ctx, hall) {
36 |
37 | let area = ctx.world.createEntity();
38 | area.name = 'area';
39 | area
40 | .addComponent(BoundingBox)
41 | //.addComponent(DebugHelper)
42 | .addComponent(ParentObject3D, {value: hall})
43 | .addComponent(Area);
44 |
45 | let component = area.getMutableComponent(BoundingBox);
46 | component.min.set(-5, 0, 4.4);
47 | component.max.set(3, 3, 7);
48 |
49 | let checker = ctx.world.createEntity();
50 | let checkerSpray = ctx.world.createEntity();
51 | let spray = ctx.assets['spray_model'].scene;
52 |
53 | function attachSprayCan(controllerData) {
54 | let controller = controllerData.controller;
55 | const handedness = controllerData.inputSource.handedness;
56 | let listener = new THREE.AudioListener();
57 |
58 | const sound = new THREE.PositionalAudio(listener);
59 | sound.loop = true;
60 | const audioLoader = new THREE.AudioLoader();
61 | audioLoader.load('assets/ogg/spray.ogg', buffer => {
62 | sound.setBuffer(buffer);
63 | sound.name = 'spraySound';
64 | controller.add(sound);
65 | });
66 |
67 | spray.getObjectByName('spraycan').geometry.rotateY(handedness === "right" ? Math.PI / 2 : -Math.PI / 2);
68 | spray.name = 'spray';
69 | spray.visible = false;
70 | const sprayTex = ctx.assets['spray_tex'];
71 | spray.getObjectByName('spraycan').material = new THREE.MeshPhongMaterial({map: sprayTex});
72 | spray.getObjectByName('spraycolor').material = new THREE.MeshLambertMaterial({color: 0xFF0000});
73 | controller.add(spray);
74 | }
75 |
76 | function attachColorWheel(controllerData) {
77 | let controller = controllerData.controller;
78 | colorWheel = new ColorWheel(ctx, controller, (rgb) => {
79 | colorize(
80 | rgb.r,
81 | rgb.g,
82 | rgb.b);
83 | spray.getObjectByName('spraycolor').material.color.setRGB(rgb.r / 255, rgb.g / 255, rgb.b / 255);
84 | });
85 |
86 | colorWheel.enter();
87 | }
88 |
89 | ctx.raycontrol.addEventListener('controllerConnected', controllerData => {
90 | if (ctx.raycontrol.matchController(controllerData, "primary")) {
91 | attachSprayCan(controllerData);
92 | checkerSpray
93 | .addComponent(AreaChecker)
94 | .addComponent(Object3D, {value: controllerData.controller})
95 | .addComponent(AreaReactor, {
96 | onEntering: entity => {
97 | const obj3D = entity.getComponent(Object3D).value;
98 | obj3D.getObjectByName('Scene').visible = false;
99 | obj3D.getObjectByName('spray').visible = true;
100 | let raycasterContext = obj3D.getObjectByName('raycasterContext');
101 | raycasterContext.rotation.set(-Math.PI / 2, (controllerData.inputSource.handedness === "right" ? 1 : -1) * Math.PI / 2, 0);
102 | raycasterContext.position.set(0,-0.015,-0.025)
103 | ctx.raycontrol.setLineStyle('basic');
104 | ctx.raycontrol.activateState('graffiti');
105 | },
106 | onExiting: entity => {
107 | const obj3D = entity.getComponent(Object3D).value;
108 | obj3D.getObjectByName('Scene').visible = true;
109 | obj3D.getObjectByName('spray').visible = false;
110 | let raycasterContext = obj3D.getObjectByName('raycasterContext');
111 | raycasterContext.rotation.set(0,0,0);
112 | raycasterContext.position.set(0,0,0);
113 | ctx.raycontrol.setLineStyle('advanced');
114 | ctx.raycontrol.deactivateState('graffiti');
115 | }
116 | });
117 | } else {
118 | attachColorWheel(controllerData);
119 | checker
120 | .addComponent(AreaChecker)
121 | .addComponent(Object3D, {value: controllerData.controller})
122 | .addComponent(AreaReactor, {
123 | onEntering: entity => {
124 | const obj3D = entity.getComponent(Object3D).value;
125 | obj3D.getObjectByName('Scene').visible = false;
126 | obj3D.getObjectByName('ColorWheel').visible = true;
127 | },
128 | onExiting: entity => {
129 | const obj3D = entity.getComponent(Object3D).value;
130 | obj3D.getObjectByName('Scene').visible = true;
131 | obj3D.getObjectByName('ColorWheel').visible = false;
132 | }
133 | });
134 | }
135 | });
136 |
137 | let width = 2048;
138 | let height = 1024;
139 | let maxDistance = 1;
140 |
141 | brushImg = new Image();
142 | canvasTmp = document.createElement('canvas');
143 | //canvasTmp.style.position = "absolute";
144 | //canvasTmp.style.width = "20%";
145 | //canvasTmp.style.backgroundColor = "#333";
146 | ctxTmp = canvasTmp.getContext('2d');
147 | // document.body.appendChild(canvasTmp);
148 |
149 | brushImg.onload = () => {
150 | canvasTmp.width = brushImg.width;
151 | canvasTmp.height = brushImg.height;
152 | colorize(0,0,0);
153 | }
154 | brushImg.src = 'assets/spray_brush.png';
155 |
156 | var drawingCanvas = document.createElement('canvas');
157 |
158 | drawingCanvas.width = width;
159 | drawingCanvas.height = height;
160 | drawContext = drawingCanvas.getContext('2d');
161 | drawContext.clearRect(0, 0, width, height);
162 | drawContext.fillStyle = '#fff';
163 | drawContext.fillRect(0, 0, width, height);
164 |
165 | let map = new THREE.CanvasTexture( drawingCanvas );
166 |
167 | material = new THREE.MeshBasicMaterial({
168 | color: 0xffffff,
169 | lightMap: ctx.assets['lightmap_tex'],
170 | map: map
171 | });
172 |
173 | wall = hall.getObjectByName('graffiti');
174 | wall.material = material;
175 |
176 | var aux2 = new THREE.Vector2();
177 |
178 | ctx.raycontrol.addState('graffiti', {
179 | colliderMesh: wall,
180 | lineStyleOnIntersection: 'basic',
181 | onHover: (intersection, active, controller) => {
182 | if (active)
183 | {
184 | var distance = intersection.distance;
185 |
186 | if (distance > maxDistance) { return; }
187 |
188 | let x = intersection.uv.x * width;
189 | let y = height - intersection.uv.y * height;
190 |
191 | aux2.set(x, y);
192 | drawContext.imageSmoothingEnabled = true;
193 | drawContext.fillStyle = '#f00';
194 | drawContext.strokeStyle = '#0f0';
195 | var dist = lastPosition.distanceTo(aux2);
196 | var angle = angleBetween(lastPosition, aux2);
197 | let alpha = THREE.Math.clamp(1 - distance, 0, 1);
198 |
199 | drawContext.globalAlpha = alpha;
200 |
201 | for (var i = 0; i < dist; i ++ /* +=4 */) {
202 | var _x = lastPosition.x + (Math.sin(angle) * i);
203 | var _y = lastPosition.y + (Math.cos(angle) * i);
204 | drawContext.save();
205 | drawContext.translate(_x, _y);
206 | let r = THREE.Math.lerp(0.001, 0.2, distance);
207 | drawContext.scale(r, r);
208 |
209 | drawContext.rotate(Math.PI * 180 / getRandomInt(0, 180));
210 |
211 | drawContext.drawImage(paintImg, -brushImg.width / 2, -brushImg.height / 2);
212 | drawContext.restore();
213 | }
214 |
215 | lastPosition.set(x, y);
216 |
217 | material.map.needsUpdate = true;
218 | }
219 | },
220 | onHoverLeave: (intersection) => {},
221 | onSelectStart: (intersection, controller) => {
222 | var distance = intersection.distance;
223 |
224 | if (distance > maxDistance) { return; }
225 |
226 | lastController = controller;
227 | controller.getObjectByName('spraySound').play();
228 |
229 | let x = intersection.uv.x * width;
230 | let y = height - intersection.uv.y * height;
231 |
232 | lastPosition.set(x, y);
233 | },
234 | onSelectEnd: (intersection) => {
235 | if (!lastController) { return; }
236 | lastController.getObjectByName('spraySound').stop();
237 | }
238 | });
239 | }
240 |
241 | export function execute(ctx, delta, time) {}
242 |
--------------------------------------------------------------------------------
/src/stations/InfoPanels.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import { Children, Object3D, Billboard, Text, Position, ParentObject3D } from '../components/index.js';
3 | import INFO_DATA from './InfoPanelsData.js';
4 |
5 | var panels = [], panelTexts = [], panelsEntity = [];
6 |
7 | export function setup(ctx, hall) {
8 | for (var i = 0; i < INFO_DATA.length; i++) {
9 | const id = i < 10 ? '0' + i : i;
10 | panels[i] = hall.getObjectByName('infopanel0'+id);
11 | panels[i].geometry.computeBoundingBox();
12 | const panelWidth = panels[i].geometry.boundingBox.max.x - panels[i].geometry.boundingBox.min.x;
13 | const panelHeight = panels[i].geometry.boundingBox.max.y - panels[i].geometry.boundingBox.min.y;
14 | const offsety = INFO_DATA[i].offsety || 0;
15 | panels[i].material = new THREE.MeshBasicMaterial({color: 0x040404, transparent: true});
16 |
17 | panelTexts[i] = ctx.world.createEntity();
18 | panelTexts[i]
19 | .addComponent(Text, {
20 | color: '#ffffff', //0xdaa056,
21 | fontSize: 0.05,
22 | anchor: 'left',
23 | textAlign: 'left',
24 | baseline: 'top',
25 | maxWidth: panelWidth * 0.8,
26 | lineHeight: 1.3,
27 | text: INFO_DATA[i].title + '\n \n' + INFO_DATA[i].description,
28 | })
29 | .addComponent(ParentObject3D, {value: panels[i]})
30 | .addComponent(Position, {x: -panelWidth / 2 * 0.82, y: panelHeight / 2 * 0.65 + offsety, z: 0.01})
31 |
32 | panelsEntity[i] = ctx.world.createEntity();
33 | panelsEntity[i]
34 | .addComponent(Object3D, {value: panels[i]})
35 | .addComponent(Billboard, {camera3D: ctx.camera})
36 | .addComponent(Children, {value: [panelTexts[i]]});
37 |
38 | }
39 | }
40 |
41 | export function enter(ctx) {}
42 | export function execute(ctx, delta, time) {}
43 |
--------------------------------------------------------------------------------
/src/stations/InfoPanelsData.js:
--------------------------------------------------------------------------------
1 | export default [
2 | {
3 | title: '360 Panoramas',
4 | description: 'Photographs wrapped around spheres provide an environment, but without stereo effect nor depth.',
5 | offsety: 0.04
6 | },
7 | {
8 | title: 'Xylophone Toy',
9 | description: 'Example of object grabbing and simple interaction.',
10 | offsety: 0.04
11 | },
12 | {
13 | title: '360 Stereo Panorama',
14 | description: 'By using one photo for each eye, panoramas can have some depth and stereo effect.',
15 | offsety: 0.03
16 | },
17 | {
18 | title: 'Photogrammetry Room',
19 | description: 'Example of an object created out of photographs using an automated software.',
20 | offsety: 0.04
21 | },
22 | {
23 | title: 'Twitter Feed',
24 | description: 'Bringing external realtime data to an XR environment.',
25 | offsety: 0.02
26 | },
27 | {
28 | title: 'Vertigo Room',
29 | description: 'An example of how this new medium can play with your perception and senses.',
30 | offsety: 0.04
31 | },
32 | {
33 | title: 'Hello WebXR!',
34 | description: 'A small compendium of interactions and little experiences introducing and celebrating the final specification of the WebXR API.\n \nMade by the Mozilla Mixed Reality team\nmixedreality.mozilla.org',
35 | offsety: 0.06
36 | },
37 | {
38 | title: 'The Pink Robe. After the Bath',
39 | description: 'Joaquín Sorolla, 1916\n208 x 126.5 cm. Oil on canvas.',
40 | },
41 | {
42 | title: 'The Garden of Earthly Delights',
43 | description: 'Hieronymus Bosch, 1490 - 1510\n205.5 x 384.9 cm. Oil on oak panels.',
44 | },
45 | {
46 | title: 'Self-Portrait',
47 | description: 'Rembrandt van Rijn, 1659\n84.5 x 66 cm. Oil on canvas.',
48 | },
49 | {
50 | title: 'The Dance Lesson',
51 | description: 'Edgar Degas, 1879\n38 x 88 cm. Oil on canvas.',
52 | },
53 | {
54 | title: 'Gray Weather, Grande Jatte',
55 | description: 'Georges Seurat, 1888\n71 x 66 cm. Oil on canvas.',
56 | offsety: 0.04
57 | },
58 | {
59 | title: 'Sound Room',
60 | description: 'Showcase of positional audio, very useful on XR experiences.\nTry to find where the sounds come from!.',
61 | offsety: 0.04
62 | },
63 | {
64 | title: 'Graffiti Wall',
65 | description: 'Get close and spray the wall!.\nLet\'s collaborate on a full masterpiece.',
66 | offsety: 0.04
67 | },
68 | {
69 | title: 'Paintings',
70 | description: 'Real-scale paintings that can be inspected very closely using the controller.',
71 | offsety: 0.04
72 | },
73 | {
74 | title: 'WELCOME!',
75 | description: 'Aim your controller and press the trigger to teleport around and interact with objects.\n\nWe currently have 3 rooms and 5 experiences to explore. Try them all!',
76 | offsety: 0.06
77 | }
78 | ];
79 |
--------------------------------------------------------------------------------
/src/stations/NewsTicker.js:
--------------------------------------------------------------------------------
1 | //import {Text} from '../lib/text.mjs';
2 | import * as THREE from 'three';
3 | import { Text, Rotation, Position, ParentObject3D } from '../components/index.js';
4 |
5 | var newsTicker = {
6 | url: 'assets/tweets.json',
7 | hashtag: '#helloWebXR',
8 | hashtagText: null,
9 | authorText: null,
10 | messageText: null,
11 | news: [],
12 | current: 0
13 | };
14 |
15 | var screenMaterial;
16 |
17 | export function setup(ctx, hall) {
18 | const newsTickerMesh = hall.getObjectByName('newsticker');
19 |
20 | ctx.world.createEntity();
21 |
22 | screenMaterial = hall.getObjectByName('screen').material;
23 |
24 | newsTicker.hashtagText = ctx.world.createEntity();
25 | newsTicker.hashtagText.addComponent(Text, {
26 | color: '#f6cdde', //0xdaa056,
27 | fontSize: 0.1,
28 | anchor: 'right',
29 | textAlign: 'right'
30 | }).addComponent(ParentObject3D, {value: newsTickerMesh});
31 |
32 | newsTicker.authorText = ctx.world.createEntity();
33 | newsTicker.authorText.addComponent(Text, {
34 | color: '#7f0c38', //0x67bccd,
35 | fontSize: 0.1,
36 | anchor: 'left',
37 | }).addComponent(ParentObject3D, {value: newsTickerMesh});
38 |
39 | newsTicker.messageText = ctx.world.createEntity();
40 | newsTicker.messageText.addComponent(Text, {
41 | color: 0x000000,
42 | fontSize: 0.13,
43 | maxWidth: 2.3,
44 | lineHeight: 1,
45 | textAlign: 'left',
46 | baseline: 'top',
47 | anchor: 'left'
48 | }).addComponent(ParentObject3D, {value: newsTickerMesh});
49 |
50 | ['hashtag', 'author', 'message'].forEach( i => {
51 |
52 | newsTicker[`${i}Text`].addComponent(Position, hall.getObjectByName(i).position);
53 | newsTicker[`${i}Text`].addComponent(Rotation, {x: 0, y: Math.PI, z: 0});
54 | });
55 | newsTicker.hashtagText.getMutableComponent(Text).text = newsTicker.hashtag;
56 |
57 | fetch(newsTicker.url).then(res => res.json()).then(res => {
58 | newsTicker.news = res;
59 | nextNews();
60 | });
61 | }
62 |
63 | function nextNews() {
64 | const n = newsTicker;
65 | n.authorText.getMutableComponent(Text).text = n.news[n.current].author;
66 | n.messageText.getMutableComponent(Text).text = n.news[n.current].message;
67 | n.current = (n.current + 1) % n.news.length;
68 | setTimeout(nextNews, 3000);
69 | }
70 |
71 | export function execute(ctx, delta, time) {
72 | var v = 0.98 + Math.sin(time * 40) * 0.02;
73 | screenMaterial.color.setRGB(v, v, v);
74 | }
75 |
--------------------------------------------------------------------------------
/src/stations/Paintings.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | var paintings;
4 | var zoom = {object: null, widget: null, controller: null, animation: 0, icon: null};
5 | const PAINTINGS = ['seurat', 'sorolla', 'bosch', 'degas', 'rembrandt'];
6 | const RATIOS = [1, 1, 0.5, 0.5, 1];
7 | const ZOOMS = [0.4, 0.2, 0.2, 0.4, 0.25];
8 |
9 |
10 | export function enter(ctx) {
11 | ctx.raycontrol.activateState('paintings');
12 | }
13 |
14 | export function setup(ctx, hall) {
15 | for (let i in PAINTINGS) {
16 | let painting = PAINTINGS[i];
17 | let mesh = hall.getObjectByName(painting);
18 | if (!mesh) { continue; }
19 |
20 | let paintingTexture = ctx.assets[`painting_${painting}_tex`];
21 | mesh.material = new THREE.MeshBasicMaterial({
22 | map: paintingTexture
23 | });
24 | mesh.userData.paintingId = i;
25 | }
26 |
27 | paintings = hall.getObjectByName('paintings');
28 |
29 | zoom.widget = new THREE.Mesh(
30 | new THREE.PlaneGeometry(),
31 | new THREE.ShaderMaterial({
32 | uniforms: {
33 | time: {value: 0},
34 | tex: {value: null},
35 | zoomPos: {value: new THREE.Vector2()},
36 | zoomAmount: {value: 0},
37 | zoomRatio: {value: 1}
38 | },
39 | vertexShader: ctx.shaders.basic_vert,
40 | fragmentShader: ctx.shaders.zoom_frag,
41 | transparent: true,
42 | depthTest: false,
43 | depthWrite: false
44 | })
45 | );
46 | zoom.widget.geometry.rotateY(-Math.PI / 2);
47 | zoom.widget.visible = false;
48 |
49 | zoom.icon = new THREE.Mesh(
50 | new THREE.PlaneGeometry(0.2, 0.2),
51 | new THREE.MeshBasicMaterial({
52 | map: ctx.assets['zoomicon_tex'],
53 | transparent: true
54 | //side: THREE.DoubleSide
55 | })
56 | );
57 | zoom.icon.geometry.rotateY(-Math.PI / 2);
58 | zoom.icon.visible = false;
59 |
60 | ctx.scene.add(zoom.icon);
61 | ctx.scene.add(zoom.widget);
62 |
63 | ctx.raycontrol.addState('paintings', {
64 | colliderMesh: hall.getObjectByName('paintings'),
65 | onHover: (intersection, active, controller) => {
66 | if (intersection.distance > 3) { return; }
67 | if (active) {
68 | zoom.painting = intersection.object;
69 | zoom.controller = controller;
70 | zoom.widget.material.uniforms.tex.value = zoom.painting.material.map;
71 | zoom.widget.material.uniforms.zoomRatio.value = RATIOS[intersection.object.userData.paintingId];
72 | zoom.widget.visible = true;
73 | refreshZoomUV(intersection);
74 | } else {
75 | zoom.icon.visible = true;
76 | zoom.icon.position.copy(intersection.point);
77 | zoom.icon.position.x -= 0.01;
78 | }
79 | },
80 | onHoverLeave: (intersection) => {
81 | zoom.painting = null;
82 | zoom.animation = 0;
83 | zoom.widget.visible = false;
84 | zoom.icon.visible = false;
85 | },
86 | onSelectStart: (intersection, controller) => {
87 | if (intersection.distance < 3) {
88 | zoom.painting = intersection.object;
89 | zoom.controller = controller;
90 | zoom.widget.material.uniforms.tex.value = zoom.painting.material.map;
91 | zoom.widget.material.uniforms.zoomRatio.value = RATIOS[intersection.object.userData.paintingId];
92 | zoom.widget.visible = true;
93 | zoom.icon.visible = false;
94 | refreshZoomUV(intersection);
95 | }
96 | },
97 | onSelectEnd: (intersection) => {
98 | zoom.painting = null;
99 | zoom.animation = 0;
100 | zoom.widget.visible = false;
101 | }
102 | });
103 | }
104 |
105 | export function execute(ctx, delta, time) {
106 | if (zoom.painting) {
107 | if (zoom.animation < 1) {
108 | zoom.animation += (1 - zoom.animation) * delta * 4.0;
109 | }
110 | zoom.widget.material.uniforms.time.value = time;
111 | }
112 | }
113 |
114 | var minUV = new THREE.Vector2();
115 | var maxUV = new THREE.Vector2();
116 |
117 | function refreshZoomUV(hit) {
118 | zoom.widget.position.copy(hit.point);
119 | zoom.widget.position.x -= 0.5 * zoom.animation;
120 |
121 | const uvs = zoom.widget.geometry.faceVertexUvs[0];
122 | zoom.widget.material.uniforms.zoomPos.value.copy(hit.uv);
123 | zoom.widget.material.uniforms.zoomAmount.value = ZOOMS[hit.object.userData.paintingId];
124 | }
125 |
--------------------------------------------------------------------------------
/src/stations/PanoBalls.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 |
3 | var
4 | panoBalls = [],
5 | panoballsParent = new THREE.Object3D(),
6 | bbox = new THREE.Box3(),
7 | panoFxMaterial,
8 | auxVec = new THREE.Vector3(),
9 | hallRef = null;
10 |
11 | const NUM_PANOBALLS = 6;
12 |
13 | export function enter(ctx) {
14 | ctx.raycontrol.activateState('panoballs');
15 | }
16 |
17 | export function setup(ctx, hall) {
18 | const assets = ctx.assets;
19 | hallRef = hall;
20 |
21 | const panoGeo = new THREE.SphereBufferGeometry(0.15, 30, 20);
22 |
23 | for (var i = 0; i < NUM_PANOBALLS; i++) {
24 | let asset = assets[`pano${i + 1}small`];
25 | var ball = new THREE.Mesh(
26 | new THREE.SphereBufferGeometry(0.15, 30, 20),
27 | new THREE.ShaderMaterial({
28 | uniforms: {
29 | time: {value: 0},
30 | tex: {value: asset},
31 | texfx: {value: assets['panoballfx_tex']},
32 | selected: {value: 0}
33 | },
34 | vertexShader: ctx.shaders.panoball_vert,
35 | fragmentShader: ctx.shaders.panoball_frag,
36 | side: THREE.BackSide,
37 | })
38 | );
39 | ball.rotation.set(Math.PI, 0, 0);
40 | ball.position.copy(hall.getObjectByName(`panoball${i + 1}`).position);
41 | ball.userData.floatY = ball.position.y;
42 | ball.userData.panoId = 4 + i;
43 | ball.userData.selected = 0;
44 |
45 | panoBalls.push(ball);
46 | panoballsParent.add(ball);
47 | }
48 |
49 | hall.add(panoballsParent);
50 |
51 | ctx.raycontrol.addState('panoballs', {
52 | colliderMesh: panoballsParent,
53 | onHover: (intersection, active, controller) => {
54 | panoBalls.forEach(panoBall => panoBall.userData.selected = 0);
55 | intersection.object.userData.selected = 1;
56 | },
57 | onHoverLeave: (intersection) => {
58 | intersection.object.userData.selected = 0;
59 | },
60 | onSelectStart: (intersection, controller) => {
61 | },
62 | onSelectEnd: (intersection) => {
63 | ctx.goto = intersection.object.userData.panoId;
64 | intersection.object.userData.selected = 0;
65 | }
66 | });
67 | }
68 |
69 | export function execute(ctx, delta, time) {
70 | for (let i = 0; i < panoBalls.length; i++) {
71 | const ball = panoBalls[i];
72 | ball.position.y = ball.userData.floatY + Math.cos(i + time * 3) * 0.02;
73 | }
74 | }
75 |
76 | export function updateUniforms(time) {
77 | for (let i = 0; i < panoBalls.length; i++) {
78 | panoBalls[i].material.uniforms.time.value = i + time;
79 | panoBalls[i].material.uniforms.selected.value +=
80 | (panoBalls[i].userData.selected - panoBalls[i].material.uniforms.selected.value) * 0.1;
81 | }
82 | }
83 |
--------------------------------------------------------------------------------
/src/stations/Xylophone.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import PositionalAudioPolyphonic from '../lib/PositionalAudioPolyphonic.js';
3 |
4 | var
5 | listener,
6 | xyloSticks = [null, null],
7 | xyloStickBalls = [null, null],
8 | xyloNotes = new Array(13),
9 | bbox = new THREE.Box3(),
10 | hallRef = null;
11 |
12 | var auxVec = new THREE.Vector3();
13 |
14 | var NUM_NOTES = 13;
15 |
16 | var stickNotesColliding = [
17 | new Array(NUM_NOTES).fill(false),
18 | new Array(NUM_NOTES).fill(false)
19 | ];
20 |
21 | var _ctx = null;
22 |
23 | export function setup(ctx, hall) {
24 | _ctx = ctx;
25 | const audioLoader = new THREE.AudioLoader();
26 | listener = new THREE.AudioListener();
27 | hallRef = hall;
28 |
29 | for (let i = 0; i < NUM_NOTES; i++) {
30 | let noteName = 'xnote0' + (i < 10 ? '0' + i : i);
31 | let note = hall.getObjectByName(noteName);
32 | note.geometry.computeBoundingBox();
33 | note.geometry.boundingBox.translate(note.position).translate(note.parent.position);
34 | note.material = new THREE.MeshLambertMaterial();
35 | note.material.color.setHSL(i / 13, 0.9, 0.2);
36 | note.material.emissive = note.material.color.clone();
37 | note.material.emissiveIntensity = 0;
38 | xyloNotes[i] = note;
39 | note.userData.animation = 0;
40 | note.userData.resetY = note.position.y;
41 | note.userData.sound = new PositionalAudioPolyphonic(listener, 10);
42 | audioLoader.load('assets/ogg/xylophone' + (i + 1) + '.ogg', buffer => {
43 | note.userData.sound.setBuffer(buffer);
44 | });
45 | }
46 |
47 | xyloSticks[0] = hall.getObjectByName('xylostick-left');
48 | xyloSticks[1] = hall.getObjectByName('xylostick-right');
49 | xyloSticks[0].userData.resetPosition = xyloSticks[0].position.clone();
50 | xyloSticks[1].userData.resetPosition = xyloSticks[1].position.clone();
51 | xyloSticks[0].userData.resetRotation = xyloSticks[0].rotation.clone();
52 | xyloSticks[1].userData.resetRotation = xyloSticks[1].rotation.clone();
53 | xyloSticks[0].userData.grabbedBy = null;
54 | xyloSticks[1].userData.grabbedBy = null;
55 | xyloSticks[0].userData.animation = 0;
56 | xyloSticks[1].userData.animation = 0;
57 | xyloStickBalls[0] = hall.getObjectByName('xylostickball-left');
58 | xyloStickBalls[1] = hall.getObjectByName('xylostickball-right');
59 | xyloStickBalls[0].geometry.computeBoundingBox();
60 | xyloStickBalls[1].geometry.computeBoundingBox();
61 | }
62 |
63 | export function enter(ctx) {
64 | ctx.camera.add(listener);
65 |
66 | let selectStart = onSelectStart.bind(this);
67 | let selectEnd = onSelectEnd.bind(this);
68 |
69 | ctx.controllers[0].addEventListener('selectstart', selectStart);
70 | ctx.controllers[1].addEventListener('selectstart', selectStart);
71 | ctx.controllers[0].addEventListener('selectend', selectEnd);
72 | ctx.controllers[1].addEventListener('selectend', selectEnd);
73 | }
74 |
75 | export function exit(ctx) {
76 | ctx.camera.remove(listener);
77 |
78 | let selectStart = onSelectStart.bind(this);
79 | let selectEnd = onSelectEnd.bind(this);
80 |
81 | ctx.controllers[0].removeEventListener('selectstart', selectStart);
82 | ctx.controllers[1].removeEventListener('selectstart', selectStart);
83 | ctx.controllers[0].removeEventListener('selectend', selectEnd);
84 | ctx.controllers[1].removeEventListener('selectend', selectEnd);
85 | }
86 |
87 | function hitTest(obj1, obj2) {
88 | bbox.setFromObject(obj2);
89 | if (obj1.boundingBox.intersectsBox(bbox)){
90 | return true;
91 | }
92 | return false;
93 | }
94 |
95 | function setStickColor(stick, color) {
96 | xyloStickBalls[stick].material.color.set(color);
97 | }
98 |
99 | export function execute(ctx, delta, time) {
100 | let controllers = ctx.controllers;
101 |
102 | if (!controllers) {return;}
103 |
104 | for (let c = 0; c < 2; c++) {
105 |
106 | if (controllers[c].userData.grabbing === null) {
107 |
108 | let stick0 = hitTest(controllers[0], xyloSticks[0]);
109 | let stick1 = hitTest(controllers[0], xyloSticks[1]);
110 |
111 | if (stick0 || stick1){
112 | ctx.raycontrol.disable();
113 | } else {
114 | ctx.raycontrol.enable();
115 | }
116 |
117 | if (!xyloSticks[0].userData.grabbedBy) {
118 | if (!stick0) stick0 = hitTest(controllers[1], xyloSticks[0]);
119 | setStickColor(0, stick0 ? 0xffffff : 0xaaaaaa);
120 | }
121 | if (!xyloSticks[1].userData.grabbedBy) {
122 | if (!stick1) stick1 = hitTest(controllers[1], xyloSticks[1]);
123 | setStickColor(1, stick1 ? 0xffffff : 0xaaaaaa);
124 | }
125 | } else {
126 | // controller grabbing stick
127 | let stick = controllers[c].userData.grabbing.children[0];
128 | bbox.setFromObject(stick).expandByScalar(-0.01);
129 | for (let i = 0; i < xyloNotes.length; i++) {
130 | let note = xyloNotes[i];
131 | if (note.userData.animation > 0) {
132 | note.userData.animation = Math.max(0, note.userData.animation - delta * 4);
133 | note.material.emissiveIntensity = note.userData.animation;
134 | note.position.y = note.userData.resetY - note.userData.animation * 0.005;
135 | }
136 |
137 | if (bbox.intersectsBox(note.geometry.boundingBox)) {
138 | if (!stickNotesColliding[c][i]) {
139 | stickNotesColliding[c][i] = true;
140 | note.userData.sound.play();
141 | note.userData.animation = 1;
142 | setStickColor(c, 0xffffff);
143 | }
144 | } else {
145 | if (stickNotesColliding[c][i]){
146 | stickNotesColliding[c][i] = false;
147 | setStickColor(c, 0xaaaaaa);
148 | }
149 | }
150 | }
151 | }
152 | if (xyloSticks[c].userData.animation > 0){
153 | auxVec.copy(xyloSticks[c].userData.resetPosition).sub(xyloSticks[c].position);
154 | auxVec.multiplyScalar(0.1);
155 | xyloSticks[c].position.add(auxVec);
156 | if (auxVec.length < 0.01) { xyloSticks[c].userData.animation = 0; }
157 | }
158 | }
159 | }
160 |
161 | export function onSelectStart(evt) {
162 | let controller = evt.target;
163 | if (controller.userData.grabbing !== null){ return; }
164 |
165 | // hand grabs stick
166 | for (let i = 0; i < 2; i++) {
167 | bbox.setFromObject(xyloSticks[i]);
168 | if (controller.boundingBox.intersectsBox(bbox)){
169 |
170 | setStickColor(i, 0xaaaaaa);
171 |
172 | _ctx.raycontrol.disable();
173 |
174 | // stick grabbed from the other hand
175 | if (xyloSticks[i].userData.grabbedBy) {
176 | xyloSticks[i].userData.grabbedBy.userData.grabbing = null;
177 | }
178 | xyloSticks[i].position.set(0, 0, 0);
179 | xyloSticks[i].rotation.set(0, 0, 0);
180 | controller.add(xyloSticks[i]);
181 | xyloSticks[i].userData.animation = 0;
182 | controller.userData.grabbing = xyloSticks[i];
183 | xyloSticks[i].userData.grabbedBy = controller;
184 | return false;
185 | }
186 | }
187 | return true;
188 | }
189 |
190 | export function onSelectEnd(evt) {
191 | _ctx.raycontrol.enable();
192 |
193 | let controller = evt.target;
194 | if (controller.userData.grabbing !== null) {
195 | let stick = controller.userData.grabbing;
196 | stick.getWorldPosition(auxVec);
197 | hallRef.add(stick);
198 | stick.position.copy(auxVec);
199 | stick.rotation.copy(stick.userData.resetRotation);
200 | stick.userData.grabbedBy = null;
201 | stick.userData.animation = 1;
202 | controller.userData.grabbing = null;
203 | return false;
204 | }
205 | return true;
206 | }
207 |
--------------------------------------------------------------------------------
/src/systems/AreaCheckerSystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {System} from 'ecsy';
3 | import {Area, AreaInside, AreaExiting, AreaEntering, Object3D, AreaChecker, BoundingBox} from '../components/index.js';
4 |
5 | export class AreaCheckerSystem extends System {
6 | execute(delta, time) {
7 | const areas = this.queries.areas.results;
8 | const checkers = this.queries.checkers.results;
9 |
10 | for (let i = 0; i < areas.length; i++) {
11 | const area = areas[i];
12 | const bboxArea = area.getComponent(BoundingBox);
13 |
14 | for (let j = 0; j < checkers.length; j++) {
15 | const checker = checkers[j];
16 | const obj3D = checker.getComponent(Object3D).value;
17 | if (obj3D.boundingBox.intersectsBox(bboxArea)) {
18 | checker.addComponent(AreaInside);
19 | } else {
20 | checker.removeComponent(AreaInside);
21 | }
22 | }
23 | }
24 | }
25 | }
26 |
27 | AreaCheckerSystem.queries = {
28 | areas: {
29 | components: [Area, BoundingBox],
30 | },
31 | checkers: {
32 | components: [AreaChecker, Object3D]
33 | }
34 | }
--------------------------------------------------------------------------------
/src/systems/BillboardSystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {System} from 'ecsy';
3 | import {Billboard, Object3D, Children, Text, Position} from '../components/index.js';
4 |
5 | const SHOW_DISTANCE = 4;
6 |
7 | var cameraPosition = new THREE.Vector3();
8 |
9 | export default class BillboardSystem extends System {
10 | execute(delta, time) {
11 |
12 | window.context.camera.getWorldPosition(cameraPosition);
13 |
14 | this.queries.entities.results.forEach(entity => {
15 | const object3D = entity.getComponent(Object3D).value;
16 | const distance = cameraPosition.distanceTo(object3D.position);
17 | let opacity = 0;
18 |
19 | if (distance < SHOW_DISTANCE){
20 | opacity = THREE.Math.clamp(Math.sqrt(SHOW_DISTANCE - distance), 0, 1);
21 | object3D.lookAt(cameraPosition);
22 | }
23 |
24 | object3D.material.opacity = opacity;
25 | // panels text parent
26 | if (entity.hasComponent(Children)) {
27 | entity.getComponent(Children).value.forEach(children => {
28 | let prevOpacity = children.getComponent(Text).opacity;
29 | if (prevOpacity !== opacity) {
30 | children.getMutableComponent(Text).opacity = opacity;
31 | }
32 | });
33 | }
34 | });
35 | }
36 | }
37 |
38 | BillboardSystem.queries = {
39 | entities: {
40 | components: [Billboard, Object3D]
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/systems/ControllersSystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {System} from 'ecsy';
3 | import {Area, AreaReactor, AreaInside, AreaExiting, AreaEntering, Object3D, AreaChecker, BoundingBox} from '../components/index.js';
4 |
5 | export class ControllersSystem extends System {
6 | execute(delta, time) {
7 | const added = this.queries.checkers.added;
8 | const removed = this.queries.checkers.removed;
9 |
10 | for (let i = 0; i < added.length; i++) {
11 | const entity = added[i];
12 | const reactor = entity.getComponent(AreaReactor);
13 | reactor.onEntering(entity);
14 | }
15 |
16 | for (let i = 0; i < removed.length; i++) {
17 | const entity = removed[i];
18 | const reactor = entity.getComponent(AreaReactor);
19 | reactor.onExiting(entity);
20 | }
21 | }
22 | }
23 |
24 | ControllersSystem.queries = {
25 | checkers: {
26 | components: [AreaChecker, Object3D, AreaInside],
27 | listen: {
28 | added: true,
29 | removed: true
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/src/systems/DebugHelperSystem.js:
--------------------------------------------------------------------------------
1 | import {Object3D, DebugHelper, BoundingBox} from '../components/index.js';
2 | import {System, SystemStateComponent, Not} from 'ecsy';
3 | import * as THREE from 'three';
4 |
5 | THREE.BoxHelper.prototype.setFromMinMax = function(min, max) {
6 | var position = this.geometry.attributes.position;
7 | var array = position.array;
8 |
9 | array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z;
10 | array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z;
11 | array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z;
12 | array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z;
13 | array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z;
14 | array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z;
15 | array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z;
16 | array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z;
17 |
18 | position.needsUpdate = true;
19 |
20 | this.geometry.computeBoundingSphere();
21 | }
22 |
23 | class DebugHelperMesh extends SystemStateComponent {
24 | constructor() {
25 | super();
26 | this.boxHelper = new THREE.BoxHelper();
27 | }
28 | }
29 |
30 | export class DebugHelperSystem extends System {
31 | execute(delta, time) {
32 | this.queries.added.results.forEach(entity => {
33 | entity.addComponent(DebugHelperMesh);
34 | var boundingBox = entity.getComponent(BoundingBox);
35 | let debugMesh = entity.getMutableComponent(DebugHelperMesh);
36 | debugMesh.boxHelper.setFromMinMax(boundingBox.min, boundingBox.max);
37 |
38 | entity.addComponent(Object3D, {value: debugMesh.boxHelper});
39 | });
40 |
41 | this.queries.removed.results.forEach(entity => {
42 | //var boundingBox = entity.getComponent(DebugHelperMesh);
43 | entity.removeComponent(Object3D).removeComponent(DebugHelperMesh);
44 |
45 | /*
46 | entity.addComponent(Object3D, {value: debugMesh.boxHelper});
47 |
48 | let debugMesh = entity.getMutableComponent(DebugHelperMesh);
49 | debugMesh.boxHelper.setFromMinMax(boundingBox.min, boundingBox.max);
50 | entity.addComponent(Object3D, {value: debugMesh.boxHelper});
51 | */
52 | });
53 | }
54 | }
55 |
56 | DebugHelperSystem.queries = {
57 | added: {
58 | components: [DebugHelper, Not(DebugHelperMesh)]
59 | },
60 | removed: {
61 | components: [Not(DebugHelper), DebugHelperMesh]
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/src/systems/HierarchySystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {System} from 'ecsy';
3 | import {Object3D, ParentObject3D} from '../components/index.js';
4 |
5 | export default class HierarchySystem extends System {
6 | execute(delta, time) {
7 | this.queries.entities.added.forEach(entity => {
8 | const parent = entity.getComponent(ParentObject3D).value;
9 | const object = entity.getComponent(Object3D).value;
10 |
11 | parent.add(object);
12 | });
13 |
14 | this.queries.entities.changed.forEach(entity => {
15 | const parent = entity.getComponent(ParentObject3D).value;
16 | const object = entity.getComponent(Object3D).value;
17 |
18 | parent.add(object);
19 | });
20 |
21 | this.queries.entities.removed.forEach(entity => {
22 | const parent = entity.getComponent(ParentObject3D, true).value;
23 | parent.remove(entity.getComponent(Object3D, true).value);
24 | });
25 | }
26 | }
27 |
28 | HierarchySystem.queries = {
29 | entities: {
30 | components: [Object3D, ParentObject3D],
31 | listen: {
32 | added: true,
33 | removed: true,
34 | changed: true // [Component]
35 | }
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/systems/SDFTextSystem.js:
--------------------------------------------------------------------------------
1 | import * as THREE from 'three';
2 | import {System} from 'ecsy';
3 | import {TextMesh} from 'troika-3d-text/dist/textmesh-standalone.esm.js';
4 | import {Object3D, Text} from '../components/index.js';
5 |
6 | const anchorMapping = {
7 | 'left': 0,
8 | 'center': 0.5,
9 | 'right': 1
10 | }
11 | const baselineMapping = {
12 | 'top': 0,
13 | 'center': 0.5,
14 | 'bottom': 1
15 | }
16 |
17 | export class SDFTextSystem extends System {
18 |
19 | updateText(textMesh, textComponent) {
20 | textMesh.text = textComponent.text;
21 | textMesh.textAlign = textComponent.textAlign;
22 | textMesh.anchor[0] = anchorMapping[textComponent.anchor];
23 | textMesh.anchor[1] = baselineMapping[textComponent.baseline];
24 | textMesh.color = textComponent.color;
25 | textMesh.font = textComponent.font;
26 | textMesh.fontSize = textComponent.fontSize;
27 | textMesh.letterSpacing = textComponent.letterSpacing || 0;
28 | textMesh.lineHeight = textComponent.lineHeight || null;
29 | textMesh.overflowWrap = textComponent.overflowWrap;
30 | textMesh.whiteSpace = textComponent.whiteSpace;
31 | textMesh.maxWidth = textComponent.maxWidth;
32 | textMesh.material.opacity = textComponent.opacity;
33 | textMesh.sync();
34 | }
35 |
36 | execute(delta, time) {
37 | var entities = this.queries.entities;
38 |
39 | entities.added.forEach(e => {
40 | var textComponent = e.getComponent(Text);
41 |
42 | const textMesh = new TextMesh();
43 | textMesh.name = 'textMesh';
44 | textMesh.anchor = [0, 0];
45 | textMesh.renderOrder = 1; //brute-force fix for ugly antialiasing, see issue #67
46 | this.updateText(textMesh, textComponent);
47 | e.addComponent(Object3D, {value: textMesh});
48 | });
49 |
50 | entities.removed.forEach(e => {
51 | var object3D = e.getComponent(Object3D).value;
52 | var textMesh = object3D.getObjectByName('textMesh');
53 | textMesh.dispose();
54 | object3D.remove(textMesh);
55 | });
56 |
57 | entities.changed.forEach(e => {
58 | var object3D = e.getComponent(Object3D).value;
59 | if (object3D instanceof TextMesh) {
60 | var textComponent = e.getComponent(Text);
61 | this.updateText(object3D, textComponent);
62 | }
63 | });
64 | }
65 | }
66 |
67 | SDFTextSystem.queries = {
68 | entities: {
69 | components: [Text],
70 | listen: {
71 | added: true,
72 | removed: true,
73 | changed: [Text]
74 | }
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/src/systems/SystemsGroup.js:
--------------------------------------------------------------------------------
1 | export default class SystemsGroup {
2 | constructor(world, Systems) {
3 | this.Systems = Systems;
4 | this.world = world;
5 | }
6 |
7 | play() {
8 | this.Systems.forEach(System => this.world.getSystem(System).play());
9 | }
10 |
11 | stop() {
12 | this.Systems.forEach(System => this.world.getSystem(System).stop());
13 | }
14 | }
--------------------------------------------------------------------------------
/src/systems/TransformSystem.js:
--------------------------------------------------------------------------------
1 | import {System} from 'ecsy';
2 | import {Object3D, Rotation, Position} from '../components/index.js';
3 |
4 | let updateRotation = entity => {
5 | const rotation = entity.getComponent(Rotation);
6 | const object3D = entity.getComponent(Object3D).value;
7 | object3D.rotation.set(rotation.x, rotation.y, rotation.z);
8 | };
9 |
10 | let updatePosition = entity => {
11 | const position = entity.getComponent(Position);
12 | const object3D = entity.getComponent(Object3D).value;
13 | object3D.position.copy(position);
14 | };
15 |
16 | export default class TransformSystem extends System {
17 | execute(delta, time) {
18 | // Position
19 | this.queries.position.added.forEach(updatePosition);
20 | this.queries.position.changed.forEach(updatePosition);
21 |
22 | // Rotation
23 | this.queries.rotation.added.forEach(updateRotation);
24 | this.queries.rotation.changed.forEach(updateRotation);
25 | }
26 | }
27 |
28 | TransformSystem.queries = {
29 | position: {
30 | components: [Position, Object3D],
31 | listen: {
32 | added: true,
33 | changed: true
34 | }
35 | },
36 | rotation: {
37 | components: [Rotation, Object3D],
38 | listen: {
39 | added: true,
40 | changed: true
41 | }
42 | }
43 | }
--------------------------------------------------------------------------------
/src/vendor/basis_transcoder.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/src/vendor/basis_transcoder.wasm
--------------------------------------------------------------------------------
/src/vendor/draco_decoder.wasm:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/MozillaReality/hello-webxr/325144288444d51b63b09f3289aaa09ddb83163b/src/vendor/draco_decoder.wasm
--------------------------------------------------------------------------------
/webpack.config.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | mode: 'development',
3 | entry: './src/index.js',
4 | output: {
5 | filename: 'bundle.js',
6 | path: __dirname
7 | },
8 | devtool: 'source-map',
9 | module: {
10 | rules: [
11 | {
12 | test: /\.(js|mjs)$/,
13 | exclude: /(node_modules)/,
14 | use: { loader: 'babel-loader' }
15 | }
16 | ]
17 | },
18 | watchOptions: {
19 | ignored: [/node_modules/],
20 | }
21 | };
22 |
--------------------------------------------------------------------------------