diff --git a/example/aframe/sandbox/index.html b/example/aframe/sandbox/index.html index f4093ba..27da388 100644 --- a/example/aframe/sandbox/index.html +++ b/example/aframe/sandbox/index.html @@ -15,17 +15,18 @@ - + - + diff --git a/example/assets/example.glb b/example/assets/example.glb index 04a8670..586691a 100644 Binary files a/example/assets/example.glb and b/example/assets/example.glb differ diff --git a/src/3rd/js/aframe/index.js b/src/3rd/js/aframe/index.js index 26cfceb..a201091 100644 --- a/src/3rd/js/aframe/index.js +++ b/src/3rd/js/aframe/index.js @@ -39,8 +39,6 @@ window.AFRAME.registerComponent('xrf', { col: THREE.ColladaLoader } }) - aScene.renderer.toneMapping = THREE.ACESFilmicToneMapping; - aScene.renderer.toneMappingExposure = 1.25; if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare ABOVE entities with xrf-attributes' if( AFRAME.utils.device.isMobile() ){ @@ -60,18 +58,22 @@ window.AFRAME.registerComponent('xrf', { // this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model aScene.canvas.addEventListener('mousedown', () => xrf.camera.el.setAttribute("look-controls","") ) }) + XRF.addEventListener('rot',(e) => { let lookcontrols = document.querySelector('[look-controls]') if( lookcontrols ) lookcontrols.removeAttribute("look-controls") }) let repositionUser = (scale) => () => { - // sometimes AFRAME resets the user position to 0,0,0 when entering VR (not sure why) - let pos = xrf.frag.pos.last - if( pos ){ AFRAME.XRF.camera.position.set(pos.x, pos.y*scale, pos.z) } + // sometimes AFRAME resets the user position to 0,0,0 when entering VR (not sure why) + setTimeout( () => { + let pos = xrf.frag.pos.lastVector3 + if( pos ){ xrf.camera.position.set(pos.x, pos.y*scale, pos.z) } + },500) } - aScene.addEventListener('enter-vr', () => setTimeout( () => repositionUser(1),100 ) ) - aScene.addEventListener('enter-ar', () => setTimeout( () => repositionUser(2),100 ) ) + + aScene.addEventListener('enter-vr', repositionUser(1) ) + aScene.addEventListener('enter-ar', repositionUser(2) ) xrf.addEventListener('navigateLoaded', (opts) => { setTimeout( () => AFRAME.fade.out(),500) @@ -145,9 +147,11 @@ window.AFRAME.registerComponent('xrf', { let {mesh,clickHandler} = opts; let createEl = function(c){ let el = document.createElement("a-entity") - el.setAttribute("xrf-get",c.name ) // turn into AFRAME entity + // raycaster el.setAttribute("pressable", '' ) // detect click via hand-detection + el.setAttribute("xrf-get",c.name ) // turn into AFRAME entity el.setAttribute("class","ray") // expose to raycaster + // respond to cursor via laser-controls (https://aframe.io/docs/1.4.0/components/laser-controls.html) el.addEventListener("click", clickHandler ) el.addEventListener("mouseenter", mesh.userData.XRF.href.selected(true) ) @@ -175,6 +179,8 @@ window.AFRAME.registerComponent('xrf', { // enable gaze-click on Mobile VR aScene.setAttribute('xrf-gaze','') + if( xrf.debug !== undefined && parseInt(xrf.debug) > 1) aScene.setAttribute('stats','') + } }, diff --git a/src/3rd/js/aframe/pressable.js b/src/3rd/js/aframe/pressable.js index 257599a..3e6b123 100644 --- a/src/3rd/js/aframe/pressable.js +++ b/src/3rd/js/aframe/pressable.js @@ -1,74 +1,55 @@ -// this makes WebXR hand controls able to click things (by touching it) +//// this makes WebXR hand controls able to click things (by touching it) AFRAME.registerComponent('pressable', { - schema: { - pressDistance: { - default: 0.01 - } - }, - init: function() { - this.worldPosition = new THREE.Vector3(); - this.fingerWorldPosition = new THREE.Vector3(); - this.raycaster = new THREE.Raycaster() - this.handEls = document.querySelectorAll('[hand-tracking-controls]'); - this.pressed = false; - this.distance = -1 - // we throttle by distance, to support scenes with loads of clickable objects (far away) - this.tick = this.throttleByDistance( () => this.detectPress() ) - }, - throttleByDistance: function(f){ - return function(){ - if( this.distance < 0 ) return f() // first call - if( !f.tid ){ - let x = this.distance - let y = x*(x*0.05)*1000 // parabolic curve - f.tid = setTimeout( function(){ - f.tid = null - f() - }, y ) - } - } - }, - detectPress: function(){ - if( !AFRAME.scenes[0].renderer.xr.isPresenting ) return - var handEls = this.handEls; - var handEl; - let minDistance = 5 + init: function(){ + let handEls = [...document.querySelectorAll('[hand-tracking-controls]')] - // compensate for xrf-get AFRAME component (which references non-reparented buffergeometries from the 3D model) - let object3D = this.el.object3D.child || this.el.object3D + for( let i in handEls ){ + let handEl = handEls[i] + handEl.addEventListener('model-loaded', () => { + if( handEl.pressable ) return - for (var i = 0; i < handEls.length; i++) { - handEl = handEls[i]; - let indexTipPosition = handEl.components['hand-tracking-controls'].indexTipPosition - // Apply the relative position to the parent's world position - handEl.object3D.updateMatrixWorld(); - handEl.object3D.getWorldPosition( this.fingerWorldPosition ) - this.fingerWorldPosition.add( indexTipPosition ) - - this.raycaster.far = this.data.pressDistance - // Create a direction vector (doesnt matter because it is supershort for 'touch' purposes) - const direction = new THREE.Vector3(1.0,0,0); - this.raycaster.set(this.fingerWorldPosition, direction) - intersects = this.raycaster.intersectObjects([object3D],true) - - object3D.getWorldPosition(this.worldPosition) - - distance = this.fingerWorldPosition.distanceTo(this.worldPosition) - minDistance = distance < minDistance ? distance : minDistance - - if (intersects.length ){ - if( !this.pressed ){ - this.el.emit('pressedstarted'); - this.el.emit('click'); - this.pressed = setTimeout( () => { - this.el.emit('pressedended'); - this.pressed = false - },300) - } + // wait for bones get initialized + setTimeout( () => { + let bones = handEl.components['hand-tracking-controls'].bones + let indexFinger + for( let i = 0; i < bones.length; i++){ + if( bones[i].name == "index-finger-tip" ){ + indexFinger = i + break } - } - this.distance = minDistance + } + // add obb-collider to index finger-tip + let aentity = document.createElement('a-entity') + trackedObject3DVariable = `parentNode.components.hand-tracking-controls.bones.${indexFinger}`; + console.log(trackedObject3DVariable) + handEl.appendChild(aentity) + aentity.setAttribute('obb-collider', {trackedObject3D: trackedObject3DVariable, size: 0.015}); + },500) + }) } -}); + + }, + + + events:{ + obbcollisionstarted: function(e){ + if( !e.detail.trackedObject3D ) return + if( e.currentTarget && e.currentTarget.emit ){ + //e.currentTarget.emit('click', + } + console.dir(e) + }, + "xrf-get": function(){ + //this.el.setAttribute('obb-collider',{trackedObject3D: 'el.object3D.child' }) // set collider on xrf-get object + let aentity = document.createElement('a-entity') + trackedObject3DVariable = this.el.object3D.child ? `parentNode.object3D.child` : `parentNode.object3D` + console.log(trackedObject3DVariable) + this.el.appendChild(aentity) + aentity.setAttribute('obb-collider', {trackedObject3D: trackedObject3DVariable}); + } + + } + +}) diff --git a/src/3rd/js/aframe/xrf-get.js b/src/3rd/js/aframe/xrf-get.js index a81a999..7c80587 100644 --- a/src/3rd/js/aframe/xrf-get.js +++ b/src/3rd/js/aframe/xrf-get.js @@ -43,11 +43,13 @@ window.AFRAME.registerComponent('xrf-get', { // as this would break animations this.el.object3D.add = (a) => a } + this.el.object3D.parent = mesh.parent this.el.setObject3D('mesh',mesh) // (doing this.el.object3D = mesh causes AFRAME to crash when resetting scene) this.el.object3D.child = mesh // keep reference (because .children will be empty) if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`) + this.el.emit('xrf-get',{}) } }, evt && evt.timeout ? evt.timeout: 500) diff --git a/src/3rd/js/index.js b/src/3rd/js/index.js index de4e679..f91d53e 100644 --- a/src/3rd/js/index.js +++ b/src/3rd/js/index.js @@ -7,12 +7,10 @@ xrf.init = function(opts){ opts = opts || {} xrf.debug = document.location.hostname.match(/^(localhost|[0-9]\.[0-9])/) || document.location.port == '8080' ? 0 : false - if( xrf.debug === false ){ - console.log("add #debug=[0-9] to URL to see XR Fragment debuglog") + if( document.location.hash.match(/debug=([0-9])/) ){ xrf.debug = parseInt( ( document.location.hash.match(/debug=([0-9])/) || [0,'0'] )[1] ) - }else{ - xrf.stats() } + if( xrf.debug === false ) console.log("add #debug=[0-9] to URL to see XR Fragment debuglog") xrf.Parser.debug = xrf.debug xrf.detectCameraRig(opts) @@ -40,15 +38,6 @@ xrf.detectCameraRig = function(opts){ } } -xrf.stats = () => { - // bookmarklet from https://github.com/zlgenuine/threejs_stats - (function(){ - let i = 0; - var script=document.createElement('script');script.onload=function(){var stats=new Stats();stats.showPanel( i ); - stats.dom.style.marginTop = `${i*48}px`; document.body.appendChild(stats.dom);requestAnimationFrame(function loop(){stats.update();requestAnimationFrame(loop)});};script.src='//rawgit.com/mrdoob/stats.js/master/build/stats.min.js';document.head.appendChild(script); - })() -} - xrf.hasTag = (tag,tags) => String(tags).match( new RegExp(`(^| )${tag}( |$)`,`g`) ) // map library functions to xrf diff --git a/src/3rd/js/plugin/frontend/frontend.js b/src/3rd/js/plugin/frontend/frontend.js index a8da400..176272d 100644 --- a/src/3rd/js/plugin/frontend/frontend.js +++ b/src/3rd/js/plugin/frontend/frontend.js @@ -281,7 +281,9 @@ window.frontend = (opts) => new Proxy({ updateHashPosition(randomize){ const pos = xrf.frag.pos.get() + xrf.navigator.reactifyHash.enabled = false // prevent teleport xrf.navigator.URI.hash.pos = `${pos.x},${pos.y},${pos.z}` + xrf.navigator.reactifyHash.enabled = true this.copyToClipboard( window.location.href ); }, diff --git a/src/3rd/js/pubsub.js b/src/3rd/js/pubsub.js index 914b527..9a2ba13 100644 --- a/src/3rd/js/pubsub.js +++ b/src/3rd/js/pubsub.js @@ -39,7 +39,7 @@ xrf.emit = function(eventName, data){ console.groupCollapsed(label) console.info(data) console.groupEnd(label) - if( xrf.debug > 1 ) debugger + if( xrf.debug > 2 ) debugger } return xrf.emit.promise(eventName,data) } diff --git a/src/3rd/js/three/index.js b/src/3rd/js/three/index.js index 25167b4..78527ec 100644 --- a/src/3rd/js/three/index.js +++ b/src/3rd/js/three/index.js @@ -5,6 +5,7 @@ xrf.mixers = [] xrf.init = ((init) => function(opts){ // operate in own subscene let scene = new opts.THREE.Group() + xrf.clock = new opts.THREE.Clock() opts.scene.add(scene) opts.sceneRoot = opts.scene opts.scene = scene @@ -17,8 +18,6 @@ xrf.init = ((init) => function(opts){ // return xrfragment lib as 'xrf' query functor (like jquery) for ( let i in xrf ) xrf.query[i] = xrf[i] - if( xrf.debug ) xrf.stats() - return xrf.query })(xrf.init) @@ -26,7 +25,6 @@ xrf.patchRenderer = function(opts){ let {renderer,camera} = opts renderer.xr.addEventListener( 'sessionstart', () => xrf.baseReferenceSpace = renderer.xr.getReferenceSpace() ); renderer.xr.enabled = true; - xrf.clock = new xrf.THREE.Clock() renderer.render = ((render) => function(scene,camera){ // update clock let time = xrf.clock.delta = xrf.clock.getDelta() @@ -65,7 +63,6 @@ xrf.parseModel.metadataInMesh = (mesh,model) => { } } - xrf.getLastModel = () => xrf.model.last xrf.reset = () => { diff --git a/src/3rd/js/three/navigator.js b/src/3rd/js/three/navigator.js index 5470494..e8bdc27 100644 --- a/src/3rd/js/three/navigator.js +++ b/src/3rd/js/three/navigator.js @@ -12,7 +12,7 @@ xrf.navigator.to = (url,flags,loader,data) => { if( !url ) throw 'xrf.navigator.to(..) no url given' let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url ) - URI.hash = xrf.navigator.reactifyHash(URI.hash) + URI.hash = xrf.navigator.reactifyHash(URI.hash) // automatically reflect hash-changes to navigator.to(...) // decorate with extra state URI.fileChange = URI.file && URI.URN + URI.file != xrf.navigator.URI.URN + xrf.navigator.URI.file URI.external = URI.file && URI.URN != document.location.origin + document.location.pathname @@ -152,7 +152,7 @@ xrf.navigator.setupNavigateFallbacks = () => { let {fileExt} = xrfragment.URI.parse(url) // handle http links - if( url.match(/^http/) && !xrf.loaders[fileExt] ){ + if( url.match(/^http/) && url != xrf.navigator.URI.URN && !xrf.loaders[fileExt] ){ let inIframe try { inIframe = window.self !== window.top; } catch (e) { inIframe = true; } return inIframe ? window.parent.postMessage({ url }, '*') : window.open( url, '_blank') @@ -189,7 +189,9 @@ xrf.navigator.reactifyHash = ( obj ) => { get(me,k) { return me[k] }, set(me,k,v){ me[k] = v - xrf.navigator.to( "#" + this.toString(me) ) + if( xrf.navigator.reactifyHash.enabled ){ + xrf.navigator.to( "#" + this.toString(me) ) + } }, toString(me){ let parts = [] @@ -200,3 +202,4 @@ xrf.navigator.reactifyHash = ( obj ) => { } }) } +xrf.navigator.reactifyHash.enabled = true diff --git a/src/3rd/js/three/xrf/pos.js b/src/3rd/js/three/xrf/pos.js index 189aee8..3a70e85 100644 --- a/src/3rd/js/three/xrf/pos.js +++ b/src/3rd/js/three/xrf/pos.js @@ -20,6 +20,7 @@ xrf.frag.pos = function(v, opts){ if( xrf.debug ) console.log(`#pos.js: setting camera to position ${pos.x},${pos.y},${pos.z}`) xrf.frag.pos.last = v.string // remember + xrf.frag.pos.lastVector3 = camera.position.clone() camera.updateMatrixWorld() } diff --git a/src/3rd/js/three/xrf/src.js b/src/3rd/js/three/xrf/src.js index 0e3d02f..3bf4674 100644 --- a/src/3rd/js/three/xrf/src.js +++ b/src/3rd/js/three/xrf/src.js @@ -34,9 +34,9 @@ xrf.frag.src.addModel = (model,url,frag,opts) => { if( mesh.material && mesh.userData.src ) mesh.material.visible = false // hide placeholder object if( opts.isPortal ){ - // only add remote objects, because - // local scene-objects are already added to scene xrf.portalNonEuclidian({...opts,model,scene:model.scene}) + // only add external objects, because + // local scene-objects are already added to scene if( !opts.isLocal ) xrf.scene.add(scene) }else{ xrf.frag.src.scale( scene, opts, url ) // scale scene @@ -45,7 +45,7 @@ xrf.frag.src.addModel = (model,url,frag,opts) => { xrf.frag.src.enableSourcePortation({scene,mesh,url,model}) // flag everything isSRC & isXRF mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true }) - + xrf.emit('parseModel', {...opts, isSRC:true, mesh, model}) // this will execute all embedded metadata/fragments e.g. } @@ -94,7 +94,7 @@ xrf.frag.src.externalSRC = (url,frag,opts) => { fetch(url, { method: 'HEAD' }) .then( (res) => { let mimetype = res.headers.get('Content-type') - if(xrf.debug > 0 ) console.log("HEAD "+url+" => "+mimetype) + if(xrf.debug != undefined ) console.log("HEAD "+url+" => "+mimetype) if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf' if( url.replace(/#.*/,'').match(/\.(frag|fs|glsl)$/) ) mimetype = 'x-shader/x-fragment' if( url.replace(/#.*/,'').match(/\.(vert|vs)$/) ) mimetype = 'x-shader/x-fragment'