refactored navigation (because of matrix-roomswitching) + updated docs with heuristic features
This commit is contained in:
parent
78eac1c12b
commit
05f4779e0e
11 changed files with 207 additions and 128 deletions
|
|
@ -11,7 +11,7 @@
|
|||
</head>
|
||||
<body>
|
||||
|
||||
<a-scene xr-mode-ui="XRMode: xr" renderer="colorManagement: true; highRefreshRate:true" light="defaultLightsEnabled: false">
|
||||
<a-scene xr-mode-ui="XRMode: xr" renderer="colorManagement: true; highRefreshRate:true; multiviewStereo:true " light="defaultLightsEnabled: false">
|
||||
<a-entity id="player" wasd-controls look-controls>
|
||||
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
|
||||
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
|
||||
|
|
|
|||
48
index.html
48
index.html
File diff suppressed because one or more lines are too long
|
|
@ -33,8 +33,16 @@ window.AFRAME.registerComponent('xrf', {
|
|||
if( ARbutton ) ARbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#AR' ) )
|
||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||
|
||||
xrf.addEventListener('navigateLoaded', () => {
|
||||
aScene.addEventListener('enter-vr', () => {
|
||||
// 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, pos.z) }
|
||||
})
|
||||
|
||||
xrf.addEventListener('navigateLoaded', (opts) => {
|
||||
setTimeout( () => AFRAME.fade.out(),500)
|
||||
let isLocal = opts.url.match(/^#/)
|
||||
if( isLocal ) return
|
||||
|
||||
// *TODO* this does not really belong here perhaps
|
||||
let blinkControls = document.querySelector('[blink-controls]')
|
||||
|
|
@ -58,29 +66,28 @@ window.AFRAME.registerComponent('xrf', {
|
|||
}
|
||||
})
|
||||
|
||||
xrf.addEventListener('href', (opts) => {
|
||||
if( opts.click){
|
||||
let p = opts.promise()
|
||||
let url = opts.xrf.string
|
||||
let isLocal = url.match(/^#/)
|
||||
let hasPos = url.match(/pos=/)
|
||||
if( isLocal && hasPos ){
|
||||
xrf.addEventListener('navigateLoading', (opts) => {
|
||||
let p = opts.promise()
|
||||
let url = opts.url
|
||||
let isLocal = url.match(/^#/)
|
||||
let hasPos = url.match(/pos=/)
|
||||
let fastFadeMs = 200
|
||||
|
||||
if( isLocal ){
|
||||
if( hasPos ){
|
||||
// local teleports only
|
||||
let fastFadeMs = 200
|
||||
AFRAME.fade.in(fastFadeMs)
|
||||
setTimeout( () => {
|
||||
p.resolve()
|
||||
AFRAME.fade.out(fastFadeMs)
|
||||
}, fastFadeMs)
|
||||
}else if( !isLocal ){
|
||||
AFRAME.fade.in()
|
||||
setTimeout( () => {
|
||||
p.resolve()
|
||||
setTimeout( () => AFRAME.fade.out(), 1000 ) // allow one second to load textures e.g.
|
||||
}, AFRAME.fade.data.fadetime )
|
||||
}else p.resolve()
|
||||
}
|
||||
}else{
|
||||
AFRAME.fade.in(fastFadeMs)
|
||||
setTimeout( () => {
|
||||
p.resolve()
|
||||
}, AFRAME.fade.data.fadetime )
|
||||
}
|
||||
})
|
||||
},{weight:-1000})
|
||||
|
||||
// convert href's to a-entity's so AFRAME
|
||||
// raycaster can find & execute it
|
||||
|
|
|
|||
|
|
@ -8,12 +8,12 @@ connectionsComponent = {
|
|||
<input type="radio" name="tab" id="login" checked>
|
||||
<label for="login">login</label>
|
||||
|
||||
<input type="radio" name="tab" id="networks">
|
||||
<label for="networks">networks</label>
|
||||
|
||||
<input type="radio" name="tab" id="io">
|
||||
<label for="io">devices</label>
|
||||
|
||||
<input type="radio" name="tab" id="networks">
|
||||
<label for="networks">advanced</label>
|
||||
|
||||
<div class="tab">
|
||||
<div id="settings"></div>
|
||||
<table>
|
||||
|
|
@ -26,31 +26,6 @@ connectionsComponent = {
|
|||
</table>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<div id="networking">
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webcam</td>
|
||||
<td>
|
||||
<select id="webcam"></select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chat</td>
|
||||
<td>
|
||||
<select id="chatnetwork"></select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>World sync</td>
|
||||
<td>
|
||||
<select id="scene"></select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<div id="devices">
|
||||
<a class="badge ruler">Webcam and/or Audio</a>
|
||||
|
|
@ -76,6 +51,32 @@ connectionsComponent = {
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<div id="networking">
|
||||
Networking a la carte:<br>
|
||||
<table>
|
||||
<tr>
|
||||
<td>Webcam</td>
|
||||
<td>
|
||||
<select id="webcam"></select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chat</td>
|
||||
<td>
|
||||
<select id="chatnetwork"></select>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>World sync</td>
|
||||
<td>
|
||||
<select id="scene"></select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`,
|
||||
|
|
|
|||
|
|
@ -14,15 +14,17 @@
|
|||
* xrf.emit('foo',123).then(...).catch(...).finally(...)
|
||||
*/
|
||||
|
||||
xrf.addEventListener = function(eventName, callback, scene) {
|
||||
xrf.addEventListener = function(eventName, callback, opts) {
|
||||
if( !this._listeners ) this._listeners = []
|
||||
callback.opts = opts || {weight: this._listeners.length}
|
||||
if (!this._listeners[eventName]) {
|
||||
// create a new array for this event name if it doesn't exist yet
|
||||
this._listeners[eventName] = [];
|
||||
}
|
||||
if( scene ) callback.scene = scene
|
||||
// add the callback to the listeners array for this event name
|
||||
this._listeners[eventName].push(callback);
|
||||
// sort
|
||||
this._listeners[eventName] = this._listeners[eventName].sort( (a,b) => a.opts.weight > b.opts.weight )
|
||||
return () => {
|
||||
this._listeners[eventName] = this._listeners[eventName].filter( (c) => c != callback )
|
||||
}
|
||||
|
|
@ -38,9 +40,6 @@ xrf.emit = function(eventName, data){
|
|||
console.groupEnd(label)
|
||||
if( xrf.debug > 1 ) debugger
|
||||
}
|
||||
// forward to THREEjs eventbus if any
|
||||
if( data.scene ) data.scene.dispatchEvent( eventName, data )
|
||||
if( data.mesh ) data.mesh.dispatchEvent( eventName, data )
|
||||
return xrf.emit.promise(eventName,data)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,47 +2,67 @@ xrf.navigator = {}
|
|||
|
||||
xrf.navigator.to = (url,flags,loader,data) => {
|
||||
if( !url ) throw 'xrf.navigator.to(..) no url given'
|
||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
||||
let hashChange = (!file && hash) || !data && xrf.model.file == file
|
||||
let hasPos = String(hash).match(/pos=/)
|
||||
|
||||
|
||||
let hashbus = xrf.hashbus
|
||||
xrf.emit('navigate', {url,loader,data})
|
||||
|
||||
return new Promise( (resolve,reject) => {
|
||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
||||
if( (!file && hash) || (!data && xrf.model.file == file) ){ // we're already loaded
|
||||
if( hash == document.location.hash.substr(1) ) return // block duplicate calls
|
||||
hashbus.pub( url, xrf.model, flags ) // and eval local URI XR fragments
|
||||
xrf.navigator.updateHash(hash)
|
||||
return resolve(xrf.model)
|
||||
}
|
||||
xrf
|
||||
.emit('navigate', {url,loader,data})
|
||||
.then( () => {
|
||||
|
||||
if( !loader ){
|
||||
const Loader = xrf.loaders[ext]
|
||||
if( !Loader ) return reject('xrfragment: no loader passed to xrfragment for extension .'+ext)
|
||||
loader = loader || new Loader().setPath( dir )
|
||||
}
|
||||
if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
|
||||
if( ext && !loader ){
|
||||
const Loader = xrf.loaders[ext]
|
||||
if( !Loader ) return resolve()
|
||||
loader = loader || new Loader().setPath( dir )
|
||||
}
|
||||
|
||||
// force relative path for files which dont include protocol or relative path
|
||||
if( dir ) dir = dir[0] == '.' || dir.match("://") ? dir : `.${dir}`
|
||||
url = url.replace(dir,"")
|
||||
loader = loader || new Loader().setPath( dir )
|
||||
const onLoad = (model) => {
|
||||
xrf.reset() // clear xrf objects from scene
|
||||
model.file = file
|
||||
// only change url when loading *another* file
|
||||
if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash )
|
||||
xrf.model = model
|
||||
// spec: 1. generate the XRWG
|
||||
xrf.XRWG.generate({model,scene:model.scene})
|
||||
if( !hash && !file && !ext ) return resolve(xrf.model) // nothing we can do here
|
||||
|
||||
xrf.add( model.scene )
|
||||
xrf.navigator.updateHash(hash)
|
||||
xrf.emit('navigateLoaded',{url,model})
|
||||
resolve(model)
|
||||
}
|
||||
if( hashChange && !hasPos ){
|
||||
hashbus.pub( url, xrf.model, flags ) // eval local URI XR fragments
|
||||
xrf.navigator.updateHash(hash) // which don't require
|
||||
return resolve(xrf.model) // positional navigation
|
||||
}
|
||||
|
||||
if( data ) loader.parse(data, "", onLoad )
|
||||
else loader.load(url, onLoad )
|
||||
xrf
|
||||
.emit('navigateLoading', {url,loader,data})
|
||||
.then( () => {
|
||||
if( hashChange && hasPos ){ // we're already loaded
|
||||
hashbus.pub( url, xrf.model, flags ) // and eval local URI XR fragments
|
||||
xrf.navigator.updateHash(hash)
|
||||
xrf.emit('navigateLoaded',{url})
|
||||
return resolve(xrf.model)
|
||||
}
|
||||
|
||||
if( xrf.model && xrf.model.scene ) xrf.model.scene.visible = false
|
||||
|
||||
// force relative path for files which dont include protocol or relative path
|
||||
if( dir ) dir = dir[0] == '.' || dir.match("://") ? dir : `.${dir}`
|
||||
url = url.replace(dir,"")
|
||||
loader = loader || new Loader().setPath( dir )
|
||||
const onLoad = (model) => {
|
||||
xrf.reset() // clear xrf objects from scene
|
||||
model.file = file
|
||||
// only change url when loading *another* file
|
||||
if( xrf.model ) xrf.navigator.pushState( `${dir}${file}`, hash )
|
||||
xrf.model = model
|
||||
// spec: 1. generate the XRWG
|
||||
xrf.XRWG.generate({model,scene:model.scene})
|
||||
|
||||
xrf.add( model.scene )
|
||||
if( hash ) xrf.navigator.updateHash(hash)
|
||||
xrf.emit('navigateLoaded',{url,model})
|
||||
resolve(model)
|
||||
}
|
||||
|
||||
if( data ) loader.parse(data, "", onLoad )
|
||||
else loader.load(url, onLoad )
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
|
@ -50,13 +70,17 @@ xrf.navigator.init = () => {
|
|||
if( xrf.navigator.init.inited ) return
|
||||
|
||||
window.addEventListener('popstate', function (event){
|
||||
xrf.navigator.to( document.location.search.substr(1) + document.location.hash )
|
||||
if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
|
||||
xrf.navigator.to( document.location.search.substr(1) + document.location.hash )
|
||||
}
|
||||
})
|
||||
|
||||
window.addEventListener('hashchange', function (e){
|
||||
xrf.emit('hash', {hash: document.location.hash })
|
||||
})
|
||||
|
||||
xrf.navigator.setupNavigateFallbacks()
|
||||
|
||||
// this allows selectionlines to be updated according to the camera (renderloop)
|
||||
xrf.focusLine = new xrf.THREE.Group()
|
||||
xrf.focusLine.material = new xrf.THREE.LineDashedMaterial({color:0xFF00FF,linewidth:3, scale: 1, dashSize: 0.2, gapSize: 0.1,opacity:0.3, transparent:true})
|
||||
|
|
@ -69,10 +93,36 @@ xrf.navigator.init = () => {
|
|||
xrf.navigator.init.inited = true
|
||||
}
|
||||
|
||||
xrf.navigator.setupNavigateFallbacks = () => {
|
||||
|
||||
xrf.addEventListener('navigate', (opts) => {
|
||||
let {url} = opts
|
||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
||||
// handle http links
|
||||
if( url.match(/^http/) && !xrf.loaders[ext] ){
|
||||
let inIframe
|
||||
try { inIframe = window.self !== window.top; } catch (e) { inIframe = true; }
|
||||
return inIframe ? window.parent.postMessage({ url }, '*') : window.open( url, '_blank')
|
||||
// in case you're running in an iframe, then use this in the parent page:
|
||||
//
|
||||
// window.addEventListener("message", (e) => {
|
||||
// if (e.data && e.data.url){
|
||||
// window.open( e.data.url, '_blank')
|
||||
// }
|
||||
// },
|
||||
// false,
|
||||
// );
|
||||
}
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
xrf.navigator.updateHash = (hash,opts) => {
|
||||
if( hash.replace(/^#/,'') == document.location.hash.substr(1) || hash.match(/\|/) ) return // skip unnecesary pushState triggers
|
||||
console.log(`URL: ${document.location.search.substr(1)}#${hash}`)
|
||||
xrf.navigator.updateHash.active = true // important to prevent recursion
|
||||
document.location.hash = hash
|
||||
xrf.navigator.updateHash.active = false
|
||||
}
|
||||
|
||||
xrf.navigator.pushState = (file,hash) => {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ xrf.addEventListener('dynamicKey', (opts) => {
|
|||
let {scene,id,match,v} = opts
|
||||
if( !scene ) return
|
||||
let remove = []
|
||||
|
||||
// erase previous lines
|
||||
xrf.focusLine.lines.map( (line) => line.parent && (line.parent.remove(line)) )
|
||||
xrf.focusLine.points = []
|
||||
|
|
|
|||
|
|
@ -3,13 +3,11 @@ xrf.frag.defaultPredefinedViews = (opts) => {
|
|||
scene.traverse( (n) => {
|
||||
if( n.userData && n.userData['#'] ){
|
||||
let frag = xrf.URI.parse( n.userData['#'] )
|
||||
xrf.hashbus.pub( n.userData['#'] ) // evaluate static XR fragments
|
||||
if( n.parent && n.parent.parent.isScene && document.location.hash.length < 2 ){
|
||||
xrf.navigator.to( n.userData['#'] ) // evaluate main scene XR fragments and update URL
|
||||
}else{
|
||||
xrf.hashbus.pub( n.userData['#'] ) // evaluate static XR fragments
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// clicking href url with predefined view
|
||||
xrf.addEventListener('href', (opts) => {
|
||||
if( !opts.click || opts.xrf.string[0] != '#' ) return
|
||||
xrf.hashbus.pub( opts.xrf.string )
|
||||
})
|
||||
|
|
|
|||
|
|
@ -39,26 +39,20 @@ xrf.frag.href = function(v, opts){
|
|||
xrf
|
||||
.emit('href',{click:true,mesh,xrf:v}) // let all listeners agree
|
||||
.then( () => {
|
||||
|
||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(v.string)
|
||||
const flags = v.string[0] == '#' ? xrf.XRF.PV_OVERRIDE : undefined
|
||||
const isLocal = v.string[0] == '#'
|
||||
const hasPos = isLocal && v.string.match(/pos=/)
|
||||
const flags = isLocal ? xrf.XRF.PV_OVERRIDE : undefined
|
||||
|
||||
let toFrag = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
||||
// always commit current location in case of teleport (keep a trail of last positions before we navigate)
|
||||
if( !e.nocommit && !document.location.hash.match(lastPos) ) xrf.navigator.to(`#${lastPos}`)
|
||||
xrf.navigator.to(v.string) // let's surf to HREF!
|
||||
.catch( (e) => { // not something we can load
|
||||
let inIframe
|
||||
try { inIframe = window.self !== window.top; } catch (e) { inIframe = true; }
|
||||
return inIframe ? window.parent.postMessage({ url: v.string }, '*') : window.open( v.string, '_blank')
|
||||
// in case you're running in an iframe, then use this in the parent page:
|
||||
//
|
||||
// window.addEventListener("message", (e) => {
|
||||
// if (e.data && e.data.url){
|
||||
// window.open( e.data.url, '_blank')
|
||||
// }
|
||||
// },
|
||||
// false,
|
||||
// );
|
||||
})
|
||||
if( isLocal && !hasPos ){
|
||||
xrf.hashbus.pub( v.string, xrf.model ) // publish to hashbus
|
||||
}else{
|
||||
//if( !e.nocommit && !document.location.hash.match(lastPos) ) xrf.navigator.updateHash(`#${lastPos}`)
|
||||
xrf.navigator.to(v.string) // let's surf
|
||||
}
|
||||
})
|
||||
.catch( console.error )
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,19 +1,23 @@
|
|||
xrf.frag.pos = function(v, opts){
|
||||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||
|
||||
let pos = v
|
||||
|
||||
// spec: indirect coordinate using objectname: https://xrfragment.org/#navigating%203D
|
||||
if( v.x == undefined ){
|
||||
if( pos.x == undefined ){
|
||||
let obj = scene.getObjectByName(v.string)
|
||||
if( !obj ) return
|
||||
let pos = obj.position.clone()
|
||||
pos = obj.position.clone()
|
||||
obj.getWorldPosition(pos)
|
||||
camera.position.copy(pos)
|
||||
}else{
|
||||
// spec: direct coordinate: https://xrfragment.org/#navigating%203D
|
||||
camera.position.x = v.x
|
||||
camera.position.y = v.y
|
||||
camera.position.z = v.z
|
||||
camera.position.x = pos.x
|
||||
camera.position.y = pos.y
|
||||
camera.position.z = pos.z
|
||||
}
|
||||
|
||||
xrf.frag.pos.last = pos // remember
|
||||
|
||||
camera.updateMatrixWorld()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ xrf.frag.src.addModel = (model,url,frag,opts) => {
|
|||
let {mesh} = opts
|
||||
let scene = model.scene
|
||||
scene = xrf.frag.src.filterScene(scene,{...opts,frag}) // get filtered scene
|
||||
if( mesh.material && !mesh.userData.src ) mesh.material.visible = false // hide placeholder object
|
||||
if( mesh.material && mesh.userData.src ) mesh.material.visible = false // hide placeholder object
|
||||
|
||||
//enableSourcePortation(scene)
|
||||
if( xrf.frag.src.renderAsPortal(mesh) ){
|
||||
// only add remote objects, because
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue