2023-05-09 17:42:29 +02:00
window . AFRAME . registerComponent ( 'xrf' , {
schema : {
2024-01-03 15:55:23 +00:00
http : { type : 'string' } ,
https : { type : 'string' } ,
2023-05-09 17:42:29 +02:00
} ,
2024-02-23 22:02:07 +00:00
init : async function ( ) {
2024-01-30 09:58:00 +00:00
// override this.data when URL has passed (`://....com/?https://foo.com/index.glb` e.g.)
if ( typeof this . data == "string" ) {
let searchIsUri = document . location . search &&
! document . location . search . match ( /=/ ) &&
2024-02-23 22:02:07 +00:00
document . location . search . match ( "/" )
2024-01-30 09:58:00 +00:00
if ( searchIsUri || document . location . hash . length > 1 ) { // override url
this . data = ` ${ document . location . search . substr ( 1 ) } ${ document . location . hash } `
}
}
2024-06-25 13:58:12 +00:00
if ( ! AFRAME . scenes [ 0 ] ) return // ignore if no scene yet
2023-09-15 19:42:37 +02:00
if ( ! AFRAME . XRF ) {
2023-11-08 18:28:18 +01:00
2023-11-29 16:45:21 +01:00
let camera = document . querySelector ( '[camera]' )
2023-11-08 18:28:18 +01:00
// start with black
2023-11-29 16:45:21 +01:00
camera . setAttribute ( 'xrf-fade' , '' )
AFRAME . fade = camera . components [ 'xrf-fade' ]
2023-11-08 18:28:18 +01:00
2024-01-03 15:55:23 +00:00
let aScene = AFRAME . scenes [ 0 ]
2023-05-12 22:06:21 +02:00
2024-01-03 15:55:23 +00:00
// enable XR fragments
let XRF = AFRAME . XRF = xrf . init ( {
THREE ,
camera : aScene . camera ,
scene : aScene . object3D ,
renderer : aScene . renderer ,
loaders : {
gltf : THREE . GLTFLoader , // which 3D assets (exts) to check for XR fragments?
2024-02-23 22:02:07 +00:00
glb : THREE . GLTFLoader ,
obj : THREE . OBJLoader ,
fbx : THREE . FBXLoader ,
usdz : THREE . USDZLoader ,
col : THREE . ColladaLoader
2024-01-03 15:55:23 +00:00
}
} )
if ( ! XRF . camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
2024-03-01 13:35:36 +00:00
if ( AFRAME . utils . device . isMobile ( ) ) {
// aScene.setAttribute('webxr',"requiredFeatures: dom-overlay; overlayElement: canvas; referenceSpaceType: local")
}
2023-12-08 13:35:19 +01:00
2024-03-01 13:35:36 +00:00
aScene . addEventListener ( 'loaded' , ( ) => {
// this is just for convenience (not part of spec): enforce AR + hide/show stuff based on VR tags in 3D model
ARbutton = document . querySelector ( '.a-enter-ar-button' )
VRbutton = document . querySelector ( '.a-enter-vr-button' )
if ( ARbutton ) ARbutton . addEventListener ( 'click' , ( ) => AFRAME . XRF . hashbus . pub ( '#-VR' ) )
if ( VRbutton ) VRbutton . addEventListener ( 'click' , ( ) => AFRAME . XRF . hashbus . pub ( '#VR' ) )
2024-04-25 15:56:29 +00:00
} )
2024-03-01 13:35:36 +00:00
let repositionUser = ( scale ) => ( ) => {
2024-06-07 07:33:00 +00:00
// 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 )
2024-03-01 13:35:36 +00:00
}
2024-06-07 07:33:00 +00:00
aScene . addEventListener ( 'enter-vr' , repositionUser ( 1 ) )
aScene . addEventListener ( 'enter-ar' , repositionUser ( 2 ) )
2024-01-24 18:11:37 +00:00
xrf . addEventListener ( 'navigateLoaded' , ( opts ) => {
2024-01-03 15:55:23 +00:00
setTimeout ( ( ) => AFRAME . fade . out ( ) , 500 )
2024-01-24 18:11:37 +00:00
let isLocal = opts . url . match ( /^#/ )
if ( isLocal ) return
2023-11-24 17:32:53 +01:00
2024-01-03 15:55:23 +00:00
// *TODO* this does not really belong here perhaps
let blinkControls = document . querySelector ( '[blink-controls]' )
if ( blinkControls ) {
let els = xrf . getCollisionMeshes ( )
let invisible = false
els . map ( ( mesh ) => {
if ( ! invisible ) {
invisible = mesh . material . clone ( )
invisible . visible = false
}
mesh . material = invisible
let el = document . createElement ( "a-entity" )
el . setAttribute ( "xrf-get" , mesh . name )
el . setAttribute ( "class" , "floor" )
$ ( 'a-scene' ) . appendChild ( el )
} )
2024-01-10 22:01:21 +01:00
let com = blinkControls . components [ 'blink-controls' ]
if ( com ) com . update ( { collisionEntities : true } )
else console . warn ( "xrfragments: blink-controls is not mounted, please run manually: $('[blink-controls]).components['blink-controls'].update({collisionEntities:true})" )
2024-02-13 17:10:24 +00:00
blinkControls . addEventListener ( 'teleported' , ( e ) => {
if ( e . detail . newPosition . z < 0 ) {
console . warn ( 'teleported to negative Z-value: https://github.com/jure/aframe-blink-controls/issues/30' )
}
} )
2024-01-03 15:55:23 +00:00
}
2024-01-31 15:03:58 +00:00
// give headset users way to debug without a cumbersome usb-tapdance
2024-03-12 11:32:59 +01:00
if ( document . location . hostname . match ( /^(localhost|[1-9])/ ) && ! aScene . getAttribute ( "vconsole" ) ) {
2024-01-31 15:03:58 +00:00
aScene . setAttribute ( 'vconsole' , '' )
}
2024-01-03 15:55:23 +00:00
} )
2024-02-12 17:21:40 +00:00
xrf . addEventListener ( 'navigateError' , ( opts ) => {
AFRAME . fade . out ( )
} )
2023-11-08 18:28:18 +01:00
2024-01-24 18:11:37 +00:00
xrf . addEventListener ( 'navigateLoading' , ( opts ) => {
let p = opts . promise ( )
let url = opts . url
let isLocal = url . match ( /^#/ )
let hasPos = url . match ( /pos=/ )
let fastFadeMs = 200
2024-02-16 16:34:32 +00:00
if ( ! AFRAME . fade ) return p . resolve ( )
2024-01-24 18:11:37 +00:00
if ( isLocal ) {
if ( hasPos ) {
2024-01-03 15:55:23 +00:00
// local teleports only
AFRAME . fade . in ( fastFadeMs )
setTimeout ( ( ) => {
p . resolve ( )
} , fastFadeMs )
2024-01-24 18:11:37 +00:00
}
} else {
AFRAME . fade . in ( fastFadeMs )
setTimeout ( ( ) => {
p . resolve ( )
} , AFRAME . fade . data . fadetime )
2023-09-15 19:42:37 +02:00
}
2024-01-24 18:11:37 +00:00
} , { weight : - 1000 } )
2023-05-23 14:41:24 +02:00
2024-01-03 15:55:23 +00:00
// convert href's to a-entity's so AFRAME
// raycaster can find & execute it
AFRAME . XRF . clickableMeshToEntity = ( opts ) => {
let { mesh , clickHandler } = opts ;
2024-02-12 17:21:40 +00:00
let createEl = function ( c ) {
let el = document . createElement ( "a-entity" )
2024-06-07 07:33:00 +00:00
// raycaster
2024-06-11 17:30:32 +00:00
el . setAttribute ( "xrf-get" , { name : c . name } ) // turn into AFRAME entity
2024-02-12 17:21:40 +00:00
el . setAttribute ( "class" , "ray" ) // expose to raycaster
2024-06-07 07:33:00 +00:00
2024-02-12 17:21:40 +00:00
// 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 ) )
el . addEventListener ( "mouseleave" , mesh . userData . XRF . href . selected ( false ) )
$ ( 'a-scene' ) . appendChild ( el )
}
createEl ( mesh )
2024-01-03 15:55:23 +00:00
}
xrf . addEventListener ( 'interactionReady' , AFRAME . XRF . clickableMeshToEntity )
2023-05-18 12:32:57 +02:00
2024-01-03 15:55:23 +00:00
if ( typeof this . data === 'string' || this . data . http || this . data . https ) {
let url
if ( typeof this . data === 'string' ) url = this . data
if ( this . data . http ) url = ` http: ${ this . data . http } `
if ( this . data . https ) url = ` https: ${ this . data . https } `
AFRAME . XRF . navigator . to ( url )
. then ( ( model ) => {
let gets = [ ... document . querySelectorAll ( '[xrf-get]' ) ]
gets . map ( ( g ) => g . emit ( 'update' ) )
} )
2024-06-25 13:58:12 +00:00
} else {
// load current AFRAME scene as model
let sceneEl = aScene . querySelector ( '[xrf]' )
if ( ! sceneEl . object3D ) return console . error ( "please model your XR Fragments scene within <a-entity xrf> .... </a-entity>" )
const scene = sceneEl . object3D
// name THREE objects according to AFRAME element ids
scene . traverse ( ( m ) => {
if ( ! m . name && m . el && m . el . id ) m . name = m . el . id
} )
// load current scene as model
xrf . model = { scene , animations : [ ] }
xrf . scene = scene
//xrf.loadModel( xrf.model, "#", true )
////if( sceneEl.components.xrf.data ){
//// xrf.navigator.to(sceneEl.components.xrf.data ) // eval default fragment
//// console.log("evaluating default fragments")
//// sceneEl.object3D.userData['#'] = sceneEl.components.xrf.data
////}
//if( document.location.hash ){
// xrf.hashbus.pub( document.location.hash, xrf.model) // eval url
AFRAME . fade . out ( )
2024-01-03 15:55:23 +00:00
}
aScene . emit ( 'XRF' , { } )
// enable gaze-click on Mobile VR
aScene . setAttribute ( 'xrf-gaze' , '' )
2023-10-14 20:10:06 +02:00
2024-06-11 17:30:32 +00:00
// detect href click via hand-detection
aScene . setAttribute ( "xrf-hands" , '' )
2024-06-07 07:33:00 +00:00
if ( xrf . debug !== undefined && parseInt ( xrf . debug ) > 1 ) aScene . setAttribute ( 'stats' , '' )
2023-09-15 19:42:37 +02:00
}
2023-05-12 22:06:21 +02:00
} ,
2023-09-15 19:42:37 +02:00
2023-05-12 22:06:21 +02:00
} )