1st attempt animation (#t=) + button-fixes

This commit is contained in:
Leon van Kammen 2023-10-11 13:46:38 +02:00
parent 20e56f3e91
commit 3aad020eb6
18 changed files with 39697 additions and 1425 deletions

View File

@ -850,7 +850,10 @@ xrf.parseModel = function(model,url){
// add animations // add animations
model.clock = new xrf.THREE.Clock(); model.clock = new xrf.THREE.Clock();
model.mixer = new xrf.THREE.AnimationMixer(model.scene) model.mixer = new xrf.THREE.AnimationMixer(model.scene)
model.animations.map( (anim) => model.mixer.clipAction( anim ).play() ) model.animations.map( (anim) => {
anim.action = model.mixer.clipAction( anim )
anim.action.play()
})
let tmp = new xrf.THREE.Vector3() let tmp = new xrf.THREE.Vector3()
model.render = function(){ model.render = function(){
@ -926,6 +929,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
const scope = this; const scope = this;
scope.objects = []
const raycaster = new Raycaster(); const raycaster = new Raycaster();
const tempMatrix = new Matrix4(); const tempMatrix = new Matrix4();
@ -952,7 +956,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.setFromCamera( _pointer, camera ); raycaster.setFromCamera( _pointer, camera );
const intersects = raycaster.intersectObjects( scope.children, false ); const intersects = raycaster.intersectObjects( scope.objects, false );
if ( intersects.length > 0 ) { if ( intersects.length > 0 ) {
@ -997,7 +1001,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
const intersections = raycaster.intersectObjects( scope.children, false ); const intersections = raycaster.intersectObjects( scope.objects, false );
if ( intersections.length > 0 ) { if ( intersections.length > 0 ) {
@ -1008,9 +1012,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
_event.type = events[ event.type ]; _event.type = events[ event.type ];
_event.data.set( uv.x, 1 - uv.y ); _event.data.set( uv.x, 1 - uv.y );
if( _event.type != "mousemove" ){
console.log(event.type+" => "+_event.type)
}
object.dispatchEvent( _event ); object.dispatchEvent( _event );
@ -1032,6 +1033,11 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
} }
add(obj){
Group.prototype.add.call( this, obj )
this.objects = ([]).concat( this.children )
}
} }
return new InteractiveGroup(renderer,camera) return new InteractiveGroup(renderer,camera)
@ -1211,6 +1217,7 @@ xrf.addEventListener('mesh', (opts) => {
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
}, 10, mesh ) }, 10, mesh )
} }
} }
@ -1397,6 +1404,7 @@ xrf.frag.href = function(v, opts){
}else if( mesh.material){ mesh.material = mesh.material.clone() } }else if( mesh.material){ mesh.material = mesh.material.clone() }
let click = mesh.userData.XRF.href.exec = (e) => { let click = mesh.userData.XRF.href.exec = (e) => {
let isLocal = v.string[0] == '#' let isLocal = v.string[0] == '#'
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}` let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
@ -1410,6 +1418,7 @@ xrf.frag.href = function(v, opts){
if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags) if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags)
xrf.navigator.to(v.string) // let's surf to HREF! xrf.navigator.to(v.string) // let's surf to HREF!
}) })
.catch( console.error )
} }
let selected = (state) => () => { let selected = (state) => () => {
@ -1440,7 +1449,8 @@ xrf.frag.href = function(v, opts){
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
}, 10, mesh ) xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
}, 0, mesh )
} }
/** /**
@ -1840,6 +1850,54 @@ xrf.frag.src.type['image/png'] = function(url,opts){
xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png'] xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png']
xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png'] xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png']
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
console.log(` └ setting animation loop to ${v.x} ${v.y} ${v.z}` )
if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene')
model.mixer.t = v
let duration = model.animations[0].duration
let frames = model.animations[0].tracks[0].times.length
let mixer = model.mixer
mixer.loop = mixer.loop || {frameStart:0,frameStop:99999999,speed: 1}
let fps = frames / duration
mixer.loop.speed = v.x
mixer.loop.speedAbs = Math.abs(v.x)
mixer.loop.frameStart = v.y || mixer.loop.frameStart
mixer.loop.frameStop = v.z || mixer.loop.frameStop
// always recalculate time using frameStart/Stop
mixer.loop.timeStart = mixer.loop.frameStart / (fps * mixer.loop.speedAbs)
mixer.loop.timeStop = mixer.loop.frameStop / (fps * mixer.loop.speedAbs)
// update speed
mixer.timeScale = mixer.loop.speed
let updateTime = (time) => {
mixer.setTime(time)
mixer.time = Math.abs(mixer.time)
mixer.update(0) // (forgetting) this little buddy costed me lots of time :]
}
if( v.y > 0 || v.z > 0 ) updateTime( mixer.loop.timeStart )
console.dir(mixer)
// update loop jump
if( !mixer.update.patched ){
let update = mixer.update
mixer.update = function(time){
mixer.time = Math.abs(mixer.time)
if( time == 0 ) return update.call(mixer,time)
if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){
setTimeout( (time) => updateTime(time),0,mixer.loop.timeStart) // prevent recursion
}
return update.call( mixer, time )
}
mixer.update.patched = true
}
}
window.AFRAME.registerComponent('xrf', { window.AFRAME.registerComponent('xrf', {
schema: { schema: {
}, },
@ -1872,6 +1930,7 @@ window.AFRAME.registerComponent('xrf', {
} }
xrf.pos = camOverride xrf.pos = camOverride
xrf.href = camOverride
// in order to set the rotation programmatically // in order to set the rotation programmatically
// we need to disable look-controls // we need to disable look-controls
@ -1886,17 +1945,27 @@ window.AFRAME.registerComponent('xrf', {
//setTimeout( () => look.setAttribute("look-controls",""), 100 ) //setTimeout( () => look.setAttribute("look-controls",""), 100 )
} }
// convert portal to a-entity so AFRAME // convert href's to a-entity's so AFRAME
// raycaster can find & execute it // raycaster can find & execute it
xrf.href = (xrf,v,opts) => { AFRAME.XRF.clickableMeshToEntity = (opts) => {
camOverride(xrf,v,opts) let {mesh,clickHandler} = opts;
let {mesh,camera} = opts;
let el = document.createElement("a-entity") let el = document.createElement("a-entity")
el.setAttribute("xrf-get",mesh.name ) el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
el.setAttribute("class","ray") el.setAttribute("class","ray") // expose to raycaster
el.addEventListener("click", mesh.userData.XRF.href.exec ) el.setAttribute("pressable", '') // detect hand-controller click
// add click
el.addEventListener("click", clickHandler )
el.addEventListener("pressedstarted", clickHandler )
// this.el.addEventListener("buttondown", console.dir )
// this.el.addEventListener("touchstart", console.dir )
// this.el.addEventListener("triggerdown", console.dir )
// this.el.addEventListener("gripdown", console.dir )
// this.el.addEventListener("abuttondown", console.dir )
// this.el.addEventListener("pinchended", console.dir )
$('a-scene').appendChild(el) $('a-scene').appendChild(el)
} }
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
// cleanup xrf-get objects when resetting scene // cleanup xrf-get objects when resetting scene
xrf.reset = ((reset) => () => { xrf.reset = ((reset) => () => {
@ -2048,24 +2117,32 @@ window.AFRAME.registerComponent('xrf-get', {
this.el.addEventListener('update', (evt) => { this.el.addEventListener('update', (evt) => {
let scene = AFRAME.XRF.scene setTimeout( () => {
let mesh = scene.getObjectByName(meshname);
if (!mesh){ if( !this.mesh && this.el.className == "ray" ){
console.error("mesh with name '"+meshname+"' not found in model") let scene = AFRAME.XRF.scene
return; let mesh = this.mesh = scene.getObjectByName(meshname);
} if (!mesh){
// convert to worldcoordinates console.error("mesh with name '"+meshname+"' not found in model")
mesh.getWorldPosition(mesh.position) return;
mesh.getWorldScale(mesh.scale) }
mesh.getWorldQuaternion(mesh.quaternion) // convert to worldcoordinates
if( !this.data.clone ) mesh.parent.remove(mesh) mesh.getWorldPosition(mesh.position)
mesh.isXRF = true // mark for deletion by xrf mesh.getWorldScale(mesh.scale)
this.el.setObject3D('mesh', mesh ); mesh.getWorldQuaternion(mesh.quaternion)
if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`) mesh.isXRF = true // mark for deletion by xrf
this.el.setObject3D('mesh',mesh)
// normalize position
//this.el.object3D.position.copy( mesh.position )
//mesh.position.fromArray([0,0,0])
if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`)
}
},500)
}) })
if( this.el.className == "ray" ) this.el.emit("update") this.el.emit("update")
} }

View File

@ -850,7 +850,10 @@ xrf.parseModel = function(model,url){
// add animations // add animations
model.clock = new xrf.THREE.Clock(); model.clock = new xrf.THREE.Clock();
model.mixer = new xrf.THREE.AnimationMixer(model.scene) model.mixer = new xrf.THREE.AnimationMixer(model.scene)
model.animations.map( (anim) => model.mixer.clipAction( anim ).play() ) model.animations.map( (anim) => {
anim.action = model.mixer.clipAction( anim )
anim.action.play()
})
let tmp = new xrf.THREE.Vector3() let tmp = new xrf.THREE.Vector3()
model.render = function(){ model.render = function(){
@ -926,6 +929,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
const scope = this; const scope = this;
scope.objects = []
const raycaster = new Raycaster(); const raycaster = new Raycaster();
const tempMatrix = new Matrix4(); const tempMatrix = new Matrix4();
@ -952,7 +956,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.setFromCamera( _pointer, camera ); raycaster.setFromCamera( _pointer, camera );
const intersects = raycaster.intersectObjects( scope.children, false ); const intersects = raycaster.intersectObjects( scope.objects, false );
if ( intersects.length > 0 ) { if ( intersects.length > 0 ) {
@ -997,7 +1001,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
const intersections = raycaster.intersectObjects( scope.children, false ); const intersections = raycaster.intersectObjects( scope.objects, false );
if ( intersections.length > 0 ) { if ( intersections.length > 0 ) {
@ -1008,9 +1012,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
_event.type = events[ event.type ]; _event.type = events[ event.type ];
_event.data.set( uv.x, 1 - uv.y ); _event.data.set( uv.x, 1 - uv.y );
if( _event.type != "mousemove" ){
console.log(event.type+" => "+_event.type)
}
object.dispatchEvent( _event ); object.dispatchEvent( _event );
@ -1032,6 +1033,11 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
} }
add(obj){
Group.prototype.add.call( this, obj )
this.objects = ([]).concat( this.children )
}
} }
return new InteractiveGroup(renderer,camera) return new InteractiveGroup(renderer,camera)
@ -1211,6 +1217,7 @@ xrf.addEventListener('mesh', (opts) => {
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
}, 10, mesh ) }, 10, mesh )
} }
} }
@ -1397,6 +1404,7 @@ xrf.frag.href = function(v, opts){
}else if( mesh.material){ mesh.material = mesh.material.clone() } }else if( mesh.material){ mesh.material = mesh.material.clone() }
let click = mesh.userData.XRF.href.exec = (e) => { let click = mesh.userData.XRF.href.exec = (e) => {
let isLocal = v.string[0] == '#' let isLocal = v.string[0] == '#'
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}` let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
@ -1410,6 +1418,7 @@ xrf.frag.href = function(v, opts){
if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags) if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags)
xrf.navigator.to(v.string) // let's surf to HREF! xrf.navigator.to(v.string) // let's surf to HREF!
}) })
.catch( console.error )
} }
let selected = (state) => () => { let selected = (state) => () => {
@ -1440,7 +1449,8 @@ xrf.frag.href = function(v, opts){
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
}, 10, mesh ) xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
}, 0, mesh )
} }
/** /**
@ -1840,3 +1850,51 @@ xrf.frag.src.type['image/png'] = function(url,opts){
xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png'] xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png']
xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png'] xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png']
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
console.log(` └ setting animation loop to ${v.x} ${v.y} ${v.z}` )
if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene')
model.mixer.t = v
let duration = model.animations[0].duration
let frames = model.animations[0].tracks[0].times.length
let mixer = model.mixer
mixer.loop = mixer.loop || {frameStart:0,frameStop:99999999,speed: 1}
let fps = frames / duration
mixer.loop.speed = v.x
mixer.loop.speedAbs = Math.abs(v.x)
mixer.loop.frameStart = v.y || mixer.loop.frameStart
mixer.loop.frameStop = v.z || mixer.loop.frameStop
// always recalculate time using frameStart/Stop
mixer.loop.timeStart = mixer.loop.frameStart / (fps * mixer.loop.speedAbs)
mixer.loop.timeStop = mixer.loop.frameStop / (fps * mixer.loop.speedAbs)
// update speed
mixer.timeScale = mixer.loop.speed
let updateTime = (time) => {
mixer.setTime(time)
mixer.time = Math.abs(mixer.time)
mixer.update(0) // (forgetting) this little buddy costed me lots of time :]
}
if( v.y > 0 || v.z > 0 ) updateTime( mixer.loop.timeStart )
console.dir(mixer)
// update loop jump
if( !mixer.update.patched ){
let update = mixer.update
mixer.update = function(time){
mixer.time = Math.abs(mixer.time)
if( time == 0 ) return update.call(mixer,time)
if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){
setTimeout( (time) => updateTime(time),0,mixer.loop.timeStart) // prevent recursion
}
return update.call( mixer, time )
}
mixer.update.patched = true
}
}

View File

@ -850,7 +850,10 @@ xrf.parseModel = function(model,url){
// add animations // add animations
model.clock = new xrf.THREE.Clock(); model.clock = new xrf.THREE.Clock();
model.mixer = new xrf.THREE.AnimationMixer(model.scene) model.mixer = new xrf.THREE.AnimationMixer(model.scene)
model.animations.map( (anim) => model.mixer.clipAction( anim ).play() ) model.animations.map( (anim) => {
anim.action = model.mixer.clipAction( anim )
anim.action.play()
})
let tmp = new xrf.THREE.Vector3() let tmp = new xrf.THREE.Vector3()
model.render = function(){ model.render = function(){
@ -926,6 +929,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
const scope = this; const scope = this;
scope.objects = []
const raycaster = new Raycaster(); const raycaster = new Raycaster();
const tempMatrix = new Matrix4(); const tempMatrix = new Matrix4();
@ -952,7 +956,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.setFromCamera( _pointer, camera ); raycaster.setFromCamera( _pointer, camera );
const intersects = raycaster.intersectObjects( scope.children, false ); const intersects = raycaster.intersectObjects( scope.objects, false );
if ( intersects.length > 0 ) { if ( intersects.length > 0 ) {
@ -997,7 +1001,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
const intersections = raycaster.intersectObjects( scope.children, false ); const intersections = raycaster.intersectObjects( scope.objects, false );
if ( intersections.length > 0 ) { if ( intersections.length > 0 ) {
@ -1008,9 +1012,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
_event.type = events[ event.type ]; _event.type = events[ event.type ];
_event.data.set( uv.x, 1 - uv.y ); _event.data.set( uv.x, 1 - uv.y );
if( _event.type != "mousemove" ){
console.log(event.type+" => "+_event.type)
}
object.dispatchEvent( _event ); object.dispatchEvent( _event );
@ -1032,6 +1033,11 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
} }
add(obj){
Group.prototype.add.call( this, obj )
this.objects = ([]).concat( this.children )
}
} }
return new InteractiveGroup(renderer,camera) return new InteractiveGroup(renderer,camera)
@ -1211,6 +1217,7 @@ xrf.addEventListener('mesh', (opts) => {
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
}, 10, mesh ) }, 10, mesh )
} }
} }
@ -1397,6 +1404,7 @@ xrf.frag.href = function(v, opts){
}else if( mesh.material){ mesh.material = mesh.material.clone() } }else if( mesh.material){ mesh.material = mesh.material.clone() }
let click = mesh.userData.XRF.href.exec = (e) => { let click = mesh.userData.XRF.href.exec = (e) => {
let isLocal = v.string[0] == '#' let isLocal = v.string[0] == '#'
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}` let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
@ -1410,6 +1418,7 @@ xrf.frag.href = function(v, opts){
if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags) if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags)
xrf.navigator.to(v.string) // let's surf to HREF! xrf.navigator.to(v.string) // let's surf to HREF!
}) })
.catch( console.error )
} }
let selected = (state) => () => { let selected = (state) => () => {
@ -1440,7 +1449,8 @@ xrf.frag.href = function(v, opts){
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
}, 10, mesh ) xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
}, 0, mesh )
} }
/** /**
@ -1840,4 +1850,52 @@ xrf.frag.src.type['image/png'] = function(url,opts){
xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png'] xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png']
xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png'] xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png']
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
console.log(` └ setting animation loop to ${v.x} ${v.y} ${v.z}` )
if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene')
model.mixer.t = v
let duration = model.animations[0].duration
let frames = model.animations[0].tracks[0].times.length
let mixer = model.mixer
mixer.loop = mixer.loop || {frameStart:0,frameStop:99999999,speed: 1}
let fps = frames / duration
mixer.loop.speed = v.x
mixer.loop.speedAbs = Math.abs(v.x)
mixer.loop.frameStart = v.y || mixer.loop.frameStart
mixer.loop.frameStop = v.z || mixer.loop.frameStop
// always recalculate time using frameStart/Stop
mixer.loop.timeStart = mixer.loop.frameStart / (fps * mixer.loop.speedAbs)
mixer.loop.timeStop = mixer.loop.frameStop / (fps * mixer.loop.speedAbs)
// update speed
mixer.timeScale = mixer.loop.speed
let updateTime = (time) => {
mixer.setTime(time)
mixer.time = Math.abs(mixer.time)
mixer.update(0) // (forgetting) this little buddy costed me lots of time :]
}
if( v.y > 0 || v.z > 0 ) updateTime( mixer.loop.timeStart )
console.dir(mixer)
// update loop jump
if( !mixer.update.patched ){
let update = mixer.update
mixer.update = function(time){
mixer.time = Math.abs(mixer.time)
if( time == 0 ) return update.call(mixer,time)
if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){
setTimeout( (time) => updateTime(time),0,mixer.loop.timeStart) // prevent recursion
}
return update.call( mixer, time )
}
mixer.update.patched = true
}
}
export default xrf; export default xrf;

BIN
doc/RFC_XR_Fragments.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 MiB

View File

@ -93,7 +93,7 @@ value: draft-XRFRAGMENTS-leonvankammen-00
.# Abstract .# Abstract
This draft is a specification for 4D URLs & navigation, which links together space, time & text together, for hypermedia browsers with- or without a network-connection.<br> This draft is a specification for 4D URLs & [hypermediatic](https://github.com/coderofsalvation/hypermediatic) navigation, which links together space, time & text together, for hypermedia browsers with- or without a network-connection.<br>
The specification promotes spatial addressibility, sharing, navigation, query-ing and annotating interactive (text)objects across for (XR) Browsers.<br> The specification promotes spatial addressibility, sharing, navigation, query-ing and annotating interactive (text)objects across for (XR) Browsers.<br>
XR Fragments allows us to enrich existing dataformats, by recursive use of existing proven technologies like [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment) and BibTags notation.<br> XR Fragments allows us to enrich existing dataformats, by recursive use of existing proven technologies like [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment) and BibTags notation.<br>
@ -105,10 +105,10 @@ XR Fragments allows us to enrich existing dataformats, by recursive use of exist
How can we add more features to existing text & 3D scenes, without introducing new dataformats?<br> How can we add more features to existing text & 3D scenes, without introducing new dataformats?<br>
Historically, there's many attempts to create the ultimate markuplanguage or 3D fileformat.<br> Historically, there's many attempts to create the ultimate markuplanguage or 3D fileformat.<br>
Their lowest common denominator is: (co)authoring using plain text.<br> The lowest common denominator is: describing/tagging/naming nodes using **plain text**.<br>
XR Fragments allows us to enrich/connect existing dataformats, by introducing existing technologies/ideas:<br> XR Fragments allows us to enrich/connect existing dataformats, by introducing existing technologies/ideas:<br>
1. addressibility and navigation of 3D scenes/objects: [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment) + src/href spatial metadata 1. addressibility and [hypermediatic](https://github.com/coderofsalvation/hypermediatic) navigation of 3D scenes/objects: [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment) + src/href spatial metadata
1. Interlinking text/& 3D by collapsing space into a Word Graph (XRWG) to show [visible links](#visible-links) (and augmenting text with [bibs](https://github.com/coderofsalvation/tagbibs) / [BibTags](https://en.wikipedia.org/wiki/BibTeX) appendices (see [visual-meta](https://visual-meta.info) e.g.) 1. Interlinking text/& 3D by collapsing space into a Word Graph (XRWG) to show [visible links](#visible-links) (and augmenting text with [bibs](https://github.com/coderofsalvation/tagbibs) / [BibTags](https://en.wikipedia.org/wiki/BibTeX) appendices (see [visual-meta](https://visual-meta.info) e.g.)
1. unlocking spatial potential of the (originally 2D) hashtag (which jumps to a chapter) for navigating XR documents 1. unlocking spatial potential of the (originally 2D) hashtag (which jumps to a chapter) for navigating XR documents
@ -157,11 +157,11 @@ Instead of combining them (in a game-editor e.g.), XR Fragments is opting for a
Traditional webbrowsers can become 4D document-ready by: Traditional webbrowsers can become 4D document-ready by:
* loading 3D assets (gltf/fbx e.g.) natively (with or without using HTML). * [hypermediatic](https://github.com/coderofsalvation/hypermediatic) loading 3D assets (gltf/fbx e.g.) natively (with or without using HTML).
* allowing assets to publish hashtags to themselves (the scene) using the hashbus (like hashtags controlling the scrollbar). * allowing assets to publish hashtags to themselves (the scene) using the hashbus (like hashtags controlling the scrollbar).
* collapsing the 3D scene to an wordgraph (for essential navigation purposes) controllable thru a hash(tag)bus * collapsing the 3D scene to an wordgraph (for essential navigation purposes) controllable thru a hash(tag)bus
XR Fragments itself are HTML-agnostic, though pseudo-XR Fragment browsers **can** be implemented on top of HTML/Javascript. XR Fragments itself are [hypermediatic](https://github.com/coderofsalvation/hypermediatic) and HTML-agnostic, though pseudo-XR Fragment browsers **can** be implemented on top of HTML/Javascript.
# Conventions and Definitions # Conventions and Definitions
@ -235,7 +235,7 @@ For example, to render a portal with a preview-version of the scene, create an 3
* href: `https://scene.fbx` * href: `https://scene.fbx`
* src: `https://otherworld.gltf#mainobject` * src: `https://otherworld.gltf#mainobject`
> It also allows **sourceportation**, which basically means the enduser can teleport to the original XR Document of an `src` embedded object, and see a visible connection to the particular embedded object. > It also allows **sourceportation**, which basically means the enduser can teleport to the original XR Document of an `src` embedded object, and see a visible connection to the particular embedded object. Basically an embedded link becoming an outbound link by activating it.
# Navigating 3D # Navigating 3D
@ -347,7 +347,7 @@ Resizing will be happen accordingly to its placeholder object `aquariumcube`, se
[» example 3D asset](https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/src.gltf#L192)<br> [» example 3D asset](https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/src.gltf#L192)<br>
[» discussion](https://github.com/coderofsalvation/xrfragment/issues/4)<br> [» discussion](https://github.com/coderofsalvation/xrfragment/issues/4)<br>
# Navigating content (href portals) # Navigating content (internal/outbound href portals)
navigation, portals & mutations navigation, portals & mutations
@ -355,7 +355,7 @@ navigation, portals & mutations
|----------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------| |----------|---------------------------------|---------------------------------------------------------------------------------------------------------------------------|
|`href` | string (uri or predefined view) | `#pos=1,1,0`<br>`#pos=1,1,0&rot=90,0,0`<br>`://somefile.gltf#pos=1,1,0`<br> | |`href` | string (uri or predefined view) | `#pos=1,1,0`<br>`#pos=1,1,0&rot=90,0,0`<br>`://somefile.gltf#pos=1,1,0`<br> |
1. clicking an ''external''- or ''file URI'' fully replaces the current scene and assumes `pos=0,0,0&rot=0,0,0` by default (unless specified) 1. clicking an outbound ''external''- or ''file URI'' fully replaces the current scene and assumes `pos=0,0,0&rot=0,0,0` by default (unless specified)
2. relocation/reorientation should happen locally for local URI's (`#pos=....`) 2. relocation/reorientation should happen locally for local URI's (`#pos=....`)

BIN
doc/RFC_XR_Fragments.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

35994
doc/RFC_XR_Fragments.svg Normal file

File diff suppressed because it is too large Load Diff

After

Width:  |  Height:  |  Size: 1002 KiB

View File

@ -6,6 +6,7 @@
mmark mmark
xml2rfc xml2rfc
wkhtmltopdf-bin
]; ];
} }

View File

@ -25,13 +25,13 @@
<textarea style="display:none"></textarea> <textarea style="display:none"></textarea>
<a-scene light="defaultLightsEnabled: false"> <a-scene light="defaultLightsEnabled: false">
<a-entity id="player" wasd-controls look-controls> <a-entity id="player" wasd-controls look-controls>
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.ray;far:5500" oculus-touch-controls="hand: left" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"> <a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor">
<a-entity rotation="-90 0 0" position="0 0.1 0"> <a-entity rotation="-90 0 0" position="0 0.1 0">
<a-entity id="back" xrf-button="label: <; width:0.05; action: history.back()" position="-0.025 0 0" class="ray"></a-entity> <a-entity id="back" xrf-button="label: <; width:0.05; action: history.back()" position="-0.025 0 0" class="ray"></a-entity>
<a-entity id="next" xrf-button="label: >; width:0.05; action: history.forward()" position=" 0.025 0 0" class="ray"></a-entity> <a-entity id="next" xrf-button="label: >; width:0.05; action: history.forward()" position=" 0.025 0 0" class="ray"></a-entity>
</a-entity> </a-entity>
</a-entity> </a-entity>
<a-entity id="right-hand" laser-controls="hand: right" raycaster="objects:.ray;far:5500" oculus-touch-controls="hand: right" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"></a-entity> <a-entity id="right-hand" laser-controls="hand: right" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: #floor"></a-entity>
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity> <a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
</a-entity> </a-entity>

File diff suppressed because one or more lines are too long

View File

@ -4,14 +4,8 @@
# nativeBuildInputs is usually what you want -- tools you need to run # nativeBuildInputs is usually what you want -- tools you need to run
nativeBuildInputs = with pkgs.buildPackages; [ nativeBuildInputs = with pkgs.buildPackages; [
haxe mmark
python3 xml2rfc
nodejs-slim_20
# extra
#php82
#mono
#jdk
]; ];
} }

View File

@ -30,6 +30,7 @@ window.AFRAME.registerComponent('xrf', {
} }
xrf.pos = camOverride xrf.pos = camOverride
xrf.href = camOverride
// in order to set the rotation programmatically // in order to set the rotation programmatically
// we need to disable look-controls // we need to disable look-controls
@ -44,17 +45,27 @@ window.AFRAME.registerComponent('xrf', {
//setTimeout( () => look.setAttribute("look-controls",""), 100 ) //setTimeout( () => look.setAttribute("look-controls",""), 100 )
} }
// convert portal to a-entity so AFRAME // convert href's to a-entity's so AFRAME
// raycaster can find & execute it // raycaster can find & execute it
xrf.href = (xrf,v,opts) => { AFRAME.XRF.clickableMeshToEntity = (opts) => {
camOverride(xrf,v,opts) let {mesh,clickHandler} = opts;
let {mesh,camera} = opts;
let el = document.createElement("a-entity") let el = document.createElement("a-entity")
el.setAttribute("xrf-get",mesh.name ) el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
el.setAttribute("class","ray") el.setAttribute("class","ray") // expose to raycaster
el.addEventListener("click", mesh.userData.XRF.href.exec ) el.setAttribute("pressable", '') // detect hand-controller click
// add click
el.addEventListener("click", clickHandler )
el.addEventListener("pressedstarted", clickHandler )
// this.el.addEventListener("buttondown", console.dir )
// this.el.addEventListener("touchstart", console.dir )
// this.el.addEventListener("triggerdown", console.dir )
// this.el.addEventListener("gripdown", console.dir )
// this.el.addEventListener("abuttondown", console.dir )
// this.el.addEventListener("pinchended", console.dir )
$('a-scene').appendChild(el) $('a-scene').appendChild(el)
} }
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
// cleanup xrf-get objects when resetting scene // cleanup xrf-get objects when resetting scene
xrf.reset = ((reset) => () => { xrf.reset = ((reset) => () => {

View File

@ -11,24 +11,32 @@ window.AFRAME.registerComponent('xrf-get', {
this.el.addEventListener('update', (evt) => { this.el.addEventListener('update', (evt) => {
let scene = AFRAME.XRF.scene setTimeout( () => {
let mesh = scene.getObjectByName(meshname);
if (!mesh){ if( !this.mesh && this.el.className == "ray" ){
console.error("mesh with name '"+meshname+"' not found in model") let scene = AFRAME.XRF.scene
return; let mesh = this.mesh = scene.getObjectByName(meshname);
} if (!mesh){
// convert to worldcoordinates console.error("mesh with name '"+meshname+"' not found in model")
mesh.getWorldPosition(mesh.position) return;
mesh.getWorldScale(mesh.scale) }
mesh.getWorldQuaternion(mesh.quaternion) // convert to worldcoordinates
if( !this.data.clone ) mesh.parent.remove(mesh) mesh.getWorldPosition(mesh.position)
mesh.isXRF = true // mark for deletion by xrf mesh.getWorldScale(mesh.scale)
this.el.setObject3D('mesh', mesh ); mesh.getWorldQuaternion(mesh.quaternion)
if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`) mesh.isXRF = true // mark for deletion by xrf
this.el.setObject3D('mesh',mesh)
// normalize position
//this.el.object3D.position.copy( mesh.position )
//mesh.position.fromArray([0,0,0])
if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`)
}
},500)
}) })
if( this.el.className == "ray" ) this.el.emit("update") this.el.emit("update")
} }

View File

@ -24,6 +24,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null ) camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
const scope = this; const scope = this;
scope.objects = []
const raycaster = new Raycaster(); const raycaster = new Raycaster();
const tempMatrix = new Matrix4(); const tempMatrix = new Matrix4();
@ -50,7 +51,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.setFromCamera( _pointer, camera ); raycaster.setFromCamera( _pointer, camera );
const intersects = raycaster.intersectObjects( scope.children, false ); const intersects = raycaster.intersectObjects( scope.objects, false );
if ( intersects.length > 0 ) { if ( intersects.length > 0 ) {
@ -95,7 +96,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld ); raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix ); raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
const intersections = raycaster.intersectObjects( scope.children, false ); const intersections = raycaster.intersectObjects( scope.objects, false );
if ( intersections.length > 0 ) { if ( intersections.length > 0 ) {
@ -106,9 +107,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
_event.type = events[ event.type ]; _event.type = events[ event.type ];
_event.data.set( uv.x, 1 - uv.y ); _event.data.set( uv.x, 1 - uv.y );
if( _event.type != "mousemove" ){
console.log(event.type+" => "+_event.type)
}
object.dispatchEvent( _event ); object.dispatchEvent( _event );
@ -130,6 +128,11 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
} }
add(obj){
Group.prototype.add.call( this, obj )
this.objects = ([]).concat( this.children )
}
} }
return new InteractiveGroup(renderer,camera) return new InteractiveGroup(renderer,camera)

View File

@ -50,7 +50,10 @@ xrf.parseModel = function(model,url){
// add animations // add animations
model.clock = new xrf.THREE.Clock(); model.clock = new xrf.THREE.Clock();
model.mixer = new xrf.THREE.AnimationMixer(model.scene) model.mixer = new xrf.THREE.AnimationMixer(model.scene)
model.animations.map( (anim) => model.mixer.clipAction( anim ).play() ) model.animations.map( (anim) => {
anim.action = model.mixer.clipAction( anim )
anim.action.play()
})
let tmp = new xrf.THREE.Vector3() let tmp = new xrf.THREE.Vector3()
model.render = function(){ model.render = function(){

View File

@ -90,6 +90,7 @@ xrf.frag.href = function(v, opts){
}else if( mesh.material){ mesh.material = mesh.material.clone() } }else if( mesh.material){ mesh.material = mesh.material.clone() }
let click = mesh.userData.XRF.href.exec = (e) => { let click = mesh.userData.XRF.href.exec = (e) => {
let isLocal = v.string[0] == '#' let isLocal = v.string[0] == '#'
let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}` let lastPos = `pos=${camera.position.x.toFixed(2)},${camera.position.y.toFixed(2)},${camera.position.z.toFixed(2)}`
@ -103,6 +104,7 @@ xrf.frag.href = function(v, opts){
if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags) if( !document.location.hash.match(/pos=/) ) xrf.navigator.to(`#${lastPos}`,flags)
xrf.navigator.to(v.string) // let's surf to HREF! xrf.navigator.to(v.string) // let's surf to HREF!
}) })
.catch( console.error )
} }
let selected = (state) => () => { let selected = (state) => () => {
@ -133,7 +135,8 @@ xrf.frag.href = function(v, opts){
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
}, 10, mesh ) xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
}, 0, mesh )
} }
/** /**

48
src/3rd/js/three/xrf/t.js Normal file
View File

@ -0,0 +1,48 @@
xrf.frag.t = function(v, opts){
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
console.log(` └ setting animation loop to ${v.x} ${v.y} ${v.z}` )
if( !model.animations || model.animations[0] == undefined ) return console.warn('no animation in scene')
model.mixer.t = v
let duration = model.animations[0].duration
let frames = model.animations[0].tracks[0].times.length
let mixer = model.mixer
mixer.loop = mixer.loop || {frameStart:0,frameStop:99999999,speed: 1}
let fps = frames / duration
mixer.loop.speed = v.x
mixer.loop.speedAbs = Math.abs(v.x)
mixer.loop.frameStart = v.y || mixer.loop.frameStart
mixer.loop.frameStop = v.z || mixer.loop.frameStop
// always recalculate time using frameStart/Stop
mixer.loop.timeStart = mixer.loop.frameStart / (fps * mixer.loop.speedAbs)
mixer.loop.timeStop = mixer.loop.frameStop / (fps * mixer.loop.speedAbs)
// update speed
mixer.timeScale = mixer.loop.speed
let updateTime = (time) => {
mixer.setTime(time)
mixer.time = Math.abs(mixer.time)
mixer.update(0) // (forgetting) this little buddy costed me lots of time :]
}
if( v.y > 0 || v.z > 0 ) updateTime( mixer.loop.timeStart )
console.dir(mixer)
// update loop jump
if( !mixer.update.patched ){
let update = mixer.update
mixer.update = function(time){
mixer.time = Math.abs(mixer.time)
if( time == 0 ) return update.call(mixer,time)
if( mixer.loop.speed > 0.0 && mixer.time > mixer.loop.timeStop * mixer.loop.speedAbs ){
setTimeout( (time) => updateTime(time),0,mixer.loop.timeStart) // prevent recursion
}
return update.call( mixer, time )
}
mixer.update.patched = true
}
}

View File

@ -64,6 +64,7 @@ xrf.addEventListener('mesh', (opts) => {
mesh.scale.copy(world.scale) mesh.scale.copy(world.scale)
mesh.setRotationFromQuaternion(world.quat); mesh.setRotationFromQuaternion(world.quat);
xrf.interactive.add(mesh) xrf.interactive.add(mesh)
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
}, 10, mesh ) }, 10, mesh )
} }
} }