non-euclidian works now

This commit is contained in:
Leon van Kammen 2023-11-18 20:50:22 +01:00
parent 969251eb7d
commit e894eeba68
15 changed files with 194978 additions and 131 deletions

View File

@ -43,7 +43,7 @@
</a-entity>
<a-entity id="home" xrf="index.glb"></a-entity>
<a-plane id="floor" position="0 0 0" rotation="-90 0 0" width="100" height="100" material="visible:false"></a-plane>
<a-plane id="floor" position="0 0 0" rotation="-90 0 0" width="1000" height="1000" material="visible:false"></a-plane>
</a-scene>
<script>

Binary file not shown.

View File

@ -0,0 +1,262 @@
{
"accessors" : [
{
"bufferView" : 0,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 3,
"max" : [
2.000000
],
"min" : [
0.000000
],
"type" : "SCALAR"
},
{
"bufferView" : 1,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 3,
"max" : [
0.000000,
1.000000,
0.000000,
1.000000
],
"min" : [
0.000000,
-8.742278e-008,
0.000000,
-1.000000
],
"type" : "VEC4"
},
{
"bufferView" : 2,
"byteOffset" : 0,
"componentType" : 5123,
"count" : 36,
"max" : [
35
],
"min" : [
0
],
"type" : "SCALAR"
},
{
"bufferView" : 3,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000,
1.000001
],
"min" : [
-1.000000,
-1.000000,
-1.000000
],
"type" : "VEC3"
},
{
"bufferView" : 4,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000,
1.000000
],
"min" : [
-1.000000,
-1.000000,
-1.000000
],
"type" : "VEC3"
},
{
"bufferView" : 5,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
-0.000000,
-0.000000,
1.000000
],
"min" : [
0.000000,
-0.000000,
-1.000000,
-1.000000
],
"type" : "VEC4"
},
{
"bufferView" : 6,
"byteOffset" : 0,
"componentType" : 5126,
"count" : 36,
"max" : [
1.000000,
1.000000
],
"min" : [
-1.000000,
-1.000000
],
"type" : "VEC2"
}
],
"animations" : [
{
"channels" : [
{
"sampler" : 0,
"target" : {
"node" : 0,
"path" : "rotation"
}
}
],
"name" : "animation_AnimatedCube",
"samplers" : [
{
"input" : 0,
"interpolation" : "LINEAR",
"output" : 1
}
]
}
],
"asset" : {
"generator" : "VKTS glTF 2.0 exporter",
"version" : "2.0"
},
"bufferViews" : [
{
"buffer" : 0,
"byteLength" : 12,
"byteOffset" : 0
},
{
"buffer" : 0,
"byteLength" : 48,
"byteOffset" : 12
},
{
"buffer" : 0,
"byteLength" : 72,
"byteOffset" : 60,
"target" : 34963
},
{
"buffer" : 0,
"byteLength" : 432,
"byteOffset" : 132,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 432,
"byteOffset" : 564,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 576,
"byteOffset" : 996,
"target" : 34962
},
{
"buffer" : 0,
"byteLength" : 288,
"byteOffset" : 1572,
"target" : 34962
}
],
"buffers" : [
{
"byteLength" : 1860,
"uri" : "AnimatedCube.bin"
}
],
"images" : [
{
"uri" : "AnimatedCube_BaseColor.png"
},
{
"uri" : "AnimatedCube_MetallicRoughness.png"
}
],
"materials" : [
{
"name" : "AnimatedCube",
"pbrMetallicRoughness" : {
"baseColorTexture" : {
"index" : 0
},
"metallicRoughnessTexture" : {
"index" : 1
}
}
}
],
"meshes" : [
{
"name" : "AnimatedCube",
"primitives" : [
{
"attributes" : {
"NORMAL" : 4,
"POSITION" : 3,
"TANGENT" : 5,
"TEXCOORD_0" : 6
},
"indices" : 2,
"material" : 0,
"mode" : 4
}
]
}
],
"nodes" : [
{
"mesh" : 0,
"name" : "AnimatedCube",
"rotation" : [
0.000000,
-1.000000,
0.000000,
0.000000
]
}
],
"samplers" : [
{}
],
"scene" : 0,
"scenes" : [
{
"nodes" : [
0
]
}
],
"textures" : [
{
"sampler" : 0,
"source" : 0
},
{
"sampler" : 0,
"source" : 1
}
]
}

