allow empty xrf
This commit is contained in:
parent
13f96e0506
commit
49cdf18472
7 changed files with 4797 additions and 345 deletions
214
dist/xrfragment.aframe.all.js
vendored
214
dist/xrfragment.aframe.all.js
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* v0.5.1 generated at Sat Dec 30 09:46:42 PM UTC 2023
|
* v0.5.1 generated at Wed Jan 3 03:55:09 PM UTC 2024
|
||||||
* https://xrfragment.org
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -2341,6 +2341,8 @@ xrf.addEventListener('t', (opts) => {
|
||||||
})
|
})
|
||||||
window.AFRAME.registerComponent('xrf', {
|
window.AFRAME.registerComponent('xrf', {
|
||||||
schema: {
|
schema: {
|
||||||
|
http: { type:'string'},
|
||||||
|
https: { type:'string'},
|
||||||
},
|
},
|
||||||
init: function () {
|
init: function () {
|
||||||
if( !AFRAME.XRF ){
|
if( !AFRAME.XRF ){
|
||||||
|
|
@ -2350,112 +2352,116 @@ window.AFRAME.registerComponent('xrf', {
|
||||||
camera.setAttribute('xrf-fade','')
|
camera.setAttribute('xrf-fade','')
|
||||||
AFRAME.fade = camera.components['xrf-fade']
|
AFRAME.fade = camera.components['xrf-fade']
|
||||||
|
|
||||||
let aScene = document.querySelector('a-scene')
|
let aScene = AFRAME.scenes[0]
|
||||||
aScene.addEventListener('loaded', () => {
|
|
||||||
|
|
||||||
// enable XR fragments
|
// enable XR fragments
|
||||||
let XRF = AFRAME.XRF = xrf.init({
|
let XRF = AFRAME.XRF = xrf.init({
|
||||||
THREE,
|
THREE,
|
||||||
camera: aScene.camera,
|
camera: aScene.camera,
|
||||||
scene: aScene.object3D,
|
scene: aScene.object3D,
|
||||||
renderer: aScene.renderer,
|
renderer: aScene.renderer,
|
||||||
loaders: {
|
loaders: {
|
||||||
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
||||||
glb: THREE.GLTFLoader,
|
glb: THREE.GLTFLoader,
|
||||||
obj: THREE.OBJLoader
|
obj: THREE.OBJLoader
|
||||||
}
|
|
||||||
})
|
|
||||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
|
||||||
|
|
||||||
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR 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( '#AR' ) )
|
|
||||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
|
||||||
|
|
||||||
xrf.addEventListener('navigateLoaded', () => {
|
|
||||||
setTimeout( () => AFRAME.fade.out(),500)
|
|
||||||
|
|
||||||
// *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)
|
|
||||||
})
|
|
||||||
blinkControls.components['blink-controls'].update({collisionEntities:true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 && !url.match(/^http/) ) return // dont fade/load for custom protocol handlers
|
|
||||||
if( isLocal && 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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// convert href's to a-entity's so AFRAME
|
|
||||||
// raycaster can find & execute it
|
|
||||||
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
|
||||||
let {mesh,clickHandler} = opts;
|
|
||||||
let el = document.createElement("a-entity")
|
|
||||||
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
|
||||||
el.setAttribute("class","ray") // expose to raycaster
|
|
||||||
el.setAttribute("pressable", '') // detect hand-controller click
|
|
||||||
// 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) )
|
|
||||||
el.addEventListener("pressedstarted", clickHandler )
|
|
||||||
$('a-scene').appendChild(el)
|
|
||||||
}
|
}
|
||||||
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
|
||||||
|
|
||||||
// cleanup xrf-get objects when resetting scene
|
|
||||||
xrf.addEventListener('reset', (opts) => {
|
|
||||||
let els = [...document.querySelectorAll('[xrf-get]')]
|
|
||||||
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
|
||||||
})
|
|
||||||
|
|
||||||
AFRAME.XRF.navigator.to(this.data)
|
|
||||||
.then( (model) => {
|
|
||||||
let gets = [ ...document.querySelectorAll('[xrf-get]') ]
|
|
||||||
gets.map( (g) => g.emit('update') )
|
|
||||||
})
|
|
||||||
|
|
||||||
aScene.emit('XRF',{})
|
|
||||||
|
|
||||||
// enable gaze-click on Mobile VR
|
|
||||||
aScene.setAttribute('xrf-gaze','')
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||||
|
|
||||||
|
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR 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( '#AR' ) )
|
||||||
|
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||||
|
|
||||||
|
xrf.addEventListener('navigateLoaded', () => {
|
||||||
|
setTimeout( () => AFRAME.fade.out(),500)
|
||||||
|
|
||||||
|
// *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)
|
||||||
|
})
|
||||||
|
blinkControls.components['blink-controls'].update({collisionEntities:true})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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 && !url.match(/^http/) ) return // dont fade/load for custom protocol handlers
|
||||||
|
if( isLocal && 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// convert href's to a-entity's so AFRAME
|
||||||
|
// raycaster can find & execute it
|
||||||
|
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
||||||
|
let {mesh,clickHandler} = opts;
|
||||||
|
let el = document.createElement("a-entity")
|
||||||
|
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
||||||
|
el.setAttribute("class","ray") // expose to raycaster
|
||||||
|
el.setAttribute("pressable", '') // detect hand-controller click
|
||||||
|
// 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) )
|
||||||
|
el.addEventListener("pressedstarted", clickHandler )
|
||||||
|
$('a-scene').appendChild(el)
|
||||||
|
}
|
||||||
|
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
||||||
|
|
||||||
|
// cleanup xrf-get objects when resetting scene
|
||||||
|
xrf.addEventListener('reset', (opts) => {
|
||||||
|
let els = [...document.querySelectorAll('[xrf-get]')]
|
||||||
|
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
||||||
|
})
|
||||||
|
|
||||||
|
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') )
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
aScene.emit('XRF',{})
|
||||||
|
|
||||||
|
// enable gaze-click on Mobile VR
|
||||||
|
aScene.setAttribute('xrf-gaze','')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( typeof this.data == "string" ){
|
if( typeof this.data == "string" ){
|
||||||
|
|
|
||||||
214
dist/xrfragment.aframe.js
vendored
214
dist/xrfragment.aframe.js
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* v0.5.1 generated at Sat Dec 30 09:46:41 PM UTC 2023
|
* v0.5.1 generated at Wed Jan 3 03:55:09 PM UTC 2024
|
||||||
* https://xrfragment.org
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
*/
|
*/
|
||||||
|
|
@ -2339,6 +2339,8 @@ xrf.addEventListener('t', (opts) => {
|
||||||
})
|
})
|
||||||
window.AFRAME.registerComponent('xrf', {
|
window.AFRAME.registerComponent('xrf', {
|
||||||
schema: {
|
schema: {
|
||||||
|
http: { type:'string'},
|
||||||
|
https: { type:'string'},
|
||||||
},
|
},
|
||||||
init: function () {
|
init: function () {
|
||||||
if( !AFRAME.XRF ){
|
if( !AFRAME.XRF ){
|
||||||
|
|
@ -2348,112 +2350,116 @@ window.AFRAME.registerComponent('xrf', {
|
||||||
camera.setAttribute('xrf-fade','')
|
camera.setAttribute('xrf-fade','')
|
||||||
AFRAME.fade = camera.components['xrf-fade']
|
AFRAME.fade = camera.components['xrf-fade']
|
||||||
|
|
||||||
let aScene = document.querySelector('a-scene')
|
let aScene = AFRAME.scenes[0]
|
||||||
aScene.addEventListener('loaded', () => {
|
|
||||||
|
|
||||||
// enable XR fragments
|
// enable XR fragments
|
||||||
let XRF = AFRAME.XRF = xrf.init({
|
let XRF = AFRAME.XRF = xrf.init({
|
||||||
THREE,
|
THREE,
|
||||||
camera: aScene.camera,
|
camera: aScene.camera,
|
||||||
scene: aScene.object3D,
|
scene: aScene.object3D,
|
||||||
renderer: aScene.renderer,
|
renderer: aScene.renderer,
|
||||||
loaders: {
|
loaders: {
|
||||||
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
||||||
glb: THREE.GLTFLoader,
|
glb: THREE.GLTFLoader,
|
||||||
obj: THREE.OBJLoader
|
obj: THREE.OBJLoader
|
||||||
}
|
|
||||||
})
|
|
||||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
|
||||||
|
|
||||||
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR 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( '#AR' ) )
|
|
||||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
|
||||||
|
|
||||||
xrf.addEventListener('navigateLoaded', () => {
|
|
||||||
setTimeout( () => AFRAME.fade.out(),500)
|
|
||||||
|
|
||||||
// *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)
|
|
||||||
})
|
|
||||||
blinkControls.components['blink-controls'].update({collisionEntities:true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 && !url.match(/^http/) ) return // dont fade/load for custom protocol handlers
|
|
||||||
if( isLocal && 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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// convert href's to a-entity's so AFRAME
|
|
||||||
// raycaster can find & execute it
|
|
||||||
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
|
||||||
let {mesh,clickHandler} = opts;
|
|
||||||
let el = document.createElement("a-entity")
|
|
||||||
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
|
||||||
el.setAttribute("class","ray") // expose to raycaster
|
|
||||||
el.setAttribute("pressable", '') // detect hand-controller click
|
|
||||||
// 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) )
|
|
||||||
el.addEventListener("pressedstarted", clickHandler )
|
|
||||||
$('a-scene').appendChild(el)
|
|
||||||
}
|
}
|
||||||
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
|
||||||
|
|
||||||
// cleanup xrf-get objects when resetting scene
|
|
||||||
xrf.addEventListener('reset', (opts) => {
|
|
||||||
let els = [...document.querySelectorAll('[xrf-get]')]
|
|
||||||
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
|
||||||
})
|
|
||||||
|
|
||||||
AFRAME.XRF.navigator.to(this.data)
|
|
||||||
.then( (model) => {
|
|
||||||
let gets = [ ...document.querySelectorAll('[xrf-get]') ]
|
|
||||||
gets.map( (g) => g.emit('update') )
|
|
||||||
})
|
|
||||||
|
|
||||||
aScene.emit('XRF',{})
|
|
||||||
|
|
||||||
// enable gaze-click on Mobile VR
|
|
||||||
aScene.setAttribute('xrf-gaze','')
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||||
|
|
||||||
|
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR 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( '#AR' ) )
|
||||||
|
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||||
|
|
||||||
|
xrf.addEventListener('navigateLoaded', () => {
|
||||||
|
setTimeout( () => AFRAME.fade.out(),500)
|
||||||
|
|
||||||
|
// *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)
|
||||||
|
})
|
||||||
|
blinkControls.components['blink-controls'].update({collisionEntities:true})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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 && !url.match(/^http/) ) return // dont fade/load for custom protocol handlers
|
||||||
|
if( isLocal && 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// convert href's to a-entity's so AFRAME
|
||||||
|
// raycaster can find & execute it
|
||||||
|
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
||||||
|
let {mesh,clickHandler} = opts;
|
||||||
|
let el = document.createElement("a-entity")
|
||||||
|
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
||||||
|
el.setAttribute("class","ray") // expose to raycaster
|
||||||
|
el.setAttribute("pressable", '') // detect hand-controller click
|
||||||
|
// 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) )
|
||||||
|
el.addEventListener("pressedstarted", clickHandler )
|
||||||
|
$('a-scene').appendChild(el)
|
||||||
|
}
|
||||||
|
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
||||||
|
|
||||||
|
// cleanup xrf-get objects when resetting scene
|
||||||
|
xrf.addEventListener('reset', (opts) => {
|
||||||
|
let els = [...document.querySelectorAll('[xrf-get]')]
|
||||||
|
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
||||||
|
})
|
||||||
|
|
||||||
|
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') )
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
aScene.emit('XRF',{})
|
||||||
|
|
||||||
|
// enable gaze-click on Mobile VR
|
||||||
|
aScene.setAttribute('xrf-gaze','')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( typeof this.data == "string" ){
|
if( typeof this.data == "string" ){
|
||||||
|
|
|
||||||
4432
dist/xrfragment.module.js
vendored
4432
dist/xrfragment.module.js
vendored
File diff suppressed because it is too large
Load diff
2
dist/xrfragment.three.js
vendored
2
dist/xrfragment.three.js
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* v0.5.1 generated at Sat Dec 30 09:46:42 PM UTC 2023
|
* v0.5.1 generated at Wed Jan 3 03:55:09 PM UTC 2024
|
||||||
* https://xrfragment.org
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
2
dist/xrfragment.three.module.js
vendored
2
dist/xrfragment.three.module.js
vendored
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* v0.5.1 generated at Sat Dec 30 09:46:42 PM UTC 2023
|
* v0.5.1 generated at Wed Jan 3 03:55:09 PM UTC 2024
|
||||||
* https://xrfragment.org
|
* https://xrfragment.org
|
||||||
* SPDX-License-Identifier: MPL-2.0
|
* SPDX-License-Identifier: MPL-2.0
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -30,42 +30,44 @@
|
||||||
<!-- -->
|
<!-- -->
|
||||||
<!-- everything below is completely optional, but handy to add a quick menu / connectivity to your space -->
|
<!-- everything below is completely optional, but handy to add a quick menu / connectivity to your space -->
|
||||||
<!-- -->
|
<!-- -->
|
||||||
|
<script>
|
||||||
|
|
||||||
|
document.addEventListener('$menu:ready', (e) => {
|
||||||
|
let {$menu} = e.detail
|
||||||
|
// add your menubuttons:
|
||||||
|
$menu.buttons = $menu.buttons.concat([
|
||||||
|
`<a class="btn" aria-label="button" aria-title="menu button" onclick="$menu.showAbout()"><i class="gg-info"></i> about</a><br>`
|
||||||
|
])
|
||||||
|
$menu.collapsed = false
|
||||||
|
$menu.showAbout = () => window.notify(`
|
||||||
|
<h1>💁 Hi there!</h1>
|
||||||
|
|
||||||
|
This XR fragments experience works almost anywhere.<br>
|
||||||
|
Allowing rich audiovisual events with(out)<br>
|
||||||
|
VR/AR headsets (it's awesome though ♥️)<br>
|
||||||
|
<br>
|
||||||
|
<b>This uses only open standards:</b><br>
|
||||||
|
📦 surf 3D models using URLs<br>
|
||||||
|
🧑🤝🧑 <b>instant</b> meetings inside hyperlinked 3D models<br>
|
||||||
|
🚫 <b>zero proprietary</b> tech/game engines or platforms<br>
|
||||||
|
🙈 unhosted, privacy-friendly, avatar-agnostic scenes<br>
|
||||||
|
🔗 <a href="https://xrfragment.org" target="_blank">XR Fragments</a> for 3D <b>hyper-linking</b> & navigation<br>
|
||||||
|
📷 Serverless & encrypted <a href="https://webrtc.org" target="_blank">P2P WebRTC</a> using <a href="https://github.com/dmotz/trystero" target="_blank">trystero</a><br>
|
||||||
|
🦍 <a href="https://immersiveweb.dev" target="_blank">WebXR</a> using <a href="https://aframe.io" target="_blank">AFRAME</a> + <a href="https://three.org" target="_blank">Three.js</a><br>
|
||||||
|
🙉 go selfhost <a href="https://github.com/coderofsalvation/xrfragment-helloworld">worlds-in-a-webpage</a><br>
|
||||||
|
♥️ Be sustainable: go 100% <a href="https://www.forbes.com/sites/adrianbridgwater/2023/02/06/the-future-for-open-source/" target="_blank">opensource</a>
|
||||||
|
<br><br>
|
||||||
|
<a href="https://nlnet.nl" target="_blank">
|
||||||
|
<img src="https://nlnet.nl/image/logo_nlnet.svg" width="100"/>
|
||||||
|
</a>
|
||||||
|
<br><br>
|
||||||
|
`,{timeout:false})
|
||||||
|
})
|
||||||
|
</script>
|
||||||
<script src="./../../../dist/aframe-blink-controls.min.js"></script> <!-- teleporting using controllers -->
|
<script src="./../../../dist/aframe-blink-controls.min.js"></script> <!-- teleporting using controllers -->
|
||||||
<script src="./../../../dist/xrfragment.plugin.frontend.js"></script> <!-- menu + chat interface -->
|
<script src="./../../../dist/xrfragment.plugin.frontend.js"></script> <!-- menu + chat interface -->
|
||||||
<script src="./../../../dist/xrfragment.plugin.p2p.js"></script> <!-- serverless p2p connectivity -->
|
<script src="./../../../dist/xrfragment.plugin.p2p.js"></script> <!-- serverless p2p connectivity -->
|
||||||
<script src="./../../../dist/xrfragment.plugin.matrix.js"></script> <!-- matrix connectivity -->
|
<script src="./../../../dist/xrfragment.plugin.matrix.js"></script> <!-- matrix connectivity -->
|
||||||
<script>
|
|
||||||
$menu.logo = 'logo_file_or_url_here'
|
|
||||||
$menu.morelabel = '⚡'
|
|
||||||
// add your menubuttons:
|
|
||||||
$menu.buttons = $menu.buttons.concat([`<a class="btn" aria-label="button" aria-title="menu button" aria-description="about menu" onclick="$menu.showAbout()">💁 about</a><br>`])
|
|
||||||
$menu.collapsed = false
|
|
||||||
$menu.showAbout = () => window.notify(`
|
|
||||||
<h1>💁 Hi there!</h1>
|
|
||||||
|
|
||||||
This XR fragments experience works almost anywhere.<br>
|
|
||||||
Allowing rich audiovisual events with(out)<br>
|
|
||||||
VR/AR headsets (it's awesome though ♥️)<br>
|
|
||||||
<br>
|
|
||||||
<b>This uses only open standards:</b><br>
|
|
||||||
📦 surf 3D models using URLs<br>
|
|
||||||
🧑🤝🧑 <b>instant</b> meetings inside hyperlinked 3D models<br>
|
|
||||||
🚫 <b>zero proprietary</b> tech/game engines or platforms<br>
|
|
||||||
🙈 unhosted, privacy-friendly, avatar-agnostic scenes<br>
|
|
||||||
🔗 <a href="https://xrfragment.org" target="_blank">XR Fragments</a> for 3D <b>hyper-linking</b> & navigation<br>
|
|
||||||
📷 Serverless & encrypted <a href="https://webrtc.org" target="_blank">P2P WebRTC</a> using <a href="https://github.com/dmotz/trystero" target="_blank">trystero</a><br>
|
|
||||||
🦍 <a href="https://immersiveweb.dev" target="_blank">WebXR</a> using <a href="https://aframe.io" target="_blank">AFRAME</a> + <a href="https://three.org" target="_blank">Three.js</a><br>
|
|
||||||
🙉 go selfhost <a href="https://github.com/coderofsalvation/xrfragment-helloworld">worlds-in-a-webpage</a><br>
|
|
||||||
♥️ Be sustainable: go 100% <a href="https://www.forbes.com/sites/adrianbridgwater/2023/02/06/the-future-for-open-source/" target="_blank">opensource</a>
|
|
||||||
<br><br>
|
|
||||||
<a href="https://nlnet.nl" target="_blank">
|
|
||||||
<img src="https://nlnet.nl/image/logo_nlnet.svg" width="100"/>
|
|
||||||
</a>
|
|
||||||
<br><br>
|
|
||||||
`,{timeout:false})
|
|
||||||
|
|
||||||
$menu.install(xrf)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
window.AFRAME.registerComponent('xrf', {
|
window.AFRAME.registerComponent('xrf', {
|
||||||
schema: {
|
schema: {
|
||||||
|
http: { type:'string'},
|
||||||
|
https: { type:'string'},
|
||||||
},
|
},
|
||||||
init: function () {
|
init: function () {
|
||||||
if( !AFRAME.XRF ){
|
if( !AFRAME.XRF ){
|
||||||
|
|
@ -9,112 +11,116 @@ window.AFRAME.registerComponent('xrf', {
|
||||||
camera.setAttribute('xrf-fade','')
|
camera.setAttribute('xrf-fade','')
|
||||||
AFRAME.fade = camera.components['xrf-fade']
|
AFRAME.fade = camera.components['xrf-fade']
|
||||||
|
|
||||||
let aScene = document.querySelector('a-scene')
|
let aScene = AFRAME.scenes[0]
|
||||||
aScene.addEventListener('loaded', () => {
|
|
||||||
|
|
||||||
// enable XR fragments
|
// enable XR fragments
|
||||||
let XRF = AFRAME.XRF = xrf.init({
|
let XRF = AFRAME.XRF = xrf.init({
|
||||||
THREE,
|
THREE,
|
||||||
camera: aScene.camera,
|
camera: aScene.camera,
|
||||||
scene: aScene.object3D,
|
scene: aScene.object3D,
|
||||||
renderer: aScene.renderer,
|
renderer: aScene.renderer,
|
||||||
loaders: {
|
loaders: {
|
||||||
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
gltf: THREE.GLTFLoader, // which 3D assets (exts) to check for XR fragments?
|
||||||
glb: THREE.GLTFLoader,
|
glb: THREE.GLTFLoader,
|
||||||
obj: THREE.OBJLoader
|
obj: THREE.OBJLoader
|
||||||
}
|
|
||||||
})
|
|
||||||
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
|
||||||
|
|
||||||
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR 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( '#AR' ) )
|
|
||||||
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
|
||||||
|
|
||||||
xrf.addEventListener('navigateLoaded', () => {
|
|
||||||
setTimeout( () => AFRAME.fade.out(),500)
|
|
||||||
|
|
||||||
// *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)
|
|
||||||
})
|
|
||||||
blinkControls.components['blink-controls'].update({collisionEntities:true})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
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 && !url.match(/^http/) ) return // dont fade/load for custom protocol handlers
|
|
||||||
if( isLocal && 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()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// convert href's to a-entity's so AFRAME
|
|
||||||
// raycaster can find & execute it
|
|
||||||
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
|
||||||
let {mesh,clickHandler} = opts;
|
|
||||||
let el = document.createElement("a-entity")
|
|
||||||
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
|
||||||
el.setAttribute("class","ray") // expose to raycaster
|
|
||||||
el.setAttribute("pressable", '') // detect hand-controller click
|
|
||||||
// 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) )
|
|
||||||
el.addEventListener("pressedstarted", clickHandler )
|
|
||||||
$('a-scene').appendChild(el)
|
|
||||||
}
|
}
|
||||||
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
|
||||||
|
|
||||||
// cleanup xrf-get objects when resetting scene
|
|
||||||
xrf.addEventListener('reset', (opts) => {
|
|
||||||
let els = [...document.querySelectorAll('[xrf-get]')]
|
|
||||||
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
|
||||||
})
|
|
||||||
|
|
||||||
AFRAME.XRF.navigator.to(this.data)
|
|
||||||
.then( (model) => {
|
|
||||||
let gets = [ ...document.querySelectorAll('[xrf-get]') ]
|
|
||||||
gets.map( (g) => g.emit('update') )
|
|
||||||
})
|
|
||||||
|
|
||||||
aScene.emit('XRF',{})
|
|
||||||
|
|
||||||
// enable gaze-click on Mobile VR
|
|
||||||
aScene.setAttribute('xrf-gaze','')
|
|
||||||
|
|
||||||
})
|
})
|
||||||
|
if( !XRF.camera ) throw 'xrfragment: no camera detected, please declare <a-entity camera..> ABOVE entities with xrf-attributes'
|
||||||
|
|
||||||
|
// this is just for convenience (not part of spec): hide/show stuff based on VR/AR 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( '#AR' ) )
|
||||||
|
if( VRbutton ) VRbutton.addEventListener('click', () => AFRAME.XRF.hashbus.pub( '#VR' ) )
|
||||||
|
|
||||||
|
xrf.addEventListener('navigateLoaded', () => {
|
||||||
|
setTimeout( () => AFRAME.fade.out(),500)
|
||||||
|
|
||||||
|
// *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)
|
||||||
|
})
|
||||||
|
blinkControls.components['blink-controls'].update({collisionEntities:true})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
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 && !url.match(/^http/) ) return // dont fade/load for custom protocol handlers
|
||||||
|
if( isLocal && 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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// convert href's to a-entity's so AFRAME
|
||||||
|
// raycaster can find & execute it
|
||||||
|
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
||||||
|
let {mesh,clickHandler} = opts;
|
||||||
|
let el = document.createElement("a-entity")
|
||||||
|
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
||||||
|
el.setAttribute("class","ray") // expose to raycaster
|
||||||
|
el.setAttribute("pressable", '') // detect hand-controller click
|
||||||
|
// 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) )
|
||||||
|
el.addEventListener("pressedstarted", clickHandler )
|
||||||
|
$('a-scene').appendChild(el)
|
||||||
|
}
|
||||||
|
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
||||||
|
|
||||||
|
// cleanup xrf-get objects when resetting scene
|
||||||
|
xrf.addEventListener('reset', (opts) => {
|
||||||
|
let els = [...document.querySelectorAll('[xrf-get]')]
|
||||||
|
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
|
||||||
|
})
|
||||||
|
|
||||||
|
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') )
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
aScene.emit('XRF',{})
|
||||||
|
|
||||||
|
// enable gaze-click on Mobile VR
|
||||||
|
aScene.setAttribute('xrf-gaze','')
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if( typeof this.data == "string" ){
|
if( typeof this.data == "string" ){
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue