update non-euclidian

This commit is contained in:
Leon van Kammen 2023-12-06 12:54:47 +01:00
parent ff05234606
commit 29744defef
10 changed files with 131 additions and 173 deletions

File diff suppressed because one or more lines are too long

View file

@ -34,8 +34,13 @@ window.AFRAME.registerComponent('xrf', {
let blinkControls = document.querySelector('[blink-controls]')
if( blinkControls ){
let els = xrf.getCollisionMeshes()
let invisible = false
els.map( (mesh) => {
mesh.material.visible = false
if( !invisible ){
invisible = mesh.material.clone()
invisible.visible = false
}
mesh.material = invisible
let el = document.createElement("a-entity")
el.setAttribute("xrf-get", mesh.name )
el.setAttribute("class","floor")
@ -124,21 +129,6 @@ window.AFRAME.registerComponent('xrf', {
els.map( (el) => document.querySelector('a-scene').removeChild(el) )
})
// *TODO* workaround no longer needed?
//
// aScene.addEventListener('enter-vr', () => {
// // undo lookup-control shenanigans (which blocks updating camerarig position in VR)
// document.querySelector('[camera]').object3D.parent.matrixAutoUpdate = true
// document.querySelector('[camera]').components['look-controls'].pause() //removeAttribute("look-controls")
// document.querySelector('[camera]').components['wasd-controls'].pause() //removeAttribute("wasd-controls")
// })
// aScene.addEventListener('exit-vr', () => {
// // redo lookup-control shenanigans (which blocks updating camerarig position in VR)
// document.querySelector('[camera]').object3D.parent.matrixAutoUpdate = false
// document.querySelector('[camera]').components['look-controls'].play() //setAttribute("look-controls",'')
// document.querySelector('[camera]').components['wasd-controls'].play() //setAttribute("wasd-controls",'')
// })
AFRAME.XRF.navigator.to(this.data)
.then( (model) => {
let gets = [ ...document.querySelectorAll('[xrf-get]') ]

View file

@ -51,8 +51,7 @@ xrf.drawLineToMesh = (opts) => {
}
}
xrf.addEventListener('navigateLoaded', (opts) => {
xrf.addEventListener('render', (opts) => {
xrf.addEventListener('render', (opts) => {
// update focusline
let {time,model} = opts
if( !xrf.clock ) return
@ -62,5 +61,4 @@ xrf.addEventListener('navigateLoaded', (opts) => {
xrf.focusLine.material.opacity = (0.25 + 0.15*Math.sin( xrf.clock.getElapsedTime() * 3 )) * xrf.focusLine.opacity;
if( xrf.focusLine.opacity > 0.0 ) xrf.focusLine.opacity -= time*0.2
if( xrf.focusLine.opacity < 0.0 ) xrf.focusLine.opacity = 0
})
})

View file

@ -24,7 +24,7 @@ xrf.filter = function(query, cb){
xrf.filter.scene = function(opts){
let {scene,frag} = opts
xrf.filter
scene = xrf.filter
.sort(frag) // get (sorted) filters from XR Fragments
.process(frag,scene,opts) // show/hide things
@ -41,12 +41,14 @@ xrf.filter.sort = function(frag){
return xrf.filter
}
// opts = {copyScene:true} in case you want a copy of the scene (not filter the current scene inplace)
xrf.filter.process = function(frag,scene,opts){
const cleanupKey = (k) => k.replace(/[-\*\/]/g,'')
let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false
const hasName = (m,name,filter) => m.name == name
const hasNameOrTag = (m,name_or_tag,filter) => hasName(m,name_or_tag) ||
String(m.userData['tag']).match( new RegExp("(^| )"+name_or_tag) )
// utility functions
const getOrCloneMaterial = (o) => {
if( o.material ){
@ -70,12 +72,20 @@ xrf.filter.process = function(frag,scene,opts){
let obj
frag.target = firstFilter
scene.traverse( (n) => hasName(n, firstFilter.key,firstFilter) && (obj = n) )
if(obj){
while( scene.children.length > 0 ) scene.children[0].removeFromParent()
console.log("reparent "+firstFilter.key+" "+((opts.copyScene)?"copy":"inplace"))
if(obj ){
obj.position.set(0,0,0)
if( opts.copyScene ){
opts.copyScene = new xrf.THREE.Scene()
opts.copyScene.children[0] = obj
scene = opts.copyScene
}else{
// empty current scene and add obj
while( scene.children.length > 0 ) scene.children[0].removeFromParent()
scene.add( obj )
}
}
}
// then show/hide things based on secondary selectors
// we don't use the XRWG (everything) because we process only the given (sub)scene
@ -88,7 +98,7 @@ xrf.filter.process = function(frag,scene,opts){
// hide external objects temporarely
scene.traverse( (m) => {
if( m.isSRCExternal ){
m.traverse( (n) => (extembeds[ n.uuid ] = m) && (n.visible = false) )
m.traverse( (n) => (extembeds[ n.uuid ] = m) && (m.visible = false) )
}
})
@ -108,6 +118,6 @@ xrf.filter.process = function(frag,scene,opts){
for ( let i in extembeds ) extembeds[i].visible = true
})
return xrf.filter
return scene
}

View file

@ -39,6 +39,13 @@ xrf.frag.href = function(v, opts){
xrf
.emit('href',{click:true,mesh,xrf:v}) // let all listeners agree
.then( () => {
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(v.string)
//if( !file.match(/\./) || file.match(/\.html/) ){
// debugger
// let inIframe
// try { inIframe = window.self !== window.top; } catch (e) { inIframe = true; }
// return inIframe ? window.parent.postMessage({ url: v.string }, '*') : window.open( v.string, '_blank')
//}
const flags = v.string[0] == '#' ? xrf.XRF.PV_OVERRIDE : undefined
let toFrag = xrf.URI.parse( v.string, xrf.XRF.NAVIGATOR | xrf.XRF.PV_OVERRIDE | xrf.XRF.METADATA )
// always commit current location (keep a trail of last positions before we navigate)

View file

@ -7,6 +7,7 @@ xrf.frag.src = function(v, opts){
let url = v.string
let srcFrag = opts.srcFrag = xrfragment.URI.parse(url)
opts.isLocal = v.string[0] == '#'
opts.isPortal = xrf.frag.src.renderAsPortal(mesh)
if( opts.isLocal ){
xrf.frag.src.localSRC(url,srcFrag,opts) // local
@ -16,22 +17,21 @@ xrf.frag.src = function(v, opts){
xrf.frag.src.addModel = (model,url,frag,opts) => {
let {mesh} = opts
let scene = model.scene
xrf.frag.src.filterScene(scene,{...opts,frag}) // filter scene
if( mesh.material ) mesh.material.visible = false // hide placeholder object
scene = xrf.frag.src.filterScene(scene,{...opts,frag}) // get filtered scene
if( mesh.material && !mesh.userData.src ) mesh.material.visible = false // hide placeholder object
//enableSourcePortation(scene)
if( xrf.frag.src.renderAsPortal(mesh) ){
// only add remote objects, because
// local scene-objects are already added to scene
xrf.portalNonEuclidian({...opts,model,scene:model.scene})
if( !opts.isLocal && !mesh.portal.isLens ) xrf.scene.add(scene)
return
if( !opts.isLocal ) xrf.scene.add(scene)
}else{
xrf.frag.src.scale( scene, opts, url ) // scale scene
mesh.add(scene)
xrf.emit('parseModel', {...opts, scene, model})
}
// flag everything isSRC & isXRF
mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true })
xrf.emit('parseModel', {...opts, scene, model})
}
xrf.frag.src.renderAsPortal = (mesh) => {
@ -77,13 +77,16 @@ xrf.frag.src.externalSRC = (url,frag,opts) => {
}
xrf.frag.src.localSRC = (url,frag,opts) => {
let {model,scene} = opts
let {model,mesh,scene} = opts
setTimeout( () => {
if( mesh.material ) mesh.material = mesh.material.clone() // clone, so we can individually highlight meshes
let _model = {
animations: model.animations,
scene: scene.clone()
scene: scene.clone() // *TODO* opts.isPortal ? scene : scene.clone()
}
_model.scenes = [_model.scene]
xrf.frag.src.addModel(_model,url,frag, opts) // current file
},500 )
}
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
@ -121,12 +124,14 @@ xrf.frag.src.scale = function(scene, opts, url){
xrf.frag.src.filterScene = (scene,opts) => {
let { mesh, model, camera, renderer, THREE, hashbus, frag} = opts
xrf.filter.scene({scene,frag,reparent:true})
scene = xrf.filter.scene({scene,frag,reparent:true}) // *TODO* ,copyScene: opts.isPortal})
if( !opts.isLocal ){
scene.traverse( (m) => {
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
})
}
return scene
}

View file

@ -11,8 +11,6 @@ let loadAudio = (mimetype) => function(url,opts){
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
let frag = xrf.URI.parse( url )
return
/* WebAudio: setup context via THREEjs */
if( !camera.listener ){
camera.listener = new THREE.AudioListener();
@ -37,7 +35,8 @@ let loadAudio = (mimetype) => function(url,opts){
}
sound.playXRF = (t) => {
mesh.add(sound)
try{
if( sound.isPlaying && t.y != undefined ) sound.stop()
if( sound.isPlaying && t.y == undefined ) sound.pause()
@ -61,11 +60,10 @@ let loadAudio = (mimetype) => function(url,opts){
}
sound.play()
}
}catch(e){ console.warn(e) }
}
mesh.add(sound)
});
mesh.audio = sound
});
}
let audioMimeTypes = [

View file

@ -0,0 +1,12 @@
let loadHTML = (mimetype) => function(url,opts){
let {mesh,src,camera} = opts
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
let frag = xrf.URI.parse( url )
console.warn("todo: html viewer for src not implemented")
}
let htmlMimeTypes = [
'text/html'
]
htmlMimeTypes.map( (mimetype) => xrf.frag.src.type[ mimetype ] = loadHTML(mimetype) )

View file

@ -8,55 +8,13 @@ xrf.frag.src.type['image/png'] = function(url,opts){
let {mesh,THREE} = opts
let restrictTo3DBoundingBox = mesh.geometry
let renderEquirect = (texture) => {
texture.mapping = THREE.EquirectangularReflectionMapping
texture.needsUpdate = true
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
texture.magFilter = THREE.NearestFilter
texture.minFilter = THREE.NearestFilter
// poor man's equi-portal
mesh.material = new THREE.ShaderMaterial( {
mesh.material = new xrf.THREE.MeshBasicMaterial({
map: null,
transparent: url.match(/(png|gif)/) ? true : false,
side: THREE.DoubleSide,
uniforms: {
pano: { value: texture },
selected: { value: false },
},
vertexShader: `
vec3 portalPosition;
varying vec3 vWorldPosition;
varying float vDistanceToCenter;
varying float vDistance;
void main() {
vDistanceToCenter = clamp(length(position - vec3(0.0, 0.0, 0.0)), 0.0, 1.0);
portalPosition = (modelMatrix * vec4(0.0, 0.0, 0.0, 1.0)).xyz;
vDistance = length(portalPosition - cameraPosition);
vWorldPosition = (modelMatrix * vec4(position, 1.0)).xyz;
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
}
`,
fragmentShader: `
#define RECIPROCAL_PI2 0.15915494
uniform sampler2D pano;
uniform bool selected;
varying float vDistanceToCenter;
varying float vDistance;
varying vec3 vWorldPosition;
void main() {
vec3 direction = normalize(vWorldPosition - cameraPosition );
vec2 sampleUV;
sampleUV.y = clamp(direction.y * 0.5 + 0.5, 0.0, 1.0);
sampleUV.x = atan(direction.z, -direction.x) * -RECIPROCAL_PI2;
sampleUV.x += 0.33; // adjust focus to AFRAME's a-scene.components.screenshot.capture()
vec4 color = texture2D(pano, sampleUV);
vec4 selected_color = selected ? color*vec4(1.5) : color;
gl_FragColor = selected_color;
}
`,
color: 0xFFFFFF,
opacity:1
});
mesh.material.needsUpdate = true
}
let renderImage = (texture) => {
let img = {w: texture.source.data.width, h: texture.source.data.height}
@ -72,27 +30,16 @@ xrf.frag.src.type['image/png'] = function(url,opts){
//}
}
}
//const geometry = new THREE.BoxGeometry();
mesh.material = new xrf.THREE.MeshBasicMaterial({
map: texture,
transparent: url.match(/(png|gif)/) ? true : false,
side: THREE.DoubleSide,
color: 0xFFFFFF,
opacity:1
});
mesh.material.map = texture
mesh.needsUpdate = true
}
let onLoad = (texture) => {
texture.colorSpace = THREE.SRGBColorSpace;
texture.wrapS = THREE.RepeatWrapping;
texture.wrapT = THREE.RepeatWrapping;
// detect equirectangular image
if( texture && texture.source.data.height == texture.source.data.width/2 ){
renderEquirect(texture)
}else{
renderImage(texture)
}
}
new THREE.TextureLoader().load( url, onLoad, null, console.error );

View file

@ -3,6 +3,7 @@
xrf.portalNonEuclidian = function(opts){
let { frag, mesh, model, camera, scene, renderer} = opts
mesh.portal = {
pos: mesh.position.clone(),
posWorld: new xrf.THREE.Vector3(),
@ -55,12 +56,6 @@ xrf.portalNonEuclidian = function(opts){
.filter( (n) => !n.portal ) // filter out (self)references to portals (prevent recursion)
.map(addStencilFeature)
//// add missing lights to make sure things get lit properly
xrf.scene.traverse( (n) => n.isLight &&
!stencilObjects.find( (o) => o.uuid == n.uuid ) &&
stencilObjects.push(n)
)
// put it into a scene (without .add() because it reparents objects) so we can render it separately
mesh.portal.stencilObjects = new xrf.THREE.Scene()
mesh.portal.stencilObjects.children = stencilObjects
@ -68,11 +63,6 @@ xrf.portalNonEuclidian = function(opts){
xrf.portalNonEuclidian.stencilRef += 1 // each portal has unique stencil id
console.log(`enabling portal for object '${mesh.name}' (stencilRef:${mesh.portal.stencilRef})`)
// clone so it won't be affected by other fragments
setTimeout( (mesh) => {
if( mesh.material ) mesh.material = mesh.material.clone() // clone, so we can individually highlight meshes
}, 0, mesh )
return this
}
@ -154,7 +144,7 @@ xrf.portalNonEuclidian = function(opts){
.setupStencilObjects(scene,opts)
// move portal objects to portalposition
mesh.portal.positionObjectsIfNeeded(mesh.portal.posWorld, mesh.scale)
if( mesh.portal.stencilObjects ) mesh.portal.positionObjectsIfNeeded(mesh.portal.posWorld, mesh.scale)
}
xrf.portalNonEuclidian.selectStencil = (n, stencilRef, nested) => {
@ -183,24 +173,25 @@ xrf.portalNonEuclidian.setMaterial = function(mesh){
xrf.addEventListener('parseModel',(opts) => {
const scene = opts.model.scene
// scene.traverse( (n) => n.renderOrder = 10 ) // rendering everything *after* the stencil buffers
//for( let i in scene.children ) scene.children[i].renderOrder = 10 // render outer layers last (worldspheres e.g.)
})
// (re)set portalObject when entering/leaving a portal
xrf.addEventListener('href', (opts) => {
let {mesh,v} = opts
if( opts.click && mesh.portal ){
if( !opts.click ) return
// (re)set portalObjects when entering/leaving a portal
let updatePortals = (opts) => {
xrf.scene.traverse( (n) => {
if( !n.portal ) return
// since we're leaving this portal destination, lets move objects back to the portal
// move objects back to the portal
if( n.portal.isInside ) n.portal.positionObjectsIfNeeded( n.portal.posWorld, n.scale )
n.portal.isInside = false
})
mesh.portal.isInside = true
mesh.portal.positionObjectsIfNeeded() // move objects back to original pos (since we are going there)
if( opts.mesh && opts.mesh.portal && opts.click ){
opts.mesh.portal.isInside = true
opts.mesh.portal.positionObjectsIfNeeded() // move objects back to original pos (since we are teleporting there)
}
})
}
xrf.addEventListener('href', (opts) => opts.click && updatePortals(opts) )
xrf.addEventListener('navigate', updatePortals )
xrf.portalNonEuclidian.stencilRef = 1