wip: uv scroll static
This commit is contained in:
parent
e9be997182
commit
93754ef5f0
|
@ -259,6 +259,7 @@ These are automatic fragment-to-metadata mappings, which only trigger if the 3D
|
|||
| **MATERIALUPDATE** | `#<tag_or_objectname>[*]=<materialname>` | string=string | `#car=metallic`| sets material of car to material with name `metallic` (`*`=including children)|
|
||||
| | | | `#soldout*=halfopacity`| set material of objects tagged with `product` to material with name `metallic` |
|
||||
| **VARIABLE UPDATE** | `#<variable>=<metadata-key>` | string=string | `#foo=bar` | sets [URI Template](https://www.rfc-editor.org/rfc/rfc6570) variable `foo` to the value `#t=0` from **existing** object metadata (`bar`:`#t=0` e.g.), This allows for reactive [URI Template](https://www.rfc-editor.org/rfc/rfc6570) defined in object metadata elsewhere (`src`:`://m.com/cat.mp4#{foo}` e.g., to play media using [media fragment URI](https://www.w3.org/TR/media-frags/#valid-uri)). NOTE: metadata-key should not start with `#` |
|
||||
| **ANIMATION** | `#<tag_or_objectname>=<animationname>` | string=string | `#people=walk` `#people=noanim` | assign a different animation to object(s) |
|
||||
|
||||
## media fragments and datatypes
|
||||
|
||||
|
@ -364,7 +365,7 @@ For example, to render a portal with a preview-version of the scene, create an 3
|
|||
1. the Y-coordinate of `pos` identifies the floorposition. This means that desktop-projections usually need to add 1.5m (average person height) on top (which is done automatically by VR/AR headsets).
|
||||
1. set the position of the camera accordingly to the vector3 values of `#pos`
|
||||
1. `rot` sets the rotation of the camera (only for non-VR/AR headsets)
|
||||
1. `t` sets the playbackspeed and animation-range of the current scene animation(s) or `src`-mediacontent (video/audioframes e.g., use `t=0,7,7` to 'STOP' at frame 7 e.g.)
|
||||
1. `t` in the top-URL sets the playbackspeed and animation-range of the global scene animation
|
||||
1. after scene load: in case an `href` does not mention any `pos`-coordinate, `pos=0,0,0` will be assumed
|
||||
|
||||
Here's an ascii representation of a 3D scene-graph which contains 3D objects `◻` and their metadata:
|
||||
|
@ -488,6 +489,8 @@ navigation, portals & mutations
|
|||
|
||||
10. href-events should bubble upward the node-tree
|
||||
|
||||
11. the end-user navigator back/forward buttons should repeat a back/forward action until a `pos=...` primitive is found (the inbetween interaction URI's are only for UX research purposes)
|
||||
|
||||
[» example implementation](https://github.com/coderofsalvation/xrfragment/blob/main/src/3rd/js/three/xrf/href.js)<br>
|
||||
[» example 3D asset](https://github.com/coderofsalvation/xrfragment/blob/main/example/assets/href.gltf#L192)<br>
|
||||
[» discussion](https://github.com/coderofsalvation/xrfragment/issues/1)<br>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
</head>
|
||||
<body>
|
||||
<a-scene xr-mode-ui="XRMode: xr" renderer="colorManagement: true; highRefreshRate:true; " light="defaultLightsEnabled: false">
|
||||
<a-entity id="player" wasd-controls="fly:true" look-controls>
|
||||
<a-entity id="player" wasd-controls="fly:false" look-controls>
|
||||
<a-entity camera="fov:90" position="0 1.6 0" id="camera"></a-entity>
|
||||
<a-entity id="left-hand" laser-controls="hand: left" raycaster="objects:.ray" blink-controls="cameraRig:#player; teleportOrigin: #camera; collisionEntities: .floor">
|
||||
<a-entity rotation="-35 0 0" position="0 0.1 0" id="navigator">
|
||||
|
@ -19,7 +19,7 @@
|
|||
<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" blink-controls="cameraRig:#player; teleportOrigin: #player; 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>
|
||||
|
||||
<a-entity id="home" xrf="index.glb" xrf-menu></a-entity>
|
||||
|
|
Binary file not shown.
|
@ -69,3 +69,17 @@ XRWG.generate = (opts) => {
|
|||
XRWG = XRWG.reverse() // the cleankey/get functions e.g. will persist
|
||||
xrf.emit('XRWG',XRWG)
|
||||
}
|
||||
|
||||
XRWG.deepApplyMatch = function(match,v,cb){
|
||||
match.map( (m) => {
|
||||
for( let i in m.types ){
|
||||
let type = m.types[i]
|
||||
let node = m.nodes[i]
|
||||
if (type == 'name' || type == 'tag'){
|
||||
cb(match,v,node,type)
|
||||
if( v.filter.q.deep ) node.traverse( (c) => cb(match,v,c,t) )
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -96,9 +96,7 @@ xrf.navigator.init = () => {
|
|||
|
||||
window.addEventListener('popstate', function (event){
|
||||
if( !xrf.navigator.updateHash.active ){ // ignore programmatic hash updates (causes infinite recursion)
|
||||
if( !document.location.hash.match(/pos=/) ){
|
||||
history.back() // go back until we find a position
|
||||
}else xrf.navigator.to( document.location.search.substr(1) + document.location.hash )
|
||||
xrf.navigator.to( document.location.search.substr(1) + document.location.hash )
|
||||
}
|
||||
})
|
||||
|
||||
|
@ -155,9 +153,6 @@ xrf.navigator.updateHash = (hash,opts) => {
|
|||
|
||||
xrf.navigator.pushState = (file,hash) => {
|
||||
if( file == document.location.search.substr(1) ) return // page is in its default state
|
||||
if( !hash.match(/pos=/) ){
|
||||
history.forward() // go forward until we find a position
|
||||
}
|
||||
window.history.pushState({},`${file}#${hash}`, document.location.pathname + `?${file}#${hash}` )
|
||||
xrf.emit('pushState', {file, hash} )
|
||||
}
|
||||
|
|
|
@ -39,21 +39,30 @@ xrf.addEventListener('dynamicKeyValue', (opts) => {
|
|||
if( !v.is( xrf.XRF.CUSTOMFRAG) ) return // only process custom frags from here
|
||||
if( v.string.match(/(<|>)/) ) return // ignore filter values
|
||||
|
||||
// check if fragment is an objectname
|
||||
if( match.length > 0 ){
|
||||
xrf.frag.dynamic.material(v,opts)
|
||||
xrf.frag.dynamic.material(v,opts) // check if fragment is an objectname
|
||||
}
|
||||
|
||||
if( !xrf.URI.vars[ v.string ] ) return console.warn(`'${v.string}' metadata not found in scene`) // only assign to known values
|
||||
if( !xrf.URI.vars[ v.string ] ) return console.error(`'${v.string}' metadata-key not found in scene`)
|
||||
if( xrf.URI.vars[ id ] && !match.length ) return console.error(`'${id}' object/tag/metadata-key not found in scene`)
|
||||
|
||||
xrf.URI.vars[ id ] = xrf.URI.vars[ v.string ] // update var
|
||||
if( xrf.debug ) console.log(`URI.vars[${id}]='${v.string}'`)
|
||||
|
||||
xrf.scene.traverse( (n) => { // reflect new changes
|
||||
if( n.userData && n.userData.src && n.userData.srcTemplate && n.userData.srcTemplate.match(`{${id}}`) ){
|
||||
let srcNewFragments = xrf.frag.src.expandURI( n ).replace(/.*#/,'')
|
||||
console.log(`URI.vars[${id}] => updating ${n.name} => ${srcNewFragments}`)
|
||||
let frag = xrf.hashbus.pub( srcNewFragments, n )
|
||||
}
|
||||
})
|
||||
if( xrf.URI.vars[id] ){
|
||||
xrf.URI.vars[ id ] = xrf.URI.vars[ v.string ] // update var
|
||||
xrf.scene.traverse( (n) => {
|
||||
// re-expand src-values which use the updated URI Template var
|
||||
if( n.userData && n.userData.src && n.userData.srcTemplate && n.userData.srcTemplate.match(`{${id}}`) ){
|
||||
let srcNewFragments = xrf.frag.src.expandURI( n ).replace(/.*#/,'')
|
||||
console.log(`URI.vars[${id}] => updating ${n.name} => ${srcNewFragments}`)
|
||||
let frag = xrf.hashbus.pub( srcNewFragments, n )
|
||||
}
|
||||
})
|
||||
}else{
|
||||
xrf.XRWG.deepApplyMatch(match, v, (match,v,node,type) => {
|
||||
console.log(v.string)
|
||||
if( node.geometry ) xrf.hashbus.pub( xrf.URI.vars[ v.string ](), node) // apply fragment mesh(es)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
@ -6,10 +6,12 @@ xrf.frag.dynamic.material = function(v,opts){
|
|||
xrf.scene.traverse( (n) => n.material && (n.material.name == v.string) && (material = n.material) )
|
||||
if( !material && !v.reset ) return // nothing to do
|
||||
|
||||
xrf.frag.dynamic.material.setMatch(match,material,v)
|
||||
xrf.XRWG.deepApplyMatch(match, v, (match,v,node,type) => {
|
||||
if( node.material ) xrf.frag.dynamic.material.set( node, material, v.reset )
|
||||
})
|
||||
}
|
||||
|
||||
xrf.frag.dynamic.material.setMaterial = function(mesh,material,reset){
|
||||
xrf.frag.dynamic.material.set = function(mesh,material,reset){
|
||||
if( !mesh.materialOriginal ) mesh.materialOriginal = mesh.material
|
||||
let visible = mesh.material.visible //remember
|
||||
if( reset ){
|
||||
|
@ -18,27 +20,15 @@ xrf.frag.dynamic.material.setMaterial = function(mesh,material,reset){
|
|||
mesh.material.visible = visible
|
||||
}
|
||||
|
||||
xrf.frag.dynamic.material.setMatch = function(match,material,v){
|
||||
const setMaterial = xrf.frag.dynamic.material.setMaterial
|
||||
|
||||
match.map( (m) => {
|
||||
for( let i in m.types ){
|
||||
let type = m.types[i]
|
||||
let node = m.nodes[i]
|
||||
if (type == 'name' || type == 'tag'){
|
||||
setMaterial( node, material, v.reset )
|
||||
if( v.filter.q.deep ) node.traverse( (c) => c.material && setMaterial( c, material, v.reset ) )
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// for reset calls like href: xrf://!myobject e.g.
|
||||
xrf.addEventListener('dynamicKey', (opts) => {
|
||||
|
||||
let {v,match} = opts
|
||||
|
||||
if( v.reset ){
|
||||
xrf.frag.dynamic.material.setMatch(match,null,v)
|
||||
xrf.XRWG.deepApplyMatch(match,v, (match,v,node,type) => {
|
||||
if( node.material ) xrf.frag.dynamic.material.set( node, null, v.reset )
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
|
|
@ -35,6 +35,8 @@ xrf.frag.href = function(v, opts){
|
|||
|
||||
let click = mesh.userData.XRF.href.exec = (e) => {
|
||||
|
||||
if( !mesh.material.visible ) return
|
||||
|
||||
// bubble up!
|
||||
mesh.traverseAncestors( (n) => n.userData && n.userData.href && n.dispatchEvent({type:e.type,data:{}}) )
|
||||
|
||||
|
@ -48,8 +50,10 @@ xrf.frag.href = function(v, opts){
|
|||
const hasPos = isLocal && v.string.match(/pos=/)
|
||||
const flags = isLocal ? xrf.XRF.PV_OVERRIDE : undefined
|
||||
|
||||
let toFrag = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
||||
xrf.navigator.to(v.string) // let's surf
|
||||
//let toFrag = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
|
||||
if( v.xrfScheme ){
|
||||
xrf.hashbus.pub(v.string)
|
||||
} else xrf.navigator.to(v.string) // let's surf
|
||||
})
|
||||
.catch( console.error )
|
||||
}
|
||||
|
|
|
@ -2,70 +2,71 @@ xrf.frag.uv = function(v, opts){
|
|||
let { frag, mesh, model, camera, scene, renderer, THREE} = opts
|
||||
|
||||
if( !mesh.geometry ) return // nothing to do here
|
||||
if( v.floats.length != 4 ) return
|
||||
if( v.floats.length < 2 ) return console.warn('xrfragment.js: got less than 4 uv values ')
|
||||
|
||||
xrf.frag.uv.init(mesh)
|
||||
mesh.uv.u = v.floats[0]
|
||||
mesh.uv.v = v.floats[1]
|
||||
mesh.uv.uspeed = v.floats[2]
|
||||
mesh.uv.vspeed = v.floats[3]
|
||||
mesh.uv.uloop = v.shift[2]
|
||||
mesh.uv.vloop = v.shift[3]
|
||||
mesh.uv.uspeed = v.floats[2] || 1.0
|
||||
mesh.uv.vspeed = v.floats[3] || 1.0
|
||||
mesh.uv.ushift = v.shift[0]
|
||||
mesh.uv.vshift = v.shift[1]
|
||||
mesh.uv.uloop = v.shift[2] || false
|
||||
mesh.uv.vloop = v.shift[3] || false
|
||||
debugger
|
||||
|
||||
mesh.onBeforeRender = xrf.frag.uv.scroll
|
||||
}
|
||||
|
||||
xrf.frag.uv.init = function(mesh){
|
||||
if( !mesh.uv ) mesh.uv = {u:0, v:0, uspeed:1, vspeed:1, uloop:false, vloop:false, uv:false}
|
||||
if( !mesh.uv ) mesh.uv = {u:0, v:0, uspeed:1, vspeed:1, uloop:false, vloop:false, uv:false, ushift:false,vshift:false}
|
||||
|
||||
let uv = mesh.geometry.getAttribute("uv")
|
||||
if( !uv.old ) uv.old = mesh.geometry.getAttribute("uv").clone()
|
||||
}
|
||||
|
||||
xrf.frag.uv.scroll = function(){
|
||||
if( this.uv.uspeed > 0.0 || this.uv.vspeed > 0.0 ){
|
||||
|
||||
let diffU = 0.0 // distance to end-state (non-looping mode)
|
||||
let diffV = 0.0 // distance to end-state (non-looping mode)
|
||||
let uv = this.geometry.getAttribute("uv")
|
||||
let diffU = 0.0 // distance to end-state (non-looping mode)
|
||||
let diffV = 0.0 // distance to end-state (non-looping mode)
|
||||
let uv = this.geometry.getAttribute("uv")
|
||||
|
||||
// translate!
|
||||
for( let i = 0; i < uv.count; i++ ){
|
||||
let u = uv.getX(i)
|
||||
let v = uv.getY(i)
|
||||
let uTarget = uv.old.getX(i) + this.uv.u
|
||||
let vTarget = uv.old.getY(i) + this.uv.v
|
||||
// translate!
|
||||
for( let i = 0; i < uv.count; i++ ){
|
||||
let u = uv.getX(i)
|
||||
let v = uv.getY(i)
|
||||
let uTarget = (this.uv.ushift ? u : uv.old.getX(i) ) + this.uv.u
|
||||
let vTarget = (this.uv.vshift ? v : uv.old.getY(i) ) + this.uv.v
|
||||
|
||||
// scroll U
|
||||
if( this.uv.uloop ){
|
||||
u += this.uv.uspeed * xrf.clock.delta
|
||||
}else{
|
||||
// recover from super-high uv-values due to looped scrolling
|
||||
if( Math.abs(u-uTarget) > 1.0 ) u = uv.old.getX(i)
|
||||
u = u > uTarget ? u + (this.uv.uspeed * -xrf.clock.delta)
|
||||
: u + (this.uv.uspeed * xrf.clock.delta)
|
||||
diffU += Math.abs( u - uTarget ) // are we done yet? (non-looping mode)
|
||||
}
|
||||
// scroll U
|
||||
if( this.uv.uloop ){
|
||||
u += this.uv.uspeed * xrf.clock.delta
|
||||
}else{
|
||||
// recover from super-high uv-values due to looped scrolling
|
||||
if( Math.abs(u-uTarget) > 1.0 ) u = uv.old.getX(i)
|
||||
u = u > uTarget ? u + (this.uv.uspeed * -uTarget ) // -xrf.clock.delta)
|
||||
: u + (this.uv.uspeed * uTarget ) // xrf.clock.delta)
|
||||
diffU += Math.abs( u - uTarget ) // are we done yet? (non-looping mode)
|
||||
}
|
||||
|
||||
// scroll V
|
||||
if( this.uv.vloop ){
|
||||
v += this.uv.vspeed * xrf.clock.delta
|
||||
}else{
|
||||
// recover from super-high uv-values due to looped scrolling
|
||||
if( Math.abs(v-vTarget) > 1.0 ) v = uv.old.getY(i)
|
||||
v = v > vTarget ? v + (this.uv.vspeed * -xrf.clock.delta)
|
||||
: v + (this.uv.vspeed * xrf.clock.delta)
|
||||
diffV += Math.abs( v - vTarget )
|
||||
// scroll V
|
||||
if( this.uv.vloop ){
|
||||
v += this.uv.vspeed * xrf.clock.delta
|
||||
}else{
|
||||
// recover from super-high uv-values due to looped scrolling
|
||||
if( Math.abs(v-vTarget) > 1.0 ) v = uv.old.getY(i)
|
||||
v = v > vTarget ? v + (this.uv.vspeed * -vTarget ) // -xrf.clock.delta)
|
||||
: v + (this.uv.vspeed * vTarget ) // xrf.clock.delta)
|
||||
diffV += Math.abs( v - vTarget )
|
||||
|
||||
}
|
||||
uv.setXY(i,u,v)
|
||||
}
|
||||
uv.needsUpdate = true
|
||||
uv.setXY(i,u,v)
|
||||
}
|
||||
uv.needsUpdate = true
|
||||
|
||||
if( (!this.uv.uloop && diffU < 0.05) &&
|
||||
(!this.uv.vloop && diffV < 0.05)
|
||||
){ // stop animating if done
|
||||
this.onBeforeRender = function(){}
|
||||
}
|
||||
if( (!this.uv.uloop && diffU < 0.05) &&
|
||||
(!this.uv.vloop && diffV < 0.05)
|
||||
){ // stop animating if done
|
||||
this.onBeforeRender = function(){}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,10 @@ import xrfragment.XRF;
|
|||
* ### XR Fragment URI Grammar
|
||||
*
|
||||
* ```
|
||||
* reserved = gen-delims / sub-delims
|
||||
* reserved = gen-delims / sub-delims / xrf-scheme
|
||||
* gen-delims = "#" / "&"
|
||||
* sub-delims = "," / "="
|
||||
* xrf-scheme = "xrf://"
|
||||
* ```
|
||||
*
|
||||
* In case your programming language has no parser ([check here](https://github.com/coderofsalvation/xrfragment/tree/main/dist)) you can [crosscompile it](https://github.com/coderofsalvation/xrfragment/blob/main/build.hxml), or roll your own `Parser.parse(k,v,store)` using the spec:
|
||||
|
@ -66,6 +67,7 @@ class URI {
|
|||
parts[1] = frag;
|
||||
return parts.join("#");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -51,7 +51,8 @@ class XRF {
|
|||
public static var isNumber:EReg = ~/^[0-9\.]+$/; // 1. detect number values like `foo=1` (reference regex= `/^[0-9\.]+$/` )
|
||||
public static var isMediaFrag:EReg = ~/^([0-9\.,\*+-]+)$/; // 1. detect (extended) media fragment
|
||||
public static var isReset:EReg = ~/^!/; // 1. detect reset operation
|
||||
public static var isShift:EReg = ~/[+-]/;
|
||||
public static var isShift:EReg = ~/^(\+|--)/;
|
||||
public static var isXRFScheme = ~/^xrf:\/\//;
|
||||
|
||||
// value holder(s) // |------|------|--------|----------------------------------|
|
||||
public var fragment:String;
|
||||
|
@ -69,6 +70,7 @@ class XRF {
|
|||
public var filter:Filter;
|
||||
public var reset:Bool;
|
||||
public var loop:Bool;
|
||||
public var xrfScheme:Bool;
|
||||
//
|
||||
public function new(_fragment:String,_flags:Int,?_index:Int){
|
||||
fragment = _fragment;
|
||||
|
@ -107,6 +109,12 @@ class XRF {
|
|||
|
||||
if( str.length > 0 ){
|
||||
|
||||
if( isXRFScheme.match(str) ){
|
||||
v.xrfScheme = true;
|
||||
str = isXRFScheme.replace(str,"");
|
||||
v.string = str;
|
||||
}
|
||||
|
||||
if( str.split(",").length > 1){ // 1. `,` assumes 1D/2D/3D vector-values like x[,y[,z]]
|
||||
var xyzn:Array<String> = str.split(","); // 1. parseFloat(..) and parseInt(..) is applied to vector/float and int values
|
||||
if( xyzn.length > 0 ) v.x = Std.parseFloat(xyzn[0]); // 1. anything else will be treated as string-value
|
||||
|
@ -114,7 +122,7 @@ class XRF {
|
|||
if( xyzn.length > 2 ) v.z = Std.parseFloat(xyzn[2]); //
|
||||
for( i in 0...xyzn.length ){
|
||||
v.shift.push( isShift.match(xyzn[i]) );
|
||||
v.floats.push( Std.parseFloat(xyzn[i]) );
|
||||
v.floats.push( Std.parseFloat( isShift.replace(xyzn[i],'') ) );
|
||||
}
|
||||
} // > the xrfragment specification should stay simple enough
|
||||
// > for anyone to write a parser using either regexes or grammar/lexers
|
||||
|
|
Loading…
Reference in New Issue