xrfragment/src/3rd/js/three/xrf/href.js

157 lines
6.2 KiB
JavaScript
Raw Normal View History

2023-05-22 13:56:33 +02:00
/**
2023-05-23 12:35:05 +02:00
*
2023-05-22 13:56:33 +02:00
* navigation, portals & mutations
2023-05-23 12:35:05 +02:00
*
2023-05-22 13:56:33 +02:00
* | fragment | type | scope | example value |
2023-05-23 13:00:28 +02:00
* |`href`| string (uri or predefined view) | 🔒 |`#pos=1,1,0`<br>`#pos=1,1,0&rot=90,0,0`<br>`#pos=pyramid`<br>`#pos=lastvisit|pyramid`<br>`://somefile.gltf#pos=1,1,0`<br> |
2023-05-23 12:35:05 +02:00
*
2023-05-23 13:00:28 +02:00
* [[» example implementation|https://github.com/coderofsalvation/xrfragment/blob/main/src/3rd/three/xrf/href.js]]<br>
* [[» example 3D asset|https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/href.gltf#L192]]<br>
2023-05-23 12:35:05 +02:00
* [[» discussion|https://github.com/coderofsalvation/xrfragment/issues/1]]<br>
2023-05-22 13:56:33 +02:00
*
2023-05-22 14:10:44 +02:00
* [img[xrfragment.jpg]]
2023-05-23 12:35:05 +02:00
*
*
2023-05-22 14:10:44 +02:00
* !!!spec 1.0
2023-05-23 12:35:05 +02:00
*
* 1. an ''external''- or ''file URI'' fully replaces the current scene and assumes `pos=0,0,0&rot=0,0,0` by default (unless specified)
*
2023-05-22 13:56:33 +02:00
* 2. navigation should not happen when queries (`q=`) are present in local url: queries will apply (`pos=`, `rot=` e.g.) to the targeted object(s) instead.
2023-05-23 12:35:05 +02:00
*
2023-05-22 14:10:44 +02:00
* 3. navigation should not happen ''immediately'' when user is more than 2 meter away from the portal/object containing the href (to prevent accidental navigation e.g.)
2023-05-23 12:35:05 +02:00
*
* 4. URL navigation should always be reflected in the client (in case of javascript: see [[here|https://github.com/coderofsalvation/xrfragment/blob/dev/src/3rd/three/navigator.js]] for an example navigator).
*
* 5. In XR mode, the navigator back/forward-buttons should be always visible (using a wearable e.g., see [[here|https://github.com/coderofsalvation/xrfragment/blob/dev/example/aframe/sandbox/index.html#L26-L29]] for an example wearable)
*
* [img[navigation.png]]
*
2023-05-22 13:56:33 +02:00
*/
xrf.frag.href = function(v, opts){
2023-09-21 13:05:30 +02:00
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
2023-05-05 18:53:42 +02:00
2023-06-09 16:40:08 +02:00
if( mesh.userData.XRF.href.exec ) return // mesh already initialized
2023-06-07 17:42:21 +02:00
let click = mesh.userData.XRF.href.exec = (e) => {
2024-03-05 11:56:06 +00:00
if( !mesh.material || !mesh.material.visible ) return // ignore invisible nodes
2024-02-14 15:47:34 +00:00
2024-03-18 17:14:16 +00:00
// update our values to the latest value (might be edited)
2024-03-19 09:53:22 +00:00
xrf.Parser.parse( "href", mesh.userData.href, frag )
const v = frag.href
2024-03-18 17:14:16 +00:00
// bubble up!
mesh.traverseAncestors( (n) => n.userData && n.userData.href && n.dispatchEvent({type:e.type,data:{}}) )
2024-06-11 17:30:32 +00:00
let fragValue = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
2023-11-06 14:42:51 +01:00
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
xrf
2024-06-11 17:30:32 +00:00
.emit('href',{click:true,mesh,xrf:v,value: fragValue}) // let all listeners agree
.then( () => {
const isLocal = v.string[0] == '#'
const hasPos = isLocal && v.string.match(/pos=/)
const flags = isLocal ? xrf.XRF.PV_OVERRIDE : undefined
2024-02-14 15:47:34 +00:00
if( v.xrfScheme ){
xrf.hashbus.pub(v.string)
} else xrf.navigator.to(v.string) // let's surf
})
.catch( console.error )
}
2023-10-14 20:10:06 +02:00
let selected = mesh.userData.XRF.href.selected = (state) => () => {
2024-03-05 11:56:06 +00:00
if( (!mesh.material && !mesh.material.visible) && !mesh.isSRC ) return // ignore invisible nodes
2023-05-22 13:56:33 +02:00
if( mesh.selected == state ) return // nothing changed
2024-02-13 17:10:24 +00:00
2023-10-12 17:04:46 +02:00
xrf.interactive.objects.map( (o) => {
let newState = o.name == mesh.name ? state : false
if( o.material ){
2024-02-08 19:40:43 +01:00
if( o.material.uniforms && o.material.uniforms.selected ) o.material.uniforms.selected.value = newState
2023-11-08 18:28:18 +01:00
//if( o.material.emissive ) o.material.emissive.r = o.material.emissive.g = o.material.emissive.b = newState ? 2.0 : 1.0
if( o.material.emissive ){
if( !o.material.emissive.original ) o.material.emissive.original = o.material.emissive.clone()
o.material.emissive.r = o.material.emissive.g = o.material.emissive.b =
2023-11-18 20:50:22 +01:00
newState ? o.material.emissive.original.r + 0.5 : o.material.emissive.original.r
2023-11-08 18:28:18 +01:00
}
2023-10-12 17:04:46 +02:00
}
})
2023-05-22 13:56:33 +02:00
// update mouse cursor
if( !renderer.domElement.lastCursor )
renderer.domElement.lastCursor = renderer.domElement.style.cursor
renderer.domElement.style.cursor = state ? 'pointer' : renderer.domElement.lastCursor
2023-10-12 17:04:46 +02:00
2023-06-02 12:00:21 +02:00
xrf
.emit('href',{selected:state,mesh,xrf:v}) // let all listeners agree
.then( () => mesh.selected = state )
2023-05-22 13:56:33 +02:00
}
2023-06-07 17:42:21 +02:00
mesh.addEventListener('click', click )
mesh.addEventListener('mousemove', selected(true) )
2023-11-08 18:28:18 +01:00
mesh.addEventListener('mouseenter', selected(true) )
2023-10-14 20:10:06 +02:00
mesh.addEventListener('mouseleave', selected(false) )
2023-05-17 21:31:28 +02:00
2023-11-18 20:50:22 +01:00
if( mesh.material ) mesh.material = mesh.material.clone() // clone, so we can individually highlight meshes
2023-05-22 13:56:33 +02:00
// lazy add mesh (because we're inside a recursive traverse)
setTimeout( (mesh) => {
xrf.interactive.add(mesh)
xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
}, 0, mesh )
2023-05-05 18:53:42 +02:00
}
2023-05-22 13:56:33 +02:00
2024-02-13 17:10:24 +00:00
xrf.addEventListener('audioInited', function(opts){
let {THREE,listener} = opts
opts.audio = opts.audio || {}
2024-06-11 17:30:32 +00:00
opts.audio.click = opts.audio.click || '/dist/audio/click.wav'
opts.audio.hover = opts.audio.hover || '/dist/audio/hover.wav'
opts.audio.teleport = opts.audio.teleport || '/dist/audio/teleport.wav'
2024-02-13 17:10:24 +00:00
let audio = xrf.frag.href.audio = {}
actions = ['click','hover','teleport']
actions.map( (action) => {
const audioLoader = new THREE.AudioLoader();
audio[action] = new THREE.Audio( xrf.camera.listener )
audioLoader.load( opts.audio[action], function( buffer ) {
audio[action].setBuffer( buffer );
})
});
xrf.addEventListener('href', (opts) => {
let v = opts.xrf
if( opts.selected ){
xrf.frag.href.audio.hover.stop()
xrf.frag.href.audio.hover.play()
return
}
if( opts.click ){
xrf.frag.href.audio.click.stop()
xrf.frag.href.audio.click.play()
return
}
})
xrf.addEventListener('navigateLoading', (e) => {
xrf.frag.href.audio.click.stop()
xrf.frag.href.audio.teleport.stop()
xrf.frag.href.audio.teleport.play()
})
})
2023-05-22 13:56:33 +02:00
/**
2023-05-23 12:35:05 +02:00
* > above solutions were abducted from [[this|https://i.imgur.com/E3En0gJ.png]] and [[this|https://i.imgur.com/lpnTz3A.png]] survey result
2023-05-22 13:56:33 +02:00
*
2023-05-23 12:35:05 +02:00
* !!!Demo
*
* <$videojs controls="controls" aspectratio="16:9" preload="auto" poster="" fluid="fluid" class="vjs-big-play-centered">
* <source src="https://coderofsalvation.github.io/xrfragment.media/href.mp4" type="video/mp4"/>
* </$videojs>
*
* > capture of <a href="./example/aframe/sandbox" target="_blank">aframe/sandbox</a>
2023-05-22 13:56:33 +02:00
*/