diff --git a/doc/RFC_XR_Fragments.md b/doc/RFC_XR_Fragments.md index 0cb13d4..2a46c57 100644 --- a/doc/RFC_XR_Fragments.md +++ b/doc/RFC_XR_Fragments.md @@ -94,8 +94,8 @@ value: draft-XRFRAGMENTS-leonvankammen-00 .# Abstract 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.
-The specification promotes spatial addressibility, sharing, navigation, query-ing and annotating interactive (text)objects across for (XR) Browsers.
-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.
+The specification promotes spatial addressibility, sharing, navigation, query-ing and databinding objects for (XR) Browsers.
+XR Fragments allows us to enrich existing dataformats, by recursive use of existing metadata inside 3D scene(files), and proven technologies like [URI Fragments](https://en.wikipedia.org/wiki/URI_fragment). > Almost every idea in this document is demonstrated at [https://xrfragment.org](https://xrfragment.org) @@ -103,34 +103,38 @@ XR Fragments allows us to enrich existing dataformats, by recursive use of exist # Introduction -How can we add more features to existing text & 3D scenes, without introducing new dataformats?
+How can we add more control to existing text & 3D scenes, without introducing new dataformats?
Historically, there's many attempts to create the ultimate markuplanguage or 3D fileformat.
-The lowest common denominator is: describing/tagging/naming nodes using **plain text**.
-XR Fragments allows us to enrich/connect existing dataformats, by introducing existing technologies/ideas:
+The lowest common denominator is: designers describing/tagging/naming things using **plain text**.
+XR Fragments exploits the fact that all 3D models already contain such metadata: + +**XR Fragments allows controlling of metadata in 3D scene(files) using URLs** + +Or more detailed: 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)objects by collapsing space into a Word Graph (XRWG) to show [visible links](#visible-links) 1. unlocking spatial potential of the (originally 2D) hashtag (which jumps to a chapter) for navigating XR documents > NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible # Core principle -XR Fragments strives to serve (nontechnical/fuzzy) humans first, and machine(implementations) later, by ensuring hasslefree text-vs-thought feedback loops.
-This also means that the repair-ability of machine-matters should be human friendly too (not too complex).
+**XR Fragments allows controlling of metadata in 3D scene(files) using URLs** + XR Fragments tries to seek to connect the world of text (semantical web / RDF), and the world of pixels.
-Instead of combining them (in a game-editor e.g.), XR Fragments is opting for a more integrated path **towards** them, by describing how to make browsers **4D URL-ready**: +Instead of combining them (in a game-editor e.g.), XR Fragments **integrates all**, by collecting metadata into an XRWG and control it via URL: | principle | XR 4D URL | HTML 2D URL | |----------------------|-------------------------------------------------|---------------------------------------| | the XRWG | wordgraph (collapses 3D scene to tags) | Ctrl-F (find) | -| the hashbus | hashtags map to camera/scene-projections | hashtags map to document positions | -| spacetime hashtags | positions camera, triggers scene-preset/time | jumps/scrolls to chapter | +| the hashbus | hashtags alter camera/scene/object-projections | hashtags alter document positions | | src metadata | renders content and offers sourceportation | renders content | | href metadata | teleports to other XR document | jumps to other HTML document | -| href metadata | repositions camera or animation-range | jumps to camera | -| href metadata | draws visible connection(s) for XRWG 'tag' | | | href metadata | triggers predefined view | Media fragments | +| href metadata | triggers camera/scene/object/projections | n/a | +| href metadata | draws visible connection(s) for XRWG 'tag' | n/a | +| href metadata | queries certain (in)visible objects | n/a | > XR Fragments does not look at XR (or the web) thru the lens of HTML.
But approaches things from a higherlevel feedbackloop/hypermedia browser-perspective: @@ -144,11 +148,14 @@ Instead of combining them (in a game-editor e.g.), XR Fragments is opting for a │ 4D URL: ://park.com /4Dscene.fbx ──> ?misc ──> #view ───> hashbus │ │ │ #query │ │ │ │ #tag │ │ + │ │ #material │ │ + │ │ #animation │ │ + │ │ #texture │ │ + │ │ #variable │ │ │ │ │ │ │ XRWG <─────────────────────<────────────+ │ │ │ │ │ - │ ├─ objects ───────────────>────────────│ │ - │ └─ text ───────────────>────────────+ │ + │ └─ objects ──────────────>────────────+ │ │ │ │ │ +──────────────────────────────────────────────────────────────────────────────────────────────+ @@ -180,22 +187,19 @@ sub-delims = "," / "=" | Demo | Explanation | |-------------------------------|---------------------------------| | `pos=1,2,3` | vector/coordinate argument e.g. | -| `pos=1,2,3&rot=0,90,0&q=.foo` | combinators | +| `pos=1,2,3&rot=0,90,0&q=foo` | combinators | > this is already implemented in all browsers # List of URI Fragments -| fragment | type | example | info | -|--------------|----------|-------------------|----------------------------------------------------------------------| -| `#pos` | vector3 | `#pos=0.5,0,0` | positions camera (or XR floor) to xyz-coord 0.5,0,0, | -| `#rot` | vector3 | `#rot=0,90,0` | rotates camera to xyz-coord 0.5,0,0 | -| `#t` | vector3 | `#t=1,500,1000` | play animation-loop range between frame 500 and 1000, at normal speed| -| `#......` | string | `#.cubes` `#cube` | predefined views, XRWG fragments and ID fragments | +| fragment | type | example | info | +|-------------------|----------|-------------------|----------------------------------------------------------------------| +| `#pos` | vector3 | `#pos=0.5,0,0` | positions camera (or XR floor) to xyz-coord 0.5,0,0, | +| `#rot` | vector3 | `#rot=0,90,0` | rotates camera to xyz-coord 0.5,0,0 | +| `#t` | vector3 | `#t=1,500,1000` | play animation-loop range between frame 500 and 1000, at normal speed| -> xyz coordinates are similar to ones found in SVG Media Fragments - -# List of metadata for 3D nodes +## List of metadata for 3D nodes | key | type | example (JSON) | function | existing compatibility | |--------------|----------|------------------------|---------------------|----------------------------------------| @@ -207,8 +211,22 @@ Supported popular compatible 3D fileformats: `.gltf`, `.obj`, `.fbx`, `.usdz`, ` > NOTE: XR Fragments are optional but also file- and protocol-agnostic, which means that programmatic 3D scene(nodes) can also use the mechanism/metadata. -# Spatial Referencing 3D +## Dynamic XR Fragments (databindings) +These are automatic fragment-to-metadata mappings, which only trigger if the 3D scene metadata matches a specific identifier (`aliasname` e.g.) + +| fragment | type | example | info | +|------------------------|----------|-------------------|-------------------------------------------------------------------------------| +| `#` | string | `#cubes` | evaluate predefined views (`#cubes: #foo&bar` e.g.) | +| `#` | string | `#person` | focus object(s) with `tag: person` or name `person` by looking up XRWG | +| `#` | string | `#cam01` | set camera as active camera | +| `#=x,x,x` | vector3 | `#myanim=1,1,0` | play (non-global) animation ID | +| `#=`| string | `horizon=fader` | animate r/g/b/o(pacity) of material `horizon` with `fader` obj (xyzw=rgbo) | +| `#=`| string | `page=scroller` | animate x/y/r(otation) of texture `page` with `scroller` object (xyz=xyr) | +| `#=` | string|vector3 | `myvar=fader` | set/animate shaderuniform- or scene-specific vars with `fader` object (*) | + +# Spatial Referencing 3D XR Fragments assume the following objectname-to-URIFragment mapping: @@ -341,6 +359,8 @@ Resizing will be happen accordingly to its placeholder object `aquariumcube`, se 1. src-values are non-recursive: when linking to an external object (`src: foo.fbx#bar`), then `src`-metadata on object `bar` should be ignored. 1. clicking on external `src`-values always allow sourceportation: teleporting to the origin URI to which the object belongs. 1. when only one object was cherrypicked (`#cube` e.g.), set its position to `0,0,0` +1. equirectangular detection: when the width of an image is twice the height (aspect 2:1), an equirectangular projection is assumed. +1. when the enduser clicks an href with `#t=1,0,0` (play) will be applied to all src mediacontent with a timeline (mp4/mp3 e.g.) * `model/gltf+json` * `image/png` @@ -439,10 +459,11 @@ controls the animation(s) of the scene (or `src` resource which contains a timel To play global audio/video items: -* add a `src: foo.mp3` or `src: bar.mp4` metadata to a 3D object (`cube` e.g.) -* to disable auto-play and global timeline ([[#t=|t]]) control: hardcode a [[#t=|t]] XR Fragment: (`src: bar.mp3#t=0,0,0` e.g.) -* to play it, add `href: #cube` somewhere else -* when the enduser clicks the `href`, `#t=1,0,0` (play) will be applied to the `src` value +1. add a `src: foo.mp3` or `src: bar.mp4` metadata to a 3D object (`cube` e.g.) +1. to disable auto-play and global timeline ([[#t=|t]]) control: hardcode a [[#t=|t]] XR Fragment: (`src: bar.mp3#t=0,0,0` e.g.) +1. to play it, add `href: #cube` somewhere else +1. when the enduser clicks the `href`, `#t=1,0,0` (play) will be applied to the `src` value +1. to play a single animation, add href: #animationname=1,0,0 somewhere else > NOTE: hardcoded framestart/framestop uses sampleRate/fps of embedded audio/video, otherwise the global fps applies. For more info see [[#t|t]]. diff --git a/src/3rd/js/aframe/index.js b/src/3rd/js/aframe/index.js index e9094ec..6d29ac3 100644 --- a/src/3rd/js/aframe/index.js +++ b/src/3rd/js/aframe/index.js @@ -5,9 +5,6 @@ window.AFRAME.registerComponent('xrf', { if( !AFRAME.XRF ){ document.querySelector('a-scene').addEventListener('loaded', () => { - //window.addEventListener('popstate', clear ) - //window.addEventListener('pushstate', clear ) - // enable XR fragments let aScene = document.querySelector('a-scene') let XRF = AFRAME.XRF = xrf.init({ @@ -30,7 +27,6 @@ window.AFRAME.registerComponent('xrf', { if( frag.q ) return // camera was not targeted for rotation let look = document.querySelector('[look-controls]') if( look ) look.removeAttribute("look-controls") - // camOverride(xrf,v,opts) // *TODO* make look-controls compatible, because simply // adding the look-controls will revert to the old rotation (cached somehow?) //setTimeout( () => look.setAttribute("look-controls",""), 100 ) @@ -46,40 +42,17 @@ window.AFRAME.registerComponent('xrf', { 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 ) - + //el.addEventListener("pressedstarted", clickHandler ) $('a-scene').appendChild(el) } xrf.addEventListener('interactionReady', AFRAME.XRF.clickableMeshToEntity ) -// xrf.addEventListener('interactionReady', () => { -// let raycasters = [ ...document.querySelectorAll('[raycaster]') ] -// raycasters.map( (rc) => { -// rc = rc.components['raycaster'] -// rc.refreshObjects = () => { -// rc.objects = xrf.interactive.objects.map( (o) => ({ ...o, el:{} }) ) // AFRAME raycaster requires 'el' property -// console.log("refreshing") -// rc.dirty = false -// } -// rc.dirty = true -// rc.refreshObjects() -// }) -// }) - - // cleanup xrf-get objects when resetting scene - xrf.reset = ((reset) => () => { - reset() + xrf.addEventListener('reset', (opts) => { console.log("aframe reset") let els = [...document.querySelectorAll('[xrf-get]')] els.map( (el) => document.querySelector('a-scene').removeChild(el) ) - })(XRF.reset) + }) // undo lookup-control shenanigans (which blocks updating camerarig position in VR) aScene.addEventListener('enter-vr', () => document.querySelector('[camera]').object3D.parent.matrixAutoUpdate = true ) diff --git a/src/3rd/js/aframe/xrf-gaze.js b/src/3rd/js/aframe/xrf-gaze.js index ef03ef7..6a672a6 100644 --- a/src/3rd/js/aframe/xrf-gaze.js +++ b/src/3rd/js/aframe/xrf-gaze.js @@ -26,25 +26,29 @@ AFRAME.registerComponent('xrf-gaze',{ init:function(data){ this.immersive = false; let enabled = () => AFRAME.utils.device.isMobile() - let setVisible = () => document.querySelector('[cursor]').setAttribute('visible', enabled() ) + let setVisible = () => { + let cursor = document.querySelector('[cursor]') + if( cursor ) cursor.setAttribute('visible', enabled() ) + } + this.setGazer(enabled()) - if( enabled() ) setVisible(); + setVisible(); document.querySelector("a-scene").addEventListener('exit-vr', () => { this.immersive = false; setVisible() }) + document.querySelector("a-scene").addEventListener('enter-vr', () => { this.immersive = true; setVisible() if( !document.querySelector("#cursor") ) return }) - let highlightMesh = (state) => (e) => { if( !e.target.object3D ) return let obj = e.target.object3D.children[0] - if( obj.userData && obj.userData.XRF && obj.userData.XRF.href ) + if( obj && obj.userData && obj.userData.XRF && obj.userData.XRF.href ) obj.userData.XRF.href.selected( state )() } this.el.addEventListener("mouseenter", highlightMesh(true) ) diff --git a/src/3rd/js/aframe/xrf-get.js b/src/3rd/js/aframe/xrf-get.js index e4133d1..797cf2c 100644 --- a/src/3rd/js/aframe/xrf-get.js +++ b/src/3rd/js/aframe/xrf-get.js @@ -1,7 +1,8 @@ window.AFRAME.registerComponent('xrf-get', { schema: { name: {type: 'string'}, - clone: {type: 'boolean', default:false} + clone: {type: 'boolean', default:false}, + reparent: {type: 'boolean', default:false} }, init: function () { @@ -20,18 +21,26 @@ window.AFRAME.registerComponent('xrf-get', { 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) + // we don't want to re-parent gltf-meshes mesh.isXRF = true // mark for deletion by xrf - this.el.object3D.add = (a) => a // dummy + if( this.data.reparent ){ + const world = { + pos: new THREE.Vector3(), + scale: new THREE.Vector3(), + quat: new THREE.Quaternion() + } + mesh.getWorldPosition(world.pos) + mesh.getWorldScale(world.scale) + mesh.getWorldQuaternion(world.quat); + mesh.position.copy(world.pos) + mesh.scale.copy(world.scale) + mesh.setRotationFromQuaternion(world.quat); + }else{ + // add() will reparent the mesh so lets create a dummy + this.el.object3D.add = (a) => a + } 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) diff --git a/src/3rd/js/three/index.js b/src/3rd/js/three/index.js index a7b4817..6e594d0 100644 --- a/src/3rd/js/three/index.js +++ b/src/3rd/js/three/index.js @@ -72,6 +72,7 @@ xrf.reset = () => { xrf.audio = [] xrf.add( xrf.interactive ) xrf.layers = 0 + xrf.emit('reset',{}) } xrf.parseUrl = (url) => { diff --git a/src/3rd/js/three/xrf/href.js b/src/3rd/js/three/xrf/href.js index b7220d6..1014708 100644 --- a/src/3rd/js/three/xrf/href.js +++ b/src/3rd/js/three/xrf/href.js @@ -34,12 +34,6 @@ xrf.frag.href = function(v, opts){ if( mesh.userData.XRF.href.exec ) return // mesh already initialized - const world = { - pos: new THREE.Vector3(), - scale: new THREE.Vector3(), - quat: new THREE.Quaternion() - } - mesh.material = mesh.material.clone() // we need this so we can individually highlight meshes let click = mesh.userData.XRF.href.exec = (e) => { @@ -84,12 +78,6 @@ xrf.frag.href = function(v, opts){ // lazy add mesh (because we're inside a recursive traverse) setTimeout( (mesh) => { - //mesh.getWorldPosition(world.pos) - //mesh.getWorldScale(world.scale) - //mesh.getWorldQuaternion(world.quat); - //mesh.position.copy(world.pos) - //mesh.scale.copy(world.scale) - //mesh.setRotationFromQuaternion(world.quat); xrf.interactive.add(mesh) xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec }) }, 0, mesh )