View File

@ -0,0 +1,160 @@
{
"asset":{
"generator":"Khronos glTF Blender I/O v3.5.30",
"version":"2.0"
},
"scene":0,
"scenes":[
{
"name":"Scene",
"nodes":[
0
]
}
],
"nodes":[
{
"mesh":0,
"name":"AnimatedCube"
}
],
"animations":[
{
"channels":[
{
"sampler":0,
"target":{
"node":0,
"path":"rotation"
}
}
],
"name":"walk",
"samplers":[
{
"input":4,
"interpolation":"LINEAR",
"output":5
}
]
}
],
"materials":[
{
"name":"AnimatedCube",
"pbrMetallicRoughness":{}
}
],
"meshes":[
{
"name":"AnimatedCube",
"primitives":[
{
"attributes":{
"POSITION":0,
"TEXCOORD_0":1,
"NORMAL":2
},
"indices":3,
"material":0
}
]
}
],
"accessors":[
{
"bufferView":0,
"componentType":5126,
"count":36,
"max":[
1,
1,
1.0000009536743164
],
"min":[
-1,
-1,
-1
],
"type":"VEC3"
},
{
"bufferView":1,
"componentType":5126,
"count":36,
"type":"VEC2"
},
{
"bufferView":2,
"componentType":5126,
"count":36,
"type":"VEC3"
},
{
"bufferView":3,
"componentType":5123,
"count":36,
"type":"SCALAR"
},
{
"bufferView":4,
"componentType":5126,
"count":3,
"max":[
2
],
"min":[
0
],
"type":"SCALAR"
},
{
"bufferView":5,
"componentType":5126,
"count":3,
"type":"VEC4"
}
],
"bufferViews":[
{
"buffer":0,
"byteLength":432,
"byteOffset":0,
"target":34962
},
{
"buffer":0,
"byteLength":288,
"byteOffset":432,
"target":34962
},
{
"buffer":0,
"byteLength":432,
"byteOffset":720,
"target":34962
},
{
"buffer":0,
"byteLength":72,
"byteOffset":1152,
"target":34963
},
{
"buffer":0,
"byteLength":12,
"byteOffset":1224
},
{
"buffer":0,
"byteLength":48,
"byteOffset":1236
}
],
"buffers":[
{
"byteLength":1284,
"uri":"data:application/octet-stream;base64,AACAPwAAgL8AAIA/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/7/9/PwAAgD8IAIA/AACAPwAAgD/v/3+/AACAPwAAgD/v/3+/AACAPwAAgL8AAIA/AACAPwAAgL8AAIC/7/9/PwAAgD8IAIA/AACAvwAAgL8AAIA/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIC/AACAvwAAgL8AAIC/AACAPwAAgL8AAIC/AACAvwAAgD8AAIC/AACAPwAAgD/v/3+/AACAPwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AACAvwAAgD8AAIA/7/9/PwAAgD8IAIA/AACAPwAAgD/v/3+/7/9/PwAAgD8IAIA/AACAPwAAgL8AAIA/7/9/PwAAgD8IAIA/AACAvwAAgD8AAIA/AACAvwAAgL8AAIA/AACAvwAAgL8AAIA/AACAvwAAgD8AAIA/AACAvwAAgD8AAIC/AACAPwAAgL8AAIC/AACAvwAAgL8AAIC/AACAvwAAgD8AAIC/AAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAAAAAAIA/AACAvwAAgD8AAAAAAACAPwAAAAAAAAAAAACAvwAAgD8AAIC/AACAPwAAAAAAAACAAACAvwAAgD8AAIC/AAAAAAAAAAAAAIA/AACAPwAAgD8AAAAAAAAAAAAAAAAAAIC/AACAPwAAAAAAAIA/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAIA/AAAAAAAAAAAAAACAAACAvwAAgD8AAIC/AACAPwAAAAAAAACAAAAAAAAAAAAAAIC/AACAPwAAAAAAAACAAAAAAAAAAIAAAIC/AAAAAAAAAAAAAAAAAACAPwAAgD8AAIA/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAIA/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAACAPwAAAAAAAACAAAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AAAAAAAAAIAAAIA/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgL8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAAAAAAAAgD8AAACAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAACAPwAAAAAAAAAAAAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AAAAgAAAAAAAAIA/AACAvwAAAAAAAACAAACAvwAAAAAAAACAAACAvwAAAAAAAACAAAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAAAAAAAAAAAAIC/AAABAAIAAwAEAAUABgAHAAgACQAKAAsADAANAA4ADwAQABEAEgATABQAFQAWABcAGAAZABoAGwAcAB0AHgAfACAAIQAiACMAAAAAAAAAgD8AAABAAAAAAAAAAAAAAACAAACAPwAAAIAAAIC/AAAAAC69OzMAAAAALr27MwAAAIAAAIA/"
}
]
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 688 KiB

BIN
example/assets/index.fbx Normal file

Binary file not shown.

Binary file not shown.

164
example/assets/index.mtl Normal file
View File

@ -0,0 +1,164 @@
# Blender MTL File: 'None'
# Material Count: 16
newmtl None
Ns 500
Ka 0.8 0.8 0.8
Kd 0.8 0.8 0.8
Ks 0.8 0.8 0.8
d 1
illum 2
newmtl environmentMap.002
Ns 359.999993
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl environmentMap.004
Ns 359.999993
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl floorstone.003
Ns 349.173522
Ka 1.000000 1.000000 1.000000
Kd 0.370787 0.337474 0.425554
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 3
map_Ke .
newmtl floorstone.004
Ns 349.173522
Ka 1.000000 1.000000 1.000000
Kd 0.370787 0.337474 0.425554
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 3
map_Ke .
newmtl glass.002
Ns 1000.000000
Ka 0.963543 0.963543 0.963543
Kd 1.000000 1.000000 1.000000
Ks 0.234677 0.234677 0.234677
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 0.143627
illum 6
newmtl hotspot
Ns 359.999993
Ka 1.000000 1.000000 1.000000
Kd 0.800000 0.800000 0.800000
Ks 0.500000 0.500000 0.500000
Ke 0.000000 0.000000 0.000000
Ni 1.000000
d 1.000000
illum 2
newmtl placeholder.003
Ns 854.195174
Ka 0.886364 0.886364 0.886364
Kd 1.000000 1.000000 1.000000
Ks 0.327273 0.327273 0.327273
Ke 0.113646 0.000000 35.199997
Ni 1.450000
d 0.159091
illum 6
newmtl placeholder.004
Ns 854.195174
Ka 0.886364 0.886364 0.886364
Kd 1.000000 1.000000 1.000000
Ks 0.327273 0.327273 0.327273
Ke 0.000000 0.330810 35.199997
Ni 1.450000
d 0.159091
illum 6
newmtl portal_bg.004
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.000000 0.000000
Ks 0.068182 0.068182 0.068182
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 0.222727
illum 6
newmtl portal_bg.005
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.000000 0.000000
Ks 0.068182 0.068182 0.068182
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 0.222727
illum 6
newmtl portal_bg.006
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.000000 0.000000 0.000000
Ks 0.068182 0.068182 0.068182
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 0.222727
illum 6
newmtl text.004
Ns 854.195174
Ka 0.886364 0.886364 0.886364
Kd 1.000000 1.000000 1.000000
Ks 0.327273 0.327273 0.327273
Ke 1.000000 1.000000 1.000000
Ni 1.450000
d 1.000000
illum 3
newmtl text.005
Ns 0.000000
Ka 1.000000 1.000000 1.000000
Kd 0.080782 0.080782 0.080782
Ks 0.068182 0.068182 0.068182
Ke 0.000000 0.000000 0.000000
Ni 1.500000
d 1.000000
illum 3
newmtl text.006
Ns 854.195174
Ka 0.886364 0.886364 0.886364
Kd 0.518619 0.518619 0.518619
Ks 0.327273 0.327273 0.327273
Ke 1.000000 1.000000 1.000000
Ni 1.450000
d 1.000000
illum 3
newmtl vaporgradient.002
Ns 951.107117
Ka 0.827273 0.827273 0.827273
Kd 0.800000 0.800000 0.800000
Ks 0.327273 0.327273 0.327273
Ke 0.000000 0.000000 0.000000
Ni 1.450000
d 1.000000
illum 3
map_Kd .
map_Ke .

194201
example/assets/index.obj Normal file

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -47,13 +47,14 @@ xrf.filter.process = function(frag,scene,opts){
String(m.userData['tag']).match( new RegExp("(^| )"+name_or_tag) )
const cleanupKey = (k) => k.replace(/[-\*]/g,'')
let firstFilter = frag.filters[0].filter.get()
let firstFilter = frag.filters.length ? frag.filters[0].filter.get() : false
let showers = frag.filters.filter( (v) => v.filter.get().show === true )
// spec 2: https://xrfragment.org/doc/RFC_XR_Macros.html#embedding-xr-content-using-src
// reparent scene based on objectname in case it matches a (non-negating) selector
if( opts.reparent && !firstFilter.value && firstFilter.show === true ){
if( opts.reparent && firstFilter && !firstFilter.value && firstFilter.show === true ){
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()

View File

@ -49,6 +49,7 @@ xrf.frag.href = function(v, opts){
}
let selected = mesh.userData.XRF.href.selected = (state) => () => {
console.log("select "+mesh.name)
if( mesh.selected == state ) return // nothing changed
xrf.interactive.objects.map( (o) => {
let newState = o.name == mesh.name ? state : false
@ -58,7 +59,7 @@ xrf.frag.href = function(v, opts){
if( o.material.emissive ){
if( !o.material.emissive.original ) o.material.emissive.original = o.material.emissive.clone()
o.material.emissive.r = o.material.emissive.g = o.material.emissive.b =
newState ? o.material.emissive.original.r + 0.2 : o.material.emissive.original.r
newState ? o.material.emissive.original.r + 0.5 : o.material.emissive.original.r
}
}
})
@ -77,10 +78,11 @@ xrf.frag.href = function(v, opts){
mesh.addEventListener('mouseenter', selected(true) )
mesh.addEventListener('mouseleave', selected(false) )
if( mesh.material ) mesh.material = mesh.material.clone() // clone, so we can individually highlight meshes
// lazy add mesh (because we're inside a recursive traverse)
setTimeout( (mesh) => {
xrf.interactive.add(mesh)
if( mesh.material ) mesh.material = mesh.material.clone() // clone, so we can individually highlight meshes
xrf.emit('interactionReady', {mesh,xrf:v,clickHandler: mesh.userData.XRF.href.exec })
}, 0, mesh )
}

View File

@ -4,73 +4,83 @@ xrf.frag.src = function(v, opts){
opts.embedded = v // indicate embedded XR fragment
let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts
const hasMaterialName = mesh.material && mesh.material.name.length > 0
let url = v.string
let srcFrag = opts.srcFrag = xrfragment.URI.parse(url)
opts.isLocal = v.string[0] == '#'
if( opts.isLocal ){
xrf.frag.src.localSRC(url,srcFrag,opts) // local
}else xrf.frag.src.externalSRC(url,srcFrag,opts) // external file
}
xrf.frag.src.addModel = (model,url,frag,opts) => {
let {mesh} = opts
let scene = model.scene
xrf.frag.src.filterScene(scene,{...opts,frag}) // filter scene
mesh.traverse( (n) => n.isSRC = n.isXRF = true ) // mark everything isSRC & isXRF
if( mesh.material ) mesh.material.visible = false // hide placeholder object
//enableSourcePortation(scene)
if( xrf.frag.src.renderAsPortal(mesh) ){
if( !opts.isLocal ) xrf.scene.add(scene)
return xrf.portalNonEuclidian({...opts,model,scene:model.scene})
}else{
xrf.frag.src.scale( scene, opts, url ) // scale scene
mesh.add(scene)
}
xrf.emit('parseModel', {...opts, scene, model})
}
xrf.frag.src.renderAsPortal = (mesh) => {
const hasTexture = mesh.material && mesh.material.map
const isPlane = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4
const hasLocalSRC = mesh.userData.src != undefined && mesh.userData.src[0] == '#'
const hasMaterialName = mesh.material && mesh.material.name.length > 0
return mesh.geometry && !hasMaterialName && !hasTexture && isPlane
}
let src;
let url = v.string
let vfrag = xrfragment.URI.parse(url)
xrf.frag.src.enableSourcePortation = (src) => {
// show sourceportation clickable plane
if( srcFrag.href || v.string[0] == '#' ) return
let scale = new THREE.Vector3()
let size = new THREE.Vector3()
mesh.getWorldScale(scale)
new THREE.Box3().setFromObject(src).getSize(size)
const geo = new THREE.SphereGeometry( Math.max(size.x, size.y, size.z) / scale.x, 10, 10 )
const mat = new THREE.MeshBasicMaterial()
mat.transparent = true
mat.roughness = 0.05
mat.metalness = 1
mat.opacity = 0
const cube = new THREE.Mesh( geo, mat )
console.log("todo: sourceportate")
return xrf.frag.src
}
// handle non-euclidian planes
if( mesh.geometry && !hasMaterialName && !hasTexture && hasLocalSRC && isPlane ){
return xrf.portalNonEuclidian(opts)
xrf.frag.src.externalSRC = (url,frag,opts) => {
fetch(url, { method: 'HEAD' })
.then( (res) => {
console.log(`loading src ${url}`)
let mimetype = res.headers.get('Content-type')
if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf'
//if( url.match(/\.(fbx|stl|obj)$/) ) mimetype =
opts = { ...opts, frag, mimetype }
return xrf.frag.src.type[ mimetype ] ? xrf.frag.src.type[ mimetype ](url,opts) : xrf.frag.src.type.unknown(url,opts)
})
.then( (model) => {
if( model && model.scene ) xrf.frag.src.addModel(model, url, frag, opts )
})
.finally( () => { })
.catch( console.error )
return xrf.frag.src
}
xrf.frag.src.localSRC = (url,frag,opts) => {
let {model,scene} = opts
let _model = {
animations: model.animations,
scene: scene.clone()
}
const addModel = (model,url,frag) => {
let scene = model.scene
xrf.frag.src.filterScene(scene,{...opts,frag})
xrf.frag.src.scale( scene, opts, url )
//enableSourcePortation(scene)
mesh.add(model.scene)
mesh.traverse( (n) => n.isSRC = n.isXRF = true ) // mark everything SRC
xrf.emit('parseModel', {...opts, scene, model})
if( mesh.material ) mesh.material.visible = false // hide placeholder object
}
const enableSourcePortation = (src) => {
// show sourceportation clickable plane
if( vfrag.href || v.string[0] == '#' ) return
let scale = new THREE.Vector3()
let size = new THREE.Vector3()
mesh.getWorldScale(scale)
new THREE.Box3().setFromObject(src).getSize(size)
const geo = new THREE.SphereGeometry( Math.max(size.x, size.y, size.z) / scale.x, 10, 10 )
const mat = new THREE.MeshBasicMaterial()
mat.transparent = true
mat.roughness = 0.05
mat.metalness = 1
mat.opacity = 0
const cube = new THREE.Mesh( geo, mat )
console.log("todo: sourceportate")
}
const externalSRC = (url,frag,src) => {
fetch(url, { method: 'HEAD' })
.then( (res) => {
console.log(`loading src ${url}`)
let mimetype = res.headers.get('Content-type')
if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf'
//if( url.match(/\.(fbx|stl|obj)$/) ) mimetype =
opts = { ...opts, src, frag, mimetype }
return xrf.frag.src.type[ mimetype ] ? xrf.frag.src.type[ mimetype ](url,opts) : xrf.frag.src.type.unknown(url,opts)
})
.then( (model) => {
if( model && model.scene ) addModel(model, url, frag )
})
.finally( () => { })
.catch( console.error )
}
if( url[0] == "#" ){
let _model = {
animations: model.animations,
scene: scene.clone()
}
_model.scenes = [_model.scene]
addModel(_model,url,vfrag) // current file
}else externalSRC(url,vfrag) // external file
_model.scenes = [_model.scene]
xrf.frag.src.addModel(_model,url,frag, opts) // current file
}
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects

View File

@ -7,16 +7,15 @@ xrf.portalNonEuclidian = function(opts){
mesh.portal = {
pos: mesh.position.clone(),
posWorld: new xrf.THREE.Vector3(),
posWorldCamera: new xrf.THREE.Vector3(),
stencilRef: xrf.portalNonEuclidian.stencilRef,
needUpdate: false
needUpdate: false,
stencilObject: false,
cameraDirection: new THREE.Vector3(),
cameraPosition: new THREE.Vector3(),
raycaster: new THREE.Raycaster()
}
// turn mesh into stencilplane
xrf
.portalNonEuclidian
.setMaterial(mesh)
.getWorldPosition(mesh.portal.posWorld)
// allow objects to flip between original and stencil position (which puts them behind stencilplane)
const addStencilFeature = (n) => {
if( n.stencil ) return n // run once
@ -31,59 +30,106 @@ xrf.portalNonEuclidian = function(opts){
return n
}
// collect related objects to render inside stencilplane
let stencilObject = scene.getObjectByName( mesh.userData.src.substr(1) ) // strip #
if( !stencilObject ) return console.warn(`no objects were found (src:${mesh.userData.src}) for (portal)object name '${mesh.name}'`)
let stencilObjects = [mesh,stencilObject]
stencilObjects = stencilObjects
.filter( (n) => !n.portal ) // filter out (self)references to portals (prevent recursion)
.map(addStencilFeature)
this.setupStencilObjects = (scene,opts) => {
// collect related objects to render inside stencilplane
let stencilObject = opts.srcFrag.target ? scene.getObjectByName( opts.srcFrag.target.key ) : scene // strip #
if( !stencilObject ) return console.warn(`no objects were found (src:${mesh.userData.src}) for (portal)object name '${mesh.name}'`)
if( !opts.isLocal ) stencilObject.visible = false
let stencilObjects = [mesh,stencilObject]
stencilObjects = stencilObjects
.filter( (n) => !n.portal ) // filter out (self)references to portals (prevent recursion)
.map(addStencilFeature)
mesh.portal.stencilObject = stencilObject
//// 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)
)
//// 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
// 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
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
}
// enable the stencil-material of the stencil objects to prevent stackoverflow (portal in portal rendering)
const showPortal = (n,show) => {
if( n.portal ) n.visible = show
return true
}
mesh.onBeforeRender = function(renderer, scene, camera, geometry, material, group ){
mesh.visible = false
}
mesh.onAfterRender = function(renderer, scene, camera, geometry, material, group ){
mesh.portal.needUpdate = true
}
this.setupListeners = () => {
xrf.addEventListener('renderPost', (opts) => {
if( mesh.portal && mesh.portal.needUpdate ){
let {scene,camera,time,render} = opts
let stencilRef = mesh.portal.stencilRef
let newPos = mesh.portal.posWorld
let newScale = mesh.scale
mesh.visible = true
mesh.portal.stencilObjects.traverse( (n) => showPortal(n,false) && n.stencil && n.stencil(stencilRef,newPos,newScale) )
renderer.autoClear = false
renderer.clearDepth()
render( mesh.portal.stencilObjects, camera )
mesh.portal.stencilObjects.traverse( (n) => showPortal(n,true) && n.stencil && (n.stencil(0)) )
mesh.portal.needUpdate = false
mesh.onBeforeRender = function(renderer, scene, camera, geometry, material, group ){
}
})
xrf.portalNonEuclidian.stencilRef += 1 // each portal has unique stencil id
console.log(`enabling portal for object '${mesh.name}' (stencilRef:${mesh.portal.stencilRef})`)
mesh.onAfterRender = function(renderer, scene, camera, geometry, material, group ){
mesh.portal.needUpdate = true
}
xrf.addEventListener('renderPost', (opts) => {
if( mesh.portal && mesh.portal.needUpdate && mesh.portal.stencilObjects ){
let {scene,camera,time,render} = opts
let stencilRef = mesh.portal.stencilRef
let newPos = mesh.portal.posWorld
let stencilObject = mesh.portal.stencilObject
let newScale = mesh.scale
let cameraDirection = mesh.portal.cameraDirection
let cameraPosition = mesh.portal.cameraPosition
let raycaster = mesh.portal.raycaster
// init
if( !opts.isLocal ) stencilObject.visible = true
mesh.portal.stencilObjects.traverse( (n) => showPortal(n,false) && n.stencil && n.stencil(stencilRef,newPos,newScale) )
renderer.autoClear = false
renderer.clearDepth()
// render
render( mesh.portal.stencilObjects, camera )
// de-init
mesh.portal.stencilObjects.traverse( (n) => showPortal(n,true) && n.stencil && (n.stencil(0)) )
if( !opts.isLocal ) stencilObject.visible = false
// trigger href upon camera collide
if( mesh.userData.XRF.href ){
raycaster.far = 0.3
let cam = xrf.camera.getCam ? xrf.camera.getCam() : camera
cam.getWorldPosition(cameraPosition)
cam.getWorldDirection(cameraDirection)
raycaster.set(cameraPosition, cameraDirection )
intersects = raycaster.intersectObjects([mesh], false)
if (intersects.length > 0 && !mesh.portal.teleporting ){
mesh.portal.teleporting = true
mesh.userData.XRF.href.exec()
setTimeout( () => mesh.portal.teleporting = false, 500) // dont flip back and forth
}
}
mesh.portal.needUpdate = false
}
})
return this
}
// turn mesh into stencilplane
xrf
.portalNonEuclidian
.setMaterial(mesh)
.getWorldPosition(mesh.portal.posWorld)
this
.setupListeners()
.setupStencilObjects(scene,opts)
}
@ -98,15 +144,16 @@ xrf.portalNonEuclidian.selectStencil = (n, stencilRef, nested) => {
xrf.portalNonEuclidian.setMaterial = function(mesh){
mesh.material = new xrf.THREE.MeshBasicMaterial({ color: 'white' });
mesh.material.depthWrite = true;
mesh.material.depthWrite = false;
mesh.material.depthTest = false;
mesh.material.colorWrite = false;
mesh.material.stencilWrite = true;
mesh.material.stencilRef = xrf.portalNonEuclidian.stencilRef;
mesh.renderOrder = xrf.portalNonEuclidian.stencilRef;
mesh.material.stencilFunc = THREE.AlwaysStencilFunc;
mesh.material.stencilZPass = THREE.ReplaceStencilOp;
mesh.material.stencilFail = THREE.ReplaceStencilOp;
mesh.material.stencilZFail = THREE.ReplaceStencilOp;
//mesh.material.stencilFail = THREE.ReplaceStencilOp;
//mesh.material.stencilZFail = THREE.ReplaceStencilOp;
return mesh
}

View File

@ -53,7 +53,7 @@ test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("c"
console.assert( test(), {scn,reason:`objectname: #a&-b `})
scn = filterScene("#-b&b")
test = () => scn.visible("a",false) && scn.visible("b",true) && scn.visible("c",true)
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
console.assert( test(), {scn,reason:`objectname: #-b&b `})
scn = filterScene("#-c")
@ -66,23 +66,23 @@ console.assert( test(), {scn,reason:`propertyfilter: #score `})
scn = filterScene("#score=>1")
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
console.assert( test(), {scn,reason:`propertyfilter: #score`})
console.assert( test(), {scn,reason:`propertyfilter: #score>=1`})
scn = filterScene("#score=2")
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("c",true)
console.assert( test(), {scn,reason:`propertyfilter: #score`})
console.assert( test(), {scn,reason:`propertyfilter: #score=2`})
scn = filterScene("#score=>3")
test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("c",false)
console.assert( test(), {scn,reason:`propertyfilter: #score`})
console.assert( test(), {scn,reason:`propertyfilter: #score=>3`})
scn = filterScene("#-score=>1")
test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("c",false)
console.assert( test(), {scn,reason:`propertyfilter: #-score`})
console.assert( test(), {scn,reason:`propertyfilter: #-score=>1`})
scn = filterScene("#-score=>1&c")
test = () => scn.visible("a",true) && scn.visible("b",true) && scn.visible("b",false,true) && scn.visible("c",true)
console.assert( test(), {scn,reason:`propertyfilter: #-score`})
console.assert( test(), {scn,reason:`propertyfilter: #-score=>1&c`})
scn = filterScene("#-foo")
test = () => scn.visible("a",true) && scn.visible("b",false) && scn.visible("b",false)
@ -100,6 +100,6 @@ scn = filterScene("#-b&-foo&bar&flop&-bar&flop")
test = () => scn.visible("a",true) && scn.visible("b",false,true) && scn.visible("c",true)
console.assert( test(), {scn,reason:`tagfilter: #-b&-foo&bar&flop&-bar&flop"`})
scn = filterScene("#-price&price=>4")
scn = filterScene("#-price&price=>5")
test = () => scn.visible("a",false,true) && scn.visible("b",true) && scn.visible("c",true)
console.assert( test(), {scn,reason:`tagfilter: #-price&price=>4"`})
console.assert( test(), {scn,reason:`tagfilter: #-price&price=>5"`})