From d4b377ef475a30955acc54fa570b2b00b083c54e Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Wed, 9 Aug 2023 18:04:19 +0200 Subject: [PATCH 01/41] poc: controller webxr into itowns --- config/threeExamples.mjs | 4 +- examples/misc_clamp_ground.html | 6 +- examples/view_3d_map_webxr.html | 197 +++++++++++++++++++++++++++++++- src/Core/View.js | 1 + src/Main.js | 2 + src/Renderer/WebXR.js | 87 ++++++++++++++ 6 files changed, 291 insertions(+), 6 deletions(-) diff --git a/config/threeExamples.mjs b/config/threeExamples.mjs index 9c1107d8a7..9254c2f035 100644 --- a/config/threeExamples.mjs +++ b/config/threeExamples.mjs @@ -9,6 +9,8 @@ export default { './utils/WorkerPool.js', './capabilities/WebGL.js', './libs/ktx-parse.module.js', - './libs/zstddec.module.js' + './libs/zstddec.module.js', + './libs/motion-controllers.module.js', + './webxr/XRControllerModelFactory.js', ], }; diff --git a/examples/misc_clamp_ground.html b/examples/misc_clamp_ground.html index 1e5317d0c8..383d9c0601 100644 --- a/examples/misc_clamp_ground.html +++ b/examples/misc_clamp_ground.html @@ -65,12 +65,12 @@ // position of the mesh var meshCoord = cameraTargetPosition; - meshCoord.altitude += 30; + // meshCoord.altitude += 30; // position and orientation of the mesh mesh.position.copy(meshCoord.as(view.referenceCrs)); - mesh.lookAt(new THREE.Vector3(0, 0, 0)); - mesh.rotateX(Math.PI / 2); + //mesh.lookAt(new THREE.Vector3(0, 0, 0)); + // mesh.rotateX(Math.PI / 2); // update coordinate of the mesh mesh.updateMatrixWorld(); diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 56bfee0a60..9ab4375497 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -38,16 +38,24 @@ // `viewerDiv` will contain iTowns' rendering area (``) const viewerDiv = document.getElementById('viewerDiv'); + const contextXR = { + tempMatrix: new itowns.THREE.Matrix4() + }; + + const scale = 1; // Create a GlobeView const view = new itowns.GlobeView(viewerDiv, placement, { - webXR: { scale: 0.005 }, + webXR: { scale: scale }, + callback: render }); // Instantiate three's VR Button const vrButton = VRButton.createButton(view.renderer); viewerDiv.appendChild(vrButton); + + // ---------- DISPLAY ORTHO-IMAGES : ---------- // Add one imagery layer to the scene. This layer's properties are @@ -65,10 +73,195 @@ // Here again, each layer's properties are defined in a json file. function addElevationLayerFromConfig(config) { config.source = new itowns.WMTSSource(config.source); + contextXR.elevationLayer = new itowns.ElevationLayer(config.id, config); view.addLayer( - new itowns.ElevationLayer(config.id, config), + contextXR.elevationLayer, ); } + + + // ------------- webxr interaction ---------------- + view.scene.matrixWorldAutoUpdate = true; + addFakeRoom(); + addControllers(); + + function addFakeRoom() { + contextXR.raycaster = new itowns.THREE.Raycaster(); + + var cameraTargetPosition = view.controls.getCameraCoordinate(); + var meshCoord = cameraTargetPosition; + meshCoord.altitude += 100; + + contextXR.raycaster.ray.origin.copy(meshCoord.as(view.referenceCrs)); + + contextXR.marker = new itowns.THREE.Mesh( + new itowns.THREE.CircleGeometry( 10, 32 ), + new itowns.THREE.MeshBasicMaterial( { color: 0xbcbcbc } ) + ); + contextXR.marker.position.copy(meshCoord.as(view.referenceCrs)); + contextXR.marker.lookAt(new itowns.THREE.Vector3(0, 0, 0)); + view.scene.add(contextXR.marker); + + contextXR.floor = new itowns.THREE.Mesh( + new itowns.THREE.PlaneGeometry( 10000, 10000, 1, 1 ), + new itowns.THREE.MeshBasicMaterial( { color: 0xff0000, side: itowns.THREE.DoubleSide } ) + ); + + + + + // position and orientation of the mesh + contextXR.floor.position.copy(meshCoord.as(view.referenceCrs)); + contextXR.floor.lookAt(new itowns.THREE.Vector3(0, 0, 0)); + // contextXR.floor.rotateX(Math.PI / 2); + + // update coordinate of the mesh + contextXR.floor.updateMatrixWorld(); + + // add the mesh to the scene + view.scene.add(contextXR.floor); + + view.notifyChange(); + } + + function addControllers() { + + contextXR.renderer = view.mainLoop.gfxEngine.renderer; + //view.mainLoop.gfxEngine.renderer.xr.enabled = true; + contextXR.controller1 = bindListeners(0); + contextXR.controller2 = bindListeners(1); + + contextXR.renderer.xr.addEventListener( 'sessionstart', function () + { + contextXR.baseReferenceSpace = contextXR.renderer.xr.getReferenceSpace(); + console.log('binding referenceSpace'); + }); + + function bindListeners(index) { + const controller = contextXR.renderer.xr.getController(index); + controller.addEventListener( 'selectstart', onSelectStart ); + controller.addEventListener( 'selectend', onSelectEnd ); + //var cameraTargetPosition = view.controls.getCameraCoordinate(); + //var meshCoord = cameraTargetPosition; + //meshCoord.altitude += 30; + + //controller.position.copy(meshCoord.as(view.referenceCrs)); + //controller.updateMatrixWorld(); + return controller; + } + + function onSelectStart() { + console.log("selecting"); + this.userData.isSelecting = true; + } + + function onSelectEnd() { + view.camera.camera3D.far = 20000000000; + console.log("stop selecting"); + this.userData.isSelecting = false; + if ( contextXR.INTERSECTION ) { + // try force z to 0 to keep its altitude but "maybe" it wont work as easy with a globe + // doesn't seem to be a delta but the destination coordinate; + + const cameraPos = view.camera.position().clone(); + const newCoordIntersection = new itowns.Coordinates(cameraPos.crs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); + const newCoordIntersection2 = newCoordIntersection.as(view.referenceCrs) + view.camera.setPosition(newCoordIntersection2); + const offsetPosition = { x: - contextXR.INTERSECTION.x, y: - contextXR.INTERSECTION.y, z: - contextXR.INTERSECTION.z, w: 1 }; + // const vectorOffsetPosition = new itowns.THREE.Vector3( contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z ); + + var position = view.camera.position(); + var geodesicNormal = new itowns.THREE.Quaternion().setFromUnitVectors(new itowns.THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); + const trans = view.camera.camera3D.position.clone().multiplyScalar(-scale).applyQuaternion(geodesicNormal); + + const transform = new XRRigidTransform( trans, geodesicNormal ); + const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); + contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); + } + console.log('and then...'); + } + // animate(); + } + + + + function render() { + contextXR.INTERSECTION = undefined; + if ( contextXR.controller1.userData.isSelecting === true ) { + let temp = {loc: new itowns.THREE.Vector3(), rot: new itowns.THREE.Quaternion(), scale: new itowns.THREE.Vector3()}; + // in threejs it is used from matrixWorld + contextXR.controller1.matrixWorld.decompose(temp.loc,temp.rot,temp.scale); + contextXR.tempMatrix.identity().extractRotation( contextXR.controller1.matrixWorld ); + contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller1.matrixWorld ); + contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); + const intersects = contextXR.raycaster.intersectObjects( [ contextXR.floor ] ); //TODO intersect terrain + if ( intersects.length > 0 ) { + contextXR.INTERSECTION = intersects[ 0 ].point; + } + } else if ( contextXR.controller2.userData.isSelecting === true ) { + contextXR.tempMatrix.identity().extractRotation( contextXR.controller2.matrixWorld ); + contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller2.matrixWorld ); + contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); + const intersects = contextXR.raycaster.intersectObjects( [ contextXR.floor ] ); + if ( intersects.length > 0 ) { + contextXR.INTERSECTION = intersects[ 0 ].point; + } + } + + if ( contextXR.INTERSECTION ) + { + console.log('intersection ', contextXR.INTERSECTION); + contextXR.marker.position.copy( contextXR.INTERSECTION ); + } + contextXR.marker.visible = contextXR.INTERSECTION !== undefined; + // FIXME this seems weird as it must be already done elsewhere. find a way to bind to it. + // contextXR.renderer.render( view.scene, view.camera.camera3D ); + } + + + + + //--------- add fake to ground + + function addMeshToScene() { + // creation of the new mesh (a cylinder) + var THREE = itowns.THREE; + var geometry = new THREE.CylinderGeometry(0, 10, 60, 8); + var material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); + var mesh = new THREE.Mesh(geometry, material); + + // get the position on the globe, from the camera + var cameraTargetPosition = view.controls.getLookAtCoordinate(); + + // position of the mesh + var meshCoord = cameraTargetPosition; + meshCoord.altitude += 30; + + // position and orientation of the mesh + mesh.position.copy(meshCoord.as(view.referenceCrs)); + mesh.lookAt(new THREE.Vector3(0, 0, 0)); + mesh.rotateX(Math.PI / 2); + + // update coordinate of the mesh + mesh.updateMatrixWorld(); + + // add the mesh to the scene + view.scene.add(mesh); + + // make the object usable from outside of the function + view.mesh = mesh; + view.notifyChange(); + } + + // Listen for globe full initialisation event + view.addEventListener(itowns.GLOBE_VIEW_EVENTS.GLOBE_INITIALIZED, function globeInitialized() { + // eslint-disable-next-line no-console + console.info('Globe initialized'); + + + // addMeshToScene(); + }); + itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json') .then(addElevationLayerFromConfig); itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json') diff --git a/src/Core/View.js b/src/Core/View.js index 4377182119..b64283eb94 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -153,6 +153,7 @@ class View extends THREE.EventDispatcher { * @param {boolean} [options.renderer.isWebGL2=true] - enable webgl 2.0 for THREE.js. * @param {boolean|Object} [options.webXR=false] - enable webxr button to switch on VR visualization. * @param {number} [options.webXR.scale=1.0] - apply webxr scale tranformation. + * @param {number} [options.webXR.callback] - rendering callback. * @param {?Scene} [options.scene3D] - [THREE.Scene](https://threejs.org/docs/#api/en/scenes/Scene) instance to use, otherwise a default one will be constructed * @param {?Color} options.diffuse - [THREE.Color](https://threejs.org/docs/?q=color#api/en/math/Color) Diffuse color terrain material. * This color is applied to terrain if there isn't color layer on terrain extent (by example on pole). diff --git a/src/Main.js b/src/Main.js index 3e849a88d5..0fc415546e 100644 --- a/src/Main.js +++ b/src/Main.js @@ -104,3 +104,5 @@ export { default as C3DTExtensions } from './Core/3DTiles/C3DTExtensions'; export { default as C3DTilesTypes } from './Core/3DTiles/C3DTilesTypes'; export { default as C3DTBatchTableHierarchyExtension } from './Core/3DTiles/C3DTBatchTableHierarchyExtension'; export { process3dTilesNode, $3dTilesCulling, $3dTilesSubdivisionControl } from 'Process/3dTilesProcessing'; + +export { XRControllerModelFactory } from 'ThreeExtended/webxr/XRControllerModelFactory'; diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 57137730be..3ac55a0ef8 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -1,6 +1,7 @@ /* global XRRigidTransform */ import * as THREE from 'three'; +import { XRControllerModelFactory } from 'ThreeExtended/webxr/XRControllerModelFactory'; async function shutdownXR(session) { if (session) { @@ -8,6 +9,11 @@ async function shutdownXR(session) { } } +/** + * + * @param {*} view dsfsdf + * @param {*} options webXR, callback + */ const initializeWebXR = (view, options) => { const scale = options.scale || 1.0; @@ -29,6 +35,9 @@ const initializeWebXR = (view, options) => { view.notifyChange(view.camera.camera3D, true); } }; + + initControllers(webXRManager); + view.scene.scale.multiplyScalar(scale); view.scene.updateMatrixWorld(); xr.enabled = true; @@ -43,6 +52,7 @@ const initializeWebXR = (view, options) => { const baseReferenceSpace = xr.getReferenceSpace(); const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace(transform); + xr.setReferenceSpace(teleportSpaceOffset); view.camera.camera3D = xr.getCamera(); @@ -54,14 +64,91 @@ const initializeWebXR = (view, options) => { // (see MainLoop#scheduleViewUpdate). xr.setAnimationLoop((timestamp) => { if (xr.isPresenting && view.camera.camera3D.cameras[0]) { + if (options.callback) { + options.callback(); + } view.camera.camera3D.updateMatrix(); view.camera.camera3D.updateMatrixWorld(true); + if (view.scene.matrixWorldAutoUpdate === true) { + view.scene.updateMatrixWorld(); + } + view.notifyChange(view.camera.camera3D, true); } view.mainLoop.step(view, timestamp); }); }); + + function initControllers(webXRManager) { + const controllerModelFactory = new XRControllerModelFactory(); + const leftController = webXRManager.getController(0); + leftController.name = 'leftController'; + const rightController = webXRManager.getController(1); + rightController.name = 'rightController'; + bindControllerListeners(leftController); + bindControllerListeners(rightController); + const leftGripController = webXRManager.getControllerGrip(0); + leftGripController.name = 'leftGripController'; + const rightGripController = webXRManager.getControllerGrip(1); + rightGripController.name = 'rightGripController'; + bindGripController(controllerModelFactory, leftGripController); + bindGripController(controllerModelFactory, rightGripController); + } + + function applyScalePosition(group3D, scale) { + group3D.position.multiplyScalar(-scale); + } + + function bindControllerListeners(controller) { + controller.addEventListener('disconnected', function removeCtrl() { + this.remove(this.children[0]); + }); + controller.addEventListener('connected', function addCtrl(event) { + this.add(buildController(event.data)); + // applyScalePosition(this, scale); + console.log('after binding'); + }); + // controller.matrix.makeScale(scale, scale, scale); + view.scene.add(controller); + } + + function bindGripController(controllerModelFactory, gripController) { + gripController.add(controllerModelFactory.createControllerModel(gripController)); + // var cameraTargetPosition = view.controls.getCameraCoordinate(); + // var meshCoord = cameraTargetPosition; + // controllerGrip.position.copy(meshCoord.as(view.referenceCrs)); + // controllerGrip.updateMatrixWorld(); + applyScalePosition(gripController, scale); + view.scene.add(gripController); + } + + function buildController(data) { + const params = { geometry: {}, material: {} }; + // let cameraTargetPosition = view.controls.getCameraCoordinate(); + // let meshCoord = cameraTargetPosition; + // let projectedCoords = meshCoord.as(view.referenceCrs); + + switch (data.targetRayMode) { + case 'tracked-pointer': + params.geometry = new THREE.BufferGeometry(); + + params.geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 0, 0, -1], 3)); + params.geometry.setAttribute('color', new THREE.Float32BufferAttribute([0.5, 0.5, 0.5, 0, 0, 0], 3)); + + params.material = new THREE.LineBasicMaterial({ vertexColors: true, blending: THREE.AdditiveBlending }); + return new THREE.Line(params.geometry, params.material); + + case 'gaze': + params.geometry = new THREE.RingGeometry(0.02, 0.04, 32).translate(0, 0, -1); + params.material = new THREE.MeshBasicMaterial({ opacity: 0.5, transparent: true }); + + // geometry.position.copy(meshCoord.as(view.referenceCrs)); + return new THREE.Mesh(params.geometry, params.material); + default: + break; + } + } }; export default initializeWebXR; From db5556c387c8fd0b15f3356c8d272a2cae1f9a23 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Thu, 17 Aug 2023 18:11:00 +0200 Subject: [PATCH 02/41] poc visualizing intersection destination --- examples/view_3d_map_webxr.html | 151 +++++++++++++++++++++----------- src/Renderer/WebXR.js | 14 +-- 2 files changed, 105 insertions(+), 60 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 9ab4375497..d7dfe98222 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -54,6 +54,9 @@ const vrButton = VRButton.createButton(view.renderer); viewerDiv.appendChild(vrButton); + // Setup loading screen and debug menu + setupLoadingScreen(viewerDiv, view); + var debugMenu = new GuiTools('menuDiv', view); // ---------- DISPLAY ORTHO-IMAGES : ---------- @@ -74,9 +77,7 @@ function addElevationLayerFromConfig(config) { config.source = new itowns.WMTSSource(config.source); contextXR.elevationLayer = new itowns.ElevationLayer(config.id, config); - view.addLayer( - contextXR.elevationLayer, - ); + view.addLayer(contextXR.elevationLayer).then(debugMenu.addLayerGUI.bind(debugMenu)); } @@ -90,44 +91,43 @@ var cameraTargetPosition = view.controls.getCameraCoordinate(); var meshCoord = cameraTargetPosition; - meshCoord.altitude += 100; + meshCoord.altitude += 50; contextXR.raycaster.ray.origin.copy(meshCoord.as(view.referenceCrs)); - contextXR.marker = new itowns.THREE.Mesh( - new itowns.THREE.CircleGeometry( 10, 32 ), - new itowns.THREE.MeshBasicMaterial( { color: 0xbcbcbc } ) - ); - contextXR.marker.position.copy(meshCoord.as(view.referenceCrs)); - contextXR.marker.lookAt(new itowns.THREE.Vector3(0, 0, 0)); - view.scene.add(contextXR.marker); + view.scene.add(contextXR.raycaster); contextXR.floor = new itowns.THREE.Mesh( new itowns.THREE.PlaneGeometry( 10000, 10000, 1, 1 ), new itowns.THREE.MeshBasicMaterial( { color: 0xff0000, side: itowns.THREE.DoubleSide } ) ); - - - - + contextXR.floor.name = "floor"; // position and orientation of the mesh contextXR.floor.position.copy(meshCoord.as(view.referenceCrs)); contextXR.floor.lookAt(new itowns.THREE.Vector3(0, 0, 0)); - // contextXR.floor.rotateX(Math.PI / 2); // update coordinate of the mesh contextXR.floor.updateMatrixWorld(); - // add the mesh to the scene view.scene.add(contextXR.floor); + meshCoord.altitude += 1; + contextXR.marker = new itowns.THREE.Mesh( + new itowns.THREE.CircleGeometry( 500, 32 ), + new itowns.THREE.MeshBasicMaterial( { color: 0xbcbcbc, side: itowns.THREE.DoubleSide } ) + ); + contextXR.marker.position.copy(meshCoord.as(view.referenceCrs)); + contextXR.marker.lookAt(new itowns.THREE.Vector3(0, 0, 0)); + contextXR.marker.name = "marker"; + contextXR.marker.updateMatrixWorld(); + view.scene.add(contextXR.marker); + view.notifyChange(); } function addControllers() { contextXR.renderer = view.mainLoop.gfxEngine.renderer; - //view.mainLoop.gfxEngine.renderer.xr.enabled = true; contextXR.controller1 = bindListeners(0); contextXR.controller2 = bindListeners(1); @@ -141,48 +141,75 @@ const controller = contextXR.renderer.xr.getController(index); controller.addEventListener( 'selectstart', onSelectStart ); controller.addEventListener( 'selectend', onSelectEnd ); - //var cameraTargetPosition = view.controls.getCameraCoordinate(); - //var meshCoord = cameraTargetPosition; - //meshCoord.altitude += 30; - - //controller.position.copy(meshCoord.as(view.referenceCrs)); - //controller.updateMatrixWorld(); return controller; } function onSelectStart() { - console.log("selecting"); this.userData.isSelecting = true; } function onSelectEnd() { - view.camera.camera3D.far = 20000000000; - console.log("stop selecting"); this.userData.isSelecting = false; if ( contextXR.INTERSECTION ) { - // try force z to 0 to keep its altitude but "maybe" it wont work as easy with a globe - // doesn't seem to be a delta but the destination coordinate; + console.log(contextXR.INTERSECTION); const cameraPos = view.camera.position().clone(); - const newCoordIntersection = new itowns.Coordinates(cameraPos.crs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); - const newCoordIntersection2 = newCoordIntersection.as(view.referenceCrs) - view.camera.setPosition(newCoordIntersection2); - const offsetPosition = { x: - contextXR.INTERSECTION.x, y: - contextXR.INTERSECTION.y, z: - contextXR.INTERSECTION.z, w: 1 }; - // const vectorOffsetPosition = new itowns.THREE.Vector3( contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z ); + const newCoordIntersectionproj = new itowns.Coordinates(cameraPos.crs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, cameraPos.z); + + showPosition('intersect', contextXR.INTERSECTION, 0x00ff00); + showPosition('simulated_intersect', newCoordIntersectionproj.as(view.referenceCrs), 0xffffff); var position = view.camera.position(); - var geodesicNormal = new itowns.THREE.Quaternion().setFromUnitVectors(new itowns.THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); - const trans = view.camera.camera3D.position.clone().multiplyScalar(-scale).applyQuaternion(geodesicNormal); + const offsetRotation = new itowns.THREE.Quaternion(); - const transform = new XRRigidTransform( trans, geodesicNormal ); + // compute targeted position relative to the origine camera. + const trans = { x: view.camera.initialPosition.x - newCoordIntersectionproj.x, y: view.camera.initialPosition.y - newCoordIntersectionproj.y, z: view.camera.initialPosition.z - newCoordIntersectionproj.z, w: 1 }; + const transform = new XRRigidTransform( trans, offsetRotation ); const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); - contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); + // contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); } - console.log('and then...'); } - // animate(); } + function showPosition(name, coordinates, color, radius = 50) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + + if(existingChild) { + existingChild.position.copy(coordinates); + } + else { + const previousPos = new itowns.THREE.Mesh( + new itowns.THREE.SphereGeometry( radius, 16, 8 ), + new itowns.THREE.MeshBasicMaterial( { color: color, wireframe: true } ) + ); + previousPos.name = name; + previousPos.position.copy(coordinates); + view.scene.add(previousPos); + } + } + + function showPositionLine(name, coordinates, color, upSize) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + + if(existingChild) { + existingChild.position.copy(coordinates); + } + else { + const points = []; + points.push( new itowns.THREE.Vector3( 0, 0, 0 ) ); + points.push( new itowns.THREE.Vector3( 0, upSize, 0 ) ); + + const line = new itowns.THREE.Line( + new itowns.THREE.BufferGeometry().setFromPoints(points), + new itowns.THREE.LineBasicMaterial({ color: color, vertexColors: true, blending: itowns.THREE.AdditiveBlending })); + line.position.copy(coordinates); + line.updateMatrixWorld(); + view.scene.add(line); + } + + } function render() { @@ -194,7 +221,7 @@ contextXR.tempMatrix.identity().extractRotation( contextXR.controller1.matrixWorld ); contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller1.matrixWorld ); contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); - const intersects = contextXR.raycaster.intersectObjects( [ contextXR.floor ] ); //TODO intersect terrain + const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // [contextXR.floor] if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; } @@ -202,20 +229,38 @@ contextXR.tempMatrix.identity().extractRotation( contextXR.controller2.matrixWorld ); contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller2.matrixWorld ); contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); - const intersects = contextXR.raycaster.intersectObjects( [ contextXR.floor ] ); + const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // [contextXR.floor] if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; } } + + // position is equals to the controller context, + // maybe apply matrix of the object to reverse ? if ( contextXR.INTERSECTION ) { console.log('intersection ', contextXR.INTERSECTION); - contextXR.marker.position.copy( contextXR.INTERSECTION ); + + var intersectionCoord = new itowns.Coordinates(view.referenceCrs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); + + var coordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord); + coordOnTerrain.coord.altitude += 30; + + showPosition('findMe', intersectionCoord, 0xfeff00, 20000); + showPositionLine('findMe2', intersectionCoord, 0xfe0c00, 20000000); + // FIXME position on the ground as an unknown delta... find why + // + var cameraTargetPosition = view.controls.getCameraCoordinate(); + console.log('intersection on ground ', coordOnTerrain.coord.as(view.referenceCrs)); + console.log('camera pos', cameraTargetPosition.as(view.referenceCrs)); + + // DEBUG if you intersect directly based on the fake floor you can just give contextXR.floor as copy position. + view.meshPointer.position.copy(intersectionCoord); + + contextXR.marker.position.copy(intersectionCoord); } contextXR.marker.visible = contextXR.INTERSECTION !== undefined; - // FIXME this seems weird as it must be already done elsewhere. find a way to bind to it. - // contextXR.renderer.render( view.scene, view.camera.camera3D ); } @@ -228,7 +273,7 @@ var THREE = itowns.THREE; var geometry = new THREE.CylinderGeometry(0, 10, 60, 8); var material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); - var mesh = new THREE.Mesh(geometry, material); + var meshPointer = new THREE.Mesh(geometry, material); // get the position on the globe, from the camera var cameraTargetPosition = view.controls.getLookAtCoordinate(); @@ -238,18 +283,18 @@ meshCoord.altitude += 30; // position and orientation of the mesh - mesh.position.copy(meshCoord.as(view.referenceCrs)); - mesh.lookAt(new THREE.Vector3(0, 0, 0)); - mesh.rotateX(Math.PI / 2); + meshPointer.position.copy(meshCoord.as(view.referenceCrs)); + meshPointer.lookAt(new THREE.Vector3(0, 0, 0)); + meshPointer.rotateX(Math.PI / 2); // update coordinate of the mesh - mesh.updateMatrixWorld(); + meshPointer.updateMatrixWorld(); // add the mesh to the scene - view.scene.add(mesh); + view.scene.add(meshPointer); // make the object usable from outside of the function - view.mesh = mesh; + view.meshPointer = meshPointer; view.notifyChange(); } @@ -259,7 +304,7 @@ console.info('Globe initialized'); - // addMeshToScene(); + addMeshToScene(); }); itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json') diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 3ac55a0ef8..ab6a2505d2 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -44,6 +44,7 @@ const initializeWebXR = (view, options) => { xr.getReferenceSpace('local'); const position = view.camera.position(); + view.camera.initialPosition = position.clone(); const geodesicNormal = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); const quat = new THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); @@ -56,6 +57,7 @@ const initializeWebXR = (view, options) => { xr.setReferenceSpace(teleportSpaceOffset); view.camera.camera3D = xr.getCamera(); + view.camera.camera3D.far = 20000000000; view.camera.resize(view.camera.width, view.camera.height); document.addEventListener('keydown', exitXRSession, false); @@ -94,8 +96,10 @@ const initializeWebXR = (view, options) => { rightGripController.name = 'rightGripController'; bindGripController(controllerModelFactory, leftGripController); bindGripController(controllerModelFactory, rightGripController); + view.scene.add(new THREE.HemisphereLight(0xa5a5a5, 0x898989, 3)); } + // TODO fix scale world. function applyScalePosition(group3D, scale) { group3D.position.multiplyScalar(-scale); } @@ -115,11 +119,7 @@ const initializeWebXR = (view, options) => { function bindGripController(controllerModelFactory, gripController) { gripController.add(controllerModelFactory.createControllerModel(gripController)); - // var cameraTargetPosition = view.controls.getCameraCoordinate(); - // var meshCoord = cameraTargetPosition; - // controllerGrip.position.copy(meshCoord.as(view.referenceCrs)); - // controllerGrip.updateMatrixWorld(); - applyScalePosition(gripController, scale); + // applyScalePosition(gripController, scale); view.scene.add(gripController); } @@ -133,8 +133,8 @@ const initializeWebXR = (view, options) => { case 'tracked-pointer': params.geometry = new THREE.BufferGeometry(); - params.geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 0, 0, -1], 3)); - params.geometry.setAttribute('color', new THREE.Float32BufferAttribute([0.5, 0.5, 0.5, 0, 0, 0], 3)); + params.geometry.setAttribute('position', new THREE.Float32BufferAttribute([0, 0, 0, 0, 0, -view.camera.camera3D.far], 3)); + params.geometry.setAttribute('color', new THREE.Float32BufferAttribute([1, 1, 1], 3)); params.material = new THREE.LineBasicMaterial({ vertexColors: true, blending: THREE.AdditiveBlending }); return new THREE.Line(params.geometry, params.material); From 483fb149fff5383cb08880cdabcf8784ff0ca9c2 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Mon, 28 Aug 2023 09:00:04 +0200 Subject: [PATCH 03/41] poc test more debug tools --- examples/view_3d_map_webxr.html | 31 ++++++++++++++++++++++++++++--- 1 file changed, 28 insertions(+), 3 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index d7dfe98222..95f7886725 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -95,7 +95,7 @@ contextXR.raycaster.ray.origin.copy(meshCoord.as(view.referenceCrs)); - view.scene.add(contextXR.raycaster); + //view.scene.add(contextXR.raycaster); contextXR.floor = new itowns.THREE.Mesh( new itowns.THREE.PlaneGeometry( 10000, 10000, 1, 1 ), @@ -166,7 +166,7 @@ const trans = { x: view.camera.initialPosition.x - newCoordIntersectionproj.x, y: view.camera.initialPosition.y - newCoordIntersectionproj.y, z: view.camera.initialPosition.z - newCoordIntersectionproj.z, w: 1 }; const transform = new XRRigidTransform( trans, offsetRotation ); const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); - // contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); + contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); } } } @@ -211,6 +211,29 @@ } + function renderRayCaster(name, raycaster, color) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + if(existingChild) { + // existingChild.position.copy(coordinates); + existingChild.geometry.attributes.position.setXYZ( 0, raycaster.ray.origin.x, raycaster.ray.origin.y, raycaster.ray.origin.z ); + existingChild.geometry.attributes.position.setXYZ(1, raycaster.ray.direction.x, raycaster.ray.direction.y, raycaster.ray.direction.z ); + existingChild.updateMatrixWorld(); + } + else { + const points = []; + points.push( raycaster.ray.origin ); + points.push( raycaster.ray.direction ); + + const line = new itowns.THREE.Line( + new itowns.THREE.BufferGeometry().setFromPoints(points), + new itowns.THREE.LineBasicMaterial({ color: color, vertexColors: true, blending: itowns.THREE.AdditiveBlending })); + line.updateMatrixWorld(); + line.name = name; + view.scene.add(line); + } + } + function render() { contextXR.INTERSECTION = undefined; @@ -224,6 +247,7 @@ const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // [contextXR.floor] if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; + renderRayCaster('raycasterLine_Left', contextXR.raycaster, 0xfe7c00); } } else if ( contextXR.controller2.userData.isSelecting === true ) { contextXR.tempMatrix.identity().extractRotation( contextXR.controller2.matrixWorld ); @@ -232,6 +256,7 @@ const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // [contextXR.floor] if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; + renderRayCaster('raycasterLine_right', contextXR.raycaster, 0xfe7c00); } } @@ -247,7 +272,7 @@ var coordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord); coordOnTerrain.coord.altitude += 30; - showPosition('findMe', intersectionCoord, 0xfeff00, 20000); + showPosition('findMe', intersectionCoord, 0xfeff00, 5500); showPositionLine('findMe2', intersectionCoord, 0xfe0c00, 20000000); // FIXME position on the ground as an unknown delta... find why // From fc3b3352418d65dc4373641704b2eea5f48367b8 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 29 Aug 2023 10:44:23 +0200 Subject: [PATCH 04/41] poc fixed some sides effect and add line debug tool --- examples/view_3d_map_webxr.html | 121 +++++++++++++++++--------------- 1 file changed, 65 insertions(+), 56 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 95f7886725..4055c8c75f 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -83,15 +83,15 @@ // ------------- webxr interaction ---------------- view.scene.matrixWorldAutoUpdate = true; - addFakeRoom(); + addControllers(); function addFakeRoom() { contextXR.raycaster = new itowns.THREE.Raycaster(); - var cameraTargetPosition = view.controls.getCameraCoordinate(); - var meshCoord = cameraTargetPosition; - meshCoord.altitude += 50; + var cameraTargetPosition = view.controls.getCameraCoordinate().clone(); + var meshCoord = new itowns.Coordinates(cameraTargetPosition.crs, cameraTargetPosition.x, cameraTargetPosition.y, cameraTargetPosition.z ); + meshCoord.altitude -= 10050; contextXR.raycaster.ray.origin.copy(meshCoord.as(view.referenceCrs)); @@ -99,7 +99,7 @@ contextXR.floor = new itowns.THREE.Mesh( new itowns.THREE.PlaneGeometry( 10000, 10000, 1, 1 ), - new itowns.THREE.MeshBasicMaterial( { color: 0xff0000, side: itowns.THREE.DoubleSide } ) + new itowns.THREE.MeshBasicMaterial( { color: 0x222222, side: itowns.THREE.DoubleSide } ) ); contextXR.floor.name = "floor"; // position and orientation of the mesh @@ -113,7 +113,7 @@ meshCoord.altitude += 1; contextXR.marker = new itowns.THREE.Mesh( - new itowns.THREE.CircleGeometry( 500, 32 ), + new itowns.THREE.CircleGeometry( 50, 32 ), new itowns.THREE.MeshBasicMaterial( { color: 0xbcbcbc, side: itowns.THREE.DoubleSide } ) ); contextXR.marker.position.copy(meshCoord.as(view.referenceCrs)); @@ -135,6 +135,7 @@ { contextXR.baseReferenceSpace = contextXR.renderer.xr.getReferenceSpace(); console.log('binding referenceSpace'); + addFakeRoom(); }); function bindListeners(index) { @@ -150,20 +151,13 @@ function onSelectEnd() { this.userData.isSelecting = false; - if ( contextXR.INTERSECTION ) { - console.log(contextXR.INTERSECTION); - - const cameraPos = view.camera.position().clone(); - const newCoordIntersectionproj = new itowns.Coordinates(cameraPos.crs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, cameraPos.z); - - showPosition('intersect', contextXR.INTERSECTION, 0x00ff00); - showPosition('simulated_intersect', newCoordIntersectionproj.as(view.referenceCrs), 0xffffff); - - var position = view.camera.position(); + if ( contextXR.coordOnCamera ) { const offsetRotation = new itowns.THREE.Quaternion(); + const projectedCoordinate = contextXR.coordOnCamera.coord.as(view.referenceCrs); + showPosition('intersect', projectedCoordinate, 0x0000ff); // compute targeted position relative to the origine camera. - const trans = { x: view.camera.initialPosition.x - newCoordIntersectionproj.x, y: view.camera.initialPosition.y - newCoordIntersectionproj.y, z: view.camera.initialPosition.z - newCoordIntersectionproj.z, w: 1 }; + const trans = { x: view.camera.initialPosition.x - projectedCoordinate.x, y: view.camera.initialPosition.y - projectedCoordinate.y, z: view.camera.initialPosition.z - projectedCoordinate.z, w: 1 }; const transform = new XRRigidTransform( trans, offsetRotation ); const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); @@ -189,7 +183,7 @@ } } - function showPositionLine(name, coordinates, color, upSize) { + function showPositionVerticalLine(name, coordinates, color, upSize) { var existingChild; view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); @@ -198,15 +192,18 @@ } else { const points = []; - points.push( new itowns.THREE.Vector3( 0, 0, 0 ) ); - points.push( new itowns.THREE.Vector3( 0, upSize, 0 ) ); - - const line = new itowns.THREE.Line( - new itowns.THREE.BufferGeometry().setFromPoints(points), - new itowns.THREE.LineBasicMaterial({ color: color, vertexColors: true, blending: itowns.THREE.AdditiveBlending })); - line.position.copy(coordinates); - line.updateMatrixWorld(); - view.scene.add(line); + points.push(new itowns.THREE.Vector3(0,0,0)); + // upward direction + points.push(new itowns.THREE.Vector3(0, 0, -upSize)); + + const line = new itowns.THREE.Line( + new itowns.THREE.BufferGeometry().setFromPoints(points), + new itowns.THREE.LineBasicMaterial({ color: color })); + line.position.copy(coordinates); + //necessary to "look" vertically + line.lookAt(new itowns.THREE.Vector3(0, 0, 1)); + line.name = name; + view.scene.add(line); } } @@ -215,28 +212,21 @@ var existingChild; view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); if(existingChild) { - // existingChild.position.copy(coordinates); - existingChild.geometry.attributes.position.setXYZ( 0, raycaster.ray.origin.x, raycaster.ray.origin.y, raycaster.ray.origin.z ); - existingChild.geometry.attributes.position.setXYZ(1, raycaster.ray.direction.x, raycaster.ray.direction.y, raycaster.ray.direction.z ); - existingChild.updateMatrixWorld(); + existingChild.setDirection(raycaster.ray.direction); + existingChild.origin = raycaster.ray.origin; } else { - const points = []; - points.push( raycaster.ray.origin ); - points.push( raycaster.ray.direction ); - const line = new itowns.THREE.Line( - new itowns.THREE.BufferGeometry().setFromPoints(points), - new itowns.THREE.LineBasicMaterial({ color: color, vertexColors: true, blending: itowns.THREE.AdditiveBlending })); - line.updateMatrixWorld(); - line.name = name; - view.scene.add(line); + const arrow = new itowns.THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 10, color); + arrow.name = name; + view.scene.add(arrow); } } function render() { contextXR.INTERSECTION = undefined; + contextXR.coordOnCamera = undefined; if ( contextXR.controller1.userData.isSelecting === true ) { let temp = {loc: new itowns.THREE.Vector3(), rot: new itowns.THREE.Quaternion(), scale: new itowns.THREE.Vector3()}; // in threejs it is used from matrixWorld @@ -244,7 +234,7 @@ contextXR.tempMatrix.identity().extractRotation( contextXR.controller1.matrixWorld ); contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller1.matrixWorld ); contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); - const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // [contextXR.floor] + const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); //( [contextXR.floor]); if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; renderRayCaster('raycasterLine_Left', contextXR.raycaster, 0xfe7c00); @@ -253,38 +243,57 @@ contextXR.tempMatrix.identity().extractRotation( contextXR.controller2.matrixWorld ); contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller2.matrixWorld ); contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); - const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // [contextXR.floor] + const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // ([contextXR.floor]); // if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; renderRayCaster('raycasterLine_right', contextXR.raycaster, 0xfe7c00); } } - - // position is equals to the controller context, - // maybe apply matrix of the object to reverse ? if ( contextXR.INTERSECTION ) { - console.log('intersection ', contextXR.INTERSECTION); var intersectionCoord = new itowns.Coordinates(view.referenceCrs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); - var coordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord); - coordOnTerrain.coord.altitude += 30; + var cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + + var justElevationAt = itowns.DEMUtils.getElevationValueAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + + var coordOnCamera = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord, itowns.DEMUtils.PRECISE_READ_Z); + coordOnCamera.coord.altitude += cameraCoordOnTerrain.coord.z; + + contextXR.coordOnCamera = coordOnCamera; + + var cameraFloorIntersect = view.controls.getCameraCoordinate().clone(); + // FIXME result is not stricly the camera position as expected, maybe due to the intersection position? + // FIXME getCameraCoordinate doesn't update on webxr camera move, maybe test it in "unbounded" / "bounded" context + var cameraTargetPosition = view.controls.getCameraCoordinate().clone(); + /* the staircase effect find explanation in regard of view.camera.camera3D.position z evolution + for some reason, position is highly rounded when not to initial vr pos. + but camera matrices seems like in the same level of definition... + */ + console.log('camera position ', view.camera.camera3D.position); + cameraFloorIntersect.altitude = justElevationAt; showPosition('findMe', intersectionCoord, 0xfeff00, 5500); - showPositionLine('findMe2', intersectionCoord, 0xfe0c00, 20000000); + showPosition('findMePRECISE', coordOnCamera.coord.as(view.referenceCrs), 0xaffb00, 100); + showPosition('findMeCamera', cameraFloorIntersect.as(view.referenceCrs), 0x44aaff, 100); + + showPositionVerticalLine('findMe2', intersectionCoord, 0xfe0c00, 10000); // FIXME position on the ground as an unknown delta... find why - // - var cameraTargetPosition = view.controls.getCameraCoordinate(); - console.log('intersection on ground ', coordOnTerrain.coord.as(view.referenceCrs)); - console.log('camera pos', cameraTargetPosition.as(view.referenceCrs)); + - // DEBUG if you intersect directly based on the fake floor you can just give contextXR.floor as copy position. - view.meshPointer.position.copy(intersectionCoord); - contextXR.marker.position.copy(intersectionCoord); + + + // showing camera floor intersection + contextXR.marker.position.copy(cameraFloorIntersect.as(view.referenceCrs)); + + + view.meshPointer.position.copy(intersectionCoord); } + + console.log('camera position ', view.camera.camera3D.position); contextXR.marker.visible = contextXR.INTERSECTION !== undefined; } From 239748bdec2f8fa84fd347ae3edeeeba59a5ddba Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Thu, 31 Aug 2023 18:05:50 +0200 Subject: [PATCH 05/41] poc added stick and buttons listeners --- examples/view_3d_map_webxr.html | 57 ++++++++++++++++++++++------- src/Renderer/WebXR.js | 63 ++++++++++++++++++++++++++++----- 2 files changed, 98 insertions(+), 22 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 4055c8c75f..c53ffd5890 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -130,6 +130,8 @@ contextXR.renderer = view.mainLoop.gfxEngine.renderer; contextXR.controller1 = bindListeners(0); contextXR.controller2 = bindListeners(1); + contextXR.controller1.addEventListener('itowns-xr-axes-changed', onLeftAxisChanged); + contextXR.controller1.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); contextXR.renderer.xr.addEventListener( 'sessionstart', function () { @@ -142,6 +144,7 @@ const controller = contextXR.renderer.xr.getController(index); controller.addEventListener( 'selectstart', onSelectStart ); controller.addEventListener( 'selectend', onSelectEnd ); + controller.addEventListener('itowns-xr-button-pressed', onButtonPressed); return controller; } @@ -163,6 +166,34 @@ contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); } } + + function onButtonPressed(data) { + // console.log('received button pressed', data); + } + + function onLeftAxisChanged(data) { + if (data.target.name !== 'leftController') { + return; + } + var ctrl = data.message.controller; + // console.log('received axis changed', data, ctrl.lastAxisItem, ctrl.gamepad.axes); + if ( contextXR.INTERSECTION ) { + contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; + } else { + // if no teleportation ongoing, just adjust camera elevation + var cameraPosition = view.controls.getCameraCoordinate(); + cameraPosition.altitude -= ctrl.gamepad.axes[3] * 100; + view.camera.setPosition(cameraPosition.as(view.referenceCrs)); + } + } + + function onRightAxisChanged(data) { + if (data.target.name !== 'rightController') { + return; + } + + console.log('right axis changed'); + } } function showPosition(name, coordinates, color, radius = 50) { @@ -226,7 +257,8 @@ function render() { contextXR.INTERSECTION = undefined; - contextXR.coordOnCamera = undefined; + + // test intersection coordinate with terrain if ( contextXR.controller1.userData.isSelecting === true ) { let temp = {loc: new itowns.THREE.Vector3(), rot: new itowns.THREE.Quaternion(), scale: new itowns.THREE.Vector3()}; // in threejs it is used from matrixWorld @@ -254,13 +286,17 @@ { var intersectionCoord = new itowns.Coordinates(view.referenceCrs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); - - var cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); - var justElevationAt = itowns.DEMUtils.getElevationValueAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); var coordOnCamera = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord, itowns.DEMUtils.PRECISE_READ_Z); - coordOnCamera.coord.altitude += cameraCoordOnTerrain.coord.z; + if (contextXR.coordOnCamera && contextXR.deltaAltitude) { + coordOnCamera.coord.altitude += contextXR.deltaAltitude; + } else { + // by default set altitude to the current camera elevation from terrain + let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + coordOnCamera.coord.altitude += cameraCoordOnTerrain.coord.z; + contextXR.deltaAltitude = cameraCoordOnTerrain.coord.z; + } contextXR.coordOnCamera = coordOnCamera; @@ -281,25 +317,20 @@ showPositionVerticalLine('findMe2', intersectionCoord, 0xfe0c00, 10000); // FIXME position on the ground as an unknown delta... find why - - - - // showing camera floor intersection contextXR.marker.position.copy(cameraFloorIntersect.as(view.referenceCrs)); view.meshPointer.position.copy(intersectionCoord); + } else { + // if no more intersection, reset coords. + contextXR.coordOnCamera = undefined; } - - console.log('camera position ', view.camera.camera3D.position); contextXR.marker.visible = contextXR.INTERSECTION !== undefined; } - - //--------- add fake to ground function addMeshToScene() { diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index ab6a2505d2..a255c6ee15 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -36,23 +36,28 @@ const initializeWebXR = (view, options) => { } }; - initControllers(webXRManager); + const xrControllers = initControllers(webXRManager); view.scene.scale.multiplyScalar(scale); view.scene.updateMatrixWorld(); xr.enabled = true; xr.getReferenceSpace('local'); + view.camera.camera3D.position.multiplyScalar(scale); + view.camera.camera3D.updateMatrixWorld(); const position = view.camera.position(); view.camera.initialPosition = position.clone(); const geodesicNormal = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); const quat = new THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); + // https://github.com/immersive-web/webxr/issues/1236 for high position value const trans = camera.position.clone().multiplyScalar(-scale).applyQuaternion(quat); const transform = new XRRigidTransform(trans, quat); + // here position seems ok {x: 4485948.637198923, y: 476198.0416370128, z: 4497216.056600053, w: 1} const baseReferenceSpace = xr.getReferenceSpace(); const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace(transform); + // there it is not anymore : originOffset Matrix is : 4485948.5, 476198.03125, 4497216 xr.setReferenceSpace(teleportSpaceOffset); @@ -69,8 +74,13 @@ const initializeWebXR = (view, options) => { if (options.callback) { options.callback(); } + + listenGamepad(xrControllers.left); + listenGamepad(xrControllers.right); + view.camera.camera3D.updateMatrix(); view.camera.camera3D.updateMatrixWorld(true); + if (view.scene.matrixWorldAutoUpdate === true) { view.scene.updateMatrixWorld(); } @@ -79,9 +89,49 @@ const initializeWebXR = (view, options) => { } view.mainLoop.step(view, timestamp); + }); }); + let endGamePadtrackEmit = false; + + /* + Listening {XRInputSource} and emit changes for convenience user binding + */ + function listenGamepad(controller) { + if (controller.gamepad) { + // gamepad.axes = [0, x, y, z]; + const gamepad = controller.gamepad; + if (gamepad.axes.lastItem === 0 && endGamePadtrackEmit) { + return; + } else { + endGamePadtrackEmit = false; + } + + controller.dispatchEvent({ type: 'itowns-xr-axes-changed', message: { controller } }); + controller.lastAxisItem = gamepad.axes.lastItem; + controller.lastAxisIndex = gamepad.axes.lastIndex; + if (gamepad.axes.lastItem === 0) { + endGamePadtrackEmit = true; + } + + for (const [index, button] of gamepad.buttons.entries()) { + if (button.pressed) { + // 0 - gachette index + // 1 - gachette majeur + // 3 - stick pressed + // 4 - botton button + // 5 - upper button + controller.dispatchEvent({ type: 'itowns-xr-button-pressed', message: { buttonIndex: index, button } }); + controller.lastButtonItem = gamepad.lastItem; + } + if (button.touched) { + // triggered really often + } + } + } + } + function initControllers(webXRManager) { const controllerModelFactory = new XRControllerModelFactory(); const leftController = webXRManager.getController(0); @@ -97,11 +147,7 @@ const initializeWebXR = (view, options) => { bindGripController(controllerModelFactory, leftGripController); bindGripController(controllerModelFactory, rightGripController); view.scene.add(new THREE.HemisphereLight(0xa5a5a5, 0x898989, 3)); - } - - // TODO fix scale world. - function applyScalePosition(group3D, scale) { - group3D.position.multiplyScalar(-scale); + return { left: leftController, right: rightController }; } function bindControllerListeners(controller) { @@ -110,8 +156,8 @@ const initializeWebXR = (view, options) => { }); controller.addEventListener('connected', function addCtrl(event) { this.add(buildController(event.data)); - // applyScalePosition(this, scale); - console.log('after binding'); + // {XRInputSource} event.data + controller.gamepad = event.data.gamepad; }); // controller.matrix.makeScale(scale, scale, scale); view.scene.add(controller); @@ -119,7 +165,6 @@ const initializeWebXR = (view, options) => { function bindGripController(controllerModelFactory, gripController) { gripController.add(controllerModelFactory.createControllerModel(gripController)); - // applyScalePosition(gripController, scale); view.scene.add(gripController); } From 482dc3d1482d396582c2a0c84413b9d364e3d517 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Mon, 4 Sep 2023 18:19:36 +0200 Subject: [PATCH 06/41] added stick translation --- examples/view_3d_map_webxr.html | 30 ++++++++++++++----- src/Renderer/WebXR.js | 53 +++++++++++++++++++++++---------- 2 files changed, 59 insertions(+), 24 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index c53ffd5890..fc538070e2 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -131,12 +131,13 @@ contextXR.controller1 = bindListeners(0); contextXR.controller2 = bindListeners(1); contextXR.controller1.addEventListener('itowns-xr-axes-changed', onLeftAxisChanged); - contextXR.controller1.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); + contextXR.controller2.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); contextXR.renderer.xr.addEventListener( 'sessionstart', function () { contextXR.baseReferenceSpace = contextXR.renderer.xr.getReferenceSpace(); console.log('binding referenceSpace'); + view.scene.children.forEach((child) => {if(child.name === 'xrHeadset') { contextXR.xrHeadSet = child;} }); addFakeRoom(); }); @@ -166,7 +167,7 @@ contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); } } - + function onButtonPressed(data) { // console.log('received button pressed', data); } @@ -176,23 +177,36 @@ return; } var ctrl = data.message.controller; - // console.log('received axis changed', data, ctrl.lastAxisItem, ctrl.gamepad.axes); if ( contextXR.INTERSECTION ) { contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; } else { // if no teleportation ongoing, just adjust camera elevation - var cameraPosition = view.controls.getCameraCoordinate(); - cameraPosition.altitude -= ctrl.gamepad.axes[3] * 100; - view.camera.setPosition(cameraPosition.as(view.referenceCrs)); - } + var speed = ctrl.gamepad.axes[3] * 100; + let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + if(!cameraCoordOnTerrain) { + console.error('Camera doesn\'t intersect terrain'); + return; + } + var direction = cameraCoordOnTerrain.coord.geodesicNormal.clone().multiplyScalar(-speed); + contextXR.xrHeadSet.position.add(direction); + } } function onRightAxisChanged(data) { if (data.target.name !== 'rightController') { return; } + var ctrl = data.message.controller; + if ( contextXR.INTERSECTION ) { - console.log('right axis changed'); + } else { + // flying following the camera look at + var speed = ctrl.gamepad.axes[3] * 100; + var matrixHeadset = new itowns.THREE.Matrix4(); + matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); + var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(matrixHeadset).multiplyScalar(speed); + contextXR.xrHeadSet.position.add(direction); + } } } diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index a255c6ee15..a248c0b0f0 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -2,6 +2,7 @@ import * as THREE from 'three'; import { XRControllerModelFactory } from 'ThreeExtended/webxr/XRControllerModelFactory'; +import Coordinates from 'Core/Geographic/Coordinates'; async function shutdownXR(session) { if (session) { @@ -36,17 +37,34 @@ const initializeWebXR = (view, options) => { } }; - const xrControllers = initControllers(webXRManager); + const vrHeadSet = new THREE.Object3D(); + vrHeadSet.name = 'xrHeadset'; view.scene.scale.multiplyScalar(scale); view.scene.updateMatrixWorld(); + + + const xrControllers = initControllers(webXRManager, vrHeadSet); + + + // avoid precision issues for controllers + allows continuous camera movements + const position = view.controls.getCameraCoordinate().as(view.referenceCrs); + view.camera.initialPosition = view.camera.position().clone(); + const cameraOrientation = view.controls.getCameraOrientation(); + + const itownsDefaultView = { loc: new THREE.Vector3(), rot: new THREE.Quaternion(), scale: new THREE.Vector3() }; + view.controls.camera.matrix.decompose(itownsDefaultView.loc, itownsDefaultView.rot, itownsDefaultView.scale); + // vrHeadSet.position.copy(new THREE.Vector3(position.x, position.y, position.z)); + // vrHeadSet.applyQuaternion(itownsDefaultView.rot); + + view.scene.add(vrHeadSet); + + + + xr.enabled = true; xr.getReferenceSpace('local'); - view.camera.camera3D.position.multiplyScalar(scale); - view.camera.camera3D.updateMatrixWorld(); - const position = view.camera.position(); - view.camera.initialPosition = position.clone(); const geodesicNormal = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); const quat = new THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); @@ -60,11 +78,15 @@ const initializeWebXR = (view, options) => { // there it is not anymore : originOffset Matrix is : 4485948.5, 476198.03125, 4497216 xr.setReferenceSpace(teleportSpaceOffset); + view.notifyChange(); view.camera.camera3D = xr.getCamera(); view.camera.camera3D.far = 20000000000; view.camera.resize(view.camera.width, view.camera.height); + vrHeadSet.add(view.camera.camera3D); + // view.camera.setPosition(new Coordinates(view.referenceCrs, 0, 0, 0)); + document.addEventListener('keydown', exitXRSession, false); // TODO Fix asynchronization between xr and MainLoop render loops. @@ -132,25 +154,25 @@ const initializeWebXR = (view, options) => { } } - function initControllers(webXRManager) { + function initControllers(webXRManager, vrHeadSet) { const controllerModelFactory = new XRControllerModelFactory(); const leftController = webXRManager.getController(0); leftController.name = 'leftController'; const rightController = webXRManager.getController(1); rightController.name = 'rightController'; - bindControllerListeners(leftController); - bindControllerListeners(rightController); + bindControllerListeners(leftController, vrHeadSet); + bindControllerListeners(rightController, vrHeadSet); const leftGripController = webXRManager.getControllerGrip(0); leftGripController.name = 'leftGripController'; const rightGripController = webXRManager.getControllerGrip(1); rightGripController.name = 'rightGripController'; - bindGripController(controllerModelFactory, leftGripController); - bindGripController(controllerModelFactory, rightGripController); - view.scene.add(new THREE.HemisphereLight(0xa5a5a5, 0x898989, 3)); + bindGripController(controllerModelFactory, leftGripController, vrHeadSet); + bindGripController(controllerModelFactory, rightGripController, vrHeadSet); + vrHeadSet.add(new THREE.HemisphereLight(0xa5a5a5, 0x898989, 3)); return { left: leftController, right: rightController }; } - function bindControllerListeners(controller) { + function bindControllerListeners(controller, vrHeadSet) { controller.addEventListener('disconnected', function removeCtrl() { this.remove(this.children[0]); }); @@ -159,13 +181,12 @@ const initializeWebXR = (view, options) => { // {XRInputSource} event.data controller.gamepad = event.data.gamepad; }); - // controller.matrix.makeScale(scale, scale, scale); - view.scene.add(controller); + vrHeadSet.add(controller); } - function bindGripController(controllerModelFactory, gripController) { + function bindGripController(controllerModelFactory, gripController, vrHeadSet) { gripController.add(controllerModelFactory.createControllerModel(gripController)); - view.scene.add(gripController); + vrHeadSet.add(gripController); } function buildController(data) { From c66955e38315e8485b657191b6d09aac95ba975e Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Wed, 6 Sep 2023 10:29:17 +0200 Subject: [PATCH 07/41] fixed side effect with Coordinates values overrides --- examples/view_3d_map_webxr.html | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index fc538070e2..aedf4b449e 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -39,7 +39,8 @@ // `viewerDiv` will contain iTowns' rendering area (``) const viewerDiv = document.getElementById('viewerDiv'); const contextXR = { - tempMatrix: new itowns.THREE.Matrix4() + tempMatrix: new itowns.THREE.Matrix4(), + coordOnCamera: {} }; const scale = 1; @@ -157,7 +158,7 @@ this.userData.isSelecting = false; if ( contextXR.coordOnCamera ) { const offsetRotation = new itowns.THREE.Quaternion(); - const projectedCoordinate = contextXR.coordOnCamera.coord.as(view.referenceCrs); + const projectedCoordinate = contextXR.coordOnCamera.as(view.referenceCrs); showPosition('intersect', projectedCoordinate, 0x0000ff); // compute targeted position relative to the origine camera. @@ -302,6 +303,7 @@ var intersectionCoord = new itowns.Coordinates(view.referenceCrs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); var justElevationAt = itowns.DEMUtils.getElevationValueAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + // not a {itowns.Coordinates} var coordOnCamera = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord, itowns.DEMUtils.PRECISE_READ_Z); if (contextXR.coordOnCamera && contextXR.deltaAltitude) { coordOnCamera.coord.altitude += contextXR.deltaAltitude; @@ -312,7 +314,9 @@ contextXR.deltaAltitude = cameraCoordOnTerrain.coord.z; } - contextXR.coordOnCamera = coordOnCamera; + // must clone Coordinate to avoid side effect, maybe with DEMUtils, but not sure. + contextXR.coordOnCamera = new itowns.Coordinates(coordOnCamera.coord.crs); + contextXR.coordOnCamera.setFromVector3(coordOnCamera.coord); var cameraFloorIntersect = view.controls.getCameraCoordinate().clone(); // FIXME result is not stricly the camera position as expected, maybe due to the intersection position? @@ -326,7 +330,7 @@ cameraFloorIntersect.altitude = justElevationAt; showPosition('findMe', intersectionCoord, 0xfeff00, 5500); - showPosition('findMePRECISE', coordOnCamera.coord.as(view.referenceCrs), 0xaffb00, 100); + showPosition('findMePRECISE', contextXR.coordOnCamera.as(view.referenceCrs), 0xaffb00, 100); showPosition('findMeCamera', cameraFloorIntersect.as(view.referenceCrs), 0x44aaff, 100); showPositionVerticalLine('findMe2', intersectionCoord, 0xfe0c00, 10000); @@ -386,6 +390,8 @@ addMeshToScene(); }); + var d = new debug.Debug(view, debugMenu.gui); + debug.createTileDebugUI(debugMenu.gui, view, view.tileLayer, d); itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json') .then(addElevationLayerFromConfig); itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json') From d797a0ac6af0822dce6f045b0c294d2a8d47e5a2 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Wed, 6 Sep 2023 12:30:23 +0200 Subject: [PATCH 08/41] better flying forward/backward --- examples/view_3d_map_webxr.html | 22 ++++++++++++++++++---- src/Renderer/WebXR.js | 22 +++++++++++++--------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index aedf4b449e..dc205137e7 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -133,6 +133,8 @@ contextXR.controller2 = bindListeners(1); contextXR.controller1.addEventListener('itowns-xr-axes-changed', onLeftAxisChanged); contextXR.controller2.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); + contextXR.controller2.addEventListener('itowns-xr-axes-stop', onRightAxisStop); + contextXR.renderer.xr.addEventListener( 'sessionstart', function () { @@ -157,6 +159,8 @@ function onSelectEnd() { this.userData.isSelecting = false; if ( contextXR.coordOnCamera ) { + // TODO for now it keeps delta with a translation, make sure to reset it when teleporting. + // TODO follow the lead of having the camera inside the xrHeadSet to track delta of continuous movements. const offsetRotation = new itowns.THREE.Quaternion(); const projectedCoordinate = contextXR.coordOnCamera.as(view.referenceCrs); showPosition('intersect', projectedCoordinate, 0x0000ff); @@ -201,14 +205,24 @@ if ( contextXR.INTERSECTION ) { } else { - // flying following the camera look at + if (!data.message.controller.flyDirectionMatrix) { + // locking camera look at + var matrixHeadset = new itowns.THREE.Matrix4(); + matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); + data.message.controller.flyDirectionMatrix = matrixHeadset; + } + + // flying following the locked camera look at var speed = ctrl.gamepad.axes[3] * 100; - var matrixHeadset = new itowns.THREE.Matrix4(); - matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); - var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(matrixHeadset).multiplyScalar(speed); + + var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(data.message.controller.flyDirectionMatrix).multiplyScalar(speed); contextXR.xrHeadSet.position.add(direction); } } + + function onRightAxisStop(data) { + data.message.controller.flyDirectionMatrix = undefined; + } } function showPosition(name, coordinates, color, radius = 50) { diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index a248c0b0f0..d74ac26f74 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -122,21 +122,25 @@ const initializeWebXR = (view, options) => { */ function listenGamepad(controller) { if (controller.gamepad) { - // gamepad.axes = [0, x, y, z]; + // gamepad.axes = [0, 0, x, y]; const gamepad = controller.gamepad; - if (gamepad.axes.lastItem === 0 && endGamePadtrackEmit) { + if (controller.isStickActive && gamepad.axes.lastItem === 0 && endGamePadtrackEmit) { + controller.dispatchEvent({ type: 'itowns-xr-axes-stop', message: { controller } }); + controller.isStickActive = false; return; - } else { + } else if (!controller.isStickActive && gamepad.axes.lastItem !== 0) { endGamePadtrackEmit = false; - } - - controller.dispatchEvent({ type: 'itowns-xr-axes-changed', message: { controller } }); - controller.lastAxisItem = gamepad.axes.lastItem; - controller.lastAxisIndex = gamepad.axes.lastIndex; - if (gamepad.axes.lastItem === 0) { + controller.isStickActive = true; + } else if (controller.isStickActive && gamepad.axes.lastItem === 0) { endGamePadtrackEmit = true; } + if (gamepad.axes.lastItem !== 0) { + controller.dispatchEvent({ type: 'itowns-xr-axes-changed', message: { controller } }); + controller.lastAxisItem = gamepad.axes.lastItem; + controller.lastAxisIndex = gamepad.axes.lastIndex; + } + for (const [index, button] of gamepad.buttons.entries()) { if (button.pressed) { // 0 - gachette index From 27cc4964d288f6ecf7f91d665512f3a81f4fff92 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Wed, 20 Sep 2023 15:35:09 +0200 Subject: [PATCH 09/41] feat rotation camera up axis --- examples/view_3d_map_webxr.html | 102 +++++++++++++++++++++----------- src/Renderer/WebXR.js | 6 +- 2 files changed, 71 insertions(+), 37 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index dc205137e7..f9383b3c5c 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -40,7 +40,8 @@ const viewerDiv = document.getElementById('viewerDiv'); const contextXR = { tempMatrix: new itowns.THREE.Matrix4(), - coordOnCamera: {} + coordOnCamera: {}, + deltaRotation: 0 }; const scale = 1; @@ -87,6 +88,13 @@ addControllers(); + function getGeodesicalQuaternion() { + //TODO can be optimized with better cache + const position = view.controls.getCameraCoordinate().as(view.referenceCrs); + const geodesicNormal = new itowns.THREE.Quaternion().setFromUnitVectors(new itowns.THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); + return new itowns.THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); + } + function addFakeRoom() { contextXR.raycaster = new itowns.THREE.Raycaster(); @@ -95,9 +103,6 @@ meshCoord.altitude -= 10050; contextXR.raycaster.ray.origin.copy(meshCoord.as(view.referenceCrs)); - - //view.scene.add(contextXR.raycaster); - contextXR.floor = new itowns.THREE.Mesh( new itowns.THREE.PlaneGeometry( 10000, 10000, 1, 1 ), new itowns.THREE.MeshBasicMaterial( { color: 0x222222, side: itowns.THREE.DoubleSide } ) @@ -139,7 +144,6 @@ contextXR.renderer.xr.addEventListener( 'sessionstart', function () { contextXR.baseReferenceSpace = contextXR.renderer.xr.getReferenceSpace(); - console.log('binding referenceSpace'); view.scene.children.forEach((child) => {if(child.name === 'xrHeadset') { contextXR.xrHeadSet = child;} }); addFakeRoom(); }); @@ -159,14 +163,12 @@ function onSelectEnd() { this.userData.isSelecting = false; if ( contextXR.coordOnCamera ) { - // TODO for now it keeps delta with a translation, make sure to reset it when teleporting. - // TODO follow the lead of having the camera inside the xrHeadSet to track delta of continuous movements. - const offsetRotation = new itowns.THREE.Quaternion(); + const offsetRotation = getGeodesicalQuaternion(); const projectedCoordinate = contextXR.coordOnCamera.as(view.referenceCrs); showPosition('intersect', projectedCoordinate, 0x0000ff); // compute targeted position relative to the origine camera. - const trans = { x: view.camera.initialPosition.x - projectedCoordinate.x, y: view.camera.initialPosition.y - projectedCoordinate.y, z: view.camera.initialPosition.z - projectedCoordinate.z, w: 1 }; + const trans = new itowns.THREE.Vector3(projectedCoordinate.x, projectedCoordinate.y, projectedCoordinate.z).multiplyScalar(-1).applyQuaternion(offsetRotation); // TODO apply quat to const transform = new XRRigidTransform( trans, offsetRotation ); const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); @@ -177,50 +179,82 @@ // console.log('received button pressed', data); } + // rotation controls function onLeftAxisChanged(data) { if (data.target.name !== 'leftController') { return; } var ctrl = data.message.controller; if ( contextXR.INTERSECTION ) { - contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; + } else { - // if no teleportation ongoing, just adjust camera elevation - var speed = ctrl.gamepad.axes[3] * 100; - let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); - if(!cameraCoordOnTerrain) { - console.error('Camera doesn\'t intersect terrain'); - return; - } - var direction = cameraCoordOnTerrain.coord.geodesicNormal.clone().multiplyScalar(-speed); - contextXR.xrHeadSet.position.add(direction); + applyRotation(ctrl); + // udpateCameraElevationLive(ctrl); } - } + } + + + function applyRotation(ctrl) { + if(ctrl.gamepad.axes[2] === 0) { + return; + } + contextXR.deltaRotation += Math.PI / (160 * ctrl.gamepad.axes[2]); + const offsetRotation = getGeodesicalQuaternion(); + var thetaRotMatrix = new itowns.THREE.Matrix4().identity().makeRotationY(contextXR.deltaRotation); + var rotationQuartenion = new itowns.THREE.Quaternion().setFromRotationMatrix(thetaRotMatrix).normalize(); + offsetRotation.premultiply(rotationQuartenion); + const trans = view.camera.camera3D.position.clone().multiplyScalar(-1).applyQuaternion(offsetRotation); + const transform = new XRRigidTransform( trans, offsetRotation ); + const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); + contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); + } + // TODO use it bound to right controller some way + function udpateCameraElevationLive(ctrl) { + // if no teleportation ongoing, just adjust camera elevation + var speed = ctrl.gamepad.axes[3] * 100; + let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + if(!cameraCoordOnTerrain) { + console.error('Camera doesn\'t intersect terrain'); + return; + } + var direction = cameraCoordOnTerrain.coord.geodesicNormal.clone().multiplyScalar(-speed); + contextXR.xrHeadSet.position.add(direction); + } + + // translation controls function onRightAxisChanged(data) { if (data.target.name !== 'rightController') { return; } var ctrl = data.message.controller; if ( contextXR.INTERSECTION ) { - + //updating elevation at intersection destination + contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; } else { - if (!data.message.controller.flyDirectionMatrix) { - // locking camera look at - var matrixHeadset = new itowns.THREE.Matrix4(); - matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); - data.message.controller.flyDirectionMatrix = matrixHeadset; - } - - // flying following the locked camera look at - var speed = ctrl.gamepad.axes[3] * 100; - - var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(data.message.controller.flyDirectionMatrix).multiplyScalar(speed); - contextXR.xrHeadSet.position.add(direction); + cameraOnFly(ctrl); } } + function cameraOnFly(ctrl) { + if (!ctrl.flyDirectionMatrix) { + // locking camera look at + var matrixHeadset = new itowns.THREE.Matrix4(); + matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); + ctrl.flyDirectionMatrix = matrixHeadset; + } + + // flying following the locked camera look at + // TODO itowns.camera.adjustAltitudeToAvoidCollisionWithLayer() to avoid collision + var speed = ctrl.gamepad.axes[3] * 100; + + var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + contextXR.xrHeadSet.position.add(direction); + //TODO add lateral movement + } + function onRightAxisStop(data) { + // camera fly reset data.message.controller.flyDirectionMatrix = undefined; } } @@ -240,7 +274,9 @@ previousPos.name = name; previousPos.position.copy(coordinates); view.scene.add(previousPos); + existingChild = previousPos; } + return existingChild; } function showPositionVerticalLine(name, coordinates, color, upSize) { diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index d74ac26f74..53e3b4396d 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -49,8 +49,6 @@ const initializeWebXR = (view, options) => { // avoid precision issues for controllers + allows continuous camera movements const position = view.controls.getCameraCoordinate().as(view.referenceCrs); - view.camera.initialPosition = view.camera.position().clone(); - const cameraOrientation = view.controls.getCameraOrientation(); const itownsDefaultView = { loc: new THREE.Vector3(), rot: new THREE.Quaternion(), scale: new THREE.Vector3() }; view.controls.camera.matrix.decompose(itownsDefaultView.loc, itownsDefaultView.rot, itownsDefaultView.scale); @@ -72,12 +70,12 @@ const initializeWebXR = (view, options) => { const trans = camera.position.clone().multiplyScalar(-scale).applyQuaternion(quat); const transform = new XRRigidTransform(trans, quat); // here position seems ok {x: 4485948.637198923, y: 476198.0416370128, z: 4497216.056600053, w: 1} - const baseReferenceSpace = xr.getReferenceSpace(); const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace(transform); // there it is not anymore : originOffset Matrix is : 4485948.5, 476198.03125, 4497216 - xr.setReferenceSpace(teleportSpaceOffset); + // Must delay replacement to allow user listening to sessionstart to get original ReferenceSpace + setTimeout(() => xr.setReferenceSpace(teleportSpaceOffset)); view.notifyChange(); view.camera.camera3D = xr.getCamera(); From 57f5607722910441d1bd415978513d527cb69348 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Wed, 20 Sep 2023 17:07:40 +0200 Subject: [PATCH 10/41] feat: added lateral translation + fixed thumbstick reactivity again --- examples/view_3d_map_webxr.html | 20 +++++++++++++------- src/Renderer/WebXR.js | 19 ++++++++----------- 2 files changed, 21 insertions(+), 18 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index f9383b3c5c..0a43b5d05f 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -244,13 +244,19 @@ ctrl.flyDirectionMatrix = matrixHeadset; } - // flying following the locked camera look at - // TODO itowns.camera.adjustAltitudeToAvoidCollisionWithLayer() to avoid collision - var speed = ctrl.gamepad.axes[3] * 100; - - var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); - contextXR.xrHeadSet.position.add(direction); - //TODO add lateral movement + if (ctrl.gamepad.axes[3] !== 0) { + // flying following the locked camera look at + // TODO itowns.camera.adjustAltitudeToAvoidCollisionWithLayer() to avoid collision + var speed = ctrl.gamepad.axes[3] * 100; + + var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + contextXR.xrHeadSet.position.add(direction); + } else if (ctrl.gamepad.axes[2] !== 0) { + var speed = ctrl.gamepad.axes[2] * 100; + + var direction = new itowns.THREE.Vector3(1,0,0).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + contextXR.xrHeadSet.position.add(direction); + } } function onRightAxisStop(data) { diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 53e3b4396d..4e618d124f 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -46,7 +46,6 @@ const initializeWebXR = (view, options) => { const xrControllers = initControllers(webXRManager, vrHeadSet); - // avoid precision issues for controllers + allows continuous camera movements const position = view.controls.getCameraCoordinate().as(view.referenceCrs); @@ -113,8 +112,6 @@ const initializeWebXR = (view, options) => { }); }); - let endGamePadtrackEmit = false; - /* Listening {XRInputSource} and emit changes for convenience user binding */ @@ -122,21 +119,20 @@ const initializeWebXR = (view, options) => { if (controller.gamepad) { // gamepad.axes = [0, 0, x, y]; const gamepad = controller.gamepad; - if (controller.isStickActive && gamepad.axes.lastItem === 0 && endGamePadtrackEmit) { + const activeValue = gamepad.axes.find(value => value !== 0); + if (controller.isStickActive && !activeValue && controller.gamepad.endGamePadtrackEmit) { controller.dispatchEvent({ type: 'itowns-xr-axes-stop', message: { controller } }); controller.isStickActive = false; return; - } else if (!controller.isStickActive && gamepad.axes.lastItem !== 0) { - endGamePadtrackEmit = false; + } else if (!controller.isStickActive && activeValue) { + controller.gamepad.endGamePadtrackEmit = false; controller.isStickActive = true; - } else if (controller.isStickActive && gamepad.axes.lastItem === 0) { - endGamePadtrackEmit = true; + } else if (controller.isStickActive && !activeValue) { + controller.gamepad.endGamePadtrackEmit = true; } - if (gamepad.axes.lastItem !== 0) { + if (activeValue) { controller.dispatchEvent({ type: 'itowns-xr-axes-changed', message: { controller } }); - controller.lastAxisItem = gamepad.axes.lastItem; - controller.lastAxisIndex = gamepad.axes.lastIndex; } for (const [index, button] of gamepad.buttons.entries()) { @@ -182,6 +178,7 @@ const initializeWebXR = (view, options) => { this.add(buildController(event.data)); // {XRInputSource} event.data controller.gamepad = event.data.gamepad; + // controller.inputSource = event.data; }); vrHeadSet.add(controller); } From 32e0cbc97d6a5ac03e87c7bf25633bce66c51a43 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Fri, 22 Sep 2023 12:15:23 +0200 Subject: [PATCH 11/41] rewrote vertical positioning --- examples/view_3d_map_webxr.html | 60 +++++++++++++++++++-------------- src/Renderer/WebXR.js | 16 +++++++-- 2 files changed, 48 insertions(+), 28 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 0a43b5d05f..b75d8f154f 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -139,6 +139,7 @@ contextXR.controller1.addEventListener('itowns-xr-axes-changed', onLeftAxisChanged); contextXR.controller2.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); contextXR.controller2.addEventListener('itowns-xr-axes-stop', onRightAxisStop); + contextXR.controller2.addEventListener('itowns-xr-button-pressed', onRightButtonPressed); contextXR.renderer.xr.addEventListener( 'sessionstart', function () @@ -152,7 +153,6 @@ const controller = contextXR.renderer.xr.getController(index); controller.addEventListener( 'selectstart', onSelectStart ); controller.addEventListener( 'selectend', onSelectEnd ); - controller.addEventListener('itowns-xr-button-pressed', onButtonPressed); return controller; } @@ -166,6 +166,9 @@ const offsetRotation = getGeodesicalQuaternion(); const projectedCoordinate = contextXR.coordOnCamera.as(view.referenceCrs); showPosition('intersect', projectedCoordinate, 0x0000ff); + // reset continuous translation applied to headSet parent. + contextXR.xrHeadSet.position.copy(new itowns.THREE.Vector3()); + // compute targeted position relative to the origine camera. const trans = new itowns.THREE.Vector3(projectedCoordinate.x, projectedCoordinate.y, projectedCoordinate.z).multiplyScalar(-1).applyQuaternion(offsetRotation); // TODO apply quat to @@ -175,8 +178,15 @@ } } - function onButtonPressed(data) { - // console.log('received button pressed', data); + function onRightButtonPressed(data) { + if (data.target.name !== 'rightController') { + return; + } + var ctrl = data.message.controller; + if (data.message.buttonIndex === 1) { + // activate vertical adjustment + udpateCameraElevationLive(ctrl); + } } // rotation controls @@ -189,7 +199,6 @@ } else { applyRotation(ctrl); - // udpateCameraElevationLive(ctrl); } } @@ -209,16 +218,13 @@ contextXR.renderer.xr.setReferenceSpace( teleportSpaceOffset ); } - // TODO use it bound to right controller some way function udpateCameraElevationLive(ctrl) { - // if no teleportation ongoing, just adjust camera elevation - var speed = ctrl.gamepad.axes[3] * 100; - let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); - if(!cameraCoordOnTerrain) { - console.error('Camera doesn\'t intersect terrain'); + if(ctrl.gamepad.axes[3] === 0) { return; } - var direction = cameraCoordOnTerrain.coord.geodesicNormal.clone().multiplyScalar(-speed); + var speed = ctrl.gamepad.axes[3] * 100; + var direction = view.controls.getCameraCoordinate().geodesicNormal.clone(); + direction.multiplyScalar(-speed); contextXR.xrHeadSet.position.add(direction); } @@ -228,6 +234,9 @@ return; } var ctrl = data.message.controller; + if (ctrl.lockButtonIndex) { + return; + } if ( contextXR.INTERSECTION ) { //updating elevation at intersection destination contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; @@ -310,20 +319,19 @@ } - function renderRayCaster(name, raycaster, color) { - var existingChild; - view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); - if(existingChild) { - existingChild.setDirection(raycaster.ray.direction); - existingChild.origin = raycaster.ray.origin; - } - else { - - const arrow = new itowns.THREE.ArrowHelper(raycaster.ray.direction, raycaster.ray.origin, 10, color); - arrow.name = name; - view.scene.add(arrow); - } + function renderdirectionArrow(name, originVector3, directionVector3, scale, color) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + if(existingChild) { + existingChild.setDirection(directionVector3); + existingChild.origin = originVector3; } + else { + const arrow = new itowns.THREE.ArrowHelper(directionVector3, originVector3, scale, color); + arrow.name = name; + view.scene.add(arrow); + } + } function render() { @@ -340,7 +348,7 @@ const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); //( [contextXR.floor]); if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; - renderRayCaster('raycasterLine_Left', contextXR.raycaster, 0xfe7c00); + renderdirectionArrow('raycasterLine_Left', contextXR.raycaster.ray.origin, contextXR.raycaster.ray.direction, 10, 0xfe7c00); } } else if ( contextXR.controller2.userData.isSelecting === true ) { contextXR.tempMatrix.identity().extractRotation( contextXR.controller2.matrixWorld ); @@ -349,7 +357,7 @@ const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // ([contextXR.floor]); // if ( intersects.length > 0 ) { contextXR.INTERSECTION = intersects[ 0 ].point; - renderRayCaster('raycasterLine_right', contextXR.raycaster, 0xfe7c00); + renderdirectionArrow('raycasterLine_right', contextXR.raycaster.ray.origin, contextXR.raycaster.ray.direction, 10, 0xfe7c00); } } diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 4e618d124f..233dd96383 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -142,9 +142,13 @@ const initializeWebXR = (view, options) => { // 3 - stick pressed // 4 - botton button // 5 - upper button - controller.dispatchEvent({ type: 'itowns-xr-button-pressed', message: { buttonIndex: index, button } }); - controller.lastButtonItem = gamepad.lastItem; + controller.dispatchEvent({ type: 'itowns-xr-button-pressed', message: { controller, buttonIndex: index, button } }); + controller.lastButtonItem = button; + } else if (controller.lastButtonItem === button && controller.lastButtonItem) { + controller.dispatchEvent({ type: 'itowns-xr-button-released', message: { controller, buttonIndex: index, button } }); + controller.lastButtonItem = undefined; } + if (button.touched) { // triggered really often } @@ -180,6 +184,14 @@ const initializeWebXR = (view, options) => { controller.gamepad = event.data.gamepad; // controller.inputSource = event.data; }); + controller.addEventListener('itowns-xr-button-released', (event) => { + const ctrl = event.message.controller; + ctrl.lockButtonIndex = undefined; + }); + controller.addEventListener('itowns-xr-button-pressed', (event) => { + const ctrl = event.message.controller; + ctrl.lockButtonIndex = event.message.buttonIndex; + }); vrHeadSet.add(controller); } From 21447d4d852cdf1819d954b9d0369c909987164e Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Mon, 25 Sep 2023 14:21:27 +0200 Subject: [PATCH 12/41] feat add bboxes helpers while intersecting --- examples/view_3d_map_webxr.html | 143 ++++++++++++++++++++++++++------ 1 file changed, 116 insertions(+), 27 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index b75d8f154f..b99dcb7931 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -41,7 +41,8 @@ const contextXR = { tempMatrix: new itowns.THREE.Matrix4(), coordOnCamera: {}, - deltaRotation: 0 + deltaRotation: 0, + interactiveLayers : [] }; const scale = 1; @@ -90,7 +91,7 @@ function getGeodesicalQuaternion() { //TODO can be optimized with better cache - const position = view.controls.getCameraCoordinate().as(view.referenceCrs); + const position = view.controls.getCameraCoordinate().clone().as(view.referenceCrs); const geodesicNormal = new itowns.THREE.Quaternion().setFromUnitVectors(new itowns.THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); return new itowns.THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); } @@ -131,6 +132,55 @@ view.notifyChange(); } + function loadBuildingGltf() { + // Load a glTF resource + itowns.glTFLoader.load( + // resource URL, do not commit as is. + "myGLB.glb", + + + // called when the resource is loaded + (gltf) => { + gltf.scene.position.copy(new itowns.THREE.Vector3(4486191.121470811, 477820.22721133975, 4495989.354518597)); + + gltf.scene.lookAt(new itowns.THREE.Vector3(0, 0, 0)); + gltf.scene.rotateX(Math.PI / 2); + gltf.scene.rotateX(Math.PI); + + // update coordinate of the mesh + gltf.scene.updateMatrixWorld(); + const gltfBbox = new itowns.THREE.Box3(); + gltfBbox.setFromObject(gltf.scene); + + view.scene.add(gltf.scene); + const helper = new itowns.THREE.Box3Helper( gltfBbox, 0xffff00 ); + helper.name="bboxGltfHelper"; + helper.visible = false; + view.scene.add(helper); + var gltfBox = generateVRBox(gltf.scene); + applyGltfBoxToCHilds(gltf.scene, gltfBox); + contextXR.interactiveLayers.push(gltf.scene); + gltf.VRBbox = helper; + view.notifyChange(); + }, + // called while loading is progressing + () => {}, + // called when loading has errors + (error) => { + // eslint-disable-next-line no-console + console.log("An error happened :"); + // eslint-disable-next-line no-console + console.log(error); + }); + + function applyGltfBoxToCHilds(gltfScene, gltfBox) { + gltfScene.traverse(o => { + o.VRBbox = gltfBox; + }); + } + } + + function addControllers() { contextXR.renderer = view.mainLoop.gfxEngine.renderer; @@ -333,39 +383,79 @@ } } + function updateBboxVisibility(object) { + if(contextXR.visibleBbox === object){ + return; + } + // proper to box3Helper + if(object.box) { + if (!object.visible) { + console.log('im still using this?'); + resetPreviousVisibleeBbox(); + contextXR.visibleBbox = object; + object.visible = true; + } + } + else if(object.geometry) { + if(!object.VRBbox) { + generateVRBox(object); + } + if (!object.VRBbox.visible) { + resetPreviousVisibleeBbox(); + contextXR.visibleBbox = object.VRBbox; + object.VRBbox.visible = true; + } + } else if(contextXR.visibleBbox) { + resetPreviousVisibleeBbox(); + } + + function resetPreviousVisibleeBbox() { + if(contextXR.visibleBbox) { + contextXR.visibleBbox.visible = false; + contextXR.visibleBbox = undefined; + } + } + } + + function generateVRBox(object) { + const objectBbox = new itowns.THREE.Box3(); + // better than object.geometry.computeBoundingBox(); as it copy parent position. + objectBbox.setFromObject(object); + object.VRBbox = objectBbox; + object.VRBbox = new itowns.THREE.Box3Helper(object.VRBbox, 0xffff00); + + object.VRBbox.name = object.name +'_VRBbox'; + view.scene.add(object.VRBbox); + object.VRBbox.visible = false; + return object.VRBbox; + } + + function intersectInteractiveLayers() { + // test intersection coordinate with terrain + contextXR.tempMatrix.identity().extractRotation(contextXR.controller2.matrixWorld); + contextXR.raycaster.ray.origin.setFromMatrixPosition(contextXR.controller2.matrixWorld); + contextXR.raycaster.ray.direction.set(0, 0, -1).applyMatrix4( contextXR.tempMatrix); + return contextXR.raycaster.intersectObjects(contextXR.interactiveLayers); + } function render() { contextXR.INTERSECTION = undefined; - - // test intersection coordinate with terrain - if ( contextXR.controller1.userData.isSelecting === true ) { - let temp = {loc: new itowns.THREE.Vector3(), rot: new itowns.THREE.Quaternion(), scale: new itowns.THREE.Vector3()}; - // in threejs it is used from matrixWorld - contextXR.controller1.matrixWorld.decompose(temp.loc,temp.rot,temp.scale); - contextXR.tempMatrix.identity().extractRotation( contextXR.controller1.matrixWorld ); - contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller1.matrixWorld ); - contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); - const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); //( [contextXR.floor]); - if ( intersects.length > 0 ) { - contextXR.INTERSECTION = intersects[ 0 ].point; - renderdirectionArrow('raycasterLine_Left', contextXR.raycaster.ray.origin, contextXR.raycaster.ray.direction, 10, 0xfe7c00); - } - } else if ( contextXR.controller2.userData.isSelecting === true ) { - contextXR.tempMatrix.identity().extractRotation( contextXR.controller2.matrixWorld ); - contextXR.raycaster.ray.origin.setFromMatrixPosition( contextXR.controller2.matrixWorld ); - contextXR.raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( contextXR.tempMatrix ); - const intersects = contextXR.raycaster.intersectObjects(view.tileLayer.level0Nodes); // ([contextXR.floor]); // - if ( intersects.length > 0 ) { + const intersects = intersectInteractiveLayers(); + if ( intersects.length > 0 ) { + updateBboxVisibility(intersects[0].object); + if ( contextXR.controller2.userData.isSelecting === true ) { contextXR.INTERSECTION = intersects[ 0 ].point; - renderdirectionArrow('raycasterLine_right', contextXR.raycaster.ray.origin, contextXR.raycaster.ray.direction, 10, 0xfe7c00); } + } else if (contextXR.visibleBbox) { + contextXR.visibleBbox.visible = false; + contextXR.visibleBbox = undefined; } if ( contextXR.INTERSECTION ) { var intersectionCoord = new itowns.Coordinates(view.referenceCrs, contextXR.INTERSECTION.x, contextXR.INTERSECTION.y, contextXR.INTERSECTION.z); - var justElevationAt = itowns.DEMUtils.getElevationValueAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + var justElevationAt = itowns.DEMUtils.getElevationValueAt(view.tileLayer, view.controls.getCameraCoordinate().clone(), itowns.DEMUtils.PRECISE_READ_Z); // not a {itowns.Coordinates} var coordOnCamera = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, intersectionCoord, itowns.DEMUtils.PRECISE_READ_Z); @@ -373,7 +463,7 @@ coordOnCamera.coord.altitude += contextXR.deltaAltitude; } else { // by default set altitude to the current camera elevation from terrain - let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate(), itowns.DEMUtils.PRECISE_READ_Z); + let cameraCoordOnTerrain = itowns.DEMUtils.getTerrainObjectAt(view.tileLayer, view.controls.getCameraCoordinate().clone(), itowns.DEMUtils.PRECISE_READ_Z); coordOnCamera.coord.altitude += cameraCoordOnTerrain.coord.z; contextXR.deltaAltitude = cameraCoordOnTerrain.coord.z; } @@ -390,7 +480,6 @@ for some reason, position is highly rounded when not to initial vr pos. but camera matrices seems like in the same level of definition... */ - console.log('camera position ', view.camera.camera3D.position); cameraFloorIntersect.altitude = justElevationAt; showPosition('findMe', intersectionCoord, 0xfeff00, 5500); @@ -426,7 +515,7 @@ var cameraTargetPosition = view.controls.getLookAtCoordinate(); // position of the mesh - var meshCoord = cameraTargetPosition; + var meshCoord = cameraTargetPosition.clone(); meshCoord.altitude += 30; // position and orientation of the mesh From bae7e8a73fd0525beda96256295c7fc6dc8154df Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Mon, 25 Sep 2023 14:21:54 +0200 Subject: [PATCH 13/41] feat: cumulative xy translation --- examples/view_3d_map_webxr.html | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index b99dcb7931..40ffac2ede 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -302,20 +302,26 @@ matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); ctrl.flyDirectionMatrix = matrixHeadset; } + if (ctrl.gamepad.axes[2] === 0 && ctrl.gamepad.axes[3] === 0) { + return; + } + var directionX = new itowns.THREE.Vector3(); + var directionY = new itowns.THREE.Vector3(); if (ctrl.gamepad.axes[3] !== 0) { // flying following the locked camera look at // TODO itowns.camera.adjustAltitudeToAvoidCollisionWithLayer() to avoid collision var speed = ctrl.gamepad.axes[3] * 100; - var direction = new itowns.THREE.Vector3(0,0,1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); - contextXR.xrHeadSet.position.add(direction); - } else if (ctrl.gamepad.axes[2] !== 0) { + directionY = new itowns.THREE.Vector3(0,0,1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + } + if (ctrl.gamepad.axes[2] !== 0) { var speed = ctrl.gamepad.axes[2] * 100; - var direction = new itowns.THREE.Vector3(1,0,0).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); - contextXR.xrHeadSet.position.add(direction); + directionX = new itowns.THREE.Vector3(1,0,0).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + } + contextXR.xrHeadSet.position.add(directionX.add(directionY)); } function onRightAxisStop(data) { From 7b46984df1fb412bb0d32e73dc55288229f1aeaf Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 26 Sep 2023 10:07:59 +0200 Subject: [PATCH 14/41] fixed arrowhelper draw --- examples/view_3d_map_webxr.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 40ffac2ede..dea7e1613b 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -380,7 +380,7 @@ view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); if(existingChild) { existingChild.setDirection(directionVector3); - existingChild.origin = originVector3; + existingChild.position.copy(originVector3); } else { const arrow = new itowns.THREE.ArrowHelper(directionVector3, originVector3, scale, color); From 96732af62b3441c0794ca61a236e0c90320e3e51 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 3 Oct 2023 12:10:02 +0200 Subject: [PATCH 15/41] refactored code, extracted into specific files --- examples/js/XR/Controllers.js | 192 +++++++++++++++++++++++++ examples/js/XR/Utils.js | 58 ++++++++ examples/view_3d_map_webxr.html | 241 ++++---------------------------- 3 files changed, 278 insertions(+), 213 deletions(-) create mode 100644 examples/js/XR/Controllers.js create mode 100644 examples/js/XR/Utils.js diff --git a/examples/js/XR/Controllers.js b/examples/js/XR/Controllers.js new file mode 100644 index 0000000000..c8b2b3f1dc --- /dev/null +++ b/examples/js/XR/Controllers.js @@ -0,0 +1,192 @@ + +const Controllers = {}; + +var renderer; + +Controllers.addControllers = function() { + renderer = view.mainLoop.gfxEngine.renderer; + + console.log('coucou'); + var controller1 = bindListeners(0); + var controller2 = bindListeners(1); + controller1.addEventListener('itowns-xr-axes-changed', onLeftAxisChanged); + controller2.addEventListener('itowns-xr-axes-changed', onRightAxisChanged); + controller2.addEventListener('itowns-xr-axes-stop', onRightAxisStop); + controller2.addEventListener('itowns-xr-button-pressed', onRightButtonPressed); + controller1.addEventListener('itowns-xr-button-pressed', onLeftButtonPressed); + + var cameraRightCtrl = new itowns.THREE.PerspectiveCamera(view.camera.camera3D.fov); + cameraRightCtrl.position.copy(view.camera.camera3D.position); + var cameraRighthelper = new itowns.THREE.CameraHelper( cameraRightCtrl ); + view.scene.add(cameraRighthelper); + contextXR.cameraRightGrp = { camera : cameraRightCtrl, cameraHelper : cameraRighthelper }; + contextXR.controller1 = controller1; + contextXR.controller2 = controller2; +} + +function bindListeners(index) { + const controller = renderer.xr.getController(index); + controller.addEventListener( 'selectstart', onSelectStart ); + controller.addEventListener( 'selectend', onSelectEnd ); + return controller; +} + +function onSelectStart() { + this.userData.isSelecting = true; +} + +function onSelectEnd() { + this.userData.isSelecting = false; + if ( contextXR.coordOnCamera ) { + const offsetRotation = getGeodesicalQuaternion(); + const projectedCoordinate = contextXR.coordOnCamera.as(view.referenceCrs); + XRUtils.showPosition('intersect', projectedCoordinate, 0x0000ff); + // reset continuous translation applied to headSet parent. + contextXR.xrHeadSet.position.copy(new itowns.THREE.Vector3()); + + // compute targeted position relative to the origine camera. + const trans = new itowns.THREE.Vector3(projectedCoordinate.x, projectedCoordinate.y, projectedCoordinate.z).multiplyScalar(-1).applyQuaternion(offsetRotation); + const transform = new XRRigidTransform( trans, offsetRotation ); + const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); + renderer.xr.setReferenceSpace( teleportSpaceOffset ); + } +} + + +function getGeodesicalQuaternion() { + //TODO can be optimized with better cache + const position = view.controls.getCameraCoordinate().clone().as(view.referenceCrs); + const geodesicNormal = new itowns.THREE.Quaternion().setFromUnitVectors(new itowns.THREE.Vector3(0, 0, 1), position.geodesicNormal).invert(); + return new itowns.THREE.Quaternion(-1, 0, 0, 1).normalize().multiply(geodesicNormal); +} + + +function onRightButtonPressed(data) { + if (data.target.name !== 'rightController') { + return; + } + var ctrl = data.message.controller; + if (data.message.buttonIndex === 1) { + // activate vertical adjustment + udpateCameraElevationLive(ctrl); + } +} + + +function onLeftButtonPressed(data) { + if (data.target.name !== 'leftController') { + return; + } + var ctrl = data.message.controller; + if (data.message.buttonIndex === 1) { + // activate vertical adjustment + // setCameraTocontroller(); + } +} + /** +function setCameraTocontroller() { + + //TODO debug this + if(!contextXR.controllerCameraRelativePos) { + contextXR.originalPosition = contextXR.cameraRightGrp.camera.position.clone(); + contextXR.controllerCameraRelativePos = contextXR.cameraRightGrp.camera.position.clone().sub(view.camera.camera3D.position); + } else { + contextXR.controllerCameraRelativePos = contextXR.originalPosition.clone().sub(view.camera.camera3D.position); + } + var quat = new itowns.THREE.Quaternion().setFromEuler(contextXR.cameraRightGrp.camera.rotation); + + const transform = new XRRigidTransform( contextXR.originalPosition.clone().add(contextXR.controllerCameraRelativePos).applyQuaternion(quat), quat ); + const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); + renderer.xr.setReferenceSpace( teleportSpaceOffset ); +}*/ + +// rotation controls +function onLeftAxisChanged(data) { + if (data.target.name !== 'leftController') { + return; + } + var ctrl = data.message.controller; + if ( contextXR.INTERSECTION ) { + + } else { + applyRotation(ctrl); + } +} + + +function applyRotation(ctrl) { + if(ctrl.gamepad.axes[2] === 0) { + return; + } + contextXR.deltaRotation += Math.PI / (160 * ctrl.gamepad.axes[2]); + const offsetRotation = getGeodesicalQuaternion(); + var thetaRotMatrix = new itowns.THREE.Matrix4().identity().makeRotationY(contextXR.deltaRotation); + var rotationQuartenion = new itowns.THREE.Quaternion().setFromRotationMatrix(thetaRotMatrix).normalize(); + offsetRotation.premultiply(rotationQuartenion); + const trans = view.camera.camera3D.position.clone().multiplyScalar(-1).applyQuaternion(offsetRotation); + const transform = new XRRigidTransform( trans, offsetRotation ); + const teleportSpaceOffset = contextXR.baseReferenceSpace.getOffsetReferenceSpace( transform ); + renderer.xr.setReferenceSpace( teleportSpaceOffset ); +} + +function udpateCameraElevationLive(ctrl) { + if(ctrl.gamepad.axes[3] === 0) { + return; + } + var speed = ctrl.gamepad.axes[3] * 100; + var direction = view.controls.getCameraCoordinate().geodesicNormal.clone(); + direction.multiplyScalar(-speed); + contextXR.xrHeadSet.position.add(direction); +} + +// translation controls +function onRightAxisChanged(data) { + if (data.target.name !== 'rightController') { + return; + } + var ctrl = data.message.controller; + if (ctrl.lockButtonIndex) { + return; + } + if ( contextXR.INTERSECTION ) { + //updating elevation at intersection destination + contextXR.deltaAltitude -= ctrl.gamepad.axes[3] * 100; + } else { + cameraOnFly(ctrl); + } +} + + +function cameraOnFly(ctrl) { + if (!ctrl.flyDirectionMatrix) { + // locking camera look at + var matrixHeadset = new itowns.THREE.Matrix4(); + matrixHeadset.identity().extractRotation( view.camera.camera3D.matrixWorld ); + ctrl.flyDirectionMatrix = matrixHeadset; + } + if (ctrl.gamepad.axes[2] === 0 && ctrl.gamepad.axes[3] === 0) { + return; + } + var directionX = new itowns.THREE.Vector3(); + var directionY = new itowns.THREE.Vector3(); + + if (ctrl.gamepad.axes[3] !== 0) { + // flying following the locked camera look at + // TODO itowns.camera.adjustAltitudeToAvoidCollisionWithLayer() to avoid collision + var speed = ctrl.gamepad.axes[3] * 100; + + directionY = new itowns.THREE.Vector3(0,0,1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + } + if (ctrl.gamepad.axes[2] !== 0) { + var speed = ctrl.gamepad.axes[2] * 100; + + directionX = new itowns.THREE.Vector3(1,0,0).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + + } + contextXR.xrHeadSet.position.add(directionX.add(directionY)); +} + +function onRightAxisStop(data) { + // camera fly reset + data.message.controller.flyDirectionMatrix = undefined; +} \ No newline at end of file diff --git a/examples/js/XR/Utils.js b/examples/js/XR/Utils.js new file mode 100644 index 0000000000..2f7558cfe3 --- /dev/null +++ b/examples/js/XR/Utils.js @@ -0,0 +1,58 @@ +const XRUtils = {}; + +XRUtils.showPosition = function(name, coordinates, color, radius = 50) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + + if(existingChild) { + existingChild.position.copy(coordinates); + } + else { + const previousPos = new itowns.THREE.Mesh( + new itowns.THREE.SphereGeometry( radius, 16, 8 ), + new itowns.THREE.MeshBasicMaterial( { color: color, wireframe: true } ) + ); + previousPos.name = name; + previousPos.position.copy(coordinates); + view.scene.add(previousPos); + existingChild = previousPos; + } + return existingChild; +} + +XRUtils.showPositionVerticalLine = function(name, coordinates, color, upSize) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + if(existingChild) { + existingChild.position.copy(coordinates); + existingChild.lookAt(new itowns.THREE.Vector3(0, 0, 1)); + } + else { + const points = []; + points.push(new itowns.THREE.Vector3(0,0,0)); + // upward direction + points.push(new itowns.THREE.Vector3(0, 0, -upSize)); + const line = new itowns.THREE.Line( + new itowns.THREE.BufferGeometry().setFromPoints(points), + new itowns.THREE.LineBasicMaterial({ color: color })); + line.position.copy(coordinates); + //necessary to "look" vertically + line.lookAt(new itowns.THREE.Vector3(0, 0, 1)); + line.name = name; + view.scene.add(line); + } +} + +XRUtils.renderdirectionArrow = function(name, originVector3, directionVector3, scale, color) { + var existingChild; + view.scene.children.forEach((child) => {if(child.name === name) { existingChild = child;} }); + if(existingChild) { + existingChild.setDirection(directionVector3); + existingChild.position.copy(originVector3); + } + else { + const arrow = new itowns.THREE.ArrowHelper(directionVector3, originVector3, scale, color); + arrow.name = name; + view.scene.add(arrow); + } +} diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index dea7e1613b..e6d82cd5c7 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -21,6 +21,14 @@ + + + + + + + +
+ + - - + // must clone Coordinate to avoid side effect, maybe with DEMUtils, but not sure. + contextXR.coordOnCamera = new itowns.Coordinates(coordOnCamera.coord.crs); + contextXR.coordOnCamera.setFromVector3(coordOnCamera.coord); + XRUtils.showPosition('positionJump', contextXR.coordOnCamera.as(view.referenceCrs), 0xfe0c00, 30, false); + XRUtils.showPositionVerticalLine('verticalAxisJump', intersectionCoord, 0xfe0c00, 10000, false); + XRUtils.showPosition('positionJumpDebug', intersectionCoord, 0xfeff00, 5500, true); + } + + //--------- add fake to ground + + function addMeshToScene() { + // creation of the new mesh (a cylinder) + var THREE = itowns.THREE; + var geometry = new THREE.CylinderGeometry(0, 10, 60, 8); + var material = new THREE.MeshBasicMaterial({ color: 0xff0000 }); + var meshPointer = new THREE.Mesh(geometry, material); + + // get the position on the globe, from the camera + var cameraTargetPosition = view.controls.getLookAtCoordinate(); + + // position of the mesh + var meshCoord = cameraTargetPosition.clone(); + meshCoord.altitude += 30; + + // position and orientation of the mesh + meshPointer.position.copy(meshCoord.as(view.referenceCrs)); + meshPointer.lookAt(new THREE.Vector3(0, 0, 0)); + meshPointer.rotateX(Math.PI / 2); + + // update coordinate of the mesh + meshPointer.updateMatrixWorld(); + + // add the mesh to the scene + view.scene.add(meshPointer); + + // make the object usable from outside of the function + view.meshPointer = meshPointer; + view.notifyChange(); + } + + // Listen for globe full initialisation event + view.addEventListener(itowns.GLOBE_VIEW_EVENTS.GLOBE_INITIALIZED, function globeInitialized() { + // eslint-disable-next-line no-console + console.info('Globe initialized'); + + + addMeshToScene(); + }); + + var d = new debug.Debug(view, debugMenu.gui); + debug.createTileDebugUI(debugMenu.gui, view, view.tileLayer, d); + itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json') + .then(addElevationLayerFromConfig); + itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json') + .then(addElevationLayerFromConfig); + + + + \ No newline at end of file diff --git a/src/Core/View.js b/src/Core/View.js index 682f25cc04..99b715477e 100644 --- a/src/Core/View.js +++ b/src/Core/View.js @@ -1067,7 +1067,7 @@ class View extends THREE.EventDispatcher { target.addVectors(this.camera3D.position, ray.direction.setLength(length)); } else { // FIXME picking doesn't work with arrayCamera - const gl_FragCoord_Z = g.depthBufferRGBAValueToOrthoZ(buffer, camera); + const gl_FragCoord_Z = g.depthBufferRGBAValueToOrthoZ(buffer, this.camera.camera3D); target.set(screen.x, screen.y, gl_FragCoord_Z); target.unproject(this.camera3D); diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index d9952fd4cd..09f8bed5a4 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -43,16 +43,13 @@ const initializeWebXR = (view, options) => { view.scene.scale.multiplyScalar(scale); view.scene.updateMatrixWorld(); - - const xrControllers = initControllers(webXRManager, vrHeadSet); - + + const xrControllers = initControllers(xr, vrHeadSet); + const position = view.controls.getCameraCoordinate().as(view.referenceCrs); // To avoid controllers precision issues, headset should handle camera position and camera should be reset to origin view.scene.add(vrHeadSet); - - - xr.enabled = true; xr.getReferenceSpace('local'); @@ -69,9 +66,9 @@ const initializeWebXR = (view, options) => { // Must delay replacement to allow user listening to sessionstart to get original ReferenceSpace setTimeout(() => { - xr.setReferenceSpace(teleportSpaceOffset); - // does a regression over controller matrixWorld update... - }); + xr.setReferenceSpace(teleportSpaceOffset); + // does a regression over controller matrixWorld update... + }); view.notifyChange(); view.camera.camera3D = xr.getCamera(); @@ -135,7 +132,7 @@ const initializeWebXR = (view, options) => { } function updateFarDistance() { - view.camera.camera3D.far = Math.min(Math.max(view.camera.elevationToGround * 1000, 10000), 100000); + view.camera.camera3D.far = Math.min(Math.max(view.camera.elevationToGround * 1000, 10000), 100000); view.camera.camera3D.updateProjectionMatrix(); } @@ -209,7 +206,6 @@ const initializeWebXR = (view, options) => { this.add(buildController(event.data)); // {XRInputSource} event.data controller.gamepad = event.data.gamepad; - // controller.inputSource = event.data; }); controller.addEventListener('itowns-xr-button-released', (event) => { const ctrl = event.message.controller; @@ -229,10 +225,6 @@ const initializeWebXR = (view, options) => { function buildController(data) { const params = { geometry: {}, material: {} }; - // let cameraTargetPosition = view.controls.getCameraCoordinate(); - // let meshCoord = cameraTargetPosition; - // let projectedCoords = meshCoord.as(view.referenceCrs); - switch (data.targetRayMode) { case 'tracked-pointer': params.geometry = new THREE.BufferGeometry(); @@ -246,8 +238,6 @@ const initializeWebXR = (view, options) => { case 'gaze': params.geometry = new THREE.RingGeometry(0.02, 0.04, 32).translate(0, 0, -1); params.material = new THREE.MeshBasicMaterial({ opacity: 0.5, transparent: true }); - - // geometry.position.copy(meshCoord.as(view.referenceCrs)); return new THREE.Mesh(params.geometry, params.material); default: break; From 72ff6cc7c05b6a396cac32164843a36314409c43 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Mon, 20 Nov 2023 16:28:14 +0100 Subject: [PATCH 34/41] doc --- src/Renderer/WebXR.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 09f8bed5a4..106404aa09 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -138,6 +138,10 @@ const initializeWebXR = (view, options) => { /* Listening {XRInputSource} and emit changes for convenience user binding + Adding a few internal states for reactivity + - controller.lockButtonIndex {number} when a button is pressed, gives its index + - controller.isStickActive {boolean} true when a controller stick is not on initial state. + - */ function listenGamepad(controller) { if (controller.gamepad) { From c0d09859d67093f6b9e75b949aca5e871b5814f8 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 21 Nov 2023 12:39:04 +0100 Subject: [PATCH 35/41] fix some random issue --- examples/view_3d_map_webxr.html | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 69f6c0878b..6d94b753f4 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -96,16 +96,6 @@ ); }); - // ---------- DISPLAY A DIGITAL ELEVATION MODEL : ---------- - - // Add two elevation layers, each with a different level of detail. - // Here again, each layer's properties are defined in a json file. - function addElevationLayerFromConfig(config) { - config.source = new itowns.WMTSSource(config.source); - contextXR.elevationLayer = new itowns.ElevationLayer(config.id, config); - view.addLayer(contextXR.elevationLayer).then(debugMenu.addLayerGUI.bind(debugMenu)); - } - // ------------- webxr interaction ---------------- view.scene.matrixWorldAutoUpdate = true; @@ -310,12 +300,27 @@ addMeshToScene(); }); + // Add the UI Debug var d = new debug.Debug(view, debugMenu.gui); debug.createTileDebugUI(debugMenu.gui, view, view.tileLayer, d); - itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json') - .then(addElevationLayerFromConfig); - itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json') - .then(addElevationLayerFromConfig); + + d.switch = function () { + Controllers.change3DTileRepresentation(); + } + debugMenu.gui.add(d, 'switch').name('Mode Switch'); + + // ---------- DISPLAY A DIGITAL ELEVATION MODEL : ---------- + + // Add two elevation layers, each with a different level of detail. Here again, each layer's properties are + // defined in a json file. + function addElevationLayerFromConfig(config) { + config.source = new itowns.WMTSSource(config.source); + var elevationLayer = new itowns.ElevationLayer(config.id, config); + view.addLayer(elevationLayer).then(debugMenu.addLayerGUI.bind(debugMenu)); + } + itowns.Fetcher.json('./layers/JSONLayers/IGN_MNT_HIGHRES.json').then(addElevationLayerFromConfig); + // must keep it, Failed to execute 'updateRenderState' on 'XRSession': Failed to read the 'depthFar' property from 'XRRenderStateInit': The provided double value is non-finite. + itowns.Fetcher.json('./layers/JSONLayers/WORLD_DTM.json').then(addElevationLayerFromConfig); From 0fb9dd4399b5c3aa38455bae834aa06c1630edae Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 21 Nov 2023 12:42:33 +0100 Subject: [PATCH 36/41] avoid far NaN value at init + sync rotation cameras --- src/Renderer/WebXR.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 106404aa09..59fc86df0d 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -63,7 +63,7 @@ const initializeWebXR = (view, options) => { const baseReferenceSpace = xr.getReferenceSpace(); const teleportSpaceOffset = baseReferenceSpace.getOffsetReferenceSpace(transform); // there it is not anymore : originOffset Matrix is : 4485948.5, 476198.03125, 4497216 - + // Must delay replacement to allow user listening to sessionstart to get original ReferenceSpace setTimeout(() => { xr.setReferenceSpace(teleportSpaceOffset); @@ -72,9 +72,8 @@ const initializeWebXR = (view, options) => { view.notifyChange(); view.camera.camera3D = xr.getCamera(); - updateFarDistance(); + view.camera.camera3D.far = 100; view.camera.resize(view.camera.width, view.camera.height); - vrHeadSet.add(view.camera.camera3D); document.addEventListener('keydown', exitXRSession, false); @@ -116,6 +115,7 @@ const initializeWebXR = (view, options) => { function resyncControlCamera() { // search for other this.camera in Itowns code for perfs issues view.controls.camera.position.copy(view.camera.camera3D.position); + view.controls.camera.rotation.copy(view.camera.camera3D.rotation); view.controls.camera.updateMatrix(); // view.controls.camera.rotation. } From fff2bccb144db9a1e633bf4b9907fd9a1ee56c9c Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 21 Nov 2023 12:43:25 +0100 Subject: [PATCH 37/41] WIP try to fix moving shift to up --- examples/js/XR/Controllers.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/js/XR/Controllers.js b/examples/js/XR/Controllers.js index 2ca8cdf146..8c17597cf2 100644 --- a/examples/js/XR/Controllers.js +++ b/examples/js/XR/Controllers.js @@ -171,7 +171,7 @@ function onLeftAxisChanged(data) { function onRightAxisStop(data) { // camera fly reset - data.message.controller.flyDirectionMatrix = undefined; + data.message.controller.flyDirectionQuat = undefined; navigationMode[currentNavigationModeIndex].onRightAxisStop(data); } @@ -276,12 +276,16 @@ function getTranslationElevation(axisValue, speedFactor) { return direction; } +/** + * FIXME flying back and forth cause a permanent shift to up. + * @param {*} ctrl + * @returns + */ function cameraOnFly(ctrl) { - if (!ctrl.flyDirectionMatrix) { + if (!ctrl.flyDirectionQuat) { // locking camera look at - var matrixHeadset = new itowns.THREE.Matrix4(); - matrixHeadset.identity().extractRotation(view.camera.camera3D.matrixWorld); - ctrl.flyDirectionMatrix = matrixHeadset; + // FIXME using {view.camera.camera3D.matrixWorld} or normalized quaternion produces the same effect and shift to the up direction. + ctrl.flyDirectionQuat = view.camera.camera3D.quaternion.clone().normalize(); } if (ctrl.gamepad.axes[2] === 0 && ctrl.gamepad.axes[3] === 0) { return; @@ -292,11 +296,11 @@ function cameraOnFly(ctrl) { if (ctrl.gamepad.axes[3] !== 0) { // flying following the locked camera look at var speed = ctrl.gamepad.axes[3] * speedFactor; - directionY = new itowns.THREE.Vector3(0, 0, 1).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); - } + directionY = new itowns.THREE.Vector3(0,0,1).applyQuaternion(ctrl.flyDirectionQuat).multiplyScalar(speed); + } if (ctrl.gamepad.axes[2] !== 0) { var speed = ctrl.gamepad.axes[2] * speedFactor; - directionX = new itowns.THREE.Vector3(1, 0, 0).applyMatrix4(ctrl.flyDirectionMatrix).multiplyScalar(speed); + directionX = new itowns.THREE.Vector3(1,0,0).applyQuaternion(ctrl.flyDirectionQuat).multiplyScalar(speed); } const offsetRotation = Controllers.getGeodesicalQuaternion(); From 3f2c010bb2852d1d0156d986287158c9a2a29fad Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 21 Nov 2023 12:35:33 +0100 Subject: [PATCH 38/41] controller binding better doc --- examples/js/XR/README.txt | 76 ++++++++++++++++++++++++++++++++++++--- src/Renderer/WebXR.js | 2 +- 2 files changed, 72 insertions(+), 6 deletions(-) diff --git a/examples/js/XR/README.txt b/examples/js/XR/README.txt index b01e454e77..8b4ad855a8 100644 --- a/examples/js/XR/README.txt +++ b/examples/js/XR/README.txt @@ -1,11 +1,77 @@ +Manettes PICO4: + switch pour tester différents set d’interaction déplacement / rotation. * faire une pression sur les 2 joysticks en même temps. -activer / désactiver le debug visuel : -* bouton x manette gauche pico. +## MODE 1 : + +thumbstick droit : +* X : translation gauche droite +* Y : translation avant arrière + +thumbstick gauche : +* X : rotation yaw +* Y : + +gachette#0 index droit : +* téléportation au relachement. +* + stick droit pendant pression : ajustement de la hauteur de téléportation. + +gachette#1 majeure droit : +* + stick ajustement de la hauteur de la caméra. + +bouton#4 bas (A) droit +* switch de scène prédéfinie au relachement. + +bouton#5 haut (B) droit +* + +gachette#0 index gauche : +* (si gachette index droit actif) : 1 clic verrou de la position de téléportation, 2 clic annulation de la téléportation. + +gachette#1 majeure gauche : +* afficher les coordonnées caméra actuelles. + +bouton#4 bas (X) gauche +* switch le mode debug + +bouton#5 haut (Y) gauche +* changer le mode du 3D tiles + + +## MODE 2 : + +thumbstick droit : +* X : rotation yaw +* Y : translation avant arrière + +thumbstick gauche : +* X : +* Y : + +gachette#0 index droit : +* téléportation au relachement. +* + stick droit pendant pression : ajustement de la hauteur de téléportation. + +gachette#1 majeure droit : +* + +bouton#4 bas (A) droit +* relachement courte : petit ajustement vertical bas +* pression longue (2sec) : ajustement max vertical sol + +bouton#5 haut (B) droit +* relachement courte : petit ajustement vertical haut +* pression longue (2sec) : ajustement max vertical (ciel) + +gachette#0 index gauche : +* (si gachette index droit actif) : 1 clic verrou de la position de téléportation, 2 clic annulation de la téléportation. + +gachette#1 majeure gauche : +bouton#4 bas (X) gauche +* -Téléportation : -* clic gachette droit + stick droit pour la hauteur de destination -* Verrouiller la position de téléportation avec un clic gachette gauche, et annuler la téléportation avec un deuxième clic gachette gauche. \ No newline at end of file +bouton#5 haut (Y) gauche +* \ No newline at end of file diff --git a/src/Renderer/WebXR.js b/src/Renderer/WebXR.js index 59fc86df0d..064687f69e 100644 --- a/src/Renderer/WebXR.js +++ b/src/Renderer/WebXR.js @@ -168,7 +168,7 @@ const initializeWebXR = (view, options) => { // 0 - gachette index // 1 - gachette majeur // 3 - stick pressed - // 4 - botton button + // 4 - bottom button // 5 - upper button controller.dispatchEvent({ type: 'itowns-xr-button-pressed', message: { controller, buttonIndex: index, button } }); controller.lastButtonItem = button; From c37c611f3832b25d945fb5f1f966dfc0451cb4d6 Mon Sep 17 00:00:00 2001 From: Jonathan GARNIER Date: Tue, 21 Nov 2023 14:11:16 +0100 Subject: [PATCH 39/41] fixed rebase --- examples/js/XR/Controllers.js | 7 ++++++- examples/view_3d_map_webxr.html | 22 ++++++++-------------- 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/examples/js/XR/Controllers.js b/examples/js/XR/Controllers.js index 8c17597cf2..c0b206ed9c 100644 --- a/examples/js/XR/Controllers.js +++ b/examples/js/XR/Controllers.js @@ -19,6 +19,9 @@ var leftCtrChangeNavMode = false; var alreadySwitched = false; var navigationMode = []; var currentNavigationModeIndex = 0; + +var view = null; +var contextXR= null; // TODO cache geodesicQuat /** @@ -28,7 +31,9 @@ var currentNavigationModeIndex = 0; * } * requires a contextXR variable. */ -Controllers.addControllers = function () { +Controllers.addControllers = function (_view, _contextXR) { + view = _view; + contextXR = _contextXR; navigationMode.push(Mode1, Mode2); renderer = view.mainLoop.gfxEngine.renderer; var controller1 = bindListeners(0); diff --git a/examples/view_3d_map_webxr.html b/examples/view_3d_map_webxr.html index 6d94b753f4..a28e13a917 100644 --- a/examples/view_3d_map_webxr.html +++ b/examples/view_3d_map_webxr.html @@ -38,20 +38,9 @@