1st attempt animation (#t=) + button-fixes
This commit is contained in:
parent
20e56f3e91
commit
3aad020eb6
|
@ -850,7 +850,10 @@ xrf.parseModel = function(model,url){
|
|||
// add animations
|
||||
model.clock = new xrf.THREE.Clock();
|
||||
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()
|
||||
model.render = function(){
|
||||
|
@ -926,6 +929,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
|
||||
|
||||
const scope = this;
|
||||
scope.objects = []
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
const tempMatrix = new Matrix4();
|
||||
|
@ -952,7 +956,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
raycaster.setFromCamera( _pointer, camera );
|
||||
|
||||
const intersects = raycaster.intersectObjects( scope.children, false );
|
||||
const intersects = raycaster.intersectObjects( scope.objects, false );
|
||||
|
||||
if ( intersects.length > 0 ) {
|
||||
|
||||
|
@ -997,7 +1001,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
|
||||
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 ) {
|
||||
|
||||
|
@ -1008,9 +1012,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
_event.type = events[ event.type ];
|
||||
_event.data.set( uv.x, 1 - uv.y );
|
||||
if( _event.type != "mousemove" ){
|
||||
console.log(event.type+" => "+_event.type)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -1211,6 +1217,7 @@ xrf.addEventListener('mesh', (opts) => {
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
xrf.interactive.add(mesh)
|
||||
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
|
||||
}, 10, mesh )
|
||||
}
|
||||
}
|
||||
|
@ -1397,6 +1404,7 @@ xrf.frag.href = function(v, opts){
|
|||
}else if( mesh.material){ mesh.material = mesh.material.clone() }
|
||||
|
||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||
|
||||
let isLocal = v.string[0] == '#'
|
||||
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)
|
||||
xrf.navigator.to(v.string) // let's surf to HREF!
|
||||
})
|
||||
.catch( console.error )
|
||||
}
|
||||
|
||||
let selected = (state) => () => {
|
||||
|
@ -1440,7 +1449,8 @@ xrf.frag.href = function(v, opts){
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
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/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', {
|
||||
schema: {
|
||||
},
|
||||
|
@ -1872,6 +1930,7 @@ window.AFRAME.registerComponent('xrf', {
|
|||
}
|
||||
|
||||
xrf.pos = camOverride
|
||||
xrf.href = camOverride
|
||||
|
||||
// in order to set the rotation programmatically
|
||||
// we need to disable look-controls
|
||||
|
@ -1886,17 +1945,27 @@ window.AFRAME.registerComponent('xrf', {
|
|||
//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
|
||||
xrf.href = (xrf,v,opts) => {
|
||||
camOverride(xrf,v,opts)
|
||||
let {mesh,camera} = opts;
|
||||
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
||||
let {mesh,clickHandler} = opts;
|
||||
let el = document.createElement("a-entity")
|
||||
el.setAttribute("xrf-get",mesh.name )
|
||||
el.setAttribute("class","ray")
|
||||
el.addEventListener("click", mesh.userData.XRF.href.exec )
|
||||
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
||||
el.setAttribute("class","ray") // expose to raycaster
|
||||
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)
|
||||
}
|
||||
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
||||
|
||||
// cleanup xrf-get objects when resetting scene
|
||||
xrf.reset = ((reset) => () => {
|
||||
|
@ -2048,24 +2117,32 @@ window.AFRAME.registerComponent('xrf-get', {
|
|||
|
||||
this.el.addEventListener('update', (evt) => {
|
||||
|
||||
let scene = AFRAME.XRF.scene
|
||||
let mesh = scene.getObjectByName(meshname);
|
||||
if (!mesh){
|
||||
console.error("mesh with name '"+meshname+"' not found in model")
|
||||
return;
|
||||
}
|
||||
// convert to worldcoordinates
|
||||
mesh.getWorldPosition(mesh.position)
|
||||
mesh.getWorldScale(mesh.scale)
|
||||
mesh.getWorldQuaternion(mesh.quaternion)
|
||||
if( !this.data.clone ) mesh.parent.remove(mesh)
|
||||
mesh.isXRF = true // mark for deletion by xrf
|
||||
this.el.setObject3D('mesh', mesh );
|
||||
if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`)
|
||||
setTimeout( () => {
|
||||
|
||||
if( !this.mesh && this.el.className == "ray" ){
|
||||
let scene = AFRAME.XRF.scene
|
||||
let mesh = this.mesh = scene.getObjectByName(meshname);
|
||||
if (!mesh){
|
||||
console.error("mesh with name '"+meshname+"' not found in model")
|
||||
return;
|
||||
}
|
||||
// convert to worldcoordinates
|
||||
mesh.getWorldPosition(mesh.position)
|
||||
mesh.getWorldScale(mesh.scale)
|
||||
mesh.getWorldQuaternion(mesh.quaternion)
|
||||
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")
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -850,7 +850,10 @@ xrf.parseModel = function(model,url){
|
|||
// add animations
|
||||
model.clock = new xrf.THREE.Clock();
|
||||
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()
|
||||
model.render = function(){
|
||||
|
@ -926,6 +929,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
|
||||
|
||||
const scope = this;
|
||||
scope.objects = []
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
const tempMatrix = new Matrix4();
|
||||
|
@ -952,7 +956,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
raycaster.setFromCamera( _pointer, camera );
|
||||
|
||||
const intersects = raycaster.intersectObjects( scope.children, false );
|
||||
const intersects = raycaster.intersectObjects( scope.objects, false );
|
||||
|
||||
if ( intersects.length > 0 ) {
|
||||
|
||||
|
@ -997,7 +1001,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
|
||||
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 ) {
|
||||
|
||||
|
@ -1008,9 +1012,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
_event.type = events[ event.type ];
|
||||
_event.data.set( uv.x, 1 - uv.y );
|
||||
if( _event.type != "mousemove" ){
|
||||
console.log(event.type+" => "+_event.type)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -1211,6 +1217,7 @@ xrf.addEventListener('mesh', (opts) => {
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
xrf.interactive.add(mesh)
|
||||
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
|
||||
}, 10, mesh )
|
||||
}
|
||||
}
|
||||
|
@ -1397,6 +1404,7 @@ xrf.frag.href = function(v, opts){
|
|||
}else if( mesh.material){ mesh.material = mesh.material.clone() }
|
||||
|
||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||
|
||||
let isLocal = v.string[0] == '#'
|
||||
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)
|
||||
xrf.navigator.to(v.string) // let's surf to HREF!
|
||||
})
|
||||
.catch( console.error )
|
||||
}
|
||||
|
||||
let selected = (state) => () => {
|
||||
|
@ -1440,7 +1449,8 @@ xrf.frag.href = function(v, opts){
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
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/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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -850,7 +850,10 @@ xrf.parseModel = function(model,url){
|
|||
// add animations
|
||||
model.clock = new xrf.THREE.Clock();
|
||||
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()
|
||||
model.render = function(){
|
||||
|
@ -926,6 +929,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
|
||||
|
||||
const scope = this;
|
||||
scope.objects = []
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
const tempMatrix = new Matrix4();
|
||||
|
@ -952,7 +956,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
raycaster.setFromCamera( _pointer, camera );
|
||||
|
||||
const intersects = raycaster.intersectObjects( scope.children, false );
|
||||
const intersects = raycaster.intersectObjects( scope.objects, false );
|
||||
|
||||
if ( intersects.length > 0 ) {
|
||||
|
||||
|
@ -997,7 +1001,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
|
||||
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 ) {
|
||||
|
||||
|
@ -1008,9 +1012,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
_event.type = events[ event.type ];
|
||||
_event.data.set( uv.x, 1 - uv.y );
|
||||
if( _event.type != "mousemove" ){
|
||||
console.log(event.type+" => "+_event.type)
|
||||
}
|
||||
|
||||
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)
|
||||
|
@ -1211,6 +1217,7 @@ xrf.addEventListener('mesh', (opts) => {
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
xrf.interactive.add(mesh)
|
||||
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
|
||||
}, 10, mesh )
|
||||
}
|
||||
}
|
||||
|
@ -1397,6 +1404,7 @@ xrf.frag.href = function(v, opts){
|
|||
}else if( mesh.material){ mesh.material = mesh.material.clone() }
|
||||
|
||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||
|
||||
let isLocal = v.string[0] == '#'
|
||||
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)
|
||||
xrf.navigator.to(v.string) // let's surf to HREF!
|
||||
})
|
||||
.catch( console.error )
|
||||
}
|
||||
|
||||
let selected = (state) => () => {
|
||||
|
@ -1440,7 +1449,8 @@ xrf.frag.href = function(v, opts){
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
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/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;
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 1.5 MiB |
|
@ -93,7 +93,7 @@ value: draft-XRFRAGMENTS-leonvankammen-00
|
|||
|
||||
.# 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>
|
||||
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>
|
||||
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>
|
||||
|
||||
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. 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:
|
||||
|
||||
* 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).
|
||||
* 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
|
||||
|
||||
|
@ -235,7 +235,7 @@ For example, to render a portal with a preview-version of the scene, create an 3
|
|||
* href: `https://scene.fbx`
|
||||
* 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
|
||||
|
||||
|
@ -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>
|
||||
[» discussion](https://github.com/coderofsalvation/xrfragment/issues/4)<br>
|
||||
|
||||
# Navigating content (href portals)
|
||||
# Navigating content (internal/outbound href portals)
|
||||
|
||||
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> |
|
||||
|
||||
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=....`)
|
||||
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 564 KiB |
File diff suppressed because it is too large
Load Diff
After Width: | Height: | Size: 1002 KiB |
|
@ -6,6 +6,7 @@
|
|||
|
||||
mmark
|
||||
xml2rfc
|
||||
wkhtmltopdf-bin
|
||||
|
||||
];
|
||||
}
|
||||
|
|
|
@ -25,13 +25,13 @@
|
|||
<textarea style="display:none"></textarea>
|
||||
<a-scene light="defaultLightsEnabled: false">
|
||||
<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 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>
|
||||
</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>
|
||||
|
||||
|
|
File diff suppressed because one or more lines are too long
10
shell.nix
10
shell.nix
|
@ -4,14 +4,8 @@
|
|||
# nativeBuildInputs is usually what you want -- tools you need to run
|
||||
nativeBuildInputs = with pkgs.buildPackages; [
|
||||
|
||||
haxe
|
||||
python3
|
||||
nodejs-slim_20
|
||||
|
||||
# extra
|
||||
#php82
|
||||
#mono
|
||||
#jdk
|
||||
mmark
|
||||
xml2rfc
|
||||
|
||||
];
|
||||
}
|
||||
|
|
|
@ -30,6 +30,7 @@ window.AFRAME.registerComponent('xrf', {
|
|||
}
|
||||
|
||||
xrf.pos = camOverride
|
||||
xrf.href = camOverride
|
||||
|
||||
// in order to set the rotation programmatically
|
||||
// we need to disable look-controls
|
||||
|
@ -44,17 +45,27 @@ window.AFRAME.registerComponent('xrf', {
|
|||
//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
|
||||
xrf.href = (xrf,v,opts) => {
|
||||
camOverride(xrf,v,opts)
|
||||
let {mesh,camera} = opts;
|
||||
AFRAME.XRF.clickableMeshToEntity = (opts) => {
|
||||
let {mesh,clickHandler} = opts;
|
||||
let el = document.createElement("a-entity")
|
||||
el.setAttribute("xrf-get",mesh.name )
|
||||
el.setAttribute("class","ray")
|
||||
el.addEventListener("click", mesh.userData.XRF.href.exec )
|
||||
el.setAttribute("xrf-get",mesh.name ) // turn into AFRAME entity
|
||||
el.setAttribute("class","ray") // expose to raycaster
|
||||
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)
|
||||
}
|
||||
xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity )
|
||||
|
||||
// cleanup xrf-get objects when resetting scene
|
||||
xrf.reset = ((reset) => () => {
|
||||
|
|
|
@ -11,24 +11,32 @@ window.AFRAME.registerComponent('xrf-get', {
|
|||
|
||||
this.el.addEventListener('update', (evt) => {
|
||||
|
||||
let scene = AFRAME.XRF.scene
|
||||
let mesh = scene.getObjectByName(meshname);
|
||||
if (!mesh){
|
||||
console.error("mesh with name '"+meshname+"' not found in model")
|
||||
return;
|
||||
}
|
||||
// convert to worldcoordinates
|
||||
mesh.getWorldPosition(mesh.position)
|
||||
mesh.getWorldScale(mesh.scale)
|
||||
mesh.getWorldQuaternion(mesh.quaternion)
|
||||
if( !this.data.clone ) mesh.parent.remove(mesh)
|
||||
mesh.isXRF = true // mark for deletion by xrf
|
||||
this.el.setObject3D('mesh', mesh );
|
||||
if( !this.el.id ) this.el.setAttribute("id",`xrf-${mesh.name}`)
|
||||
setTimeout( () => {
|
||||
|
||||
if( !this.mesh && this.el.className == "ray" ){
|
||||
let scene = AFRAME.XRF.scene
|
||||
let mesh = this.mesh = scene.getObjectByName(meshname);
|
||||
if (!mesh){
|
||||
console.error("mesh with name '"+meshname+"' not found in model")
|
||||
return;
|
||||
}
|
||||
// convert to worldcoordinates
|
||||
mesh.getWorldPosition(mesh.position)
|
||||
mesh.getWorldScale(mesh.scale)
|
||||
mesh.getWorldQuaternion(mesh.quaternion)
|
||||
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")
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -24,6 +24,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
camera.traverse( (n) => String(n.type).match(/Camera/) ? camera = n : null )
|
||||
|
||||
const scope = this;
|
||||
scope.objects = []
|
||||
|
||||
const raycaster = new Raycaster();
|
||||
const tempMatrix = new Matrix4();
|
||||
|
@ -50,7 +51,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
raycaster.setFromCamera( _pointer, camera );
|
||||
|
||||
const intersects = raycaster.intersectObjects( scope.children, false );
|
||||
const intersects = raycaster.intersectObjects( scope.objects, false );
|
||||
|
||||
if ( intersects.length > 0 ) {
|
||||
|
||||
|
@ -95,7 +96,7 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
|
||||
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 ) {
|
||||
|
||||
|
@ -106,9 +107,6 @@ xrf.InteractiveGroup = function(THREE,renderer,camera){
|
|||
|
||||
_event.type = events[ event.type ];
|
||||
_event.data.set( uv.x, 1 - uv.y );
|
||||
if( _event.type != "mousemove" ){
|
||||
console.log(event.type+" => "+_event.type)
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
@ -50,7 +50,10 @@ xrf.parseModel = function(model,url){
|
|||
// add animations
|
||||
model.clock = new xrf.THREE.Clock();
|
||||
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()
|
||||
model.render = function(){
|
||||
|
|
|
@ -90,6 +90,7 @@ xrf.frag.href = function(v, opts){
|
|||
}else if( mesh.material){ mesh.material = mesh.material.clone() }
|
||||
|
||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||
|
||||
let isLocal = v.string[0] == '#'
|
||||
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)
|
||||
xrf.navigator.to(v.string) // let's surf to HREF!
|
||||
})
|
||||
.catch( console.error )
|
||||
}
|
||||
|
||||
let selected = (state) => () => {
|
||||
|
@ -133,7 +135,8 @@ xrf.frag.href = function(v, opts){
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
xrf.interactive.add(mesh)
|
||||
}, 10, mesh )
|
||||
xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
|
||||
}, 0, mesh )
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -64,6 +64,7 @@ xrf.addEventListener('mesh', (opts) => {
|
|||
mesh.scale.copy(world.scale)
|
||||
mesh.setRotationFromQuaternion(world.quat);
|
||||
xrf.interactive.add(mesh)
|
||||
xrf.emit('interactionReady', {mesh,xrf:fragment, clickHandler: fragment.trigger})
|
||||
}, 10, mesh )
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue