2023-06-07 17:42:21 +02:00
|
|
|
// *TODO* use webgl instancing
|
|
|
|
|
|
|
|
|
|
xrf.frag.src = function(v, opts){
|
2023-08-04 09:11:26 +02:00
|
|
|
|
2023-06-08 17:45:21 +02:00
|
|
|
opts.embedded = v // indicate embedded XR fragment
|
2023-09-15 19:42:37 +02:00
|
|
|
let { mesh, model, camera, scene, renderer, THREE, hashbus} = opts
|
2023-07-04 15:23:10 +02:00
|
|
|
|
|
|
|
|
console.log(" └ instancing src")
|
2023-09-21 13:05:30 +02:00
|
|
|
let src;
|
|
|
|
|
let url = v.string
|
|
|
|
|
let frag = xrfragment.URI.parse(url)
|
|
|
|
|
opts.isPlane = mesh.geometry && mesh.geometry.attributes.uv && mesh.geometry.attributes.uv.count == 4
|
|
|
|
|
|
|
|
|
|
const addScene = (scene,url,frag) => {
|
|
|
|
|
src = xrf.frag.src.filterScene(scene,{...opts,frag})
|
|
|
|
|
xrf.frag.src.scale( src, opts, url )
|
|
|
|
|
xrf.frag.src.eval( src, opts, url )
|
|
|
|
|
mesh.add(src)
|
|
|
|
|
if( mesh.material ) mesh.material.visible = false
|
2023-06-07 17:42:21 +02:00
|
|
|
}
|
2023-07-04 15:23:10 +02:00
|
|
|
|
2023-09-21 13:05:30 +02:00
|
|
|
const externalSRC = (url,frag,src) => {
|
|
|
|
|
fetch(url, { method: 'HEAD' })
|
2023-07-05 16:43:07 +02:00
|
|
|
.then( (res) => {
|
2023-09-21 13:05:30 +02:00
|
|
|
console.log(`loading src ${url}`)
|
2023-07-05 16:43:07 +02:00
|
|
|
let mimetype = res.headers.get('Content-type')
|
2023-09-21 13:05:30 +02:00
|
|
|
if( url.replace(/#.*/,'').match(/\.(gltf|glb)$/) ) mimetype = 'gltf'
|
|
|
|
|
//if( url.match(/\.(fbx|stl|obj)$/) ) mimetype =
|
2023-07-05 16:43:07 +02:00
|
|
|
console.log("src mimetype: "+mimetype)
|
2023-09-21 13:05:30 +02:00
|
|
|
opts = { ...opts, src, frag }
|
|
|
|
|
return xrf.frag.src.type[ mimetype ] ? xrf.frag.src.type[ mimetype ](url,opts) : xrf.frag.src.type.unknown(url,opts)
|
2023-07-05 16:43:07 +02:00
|
|
|
})
|
2023-09-21 13:05:30 +02:00
|
|
|
.then( (model) => {
|
|
|
|
|
if( model && model.scene ) addScene(model.scene, url, frag )
|
2023-07-05 16:43:07 +02:00
|
|
|
})
|
2023-09-21 13:05:30 +02:00
|
|
|
.finally( () => { })
|
2023-07-05 16:43:07 +02:00
|
|
|
.catch( console.error )
|
2023-07-04 15:23:10 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-21 13:05:30 +02:00
|
|
|
if( url[0] == "#" ) addScene(scene,url,frag) // current file
|
|
|
|
|
else externalSRC(url,frag) // external file
|
2023-07-05 16:43:07 +02:00
|
|
|
}
|
|
|
|
|
|
2023-09-15 19:42:37 +02:00
|
|
|
xrf.frag.src.eval = function(scene, opts, url){
|
|
|
|
|
let { mesh, model, camera, renderer, THREE, hashbus} = opts
|
2023-07-05 16:43:07 +02:00
|
|
|
if( url ){
|
2023-09-21 13:05:30 +02:00
|
|
|
console.log(mesh.name+" url="+url)
|
|
|
|
|
console.dir(mesh)
|
|
|
|
|
//let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
|
|
|
|
//let frag = xrfragment.URI.parse(url)
|
|
|
|
|
//// scale URI XR Fragments (queries) inside src-value
|
|
|
|
|
//for( var i in frag ){
|
|
|
|
|
// hashbus.pub.fragment(i, Object.assign(opts,{frag, model:{scene},scene}))
|
|
|
|
|
//}
|
|
|
|
|
//hashbus.pub( '#', {scene} ) // execute the default projection '#' (if exist)
|
|
|
|
|
//hashbus.pub( url, {scene} ) // and eval URI XR fragments
|
2023-07-06 15:27:27 +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
|
|
|
|
|
|
|
|
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()
|
|
|
|
|
|
|
|
|
|
// *TODO* exclude invisible objects from boundingbox size-detection
|
|
|
|
|
//
|
|
|
|
|
// THREE.Box3.prototype.expandByObject = (function(expandByObject){
|
|
|
|
|
// return function(object,precise){
|
|
|
|
|
// return expandByObject.call(this, object.visible ? object : empty, precise)
|
|
|
|
|
// }
|
|
|
|
|
// })(THREE.Box3.prototype.expandByObject)
|
|
|
|
|
|
|
|
|
|
new THREE.Box3().setFromObject(mesh).getSize(sizeTo)
|
|
|
|
|
new THREE.Box3().setFromObject(scene).getSize(sizeFrom)
|
|
|
|
|
let ratio = sizeFrom.divide(sizeTo)
|
|
|
|
|
scene.scale.multiplyScalar( 1.0 / Math.max(ratio.x, ratio.y, ratio.z));
|
|
|
|
|
// let factor = getMax(sizeTo) < getMax(sizeFrom) ? getMax(sizeTo) / getMax(sizeFrom) : getMax(sizeFrom) / getMax(sizeTo)
|
|
|
|
|
// scene.scale.multiplyScalar( factor )
|
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-09-21 13:05:30 +02:00
|
|
|
console.log("normal scale: "+url)
|
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
|
|
|
|
|
let obj, src
|
|
|
|
|
console.dir(frag)
|
|
|
|
|
// cherrypicking of object(s)
|
|
|
|
|
if( !frag.q ){
|
|
|
|
|
src = new THREE.Group()
|
|
|
|
|
if( Object.keys(frag).length > 0 ){
|
|
|
|
|
for( var i in frag ){
|
|
|
|
|
if( scene.getObjectByName(i) ){
|
|
|
|
|
src.add( obj = scene.getObjectByName(i).clone(true) )
|
|
|
|
|
}
|
|
|
|
|
hashbus.pub.fragment(i, Object.assign(opts,{frag, model,scene}))
|
|
|
|
|
}
|
|
|
|
|
}else src = scene.clone(true)
|
|
|
|
|
console.dir({name: mesh.name, scene, frag})
|
|
|
|
|
if( src.children.length == 1 ) obj.position.set(0,0,0);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// filtering of objects using query
|
|
|
|
|
if( frag.q ){
|
|
|
|
|
src = scene.clone(true);
|
|
|
|
|
src.isSRC = src.isXRF = true;
|
|
|
|
|
xrf.frag.q.filter(src,frag)
|
|
|
|
|
}
|
|
|
|
|
src.traverse( (m) => {
|
|
|
|
|
src.isSRC = src.isXRF = true;
|
|
|
|
|
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 src
|
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) => {
|
|
|
|
|
reject(`${url} mimetype not supported (yet)`)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* mimetype: model/gltf+json
|
|
|
|
|
*/
|
|
|
|
|
|
2023-09-21 13:05:30 +02:00
|
|
|
xrf.frag.src.type['gltf'] = function( url, opts ){
|
2023-07-05 16:43:07 +02:00
|
|
|
return new Promise( (resolve,reject) => {
|
2023-09-21 13:05:30 +02:00
|
|
|
let {mesh,src} = opts
|
2023-07-05 16:43:07 +02:00
|
|
|
let {urlObj,dir,file,hash,ext} = xrf.parseUrl(url)
|
|
|
|
|
let loader
|
|
|
|
|
|
|
|
|
|
const Loader = xrf.loaders[ext]
|
|
|
|
|
if( !Loader ) throw 'xrfragment: no loader passed to xrfragment for extension .'+ext
|
|
|
|
|
if( !dir.match("://") ){ // force relative path
|
2023-09-21 13:05:30 +02:00
|
|
|
dir = dir[0] == './' ? dir : `./${dir}`
|
2023-07-05 16:43:07 +02:00
|
|
|
loader = new Loader().setPath( dir )
|
|
|
|
|
}else loader = new Loader()
|
|
|
|
|
|
2023-09-21 13:05:30 +02:00
|
|
|
loader.load(url, (model) => {
|
2023-07-05 16:43:07 +02:00
|
|
|
resolve(model)
|
2023-09-21 13:05:30 +02:00
|
|
|
})
|
2023-07-05 16:43:07 +02:00
|
|
|
})
|
2023-06-07 17:42:21 +02:00
|
|
|
}
|
2023-07-06 15:27:27 +02:00
|
|
|
|
|
|
|
|
/*
|
|
|
|
|
* mimetype: image/png
|
|
|
|
|
* mimetype: image/jpg
|
|
|
|
|
* mimetype: image/gif
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
xrf.frag.src.type['image/png'] = function(url,opts){
|
2023-07-06 16:54:12 +02:00
|
|
|
let {mesh} = opts
|
2023-09-21 13:05:30 +02:00
|
|
|
let restrictTo3DBoundingBox = mesh.geometry
|
2023-07-06 16:54:12 +02:00
|
|
|
const texture = new THREE.TextureLoader().load( url );
|
|
|
|
|
texture.colorSpace = THREE.SRGBColorSpace;
|
|
|
|
|
|
|
|
|
|
//const geometry = new THREE.BoxGeometry();
|
|
|
|
|
const material = new THREE.MeshBasicMaterial({
|
|
|
|
|
map: texture,
|
|
|
|
|
transparent: url.match(/(png|gif)/) ? true : false,
|
|
|
|
|
side: THREE.DoubleSide,
|
|
|
|
|
color: 0xFFFFFF,
|
|
|
|
|
opacity:1
|
|
|
|
|
});
|
|
|
|
|
|
2023-08-04 09:11:26 +02:00
|
|
|
// stretch image by pinning uv-coordinates to corners
|
2023-07-06 16:54:12 +02:00
|
|
|
if( mesh.geometry ){
|
|
|
|
|
if( mesh.geometry.attributes.uv ){ // buffergeometries
|
|
|
|
|
let uv = mesh.geometry.attributes.uv;
|
|
|
|
|
// i u v
|
|
|
|
|
uv.setXY(0, 0, 0 )
|
|
|
|
|
uv.setXY(1, 1, 0 )
|
|
|
|
|
uv.setXY(2, 0, 1 )
|
|
|
|
|
uv.setXY(3, 1, 1 )
|
|
|
|
|
}else {
|
|
|
|
|
console.warn("xrfragment: uv's of ${url} might be off for non-buffer-geometries *TODO*")
|
|
|
|
|
//if( geometry.faceVertexUvs ){
|
|
|
|
|
// *TODO* force uv's of dynamically created geometries (in threejs)
|
|
|
|
|
//}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
mesh.material = material
|
2023-07-06 15:27:27 +02:00
|
|
|
}
|
|
|
|
|
xrf.frag.src.type['image/gif'] = xrf.frag.src.type['image/png']
|
|
|
|
|
xrf.frag.src.type['image/jpg'] = xrf.frag.src.type['image/png']
|
|
|
|
|
|