diff --git a/.gitignore b/.gitignore index 4816674..2ee6ac7 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ yarn-debug.log* yarn-error.log* # bundle.* .parcel-cache +dist *DS_Store diff --git a/src/components/index.js b/src/components/index.js index 3f39529..8bca5a7 100644 --- a/src/components/index.js +++ b/src/components/index.js @@ -1,8 +1,9 @@ -import * as THREE from 'three'; -import {TagComponent} from 'ecsy'; +import * as THREE from "three"; +import { TagComponent, Component, SystemStateComponent, Types } from "ecsy"; -export class Object3D { +class Object3D extends Component { constructor() { + super(); this.value = null; } @@ -11,24 +12,39 @@ export class Object3D { } } -export class Rotation { +Object3D.schema = { + value: { type: Types.Ref }, +}; + +class Rotation extends Component { constructor() { + super(); this.rotation = new THREE.Vector3(); } reset() {} } -export class Position { +Rotation.schema = { + rotation: { type: Types.Ref }, +}; + +class Position extends Component { constructor() { + super(); this.position = new THREE.Vector3(); } reset() {} } -export class ParentObject3D { +Position.schema = { + position: { type: Types.Ref }, +}; + +class ParentObject3D extends Component { constructor() { + super(); this.value = null; } @@ -37,43 +53,71 @@ export class ParentObject3D { } } -export class Text { +ParentObject3D.schema = { + value: { type: Types.Ref }, +}; + +class Text extends Component { constructor() { - this.text = ''; - this.textAlign = 'left'; // ['left', 'right', 'center'] - this.anchor = 'center'; // ['left', 'right', 'center', 'align'] - this.baseline = 'center'; // ['top', 'center', 'bottom'] - this.color = '#FFF'; - this.font = 'https://code.cdn.mozilla.net/fonts/ttf/ZillaSlab-SemiBold.ttf'; + super(); + this.text = ""; + this.textAlign = "left"; // ['left', 'right', 'center'] + this.anchorX = "center"; // ['left', 'right', 'center', 'align'] + this.baseline = "center"; // ['top', 'center', 'bottom'] + this.color = "#FFF"; + this.font = "https://code.cdn.mozilla.net/fonts/ttf/ZillaSlab-SemiBold.ttf"; this.fontSize = 0.2; this.letterSpacing = 0; this.lineHeight = 0; this.maxWidth = Infinity; - this.overflowWrap = 'normal'; // ['normal', 'break-word'] - this.whiteSpace = 'normal'; // ['normal', 'nowrap'] + this.overflowWrap = "normal"; // ['normal', 'break-word'] + this.whiteSpace = "normal"; // ['normal', 'nowrap'] this.opacity = 1; } reset() { - this.text = ''; + this.text = ""; } } -export class BoundingBox { +Text.schema = { + text: { type: Types.String }, + textAlign: { type: Types.String }, + anchorX: { type: Types.String }, + baseline: { type: Types.String }, + color: { type: Types.String }, + font: { type: Types.String }, + fontSize: { type: Types.Number }, + letterSpacing: { type: Types.Number }, + lineHeight: { type: Types.Number }, + maxWidth: { type: Types.Number }, + overflowWrap: { type: Types.String }, + whiteSpace: { type: Types.String }, + opacity: { type: Types.Number }, +}; + +class BoundingBox extends Component { constructor() { + super(); this.min = new THREE.Vector3(); this.max = new THREE.Vector3(); // this.box3? } reset() { - this.min.set(0,0,0); - this.max.set(0,0,0); + this.min.set(0, 0, 0); + this.max.set(0, 0, 0); } } -export class BoundingSphere { +BoundingBox.schema = { + min: { type: Types.Ref }, + max: { type: Types.Ref }, +}; + +class BoundingSphere extends Component { constructor() { + super(); this.debug = true; this.center = new THREE.Vector3(); this.radius = 0; @@ -81,52 +125,58 @@ export class BoundingSphere { } reset() { - this.center.set(0,0,0); + this.center.set(0, 0, 0); this.radius = 0; } } -export class Area { - constructor() { +BoundingSphere.schema = { + debug: { type: Types.Boolean }, + center: { type: Types.Ref }, + radius: { type: Types.Number }, +}; - } +class Area extends TagComponent {} +class AreaEntering extends TagComponent {} +class AreaExiting extends TagComponent {} +class AreaInside extends TagComponent {} +class AreaChecker extends TagComponent {} - reset() { - - } -} - -export class AreaEntering extends TagComponent {} -export class AreaExiting extends TagComponent {} -export class AreaInside extends TagComponent {} +const empty = () => {}; -export class AreaChecker { +class AreaReactor extends Component { constructor() { - + super(); + this.reset(); } reset() { - + this.onEntering = empty; + this.onExiting = empty; } } -const empty = () => {}; +AreaReactor.schema = { + onEntering: { type: Types.Ref }, + onExiting: { type: Types.Ref }, +}; -export class AreaReactor { - constructor() { - this.reset(); - } +class DebugHelper extends TagComponent {} - reset() { - this.onEntering = empty; - this.onExiting = empty; +class DebugHelperMesh extends SystemStateComponent { + constructor() { + super(); + this.boxHelper = new THREE.BoxHelper(); } } -export class DebugHelper extends TagComponent {} +DebugHelperMesh.schema = { + boxHelper: { type: Types.Ref }, +}; -export class Billboard { +class Billboard extends Component { constructor() { + super(); this.camera3D = null; } @@ -135,8 +185,13 @@ export class Billboard { } } -export class Children { +Billboard.schema = { + camera3D: { type: Types.Ref }, +}; + +class Children extends Component { constructor() { + super(); this.value = []; } @@ -145,8 +200,13 @@ export class Children { } } -export class Opacity { +Children.schema = { + value: { type: Types.Array }, +}; + +class Opacity extends Component { constructor() { + super(); this.opacity = 0; } @@ -154,3 +214,28 @@ export class Opacity { this.opacity = 0; } } + +Opacity.schema = { + opacity: { type: Types.Number }, +}; + +export { + Object3D, + Rotation, + Position, + ParentObject3D, + Text, + BoundingBox, + BoundingSphere, + Area, + AreaEntering, + AreaExiting, + AreaInside, + AreaChecker, + AreaReactor, + DebugHelper, + DebugHelperMesh, + Billboard, + Children, + Opacity, +}; diff --git a/src/index.js b/src/index.js index 97f7d29..f82d331 100644 --- a/src/index.js +++ b/src/index.js @@ -1,45 +1,71 @@ -import * as THREE from 'three'; -import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls.js'; +import * as THREE from "three"; +import { PointerLockControls } from "three/examples/jsm/controls/PointerLockControls.js"; -import {VRButton} from './lib/VRButton.js'; -import {slideshow} from './lib/slideshow.js'; -import {loadAssets} from './lib/assetManager.js'; +import { VRButton } from "./lib/VRButton.js"; +import { slideshow } from "./lib/slideshow.js"; +import { loadAssets } from "./lib/assetManager.js"; // ECSY -import { World } from 'ecsy'; -import { SDFTextSystem } from './systems/SDFTextSystem.js'; -import { DebugHelperSystem } from './systems/DebugHelperSystem.js'; -import { AreaCheckerSystem } from './systems/AreaCheckerSystem.js'; -import { ControllersSystem } from './systems/ControllersSystem.js'; -import HierarchySystem from './systems/HierarchySystem.js'; -import TransformSystem from './systems/TransformSystem.js'; -import BillboardSystem from './systems/BillboardSystem.js'; - -import SystemsGroup from './systems/SystemsGroup.js'; - -import assets from './assets.js'; - -import { Text, Object3D, AreaChecker } from './components/index.js'; - -import RayControl from './lib/RayControl.js'; -import Teleport from './lib/Teleport.js'; - -import * as roomHall from './rooms/Hall.js'; -import * as roomPanorama from './rooms/Panorama.js'; -import * as roomPanoramaStereo from './rooms/PanoramaStereo.js'; -import * as roomPhotogrammetryObject from './rooms/PhotogrammetryObject.js'; -import * as roomVertigo from './rooms/Vertigo.js'; -import * as roomSound from './rooms/Sound.js'; - -import {shaders} from './lib/shaders.js'; - -import WebXRPolyfill from 'webxr-polyfill'; +import { World } from "ecsy"; +import { SDFTextSystem } from "./systems/SDFTextSystem.js"; +import { DebugHelperSystem } from "./systems/DebugHelperSystem.js"; +import { AreaCheckerSystem } from "./systems/AreaCheckerSystem.js"; +import { ControllersSystem } from "./systems/ControllersSystem.js"; +import HierarchySystem from "./systems/HierarchySystem.js"; +import TransformSystem from "./systems/TransformSystem.js"; +import BillboardSystem from "./systems/BillboardSystem.js"; + +import SystemsGroup from "./systems/SystemsGroup.js"; + +import assets from "./assets.js"; + +import { + Object3D, + Rotation, + Position, + ParentObject3D, + Text, + BoundingBox, + BoundingSphere, + Area, + AreaEntering, + AreaExiting, + AreaInside, + AreaChecker, + AreaReactor, + DebugHelper, + DebugHelperMesh, + Billboard, + Children, + Opacity, +} from "./components/index.js"; + +import RayControl from "./lib/RayControl.js"; +import Teleport from "./lib/Teleport.js"; + +import * as roomHall from "./rooms/Hall.js"; +import * as roomPanorama from "./rooms/Panorama.js"; +import * as roomPanoramaStereo from "./rooms/PanoramaStereo.js"; +import * as roomPhotogrammetryObject from "./rooms/PhotogrammetryObject.js"; +import * as roomVertigo from "./rooms/Vertigo.js"; +import * as roomSound from "./rooms/Sound.js"; + +import { shaders } from "./lib/shaders.js"; + +import WebXRPolyfill from "webxr-polyfill"; const polyfill = new WebXRPolyfill(); var clock = new THREE.Clock(); -var scene, parent, renderer, camera, controls, context = {}; -var raycontrol, teleport, controllers = []; +var scene, + parent, + renderer, + camera, + controls, + context = {}; +var raycontrol, + teleport, + controllers = []; var listener, ambientMusic; @@ -57,54 +83,57 @@ var rooms = [ ]; const roomNames = [ - 'hall', - 'sound', - 'photogrammetry', - 'vertigo', - 'panoramastereo', - 'panorama1', - 'panorama2', - 'panorama3', - 'panorama4', - 'panorama5', + "hall", + "sound", + "photogrammetry", + "vertigo", + "panoramastereo", + "panorama1", + "panorama2", + "panorama3", + "panorama4", + "panorama5", ]; const musicThemes = [ false, false, - 'chopin_snd', - 'wind_snd', + "chopin_snd", + "wind_snd", false, - 'birds_snd', - 'birds_snd', - 'forest_snd', - 'wind_snd', - 'birds_snd', + "birds_snd", + "birds_snd", + "forest_snd", + "wind_snd", + "birds_snd", ]; const urlObject = new URL(window.location); -const roomName = urlObject.searchParams.get('room'); -context.room = roomNames.indexOf(roomName) !== -1 ? roomNames.indexOf(roomName) : 0; +const roomName = urlObject.searchParams.get("room"); +context.room = + roomNames.indexOf(roomName) !== -1 ? roomNames.indexOf(roomName) : 0; // console.log(`Current room "${roomNames[context.room]}", ${context.room}`); -const debug = urlObject.searchParams.has('debug'); -const handedness = urlObject.searchParams.has('handedness') ? urlObject.searchParams.get('handedness') : "right"; +const debug = urlObject.searchParams.has("debug"); +const handedness = urlObject.searchParams.has("handedness") + ? urlObject.searchParams.get("handedness") + : "right"; // Target positions when moving from one room to another const targetPositions = { hall: { sound: new THREE.Vector3(0, 0, 0), photogrammetry: new THREE.Vector3(1, 0, 0), - vertigo: new THREE.Vector3(0, 0, 0) + vertigo: new THREE.Vector3(0, 0, 0), }, photogrammetry: { - hall: new THREE.Vector3(-3.6, 0, 2.8) + hall: new THREE.Vector3(-3.6, 0, 2.8), }, sound: { - hall: new THREE.Vector3(4.4, 0, 4.8) + hall: new THREE.Vector3(4.4, 0, 4.8), }, vertigo: { - hall: new THREE.Vector3(-1.8, 0, -5) - } + hall: new THREE.Vector3(-1.8, 0, -5), + }, }; function gotoRoom(room) { @@ -136,7 +165,9 @@ function playMusic(room) { if (ambientMusic.source) ambientMusic.stop(); const music = musicThemes[room]; - if (!music) { return; } + if (!music) { + return; + } ambientMusic.setBuffer(assets[music]); ambientMusic.setLoop(true); ambientMusic.setVolume(1.0); @@ -148,19 +179,18 @@ var ecsyWorld; var systemsGroup = {}; function detectWebXR() { - if ('xr' in navigator) { - navigator.xr.isSessionSupported('immersive-vr').then( supported => { - if (!supported) document.getElementById('no-webxr').classList.remove('hidden'); - } ); - + if ("xr" in navigator) { + navigator.xr.isSessionSupported("immersive-vr").then((supported) => { + if (!supported) + document.getElementById("no-webxr").classList.remove("hidden"); + }); } else { - document.getElementById('no-webxr').classList.remove('hidden'); + document.getElementById("no-webxr").classList.remove("hidden"); } } export function init() { - - document.getElementById(handedness + 'hand').classList.add('activehand'); + document.getElementById(handedness + "hand").classList.add("activehand"); detectWebXR(); @@ -175,19 +205,29 @@ export function init() { .registerSystem(BillboardSystem) .registerSystem(HierarchySystem); - systemsGroup['roomHall'] = new SystemsGroup(ecsyWorld, [ - AreaCheckerSystem, ControllersSystem, DebugHelperSystem + systemsGroup["roomHall"] = new SystemsGroup(ecsyWorld, [ + AreaCheckerSystem, + ControllersSystem, + DebugHelperSystem, ]); - renderer = new THREE.WebGLRenderer({antialias: true, logarithmicDepthBuffer: false}); + renderer = new THREE.WebGLRenderer({ + antialias: true, + logarithmicDepthBuffer: false, + }); renderer.gammaFactor = 2.2; renderer.outputEncoding = THREE.sRGBEncoding; - renderer.setPixelRatio( window.devicePixelRatio ); + renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(window.innerWidth, window.innerHeight); renderer.xr.enabled = true; scene = new THREE.Scene(); - camera = new THREE.PerspectiveCamera(80, window.innerWidth / window.innerHeight, 0.005, 10000); + camera = new THREE.PerspectiveCamera( + 80, + window.innerWidth / window.innerHeight, + 0.005, + 10000 + ); camera.position.set(0, 1.6, 0); listener = new THREE.AudioListener(); camera.add(listener); @@ -196,14 +236,24 @@ export function init() { controls = new PointerLockControls(camera, renderer.domElement); if (debug) { - document.body.addEventListener('click', () => controls.lock()); - document.body.addEventListener('keydown', ev => { - switch(ev.keyCode) { - case 87: controls.moveForward(0.2); break; - case 65: controls.moveRight(-0.2); break; - case 83: controls.moveForward(-0.2); break; - case 68: controls.moveRight(0.2); break; - case 78: gotoRoom((context.room + 1) % rooms.length); break; + document.body.addEventListener("click", () => controls.lock()); + document.body.addEventListener("keydown", (ev) => { + switch (ev.keyCode) { + case 87: + controls.moveForward(0.2); + break; + case 65: + controls.moveRight(-0.2); + break; + case 83: + controls.moveForward(-0.2); + break; + case 68: + controls.moveRight(0.2); + break; + case 78: + gotoRoom((context.room + 1) % rooms.length); + break; default: { var room = ev.keyCode - 48; if (!ev.metaKey && room >= 0 && room < rooms.length) { @@ -218,22 +268,22 @@ export function init() { parent = new THREE.Object3D(); scene.add(parent); - window.addEventListener('resize', onWindowResize, false); + window.addEventListener("resize", onWindowResize, false); for (let i = 0; i < 2; i++) { controllers[i] = renderer.xr.getController(i); controllers[i].raycaster = new THREE.Raycaster(); controllers[i].raycaster.near = 0.1; - controllers[i].addEventListener('selectstart', onSelectStart); - controllers[i].addEventListener('selectend', onSelectEnd); + controllers[i].addEventListener("selectstart", onSelectStart); + controllers[i].addEventListener("selectend", onSelectEnd); } // global lights const lightSun = new THREE.DirectionalLight(0xeeffff); - lightSun.name = 'sun'; + lightSun.name = "sun"; lightSun.position.set(0.2, 1, 0.1); const lightFill = new THREE.DirectionalLight(0xfff0ee, 0.3); - lightFill.name = 'fillLight'; + lightFill.name = "fillLight"; lightFill.position.set(-0.2, -1, -0.1); scene.add(lightSun, lightFill); @@ -263,66 +313,76 @@ export function init() { const loadTotal = Object.keys(assets).length; - loadAssets(renderer, 'assets/', assets, () => { - raycontrol = new RayControl(context, handedness); - context.raycontrol = raycontrol; - - teleport = new Teleport(context); - context.teleport = teleport; - - setupControllers(); - roomHall.setup(context); - roomPanorama.setup(context); - roomPanoramaStereo.setup(context); - roomPhotogrammetryObject.setup(context); - roomVertigo.setup(context); - roomSound.setup(context); - - rooms[context.room].enter(context); - - slideshow.setup(context); - - document.body.appendChild(renderer.domElement); - document.body.appendChild(VRButton.createButton(renderer, status => { - context.vrMode = status === 'sessionStarted'; - if (context.vrMode) { - gotoRoom(0); - context.cameraRig.position.set(0, 0, 2); - context.goto = null; - } else { - slideshow.setup(context); - } - })); - renderer.setAnimationLoop(animate); - - document.getElementById('loading').style.display = 'none'; - }, - - loadProgress => { - document.querySelector('#progressbar').setAttribute('stroke-dashoffset', - - (282 - Math.floor(loadProgress / loadTotal * 282))); - }, - debug); - + loadAssets( + renderer, + "assets/", + assets, + () => { + raycontrol = new RayControl(context, handedness); + context.raycontrol = raycontrol; + + teleport = new Teleport(context); + context.teleport = teleport; + + setupControllers(); + roomHall.setup(context); + roomPanorama.setup(context); + roomPanoramaStereo.setup(context); + roomPhotogrammetryObject.setup(context); + roomVertigo.setup(context); + roomSound.setup(context); + + rooms[context.room].enter(context); + + slideshow.setup(context); + + document.body.appendChild(renderer.domElement); + document.body.appendChild( + VRButton.createButton(renderer, (status) => { + context.vrMode = status === "sessionStarted"; + if (context.vrMode) { + gotoRoom(0); + context.cameraRig.position.set(0, 0, 2); + context.goto = null; + } else { + slideshow.setup(context); + } + }) + ); + renderer.setAnimationLoop(animate); + + document.getElementById("loading").style.display = "none"; + }, + + (loadProgress) => { + document + .querySelector("#progressbar") + .setAttribute( + "stroke-dashoffset", + -(282 - Math.floor((loadProgress / loadTotal) * 282)) + ); + }, + debug + ); } function setupControllers() { - var model = assets['generic_controller_model'].scene; + var model = assets["generic_controller_model"].scene; var material = new THREE.MeshLambertMaterial({ - map: assets['controller_tex'], + map: assets["controller_tex"], }); - model.getObjectByName('body').material = material; - model.getObjectByName('trigger').material = material; + model.getObjectByName("body").material = material; + model.getObjectByName("trigger").material = material; - for (let i = 0;i < 2; i++) { + for (let i = 0; i < 2; i++) { let controller = controllers[i]; controller.boundingBox = new THREE.Box3(); controller.userData.grabbing = null; - controller.addEventListener( 'connected', function ( event ) { + controller.addEventListener("connected", function (event) { this.add(model.clone()); raycontrol.addController(this, event.data); - } ); - controller.addEventListener( 'disconnect', function () { + }); + controller.addEventListener("disconnect", function () { this.remove(this.children[0]); raycontrol.removeController(this, event.data); }); @@ -332,7 +392,8 @@ function setupControllers() { // @FIXME Hack for Oculus Browser issue var selectStartSkip = {}; var selectEndSkip = {}; -var OculusBrowser = navigator.userAgent.indexOf("OculusBrowser") !== -1 && +var OculusBrowser = + navigator.userAgent.indexOf("OculusBrowser") !== -1 && parseInt(navigator.userAgent.match(/OculusBrowser\/([0-9]+)./)[1]) < 8; // <@FIXME @@ -349,7 +410,7 @@ function onSelectStart(ev) { } // <@FIXME - const trigger = ev.target.getObjectByName('trigger'); + const trigger = ev.target.getObjectByName("trigger"); trigger.rotation.x = -0.3; raycontrol.onSelectStart(ev); } @@ -366,7 +427,7 @@ function onSelectEnd(ev) { } // <@FIXME - const trigger = ev.target.getObjectByName('trigger'); + const trigger = ev.target.getObjectByName("trigger"); trigger.rotation.x = 0; raycontrol.onSelectEnd(ev); } @@ -385,7 +446,7 @@ function animate() { // update controller bounding boxes for (let i = 0; i < controllers.length; i++) { - const model = controllers[i].getObjectByName('Scene'); + const model = controllers[i].getObjectByName("Scene"); if (model) { controllers[i].boundingBox.setFromObject(model); } @@ -405,4 +466,6 @@ function animate() { } } -window.onload = () => {init()}; +window.onload = () => { + init(); +};