wip: uv scroll static

This commit is contained in:
Leon van Kammen 2024-02-14 15:47:34 +00:00
parent e9be997182
commit 93754ef5f0
11 changed files with 112 additions and 86 deletions

View File

@ -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>

View File

@ -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.

View File

@ -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) )
}
}
})
}

View File

@ -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} )
}

View File

@ -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( 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)
})
}
})

View File

@ -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 )
})
}
})

View File

@ -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 )
}

View File

@ -2,28 +2,30 @@ 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)
@ -33,8 +35,8 @@ xrf.frag.uv.scroll = function(){
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
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 ){
@ -42,8 +44,8 @@ xrf.frag.uv.scroll = function(){
}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)
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)
}
@ -53,8 +55,8 @@ xrf.frag.uv.scroll = function(){
}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)
v = v > vTarget ? v + (this.uv.vspeed * -vTarget ) // -xrf.clock.delta)
: v + (this.uv.vspeed * vTarget ) // xrf.clock.delta)
diffV += Math.abs( v - vTarget )
}
@ -68,4 +70,3 @@ xrf.frag.uv.scroll = function(){
this.onBeforeRender = function(){}
}
}
}

View File

@ -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("#");
}
}
/**

View File

@ -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