2023-06-07 17:42:21 +02:00
|
|
|
// *TODO* use webgl instancing
|
|
|
|
|
|
|
|
|
|
xrf.frag.src = function(v, opts){
|
2023-06-08 17:45:21 +02:00
|
|
|
opts.embedded = v // indicate embedded XR fragment
|
2023-10-12 17:04:46 +02:00
|
|
|
let { mesh, model, camera, scene, renderer, THREE, hashbus, frag} = opts
|
2023-07-04 15:23:10 +02:00
|
|
|
|
2023-09-21 13:05:30 +02:00
|
|
|
let url = v.string
|
2023-11-18 20:50:22 +01:00
|
|
|
let srcFrag = opts.srcFrag = xrfragment.URI.parse(url)
|
|
|
|
|
opts.isLocal = v.string[0] == '#'
|
2023-12-06 12:54:47 +01:00
|
|
|
opts.isPortal = xrf.frag.src.renderAsPortal(mesh)
|
2023-12-12 17:25:21 +01:00
|
|
|
opts.isSRC = true
|
2023-11-14 18:08:19 +01:00
|
|
|
|
2023-11-18 20:50:22 +01:00
|
|
|
if( opts.isLocal ){
|
|
|
|
|
xrf.frag.src.localSRC(url,srcFrag,opts) // local
|
|
|
|
|
}else xrf.frag.src.externalSRC(url,srcFrag,opts) // external file
|
|
|
|
|
}
|
2023-09-21 13:05:30 +02:00
|
|
|
|
2023-11-18 20:50:22 +01:00
|
|
|
xrf.frag.src.addModel = (model,url,frag,opts) => {
|
|
|
|
|
let {mesh} = opts
|
|
|
|
|
let scene = model.scene
|
2023-12-06 12:54:47 +01:00
|
|
|
scene = xrf.frag.src.filterScene(scene,{...opts,frag}) // get filtered scene
|
2024-01-24 18:11:37 +00:00
|
|
|
if( mesh.material && mesh.userData.src ) mesh.material.visible = false // hide placeholder object
|
|
|
|
|
|
2023-11-18 20:50:22 +01:00
|
|
|
//enableSourcePortation(scene)
|
|
|
|
|
if( xrf.frag.src.renderAsPortal(mesh) ){
|
2023-11-25 12:40:38 +01:00
|
|
|
// only add remote objects, because
|
|
|
|
|
// local scene-objects are already added to scene
|
|
|
|
|
xrf.portalNonEuclidian({...opts,model,scene:model.scene})
|
2023-12-06 12:54:47 +01:00
|
|
|
if( !opts.isLocal ) xrf.scene.add(scene)
|
2023-11-18 20:50:22 +01:00
|
|
|
}else{
|
|
|
|
|
xrf.frag.src.scale( scene, opts, url ) // scale scene
|
|
|
|
|
mesh.add(scene)
|
2023-12-06 12:54:47 +01:00
|
|
|
xrf.emit('parseModel', {...opts, scene, model})
|
2023-06-07 17:42:21 +02:00
|
|
|
}
|
2023-11-24 17:32:53 +01:00
|
|
|
// flag everything isSRC & isXRF
|
|
|
|
|
mesh.traverse( (n) => { n.isSRC = n.isXRF = n[ opts.isLocal ? 'isSRCLocal' : 'isSRCExternal' ] = true })
|
2023-11-18 20:50:22 +01:00
|
|
|
}
|
2023-07-04 15:23:10 +02:00
|
|
|
|
2023-11-18 20:50:22 +01:00
|
|
|
xrf.frag.src.renderAsPortal = (mesh) => {
|
2023-11-24 17:32:53 +01:00
|
|
|
// *TODO* should support better isFlat(mesh) check
|
2023-11-18 20:50:22 +01:00
|
|
|
const isPlane = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4
|
2023-11-24 17:32:53 +01:00
|
|
|
return xrf.hasNoMaterial(mesh) && isPlane
|
2023-11-18 20:50:22 +01:00
|
|
|
}
|
2023-10-12 17:04:46 +02:00
|
|
|
|
2023-11-18 20:50:22 +01:00
|
|
|
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 )
|
2024-01-30 09:58:00 +00:00
|
|
|
// *TODO* sourceportate?
|
2023-11-18 20:50:22 +01:00
|
|
|
return xrf.frag.src
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xrf.frag.src.externalSRC = (url,frag,opts) => {
|
|
|
|
|
fetch(url, { method: 'HEAD' })
|
|
|
|
|
.then( (res) => {
|
|
|
|
|
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
|
|
|
|
|
}
|
2023-07-04 15:23:10 +02:00
|
|
|
|
2023-11-18 20:50:22 +01:00
|
|
|
xrf.frag.src.localSRC = (url,frag,opts) => {
|
2023-12-06 12:54:47 +01:00
|
|
|
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() // *TODO* opts.isPortal ? scene : scene.clone()
|
|
|
|
|
}
|
|
|
|
|
_model.scenes = [_model.scene]
|
|
|
|
|
xrf.frag.src.addModel(_model,url,frag, opts) // current file
|
|
|
|
|
},500 )
|
2023-07-05 16:43:07 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-15 19:42:37 +02:00
|
|
|
// scale embedded XR fragments https://xrfragment.org/#scaling%20of%20instanced%20objects
|
|
|
|
|
xrf.frag.src.scale = function(scene, opts, url){
|
|
|
|
|
let { mesh, model, camera, renderer, THREE} = opts
|
2023-09-21 13:05:30 +02:00
|
|
|
|
2023-11-17 09:30:39 +01:00
|
|
|
// remove invisible objects (hidden by selectors) which might corrupt boundingbox size-detection
|
|
|
|
|
let cleanScene = scene.clone()
|
|
|
|
|
if( !cleanScene ) debugger
|
|
|
|
|
let remove = []
|
2023-11-17 15:30:18 +01:00
|
|
|
const notVisible = (n) => !n.visible || (n.material && !n.material.visible)
|
|
|
|
|
cleanScene.traverse( (n) => notVisible(n) && n.children.length == 0 && (remove.push(n)) )
|
2023-11-17 09:30:39 +01:00
|
|
|
remove.map( (n) => n.removeFromParent() )
|
|
|
|
|
|
2023-09-21 13:05:30 +02:00
|
|
|
let restrictTo3DBoundingBox = mesh.geometry
|
|
|
|
|
if( restrictTo3DBoundingBox ){
|
2023-08-04 09:11:26 +02:00
|
|
|
// spec 3 of https://xrfragment.org/#src
|
|
|
|
|
// spec 1 of https://xrfragment.org/#scaling%20of%20instanced%20objects
|
|
|
|
|
// normalize instanced objectsize to boundingbox
|
2023-09-21 13:05:30 +02:00
|
|
|
let sizeFrom = new THREE.Vector3()
|
|
|
|
|
let sizeTo = new THREE.Vector3()
|
|
|
|
|
let empty = new THREE.Object3D()
|
|
|
|
|
new THREE.Box3().setFromObject(mesh).getSize(sizeTo)
|
2023-11-17 09:30:39 +01:00
|
|
|
new THREE.Box3().setFromObject(cleanScene).getSize(sizeFrom)
|
2023-09-21 13:05:30 +02:00
|
|
|
let ratio = sizeFrom.divide(sizeTo)
|
|
|
|
|
scene.scale.multiplyScalar( 1.0 / Math.max(ratio.x, ratio.y, ratio.z));
|
2023-08-04 09:11:26 +02:00
|
|
|
}else{
|
|
|
|
|
// spec 4 of https://xrfragment.org/#src
|
2023-08-08 12:40:38 +02:00
|
|
|
// spec 2 of https://xrfragment.org/#scaling%20of%20instanced%20objects
|
2023-08-04 09:11:26 +02:00
|
|
|
scene.scale.multiply( mesh.scale )
|
2023-07-05 16:43:07 +02:00
|
|
|
}
|
2023-08-04 09:11:26 +02:00
|
|
|
scene.isXRF = model.scene.isSRC = true
|
2023-09-21 13:05:30 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
xrf.frag.src.filterScene = (scene,opts) => {
|
|
|
|
|
let { mesh, model, camera, renderer, THREE, hashbus, frag} = opts
|
2023-11-16 14:50:57 +01:00
|
|
|
|
2023-12-06 12:54:47 +01:00
|
|
|
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
|
|
|
|
|
})
|
|
|
|
|
}
|
2023-11-16 14:50:57 +01:00
|
|
|
return scene
|
2023-07-05 16:43:07 +02:00
|
|
|
}
|
|
|
|
|
|
2023-07-06 16:54:12 +02:00
|
|
|
/*
|
|
|
|
|
* replace the src-mesh with the contents of the src
|
|
|
|
|
*/
|
|
|
|
|
|
2023-07-05 16:43:07 +02:00
|
|
|
xrf.frag.src.type = {}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* mimetype: unknown
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
xrf.frag.src.type['unknown'] = function( url, opts ){
|
|
|
|
|
return new Promise( (resolve,reject) => {
|
2023-10-24 18:18:29 +02:00
|
|
|
reject(`${url} mimetype '${opts.mimetype}' not found or supported (yet)`)
|
2023-07-05 16:43:07 +02:00
|
|
|
})
|
|
|
|
|
}
|