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 3 D 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
* /
2023-05-12 22:06:21 +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 ) => {
2023-10-11 13:46:38 +02:00
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
2024-02-12 17:21:40 +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 ) } `
2023-06-08 17:45:21 +02:00
xrf
2024-06-11 17:30:32 +00:00
. emit ( 'href' , { click : true , mesh , xrf : v , value : fragValue } ) // let all listeners agree
2023-06-08 17:45:21 +02:00
. then ( ( ) => {
2024-01-24 18:11:37 +00:00
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
2023-06-08 17:45:21 +02:00
} )
2023-10-11 13:46:38 +02:00
. catch ( console . error )
2023-05-12 22:06:21 +02:00
}
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 )
2023-10-11 13:46:38 +02:00
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 = { }
2024-06-12 08:51:42 +00:00
const actions = [ 'click' , 'hover' , 'teleport' ]
2024-02-13 17:10:24 +00:00
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
2023-05-23 12:51:13 +02:00
*
* < $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" / >
* < / $ v i d e o j s >
*
* > capture of < a href = "./example/aframe/sandbox" target = "_blank" > aframe / sandbox < / a >
2023-05-22 13:56:33 +02:00
* /