From 154350897f18006c2b83c1c7a1da587560af51da Mon Sep 17 00:00:00 2001 From: Leon van Kammen Date: Mon, 22 May 2023 17:18:15 +0200 Subject: [PATCH] XR/desktop href-navigation works! --- dist/xrfragment.aframe.js | 69 +++++++++++++++++++++++++++--- dist/xrfragment.three.js | 53 ++++++++++++++++++++--- example/aframe/sandbox/index.html | 6 +-- example/threejs/sandbox/index.html | 24 +++++------ src/3rd/aframe/index.js | 17 +++++++- src/3rd/three/InteractiveGroup.js | 4 ++ src/3rd/three/navigator.js | 2 +- src/3rd/three/xrf/href.js | 6 ++- src/3rd/three/xrf/pos.js | 46 ++++++++++++++++++-- 9 files changed, 191 insertions(+), 36 deletions(-) diff --git a/dist/xrfragment.aframe.js b/dist/xrfragment.aframe.js index adb6c08..d5a0959 100644 --- a/dist/xrfragment.aframe.js +++ b/dist/xrfragment.aframe.js @@ -614,6 +614,10 @@ xrfragment.InteractiveGroup = function(THREE,renderer,camera){ super(); if( !renderer || !camera ) return + + // extract camera when camera-rig is passed + camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) + const scope = this; const raycaster = new Raycaster(); @@ -877,7 +881,7 @@ xrf.navigator.to = (url,event) => { let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) console.log("xrfragment: navigating to "+url) - if( !file || xrf.model.file == file ){ // we're already loaded + if( !file || xrf.model.file == file ){ // we're already loaded document.location.hash = `#${hash}` // just update the hash xrf.eval( url, xrf.model ) // and eval URI XR fragments return resolve(xrf.model) @@ -968,8 +972,6 @@ xrf.frag.env = function(v, opts){ xrf.frag.href = function(v, opts){ let { mesh, model, camera, scene, renderer, THREE} = opts - console.log("INIT HREF "+mesh.name) - const world = { pos: new THREE.Vector3(), scale: new THREE.Vector3(), @@ -1088,9 +1090,48 @@ xrf.frag.pos = function(v, opts){ //if( renderer.xr.isPresenting ) return // too far away let { frag, mesh, model, camera, scene, renderer, THREE} = opts console.log(" └ setting camera position to "+v.string) - camera.position.x = v.x - camera.position.y = v.y - camera.position.z = v.z + + if( !frag.q ){ + + if( true ){//!renderer.xr.isPresenting ){ + console.dir(camera) + camera.position.x = v.x + camera.position.y = v.y + camera.position.z = v.z + } + /* + else{ // XR + let cameraWorldPosition = new THREE.Vector3() + camera.object3D.getWorldPosition(this.cameraWorldPosition) + let newRigWorldPosition = new THREE.Vector3(v.x,v.y,x.z) + + // Finally update the cameras position + let newRigLocalPosition.copy(this.newRigWorldPosition) + if (camera.object3D.parent) { + camera.object3D.parent.worldToLocal(newRigLocalPosition) + } + camera.setAttribute('position', newRigLocalPosition) + + // Also take the headset/camera rotation itself into account + if (this.data.rotateOnTeleport) { + this.teleportOcamerainQuaternion + .setFromEuler(new THREE.Euler(0, this.teleportOcamerain.object3D.rotation.y, 0)) + this.teleportOcamerainQuaternion.invert() + this.teleportOcamerainQuaternion.multiply(this.hitEntityQuaternion) + // Rotate the camera based on calculated teleport ocamerain rotation + this.cameraRig.object3D.setRotationFromQuaternion(this.teleportOcamerainQuaternion) + } + + console.log("XR") + const offsetPosition = { x: - v.x, y: - v.y, z: - v.z, w: 1 }; + const offsetRotation = new THREE.Quaternion(); + const transform = new XRRigidTransform( offsetPosition, offsetRotation ); + const teleportSpaceOffset = xrf.baseReferenceSpace.getOffsetReferenceSpace( transform ); + renderer.xr.setReferenceSpace( teleportSpaceOffset ); + } + */ + + } } xrf.frag.q = function(v, opts){ let { frag, mesh, model, camera, scene, renderer, THREE} = opts @@ -1187,6 +1228,7 @@ window.AFRAME.registerComponent('xrf', { // override the camera-related XR Fragments so the camera-rig is affected let camOverride = (xrf,v,opts) => { opts.camera = $('[camera]').object3D.parent + console.dir(opts.camera) xrf(v,opts) } @@ -1210,7 +1252,20 @@ window.AFRAME.registerComponent('xrf', { let els = [...document.querySelectorAll('[xrf-get]')] els.map( (el) => document.querySelector('a-scene').removeChild(el) ) })(XRF.reset) - + + // disable cam-controls (which will block updating camerarig position) + let coms = ['look-controls','wasd-controls'] + const setComponents = (com,state) => () => { + coms.forEach( (com) => { + let el = document.querySelector('['+com+']') + if(!el) return + el.components[com].enabled = state + }) + } + aScene.addEventListener('enter-vr', setComponents(coms,false) ) + aScene.addEventListener('enter-ar', setComponents(coms,false) ) + aScene.addEventListener('exit-vr', setComponents(coms,true) ) + aScene.addEventListener('exit-ar', setComponents(coms,true) ) }, }) diff --git a/dist/xrfragment.three.js b/dist/xrfragment.three.js index f55a7f9..2302f9f 100644 --- a/dist/xrfragment.three.js +++ b/dist/xrfragment.three.js @@ -614,6 +614,10 @@ xrfragment.InteractiveGroup = function(THREE,renderer,camera){ super(); if( !renderer || !camera ) return + + // extract camera when camera-rig is passed + camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) + const scope = this; const raycaster = new Raycaster(); @@ -877,7 +881,7 @@ xrf.navigator.to = (url,event) => { let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) console.log("xrfragment: navigating to "+url) - if( !file || xrf.model.file == file ){ // we're already loaded + if( !file || xrf.model.file == file ){ // we're already loaded document.location.hash = `#${hash}` // just update the hash xrf.eval( url, xrf.model ) // and eval URI XR fragments return resolve(xrf.model) @@ -968,8 +972,6 @@ xrf.frag.env = function(v, opts){ xrf.frag.href = function(v, opts){ let { mesh, model, camera, scene, renderer, THREE} = opts - console.log("INIT HREF "+mesh.name) - const world = { pos: new THREE.Vector3(), scale: new THREE.Vector3(), @@ -1088,9 +1090,48 @@ xrf.frag.pos = function(v, opts){ //if( renderer.xr.isPresenting ) return // too far away let { frag, mesh, model, camera, scene, renderer, THREE} = opts console.log(" └ setting camera position to "+v.string) - camera.position.x = v.x - camera.position.y = v.y - camera.position.z = v.z + + if( !frag.q ){ + + if( true ){//!renderer.xr.isPresenting ){ + console.dir(camera) + camera.position.x = v.x + camera.position.y = v.y + camera.position.z = v.z + } + /* + else{ // XR + let cameraWorldPosition = new THREE.Vector3() + camera.object3D.getWorldPosition(this.cameraWorldPosition) + let newRigWorldPosition = new THREE.Vector3(v.x,v.y,x.z) + + // Finally update the cameras position + let newRigLocalPosition.copy(this.newRigWorldPosition) + if (camera.object3D.parent) { + camera.object3D.parent.worldToLocal(newRigLocalPosition) + } + camera.setAttribute('position', newRigLocalPosition) + + // Also take the headset/camera rotation itself into account + if (this.data.rotateOnTeleport) { + this.teleportOcamerainQuaternion + .setFromEuler(new THREE.Euler(0, this.teleportOcamerain.object3D.rotation.y, 0)) + this.teleportOcamerainQuaternion.invert() + this.teleportOcamerainQuaternion.multiply(this.hitEntityQuaternion) + // Rotate the camera based on calculated teleport ocamerain rotation + this.cameraRig.object3D.setRotationFromQuaternion(this.teleportOcamerainQuaternion) + } + + console.log("XR") + const offsetPosition = { x: - v.x, y: - v.y, z: - v.z, w: 1 }; + const offsetRotation = new THREE.Quaternion(); + const transform = new XRRigidTransform( offsetPosition, offsetRotation ); + const teleportSpaceOffset = xrf.baseReferenceSpace.getOffsetReferenceSpace( transform ); + renderer.xr.setReferenceSpace( teleportSpaceOffset ); + } + */ + + } } xrf.frag.q = function(v, opts){ let { frag, mesh, model, camera, scene, renderer, THREE} = opts diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html index d096485..00f21a1 100644 --- a/example/aframe/sandbox/index.html +++ b/example/aframe/sandbox/index.html @@ -21,8 +21,8 @@ ⬇️ model - - + + @@ -46,8 +46,6 @@ setupConsole( $('textarea') ) setupUrlBar( $('input#uri') ) - // add look-controls at last (otherwise it'll be buggy after scene-updates) - $('[camera]').setAttribute("look-controls","") // add screenshot component with camera to capture proper equirects $('a-scene').setAttribute("screenshot",{camera: "[camera]",width: 4096*2, height:2048*2}) diff --git a/example/threejs/sandbox/index.html b/example/threejs/sandbox/index.html index 93260bf..2796331 100644 --- a/example/threejs/sandbox/index.html +++ b/example/threejs/sandbox/index.html @@ -83,10 +83,16 @@ window.addEventListener( 'resize', onWindowResize ); + let cameraRig = new THREE.Group() + cameraRig.position.set( 0, 0, 0 ); + cameraRig.add(camera) + scene.add(cameraRig) + + // enable XR fragments let XRF = xrfragment.init({ THREE, - camera, + camera:cameraRig, scene, renderer, debug: true, @@ -116,7 +122,7 @@ window.XRF = XRF // expose to form - let file = document.location.search.length > 2 ? document.location.search.substr(1) + document.location.hash : './../../assets/example3.gltf#pos=0,1,15' + let file = document.location.search.length > 2 ? document.location.search.substr(1) + document.location.hash : './../../assets/example3.gltf#pos=1,0,4&rot=0,-30,0' $('#model').setAttribute("href","./../../asset/"+file) XRF.navigator.to( file ) @@ -134,12 +140,6 @@ //controls.maxPolarAngle = Math.PI / 2; //controls.target = new THREE.Vector3(0,1.6,0) - //let cameraRig = new THREE.Group() - //cameraRig.position.set( 0, 1.6, 15 ); - camera.position.set( 0, 1.6, 15 ); - //cameraRig.add(camera) - //cameraRig.position.set( 0, 4, 15 ); - //controls.update() const geometry = new THREE.BufferGeometry(); @@ -147,22 +147,22 @@ const controller1 = renderer.xr.getController( 0 ); controller1.add( new THREE.Line( geometry ) ); - scene.add( controller1 ); + cameraRig.add( controller1 ); const controller2 = renderer.xr.getController( 1 ); controller2.add( new THREE.Line( geometry ) ); - scene.add( controller2 ); + cameraRig.add( controller2 ); const controllerModelFactory = new XRControllerModelFactory(); const controllerGrip1 = renderer.xr.getControllerGrip( 0 ); controllerGrip1.add( controllerModelFactory.createControllerModel( controllerGrip1 ) ); - scene.add( controllerGrip1 ); + cameraRig.add( controllerGrip1 ); const controllerGrip2 = renderer.xr.getControllerGrip( 1 ); controllerGrip2.add( controllerModelFactory.createControllerModel( controllerGrip2 ) ); - scene.add( controllerGrip2 ); + cameraRig.add( controllerGrip2 ); setupConsole() diff --git a/src/3rd/aframe/index.js b/src/3rd/aframe/index.js index 5ed5394..f4918f6 100644 --- a/src/3rd/aframe/index.js +++ b/src/3rd/aframe/index.js @@ -32,6 +32,7 @@ window.AFRAME.registerComponent('xrf', { // override the camera-related XR Fragments so the camera-rig is affected let camOverride = (xrf,v,opts) => { opts.camera = $('[camera]').object3D.parent + console.dir(opts.camera) xrf(v,opts) } @@ -50,11 +51,25 @@ window.AFRAME.registerComponent('xrf', { // cleanup xrf-get objects when resetting scene XRF.reset = ((reset) => () => { + reset() console.log("aframe reset") let els = [...document.querySelectorAll('[xrf-get]')] els.map( (el) => document.querySelector('a-scene').removeChild(el) ) - reset() })(XRF.reset) + + // disable cam-controls (which block updating camerarig position) + let coms = ['look-controls','wasd-controls'] + const setComponents = (com,state) => () => { + coms.forEach( (com) => { + let el = document.querySelector('['+com+']') + if(!el) return + el.components[com].enabled = state + }) + } + aScene.addEventListener('enter-vr', setComponents(coms,false) ) + aScene.addEventListener('enter-ar', setComponents(coms,false) ) + aScene.addEventListener('exit-vr', setComponents(coms,true) ) + aScene.addEventListener('exit-ar', setComponents(coms,true) ) }, }) diff --git a/src/3rd/three/InteractiveGroup.js b/src/3rd/three/InteractiveGroup.js index 597e629..47a03de 100644 --- a/src/3rd/three/InteractiveGroup.js +++ b/src/3rd/three/InteractiveGroup.js @@ -19,6 +19,10 @@ xrfragment.InteractiveGroup = function(THREE,renderer,camera){ super(); if( !renderer || !camera ) return + + // extract camera when camera-rig is passed + camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) + const scope = this; const raycaster = new Raycaster(); diff --git a/src/3rd/three/navigator.js b/src/3rd/three/navigator.js index 56e67e5..1d2744a 100644 --- a/src/3rd/three/navigator.js +++ b/src/3rd/three/navigator.js @@ -6,7 +6,7 @@ xrf.navigator.to = (url,event) => { let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url) console.log("xrfragment: navigating to "+url) - if( !file || xrf.model.file == file ){ // we're already loaded + if( !file || xrf.model.file == file ){ // we're already loaded document.location.hash = `#${hash}` // just update the hash xrf.eval( url, xrf.model ) // and eval URI XR fragments return resolve(xrf.model) diff --git a/src/3rd/three/xrf/href.js b/src/3rd/three/xrf/href.js index c969b7f..fade04c 100644 --- a/src/3rd/three/xrf/href.js +++ b/src/3rd/three/xrf/href.js @@ -81,10 +81,10 @@ xrf.frag.href = function(v, opts){ } let teleport = mesh.userData.XRF.href.exec = (e) => { - let portalArea = 1 // 2 meter const meshWorldPosition = new THREE.Vector3(); meshWorldPosition.setFromMatrixPosition(mesh.matrixWorld); + let portalArea = 1 // 2 meter const cameraDirection = new THREE.Vector3(); camera.getWorldPosition(cameraDirection); cameraDirection.sub(meshWorldPosition); @@ -93,9 +93,11 @@ xrf.frag.href = function(v, opts){ const newPos = meshWorldPosition.clone().add(cameraDirection); const distance = camera.position.distanceTo(newPos); - if( renderer.xr.isPresenting && distance > portalArea ) return // too far away + //if( distance > portalArea ){ + if( !renderer.xr.isPresenting && !confirm("teleport to "+v.string+" ?") ) return xrf.navigator.to(v.string) // ok let's surf to HREF! + console.log("teleport!") xrf.emit('href',{click:true,mesh,xrf:v}) } diff --git a/src/3rd/three/xrf/pos.js b/src/3rd/three/xrf/pos.js index 9adc006..5e4fec3 100644 --- a/src/3rd/three/xrf/pos.js +++ b/src/3rd/three/xrf/pos.js @@ -1,7 +1,47 @@ xrf.frag.pos = function(v, opts){ + //if( renderer.xr.isPresenting ) return // too far away let { frag, mesh, model, camera, scene, renderer, THREE} = opts console.log(" └ setting camera position to "+v.string) - camera.position.x = v.x - camera.position.y = v.y - camera.position.z = v.z + + if( !frag.q ){ + + if( true ){//!renderer.xr.isPresenting ){ + console.dir(camera) + camera.position.x = v.x + camera.position.y = v.y + camera.position.z = v.z + } + /* + else{ // XR + let cameraWorldPosition = new THREE.Vector3() + camera.object3D.getWorldPosition(this.cameraWorldPosition) + let newRigWorldPosition = new THREE.Vector3(v.x,v.y,x.z) + + // Finally update the cameras position + let newRigLocalPosition.copy(this.newRigWorldPosition) + if (camera.object3D.parent) { + camera.object3D.parent.worldToLocal(newRigLocalPosition) + } + camera.setAttribute('position', newRigLocalPosition) + + // Also take the headset/camera rotation itself into account + if (this.data.rotateOnTeleport) { + this.teleportOcamerainQuaternion + .setFromEuler(new THREE.Euler(0, this.teleportOcamerain.object3D.rotation.y, 0)) + this.teleportOcamerainQuaternion.invert() + this.teleportOcamerainQuaternion.multiply(this.hitEntityQuaternion) + // Rotate the camera based on calculated teleport ocamerain rotation + this.cameraRig.object3D.setRotationFromQuaternion(this.teleportOcamerainQuaternion) + } + + console.log("XR") + const offsetPosition = { x: - v.x, y: - v.y, z: - v.z, w: 1 }; + const offsetRotation = new THREE.Quaternion(); + const transform = new XRRigidTransform( offsetPosition, offsetRotation ); + const teleportSpaceOffset = xrf.baseReferenceSpace.getOffsetReferenceSpace( transform ); + renderer.xr.setReferenceSpace( teleportSpaceOffset ); + } + */ + + } }