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(); +}; diff --git a/src/lib/assetManager.js b/src/lib/assetManager.js index 2ef793b..6220a41 100644 --- a/src/lib/assetManager.js +++ b/src/lib/assetManager.js @@ -1,31 +1,43 @@ -import * as THREE from 'three'; -import { BasisTextureLoader } from 'three/examples/jsm/loaders/BasisTextureLoader.js'; -import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader.js'; -import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader.js'; -import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader.js'; +import * as THREE from "three"; +import { BasisTextureLoader } from "three/examples/jsm/loaders/BasisTextureLoader.js"; +import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader.js"; +import { OBJLoader } from "three/examples/jsm/loaders/OBJLoader.js"; +import { DRACOLoader } from "three/examples/jsm/loaders/DRACOLoader.js"; +import { FontLoader } from "three/examples/jsm/loaders/FontLoader"; //const BASIS_LIB_PATH = 'src/vendor/'; -const BASIS_LIB_PATH = 'src/vendor/'; -const DRACO_LIB_PATH = 'src/vendor/'; +const BASIS_LIB_PATH = "src/vendor/"; +const DRACO_LIB_PATH = "src/vendor/"; function getLoadedCount(assets) { let count = 0; for (var i in assets) { - if (assets[i].loading !== true) { count ++; } + if (assets[i].loading !== true) { + count++; + } } return count; } function allAssetsLoaded(assets) { for (var i in assets) { - if (assets[i].loading === true) { return false; } + if (assets[i].loading === true) { + return false; + } } return true; } -export function loadAssets(renderer, basePath, assets, onComplete, onProgress, debug) { - if (basePath && basePath[basePath.length - 1] != '/') { - basePath += '/'; +export function loadAssets( + renderer, + basePath, + assets, + onComplete, + onProgress, + debug +) { + if (basePath && basePath[basePath.length - 1] != "/") { + basePath += "/"; } var basisLoader = new BasisTextureLoader(); @@ -39,53 +51,59 @@ export function loadAssets(renderer, basePath, assets, onComplete, onProgress, d var texLoader = new THREE.TextureLoader(); var objLoader = new OBJLoader(); - var fontLoader = new THREE.FontLoader(); + var fontLoader = new FontLoader(); var audioLoader = new THREE.AudioLoader(); - var loaders = { - 'gltf': gltfLoader, - 'glb': gltfLoader, - 'obj': objLoader, - 'gif': texLoader, - 'png': texLoader, - 'jpg': texLoader, - 'basis': basisLoader, - 'font': fontLoader, - 'ogg': audioLoader + gltf: gltfLoader, + glb: gltfLoader, + obj: objLoader, + gif: texLoader, + png: texLoader, + jpg: texLoader, + basis: basisLoader, + font: fontLoader, + ogg: audioLoader, }; for (var i in assets) { let assetId = i; let assetPath = assets[i].url; assets[i].loading = true; - let ext = assetPath.substr(assetPath.lastIndexOf('.') + 1).toLowerCase(); - loaders[ext].load(basePath + assetPath, asset => { - if (debug) { - console.info(`%c ${assetPath} loaded`, 'color:green'); - } - var options = assets[assetId].options; - assets[assetId] = ext == 'font'? asset.data : asset; - - if (typeof options !== "undefined") { - if (typeof options.repeat !== "undefined") { - assets[assetId].repeat.set(options.repeat[0], options.repeat[1]); - delete options.repeat; + let ext = assetPath.substr(assetPath.lastIndexOf(".") + 1).toLowerCase(); + loaders[ext].load( + basePath + assetPath, + (asset) => { + if (debug) { + console.info(`%c ${assetPath} loaded`, "color:green"); } - for (let opt in options) { - assets[assetId][opt] = options[opt]; + var options = assets[assetId].options; + assets[assetId] = ext == "font" ? asset.data : asset; + + if (typeof options !== "undefined") { + if (typeof options.repeat !== "undefined") { + assets[assetId].repeat.set(options.repeat[0], options.repeat[1]); + delete options.repeat; + } + for (let opt in options) { + assets[assetId][opt] = options[opt]; + } + + if (onProgress) { + onProgress(getLoadedCount(assets)); + } } - if (onProgress) { onProgress(getLoadedCount(assets)) }; + if (onComplete && allAssetsLoaded(assets)) { + onComplete(); + } + }, + () => { + /* on progress */ + }, + (e) => { + console.error("Error loading asset", e); } - - if (onComplete && allAssetsLoaded(assets)) { onComplete(); } - }, () => { - /* on progress */ - }, - (e) => { - console.error('Error loading asset', e); - } ); } } diff --git a/src/systems/DebugHelperSystem.js b/src/systems/DebugHelperSystem.js index 98c7655..b3775ec 100644 --- a/src/systems/DebugHelperSystem.js +++ b/src/systems/DebugHelperSystem.js @@ -1,48 +1,62 @@ -import {Object3D, DebugHelper, BoundingBox} from '../components/index.js'; -import {System, SystemStateComponent, Not} from 'ecsy'; -import * as THREE from 'three'; - -THREE.BoxHelper.prototype.setFromMinMax = function(min, max) { - var position = this.geometry.attributes.position; - var array = position.array; - - array[ 0 ] = max.x; array[ 1 ] = max.y; array[ 2 ] = max.z; - array[ 3 ] = min.x; array[ 4 ] = max.y; array[ 5 ] = max.z; - array[ 6 ] = min.x; array[ 7 ] = min.y; array[ 8 ] = max.z; - array[ 9 ] = max.x; array[ 10 ] = min.y; array[ 11 ] = max.z; - array[ 12 ] = max.x; array[ 13 ] = max.y; array[ 14 ] = min.z; - array[ 15 ] = min.x; array[ 16 ] = max.y; array[ 17 ] = min.z; - array[ 18 ] = min.x; array[ 19 ] = min.y; array[ 20 ] = min.z; - array[ 21 ] = max.x; array[ 22 ] = min.y; array[ 23 ] = min.z; - - position.needsUpdate = true; - - this.geometry.computeBoundingSphere(); -} +import { + Object3D, + DebugHelper, + DebugHelperMesh, + BoundingBox, +} from "../components/index.js"; +import { System, Not } from "ecsy"; +import * as THREE from "three"; -class DebugHelperMesh extends SystemStateComponent { - constructor() { - super(); - this.boxHelper = new THREE.BoxHelper(); - } -} +THREE.BoxHelper.prototype.setFromMinMax = function (min, max) { + var position = this.geometry.attributes.position; + var array = position.array; + + array[0] = max.x; + array[1] = max.y; + array[2] = max.z; + array[3] = min.x; + array[4] = max.y; + array[5] = max.z; + array[6] = min.x; + array[7] = min.y; + array[8] = max.z; + array[9] = max.x; + array[10] = min.y; + array[11] = max.z; + array[12] = max.x; + array[13] = max.y; + array[14] = min.z; + array[15] = min.x; + array[16] = max.y; + array[17] = min.z; + array[18] = min.x; + array[19] = min.y; + array[20] = min.z; + array[21] = max.x; + array[22] = min.y; + array[23] = min.z; + + position.needsUpdate = true; + + this.geometry.computeBoundingSphere(); +}; export class DebugHelperSystem extends System { execute(delta, time) { - this.queries.added.results.forEach(entity => { + this.queries.added.results.forEach((entity) => { entity.addComponent(DebugHelperMesh); var boundingBox = entity.getComponent(BoundingBox); let debugMesh = entity.getMutableComponent(DebugHelperMesh); debugMesh.boxHelper.setFromMinMax(boundingBox.min, boundingBox.max); - entity.addComponent(Object3D, {value: debugMesh.boxHelper}); + entity.addComponent(Object3D, { value: debugMesh.boxHelper }); }); - this.queries.removed.results.forEach(entity => { + this.queries.removed.results.forEach((entity) => { //var boundingBox = entity.getComponent(DebugHelperMesh); entity.removeComponent(Object3D).removeComponent(DebugHelperMesh); -/* + /* entity.addComponent(Object3D, {value: debugMesh.boxHelper}); let debugMesh = entity.getMutableComponent(DebugHelperMesh); @@ -55,9 +69,9 @@ export class DebugHelperSystem extends System { DebugHelperSystem.queries = { added: { - components: [DebugHelper, Not(DebugHelperMesh)] + components: [DebugHelper, Not(DebugHelperMesh)], }, removed: { - components: [Not(DebugHelper), DebugHelperMesh] - } -} + components: [Not(DebugHelper), DebugHelperMesh], + }, +};