wip
This commit is contained in:
parent
2624936229
commit
28fb41610f
19 changed files with 150 additions and 56 deletions
|
|
@ -116,6 +116,7 @@ It solves:
|
||||||
1. Interlinking text & spatial objects by collapsing space into a Word Graph (XRWG) to show [visible links](#visible-links)
|
1. Interlinking text & spatial 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
|
1. unlocking spatial potential of the (originally 2D) hashtag (which jumps to a chapter) for navigating XR documents
|
||||||
1. refraining from introducing scripting-engines for mundane tasks (and preventing its inevitable security-headaches)
|
1. refraining from introducing scripting-engines for mundane tasks (and preventing its inevitable security-headaches)
|
||||||
|
1. the gap between text an 3d objects: object-names directly map to hashtags (=fragments), which allows 3D to text transcription.
|
||||||
|
|
||||||
> NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible
|
> NOTE: The chapters in this document are ordered from highlevel to lowlevel (technical) as much as possible
|
||||||
|
|
||||||
|
|
@ -1038,10 +1039,12 @@ The following demonstrates a simple video player:
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
# Author/Sharing metadata
|
# Additional scene metadata
|
||||||
|
|
||||||
XR Fragments does not contain static metadata attributes for this, but encourages browsers to scan nodes for the following custom properties:
|
XR Fragments does not aim to redefine the metadata-space by introducing its own cataloging-metadata fields.
|
||||||
|
Instead, it encourages browsers to scan nodes for the following custom properties:
|
||||||
|
|
||||||
|
* [SPDX](https://spdx.dev/) license information
|
||||||
* [ARIA](https://www.w3.org/WAI/standards-guidelines/aria/) attributes (`aria-*: .....`)
|
* [ARIA](https://www.w3.org/WAI/standards-guidelines/aria/) attributes (`aria-*: .....`)
|
||||||
* [Open Graph](https://ogp.me) attributes (`og:*: .....`)
|
* [Open Graph](https://ogp.me) attributes (`og:*: .....`)
|
||||||
* [Dublin-Core](https://www.dublincore.org/specifications/dublin-core/application-profile-guidelines/) attributes(`dc:*: .....`)
|
* [Dublin-Core](https://www.dublincore.org/specifications/dublin-core/application-profile-guidelines/) attributes(`dc:*: .....`)
|
||||||
|
|
@ -1054,15 +1057,16 @@ Individual nodes can be enriched with such metadata, but most importantly the sc
|
||||||
|
|
||||||
| metadata key | example value |
|
| metadata key | example value |
|
||||||
|--------------------------------------------------------|-------------------------------------------------|
|
|--------------------------------------------------------|-------------------------------------------------|
|
||||||
| `dc:creator` | `John Doe` |
|
|
||||||
| `aria-description`, `og:description`, `dc:description` | `An immersive experience about Triceratops` (*) |
|
| `aria-description`, `og:description`, `dc:description` | `An immersive experience about Triceratops` (*) |
|
||||||
|
| `SPDX` | `CC0-1.0` |
|
||||||
|
| `dc:creator` | `John Doe` |
|
||||||
| `dc:title`, `og:title` | 'Triceratops` (*) |
|
| `dc:title`, `og:title` | 'Triceratops` (*) |
|
||||||
| `og:site_name` | `https://xrfragment.org` |
|
| `og:site_name` | `https://xrfragment.org` |
|
||||||
| `dc.publisher` | `NLNET` |
|
| `dc.publisher` | `NLNET` |
|
||||||
| `dc.date` | `2024-01-01` |
|
| `dc.date` | `2024-01-01` |
|
||||||
| `dc.identifier` | `XRFRAGMENT-001` |
|
| `dc.identifier` | `XRFRAGMENT-001` |
|
||||||
| `journal` (bibTeX) | `{Future Of Text Vol 3},` |
|
| `journal` (bibTeX) | `{Future Of Text Vol 3},` |
|
||||||
| Person (JSON-LD) | `{"@type":"Person",................}` |
|
| `Person` (JSON-LD) | `{"@type":"Person",................}` |
|
||||||
|
|
||||||
> \* = these are interchangable (only one needs to be defined)
|
> \* = these are interchangable (only one needs to be defined)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -47,6 +47,7 @@
|
||||||
<br>
|
<br>
|
||||||
<b>This uses only open standards:</b><br>
|
<b>This uses only open standards:</b><br>
|
||||||
📦 surf 3D models using URLs<br>
|
📦 surf 3D models using URLs<br>
|
||||||
|
🎞 interactive <a href="https://www.w3.org/TR/media-frags/" target="_blank">W3C Media Fragments</a> and <a href="https://en.wikipedia.org/wiki/URI_Template" target="_blank">URI Templates</a><br>
|
||||||
🧑🤝🧑 <b>instant</b> meetings inside hyperlinked 3D models<br>
|
🧑🤝🧑 <b>instant</b> meetings inside hyperlinked 3D models<br>
|
||||||
🚫 <b>zero proprietary</b> tech/game engines or platforms<br>
|
🚫 <b>zero proprietary</b> tech/game engines or platforms<br>
|
||||||
🙈 unhosted, privacy-friendly, avatar-agnostic scenes<br>
|
🙈 unhosted, privacy-friendly, avatar-agnostic scenes<br>
|
||||||
|
|
|
||||||
1
example/aframe/sandbox/other.glb
Symbolic link
1
example/aframe/sandbox/other.glb
Symbolic link
|
|
@ -0,0 +1 @@
|
||||||
|
../../assets/other.glb
|
||||||
BIN
example/assets/audio/nobodyhere.wav
Normal file
BIN
example/assets/audio/nobodyhere.wav
Normal file
Binary file not shown.
Binary file not shown.
BIN
example/assets/other.glb
Normal file
BIN
example/assets/other.glb
Normal file
Binary file not shown.
5
make
5
make
|
|
@ -56,7 +56,8 @@ build(){
|
||||||
test -d src/3rd/js/aframe/build/aframe || git clone https://github.com/aframevr/aframe src/3rd/js/aframe/build/aframe --depth=1
|
test -d src/3rd/js/aframe/build/aframe || git clone https://github.com/aframevr/aframe src/3rd/js/aframe/build/aframe --depth=1
|
||||||
curdir=$(pwd)
|
curdir=$(pwd)
|
||||||
cd src/3rd/js/aframe/build && cp three.module.js aframe/src/lib/. # override to add extra loaders like fbx/collada e.g.
|
cd src/3rd/js/aframe/build && cp three.module.js aframe/src/lib/. # override to add extra loaders like fbx/collada e.g.
|
||||||
cd aframe && npm run dist
|
#cd aframe && npm install && npm install troika-three-text && npm run dist
|
||||||
|
cd aframe && npm install && npm run dist
|
||||||
cd "$curdir"
|
cd "$curdir"
|
||||||
cp src/3rd/js/aframe/build/aframe/dist/aframe-master.min.js dist/aframe.min.js
|
cp src/3rd/js/aframe/build/aframe/dist/aframe-master.min.js dist/aframe.min.js
|
||||||
test -f dist/aframe-blink-controls.min.js || {
|
test -f dist/aframe-blink-controls.min.js || {
|
||||||
|
|
@ -124,7 +125,7 @@ build(){
|
||||||
return $ok
|
return $ok
|
||||||
}
|
}
|
||||||
|
|
||||||
test -z $1 && { parser && js; }
|
test -z $1 && { parser && aframe && js; }
|
||||||
test -z $1 || "$@"
|
test -z $1 || "$@"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import { ColladaLoader } from 'super-three/examples/jsm/loaders/ColladaLoader';
|
||||||
import { MTLLoader } from 'super-three/examples/jsm/loaders/MTLLoader';
|
import { MTLLoader } from 'super-three/examples/jsm/loaders/MTLLoader';
|
||||||
import * as BufferGeometryUtils from 'super-three/examples/jsm/utils/BufferGeometryUtils';
|
import * as BufferGeometryUtils from 'super-three/examples/jsm/utils/BufferGeometryUtils';
|
||||||
import { LightProbeGenerator } from 'super-three/examples/jsm/lights/LightProbeGenerator';
|
import { LightProbeGenerator } from 'super-three/examples/jsm/lights/LightProbeGenerator';
|
||||||
|
//import {Text} from 'troika-three-text'
|
||||||
|
|
||||||
var THREE = window.THREE = SUPER_THREE;
|
var THREE = window.THREE = SUPER_THREE;
|
||||||
|
|
||||||
|
|
@ -26,5 +27,6 @@ THREE.ColladaLoader = ColladaLoader;
|
||||||
THREE.OBB = OBB;
|
THREE.OBB = OBB;
|
||||||
THREE.BufferGeometryUtils = BufferGeometryUtils;
|
THREE.BufferGeometryUtils = BufferGeometryUtils;
|
||||||
THREE.LightProbeGenerator = LightProbeGenerator;
|
THREE.LightProbeGenerator = LightProbeGenerator;
|
||||||
|
//THREE.Text = Text
|
||||||
|
|
||||||
export default THREE;
|
export default THREE;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
AFRAME.registerComponent('vconsole', {
|
AFRAME.registerComponent('vconsole', {
|
||||||
init: function () {
|
init: function () {
|
||||||
//AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
//AFRAME.XRF.navigator.to("https://coderofsalvation.github.io/xrsh-media/assets/background.glb")
|
||||||
|
return
|
||||||
|
|
||||||
document.head.innerHTML += `
|
document.head.innerHTML += `
|
||||||
<style type="text/css">
|
<style type="text/css">
|
||||||
.vc-panel {
|
.vc-panel {
|
||||||
|
|
|
||||||
|
|
@ -290,16 +290,21 @@ document.head.innerHTML += `
|
||||||
|
|
||||||
.badge,
|
.badge,
|
||||||
#messages .msg.ui div.badge{
|
#messages .msg.ui div.badge{
|
||||||
|
box-sizing:border-box;
|
||||||
display:inline-block;
|
display:inline-block;
|
||||||
color: var(--xrf-white);
|
color: var(--xrf-white);
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
background: var(--xrf-gray);
|
background: var(--xrf-dark-gray);
|
||||||
border-radius:5px;
|
border-radius:16px;
|
||||||
padding:0px 4px;
|
padding:0px 12px;
|
||||||
font-size: var(--xrf-font-size-0);
|
font-size: var(--xrf-font-size-0);
|
||||||
margin-right:10px;
|
margin-right:10px;
|
||||||
text-decoration:none !important;
|
text-decoration:none !important;
|
||||||
}
|
}
|
||||||
|
#messages .msg.ui div.badge a{
|
||||||
|
color:#FFF;
|
||||||
|
}
|
||||||
|
|
||||||
.ruler{
|
.ruler{
|
||||||
width:97%;
|
width:97%;
|
||||||
margin:7px 0px;
|
margin:7px 0px;
|
||||||
|
|
@ -358,6 +363,15 @@ document.head.innerHTML += `
|
||||||
top: 64px;
|
top: 64px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.transcript{
|
||||||
|
max-height:105px;
|
||||||
|
max-width:405px;
|
||||||
|
overflow-y:auto;
|
||||||
|
border: 1px solid var(--xrf-gray);
|
||||||
|
border-radius: 5px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
.right { float:right }
|
.right { float:right }
|
||||||
.left { float:left }
|
.left { float:left }
|
||||||
|
|
||||||
|
|
@ -535,7 +549,7 @@ document.head.innerHTML += `
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
-moz-transform: rotate(-45deg) scale(var(--ggs,1));
|
-moz-transform: rotate(-45deg) scale(var(--ggs,1));
|
||||||
transform: translate(4px,1px) rotate(-45deg) scale(var(--ggs,1));
|
transform: translate(4px,-5px) rotate(-45deg) scale(var(--ggs,1));
|
||||||
width: 8px;
|
width: 8px;
|
||||||
height: 2px;
|
height: 2px;
|
||||||
background: currentColor;
|
background: currentColor;
|
||||||
|
|
@ -701,7 +715,7 @@ document.head.innerHTML += `
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
position: relative;
|
position: relative;
|
||||||
display: inline-block;
|
display: inline-block;
|
||||||
transform: scale(var(--ggs,1)) translate(3px,9px);
|
transform: scale(var(--ggs,1)) translate(3px,3px);
|
||||||
width: 16px;
|
width: 16px;
|
||||||
height: 6px;
|
height: 6px;
|
||||||
border: 2px solid;
|
border: 2px solid;
|
||||||
|
|
|
||||||
|
|
@ -88,8 +88,40 @@ window.frontend = (opts) => new Proxy({
|
||||||
// notify navigation + href mouseovers to user
|
// notify navigation + href mouseovers to user
|
||||||
setTimeout( () => {
|
setTimeout( () => {
|
||||||
window.notify('loading '+document.location.search.substr(1))
|
window.notify('loading '+document.location.search.substr(1))
|
||||||
|
|
||||||
setTimeout( () => window.notify("use WASD-keys and mouse-drag to move around",{timeout:false}),2000 )
|
setTimeout( () => window.notify("use WASD-keys and mouse-drag to move around",{timeout:false}),2000 )
|
||||||
setTimeout( () => xrf.addEventListener('href', (data) => data.selected ? window.notify(`href: ${data.xrf.string}`) : false ), 5000)
|
|
||||||
|
xrf.addEventListener('href', (data) => {
|
||||||
|
if( !data.selected ) return
|
||||||
|
let html = `<b class="badge">${data.mesh.isSRC && !data.mesh.portal ? 'src' : 'href'}</b>${data.xrf.string}<br>`
|
||||||
|
let metadata = data.mesh.userData
|
||||||
|
let meta = xrf.Parser.getMetaData()
|
||||||
|
|
||||||
|
let hasMeta = false
|
||||||
|
for ( let label in meta ) {
|
||||||
|
let fields = meta[label]
|
||||||
|
for ( let i = 0; i < fields.length;i++ ) {
|
||||||
|
let field = fields[i]
|
||||||
|
if( metadata[field] ){
|
||||||
|
hasMeta = true
|
||||||
|
html += `<br><b style="min-width:110px;display:inline-block">${label}:</b> ${metadata[field]}\n`
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let transcript = ''
|
||||||
|
let root = data.mesh.portal ? data.mesh.portal.stencilObject : data.mesh
|
||||||
|
root.traverse( (n) => {
|
||||||
|
if( n.userData['aria-description'] && n.uuid != data.mesh.uuid ){
|
||||||
|
transcript += `<b>#${n.name}</b> ${n.userData['aria-description']}. `
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if( transcript.length ) html += `<br><b>transcript:</b><br><div class="transcript">${transcript}</div>`
|
||||||
|
|
||||||
|
if (hasMeta && !data.mesh.portal ) html += `<br><br><a class="btn" style="float:right" onclick="xrf.navigator.to('${data.mesh.userData.href}')">Visit embedded scene</a>`
|
||||||
|
window.notify(html,{timeout: 7000 * (hasMeta ? 1.5 : 1) })
|
||||||
|
})
|
||||||
|
|
||||||
},100)
|
},100)
|
||||||
return this
|
return this
|
||||||
},
|
},
|
||||||
|
|
|
||||||
|
|
@ -9,13 +9,14 @@ let pub = function( url, node_or_model, flags ){ // evaluate fragments in url
|
||||||
let { THREE, camera } = xrf
|
let { THREE, camera } = xrf
|
||||||
let frag = xrf.URI.parse( url, flags )
|
let frag = xrf.URI.parse( url, flags )
|
||||||
let fromNode = node_or_model != xrf.model
|
let fromNode = node_or_model != xrf.model
|
||||||
|
let isNode = node_or_model && node_or_model.children
|
||||||
|
|
||||||
let opts = {
|
let opts = {
|
||||||
frag,
|
frag,
|
||||||
mesh: fromNode ? node_or_model : xrf.camera,
|
mesh: fromNode ? node_or_model : xrf.camera,
|
||||||
model: xrf.model,
|
model: xrf.model,
|
||||||
camera: xrf.camera,
|
camera: xrf.camera,
|
||||||
scene: xrf.scene,
|
scene: isNode ? node_or_model : xrf.scene,
|
||||||
renderer: xrf.renderer,
|
renderer: xrf.renderer,
|
||||||
THREE: xrf.THREE,
|
THREE: xrf.THREE,
|
||||||
hashbus: xrf.hashbus
|
hashbus: xrf.hashbus
|
||||||
|
|
@ -31,20 +32,6 @@ let pub = function( url, node_or_model, flags ){ // evaluate fragments in url
|
||||||
return frag
|
return frag
|
||||||
}
|
}
|
||||||
|
|
||||||
// deprecated: (XR Macros) evaluate embedded fragments (metadata) inside mesh of model *REMOVEME*
|
|
||||||
pub.mesh = (mesh,model) => {
|
|
||||||
if( mesh.userData ){
|
|
||||||
let frag = {}
|
|
||||||
for( let k in mesh.userData ) xrf.Parser.parse( k, mesh.userData[k], frag )
|
|
||||||
for( let k in frag ){
|
|
||||||
let opts = {frag, mesh, model, camera: xrf.camera, scene: model.scene, renderer: xrf.renderer, THREE: xrf.THREE, hashbus: xrf.hashbus }
|
|
||||||
mesh.userData.XRF = frag // allow fragment impl to access XRF obj already
|
|
||||||
xrf.emit('frag2mesh',opts)
|
|
||||||
.then( () => pub.fragment(k, {...opts, skipXRWG:true}) )
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub.fragment = (k, opts ) => { // evaluate one fragment
|
pub.fragment = (k, opts ) => { // evaluate one fragment
|
||||||
let frag = opts.frag[k];
|
let frag = opts.frag[k];
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -42,9 +42,26 @@ xrf.parseModel = function(model,url){
|
||||||
model.file = file
|
model.file = file
|
||||||
model.isXRF = true
|
model.isXRF = true
|
||||||
model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
|
model.scene.traverse( (n) => n.isXRF = true ) // mark for deletion during reset()
|
||||||
|
|
||||||
xrf.emit('parseModel',{model,url,file})
|
xrf.emit('parseModel',{model,url,file})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
xrf.parseModel.metadataInMesh = (mesh,model) => {
|
||||||
|
if( mesh.userData ){
|
||||||
|
let frag = {}
|
||||||
|
for( let k in mesh.userData ) xrf.Parser.parse( k, mesh.userData[k], frag )
|
||||||
|
for( let k in frag ){
|
||||||
|
let opts = {frag, mesh, model, camera: xrf.camera, scene: model.scene, renderer: xrf.renderer, THREE: xrf.THREE, hashbus: xrf.hashbus }
|
||||||
|
mesh.userData.XRF = frag // allow fragment impl to access XRF obj already
|
||||||
|
xrf.emit('frag2mesh',opts)
|
||||||
|
.then( () => {
|
||||||
|
xrf.hashbus.pub.fragment(k, {...opts, skipXRWG:true})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
xrf.getLastModel = () => xrf.model.last
|
xrf.getLastModel = () => xrf.model.last
|
||||||
|
|
||||||
xrf.reset = () => {
|
xrf.reset = () => {
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ xrf.navigator = {}
|
||||||
|
|
||||||
xrf.navigator.to = (url,flags,loader,data) => {
|
xrf.navigator.to = (url,flags,loader,data) => {
|
||||||
if( !url ) throw 'xrf.navigator.to(..) no url given'
|
if( !url ) throw 'xrf.navigator.to(..) no url given'
|
||||||
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
let {urlObj,dir,file,hash,ext} = xrf.navigator.origin = xrf.parseUrl(url)
|
||||||
let hashChange = (!file && hash) || !data && xrf.model.file == file
|
let hashChange = (!file && hash) || !data && xrf.model.file == file
|
||||||
let hasPos = String(hash).match(/pos=/)
|
let hasPos = String(hash).match(/pos=/)
|
||||||
|
|
||||||
|
|
@ -62,7 +62,7 @@ xrf.navigator.to = (url,flags,loader,data) => {
|
||||||
|
|
||||||
// spec: 2. init metadata inside model for non-SRC data
|
// spec: 2. init metadata inside model for non-SRC data
|
||||||
if( !model.isSRC ){
|
if( !model.isSRC ){
|
||||||
model.scene.traverse( (mesh) => xrf.hashbus.pub.mesh(mesh,model) )
|
model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
|
||||||
}
|
}
|
||||||
|
|
||||||
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
|
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
|
||||||
|
|
|
||||||
|
|
@ -101,7 +101,7 @@ xrf.filter.process = function(frag,scene,opts){
|
||||||
let processed = {}
|
let processed = {}
|
||||||
let extembeds = {}
|
let extembeds = {}
|
||||||
|
|
||||||
// hide external objects temporarely
|
// hide external objects temporarely (prevent them getting filtered too)
|
||||||
scene.traverse( (m) => {
|
scene.traverse( (m) => {
|
||||||
if( m.isSRCExternal ){
|
if( m.isSRCExternal ){
|
||||||
m.traverse( (n) => (extembeds[ n.uuid ] = m) && (m.visible = false) )
|
m.traverse( (n) => (extembeds[ n.uuid ] = m) && (m.visible = false) )
|
||||||
|
|
|
||||||
|
|
@ -4,11 +4,13 @@ xrf.frag.src = function(v, opts){
|
||||||
opts.embedded = v // indicate embedded XR fragment
|
opts.embedded = v // indicate embedded XR fragment
|
||||||
let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts
|
let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts
|
||||||
|
|
||||||
|
if( mesh.isSRC ) return // only embed src once
|
||||||
|
|
||||||
let url = xrf.frag.src.expandURI( mesh, v.string )
|
let url = xrf.frag.src.expandURI( mesh, v.string )
|
||||||
let srcFrag = opts.srcFrag = xrfragment.URI.parse(url)
|
let srcFrag = opts.srcFrag = xrfragment.URI.parse(url)
|
||||||
opts.isLocal = v.string[0] == '#'
|
opts.isLocal = v.string[0] == '#'
|
||||||
opts.isPortal = xrf.frag.src.renderAsPortal(mesh)
|
opts.isPortal = xrf.frag.src.renderAsPortal(mesh)
|
||||||
opts.isSRC = true
|
opts.isSRC = mesh.isSRC = true
|
||||||
|
|
||||||
if(xrf.debug) console.log(`src.js: instancing ${opts.isLocal?'local':'remote'} object ${url}`)
|
if(xrf.debug) console.log(`src.js: instancing ${opts.isLocal?'local':'remote'} object ${url}`)
|
||||||
|
|
||||||
|
|
@ -31,7 +33,6 @@ xrf.frag.src.addModel = (model,url,frag,opts) => {
|
||||||
scene = xrf.frag.src.filterScene(scene,{...opts,frag}) // get filtered scene
|
scene = xrf.frag.src.filterScene(scene,{...opts,frag}) // get filtered scene
|
||||||
if( mesh.material && mesh.userData.src ) mesh.material.visible = false // hide placeholder object
|
if( mesh.material && mesh.userData.src ) mesh.material.visible = false // hide placeholder object
|
||||||
|
|
||||||
//enableSourcePortation(scene)
|
|
||||||
if( opts.isPortal ){
|
if( opts.isPortal ){
|
||||||
// only add remote objects, because
|
// only add remote objects, because
|
||||||
// local scene-objects are already added to scene
|
// local scene-objects are already added to scene
|
||||||
|
|
@ -41,10 +42,11 @@ xrf.frag.src.addModel = (model,url,frag,opts) => {
|
||||||
xrf.frag.src.scale( scene, opts, url ) // scale scene
|
xrf.frag.src.scale( scene, opts, url ) // scale scene
|
||||||
mesh.add(scene)
|
mesh.add(scene)
|
||||||
}
|
}
|
||||||
|
xrf.frag.src.enableSourcePortation({scene,mesh,url,model})
|
||||||
// flag everything isSRC & isXRF
|
// flag everything isSRC & isXRF
|
||||||
mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true })
|
mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true })
|
||||||
|
|
||||||
xrf.emit('parseModel', {...opts, isSRC:true, scene, model})
|
xrf.emit('parseModel', {...opts, isSRC:true, mesh, model}) // this will execute all embedded metadata/fragments e.g.
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf.frag.src.renderAsPortal = (mesh) => {
|
xrf.frag.src.renderAsPortal = (mesh) => {
|
||||||
|
|
@ -53,28 +55,42 @@ xrf.frag.src.renderAsPortal = (mesh) => {
|
||||||
return xrf.hasNoMaterial(mesh) && isPlane
|
return xrf.hasNoMaterial(mesh) && isPlane
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf.frag.src.enableSourcePortation = (src) => {
|
xrf.frag.src.enableSourcePortation = (opts) => {
|
||||||
// show sourceportation clickable plane
|
let {scene,mesh,url,model} = opts
|
||||||
if( srcFrag.href || v.string[0] == '#' ) return
|
if( url[0] == '#' ) return
|
||||||
|
if( !mesh.userData.href ){
|
||||||
|
// show sourceportation clickable sphere for non-portals
|
||||||
let scale = new THREE.Vector3()
|
let scale = new THREE.Vector3()
|
||||||
let size = new THREE.Vector3()
|
let size = new THREE.Vector3()
|
||||||
mesh.getWorldScale(scale)
|
scene.getWorldScale(scale)
|
||||||
new THREE.Box3().setFromObject(src).getSize(size)
|
new THREE.Box3().setFromObject(scene).getSize(size)
|
||||||
const geo = new THREE.SphereGeometry( Math.max(size.x, size.y, size.z) / scale.x, 10, 10 )
|
const geo = new THREE.SphereGeometry( Math.max(size.x, size.y, size.z) * scale.x * 0.6, 10, 10 )
|
||||||
const mat = new THREE.MeshBasicMaterial()
|
const mat = new THREE.MeshBasicMaterial()
|
||||||
mat.transparent = true
|
mat.visible = false // we just use this for collisions
|
||||||
mat.roughness = 0.05
|
const sphere = new THREE.Mesh( geo, mat )
|
||||||
mat.metalness = 1
|
// reparent scene to sphere
|
||||||
mat.opacity = 0
|
let children = mesh.children
|
||||||
const cube = new THREE.Mesh( geo, mat )
|
mesh.children = []
|
||||||
// *TODO* sourceportate?
|
mesh.add(sphere)
|
||||||
return xrf.frag.src
|
children.map( (c) => sphere.add(c) )
|
||||||
|
// make sphere clickable/hoverable
|
||||||
|
let frag = {}
|
||||||
|
xrf.Parser.parse("href", url, frag)
|
||||||
|
sphere.userData = scene.userData // allow rich href notifications/hovers
|
||||||
|
sphere.userData.href = url.replace(/(&)?[-][\w-+\.]+(&)?/g,'&') // remove negative selectors to refer to original scene
|
||||||
|
sphere.userData.XRF = frag
|
||||||
|
xrf.hashbus.pub.fragment("href", {...opts, mesh:sphere, frag, skipXRWG:true, renderer:xrf.renderer, camera:xrf.camera })
|
||||||
|
}
|
||||||
|
for ( let i in scene.userData ) {
|
||||||
|
if( !mesh.userData[i] ) mesh.userData[i] = scene.userData[i] // allow rich href notifications/hovers
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
xrf.frag.src.externalSRC = (url,frag,opts) => {
|
xrf.frag.src.externalSRC = (url,frag,opts) => {
|
||||||
fetch(url, { method: 'HEAD' })
|
fetch(url, { method: 'HEAD' })
|
||||||
.then( (res) => {
|
.then( (res) => {
|
||||||
let mimetype = res.headers.get('Content-type')
|
let mimetype = res.headers.get('Content-type')
|
||||||
|
if(xrf.debug != undefined ) console.log("HEAD "+url+" => "+mimetype)
|
||||||
if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf'
|
if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf'
|
||||||
if( url.replace(/#.*/,'').match(/\.(frag|fs|glsl)$/) ) mimetype = 'x-shader/x-fragment'
|
if( url.replace(/#.*/,'').match(/\.(frag|fs|glsl)$/) ) mimetype = 'x-shader/x-fragment'
|
||||||
if( url.replace(/#.*/,'').match(/\.(vert|vs)$/) ) mimetype = 'x-shader/x-fragment'
|
if( url.replace(/#.*/,'').match(/\.(vert|vs)$/) ) mimetype = 'x-shader/x-fragment'
|
||||||
|
|
@ -138,12 +154,12 @@ xrf.frag.src.scale = function(scene, opts, url){
|
||||||
xrf.frag.src.filterScene = (scene,opts) => {
|
xrf.frag.src.filterScene = (scene,opts) => {
|
||||||
let { mesh, model, camera, renderer, THREE, hashbus, frag} = opts
|
let { mesh, model, camera, renderer, THREE, hashbus, frag} = opts
|
||||||
|
|
||||||
scene = xrf.filter.scene({scene,frag,reparent:true,copyScene: opts.isPortal})
|
scene = xrf.filter.scene({scene,frag,reparent:true}) //,copyScene: opts.isPortal})
|
||||||
|
|
||||||
if( !opts.isLocal ){
|
if( !opts.isLocal ){
|
||||||
scene.traverse( (m) => {
|
scene.traverse( (m) => {
|
||||||
if( m.userData && (m.userData.src || m.userData.href) ) return ; // prevent infinite recursion
|
if( m.userData && (m.userData.src || m.userData.href) ) return ; // prevent infinite recursion
|
||||||
hashbus.pub.mesh(m,{scene,recursive:true}) // cool idea: recursion-depth based distance between face & src
|
xrf.parseModel.metadataInMesh(m,{scene,recursive:true})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
return scene
|
return scene
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,9 @@ let loadAudio = (mimetype) => function(url,opts){
|
||||||
mesh.media = mesh.media || {}
|
mesh.media = mesh.media || {}
|
||||||
mesh.media.audio = { set: (mediafragment,v) => mesh.media.audio[mediafragment] = v }
|
mesh.media.audio = { set: (mediafragment,v) => mesh.media.audio[mediafragment] = v }
|
||||||
|
|
||||||
audioLoader.load( url.replace(/#.*/,''), function( buffer ) {
|
let finalUrl = url.replace(/#.*/,'')
|
||||||
|
if( xrf.debug != undefined ) console.log("GET "+finalUrl)
|
||||||
|
audioLoader.load( finalUrl, function( buffer ) {
|
||||||
|
|
||||||
sound.setBuffer( buffer );
|
sound.setBuffer( buffer );
|
||||||
sound.setLoop(false);
|
sound.setLoop(false);
|
||||||
|
|
@ -68,7 +70,7 @@ let loadAudio = (mimetype) => function(url,opts){
|
||||||
}
|
}
|
||||||
|
|
||||||
let lazySet = {}
|
let lazySet = {}
|
||||||
let mediaFragments = ['t','loop','s']
|
let mediaFragments = ['loop','s','t']
|
||||||
mediaFragments.map( (f) => mesh.media.audio[f] && (lazySet[f] = mesh.media.audio[f]) )
|
mediaFragments.map( (f) => mesh.media.audio[f] && (lazySet[f] = mesh.media.audio[f]) )
|
||||||
mesh.media.audio = sound
|
mesh.media.audio = sound
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,11 @@ xrf.frag.src.type['gltf'] = function( url, opts ){
|
||||||
const Loader = xrf.loaders[ext]
|
const Loader = xrf.loaders[ext]
|
||||||
if( !Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .'+ext
|
if( !Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .'+ext
|
||||||
if( !dir.match("://") ){ // force relative path
|
if( !dir.match("://") ){ // force relative path
|
||||||
dir = dir[0] == './' ? dir : `./${dir}`
|
dir = dir.substr(0,2) == './' ? dir : `./${dir}`
|
||||||
loader = new Loader().setPath( dir )
|
loader = new Loader().setPath( dir )
|
||||||
}else loader = new Loader()
|
}else{
|
||||||
|
loader = new Loader()
|
||||||
|
}
|
||||||
|
|
||||||
loader.load(url, (model) => {
|
loader.load(url, (model) => {
|
||||||
model.isSRC = true
|
model.isSRC = true
|
||||||
|
|
|
||||||
|
|
@ -77,6 +77,19 @@ class Parser {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@:keep
|
||||||
|
public static function getMetaData(): Dynamic {
|
||||||
|
var meta:Dynamic = {
|
||||||
|
title: ["title", "og:title", "dc.title"],
|
||||||
|
description: ["aria-description", "og:description", "dc.description"],
|
||||||
|
author: ["author", "dc.creator"],
|
||||||
|
publisher: ["publisher", "dc.publisher"],
|
||||||
|
website: ["og:site_name", "og:url", "dc.publisher"],
|
||||||
|
license: ["SPDX","dc.rights"],
|
||||||
|
};
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue