feat/model-viewer: work in progress [might break]
This commit is contained in:
parent
057dc5d7ea
commit
720b17f75c
|
@ -7,24 +7,32 @@
|
|||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<link type="text/css" href="./styles.css" rel="stylesheet" />
|
||||
</head>
|
||||
<style type="text/css">
|
||||
#url,
|
||||
.btn { display:inline-block;width:40px; height:40px; background:#EEE; font-weight:bold; border-radius:5px; margin-right:5px; text-align:center; line-height:41px; }
|
||||
#url { width:100%; max-width: calc(100% - 140px); text-align:left; padding:0px 10px; font-weight:normal; }
|
||||
<body style="overflow:hidden; padding:10%">
|
||||
|
||||
<h1><model-viewer> example</h1>
|
||||
<br><br>
|
||||
|
||||
</style>
|
||||
<body style="overflow:hidden">
|
||||
<!-- <model-viewer> HTML element -->
|
||||
<model-viewer
|
||||
src="https://xrfragment.org/index.glb"
|
||||
environment-image="https://cdn.glitch.global/8e507517-31ff-4aa5-80c1-10ea6de9483d/white_furnace.hdr"
|
||||
camera-controls interaction-prompt="none"
|
||||
style="width:100%; height:99vh"
|
||||
interaction-prompt="auto" camera-controls field-of-view="90deg" max-field-of-view="180deg" min-field-of-view="70deg" zoom-sensitivity="0.01" ar
|
||||
style="width:80%; height:50vh"
|
||||
>
|
||||
<b class="btn" id="back"><</b>
|
||||
<b class="btn" id="forward">></b>
|
||||
<input id="url" value=""></b>
|
||||
<div id="navigator">
|
||||
<b class="btn" id="back"><</b>
|
||||
<b class="btn" id="forward">></b>
|
||||
<input id="url" value=""></b>
|
||||
</div>
|
||||
<style type="text/css">
|
||||
#url,
|
||||
.btn { display:inline-block; cursor:pointer; width:40px; height:40px; background:#EEE; margin-right:5px; text-align:center; line-height:41px }
|
||||
#url { width:100%; max-width: calc(100% - 142px); text-align:left; padding:0px 10px; font-weight:normal; transform: translateY(-2px) }
|
||||
.btn { font-weight:bold; margin-top:10px }
|
||||
#navigator { margin-left:10px; margin-top:10px; }
|
||||
model-viewer,.btn,#url { border:2px solid #999; border-radius:5px }
|
||||
</style>
|
||||
</model-viewer>
|
||||
|
||||
<script type="importmap">
|
||||
{
|
||||
"imports": {
|
||||
|
@ -41,6 +49,8 @@
|
|||
import { USDZLoader } from 'three/addons/loaders/USDZLoader.js';
|
||||
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
|
||||
|
||||
console.dir(xrf)
|
||||
|
||||
const mv = document.querySelector("model-viewer");
|
||||
const $url = document.querySelector('#url')
|
||||
|
||||
|
@ -56,32 +66,66 @@
|
|||
} while ((obj = Object.getPrototypeOf(obj)));
|
||||
}
|
||||
|
||||
mv.addEventListener("load", () => {
|
||||
const opts = {
|
||||
THREE,
|
||||
scene: mv[getSymbol('scene')],
|
||||
renderer: mv[getSymbol("renderer")].threeRenderer,
|
||||
controls: mv[getSymbol("controls")],
|
||||
camera: mv[getSymbol("scene")].getCamera()
|
||||
}
|
||||
|
||||
opts.controls._options.minimumRadius = 1
|
||||
const setCamera = (x,y,z,opts) => {
|
||||
opts.controls._options.minimumRadius = 0
|
||||
opts.controls._options.maximumRadius = 100000
|
||||
|
||||
opts.controls._options.maximumFieldOfView = 360
|
||||
console.dir(opts.controls)
|
||||
let spherical = opts.controls.goalSpherical
|
||||
spherical.setFromCartesianCoords(0,0,0)
|
||||
opts.controls.moveCamera()
|
||||
opts.control.update = (e) => e
|
||||
spherical.setFromVector3( new THREE.Vector3(x,y,z) ) // setFromCartesianCoords(x,y,z)
|
||||
//spherical.phi = Math.PI / 2; // always look in horizontal direction
|
||||
// const direction = new THREE.Vector3();
|
||||
// direction.setFromSphericalCoords(1, spherical.phi, spherical.theta);
|
||||
// direction.applyEuler(cameraRotation);
|
||||
// opts.controls.moveCamera()
|
||||
}
|
||||
|
||||
$url.value = mv.src
|
||||
window.opts = opts
|
||||
const opts = {
|
||||
xrf,
|
||||
THREE,
|
||||
}
|
||||
|
||||
const onLoad = (opts) => function(){
|
||||
const scene = mv[getSymbol('scene')]
|
||||
const renderer = mv[getSymbol("renderer")].threeRenderer
|
||||
const controls = mv[getSymbol("controls")]
|
||||
const camera = mv[getSymbol("scene")].getCamera()
|
||||
window.opts = opts
|
||||
|
||||
if( camera.parent == null ) scene.add(camera) // xr fragments expects in-scene camera
|
||||
|
||||
// enable XR fragments
|
||||
let XRF = xrf.init({...opts,
|
||||
loaders: { gltf: GLTFLoader, glb: GLTFLoader, fbx: FBXLoader, obj: OBJLoader, usdz: USDZLoader }
|
||||
let xrf = opts.xrf.init({
|
||||
...opts,
|
||||
scene,
|
||||
renderer,
|
||||
camera,
|
||||
loaders: { gltf: GLTFLoader, glb: GLTFLoader, fbx: FBXLoader, obj: OBJLoader, usdz: USDZLoader },
|
||||
controls
|
||||
})
|
||||
console.dir(XRF)
|
||||
});
|
||||
window.xrf = xrf
|
||||
|
||||
xrf.addEventListener('pos', function(opts){
|
||||
setTimeout( () => {
|
||||
setCamera( camera.position.x, camera.position.y, camera.position.z ,{camera,controls})
|
||||
},1)
|
||||
})
|
||||
|
||||
let url = mv.src
|
||||
$url.value = url
|
||||
// mark current loaded scene for deletion by xrfragment library (except camera)
|
||||
scene.traverse( (o) => o.isXRF = o.id != camera.id )
|
||||
// now we re-insert the model via the XR Fragments lib (so it will parse the XRF metadata)
|
||||
xrf.navigator.to(url)
|
||||
scene.visible = true
|
||||
//xrf.loadModel( scene._currentGLTF, url, false)
|
||||
}
|
||||
|
||||
mv.addEventListener("load", onLoad(opts) )
|
||||
mv.addEventListener('before-render', function(){
|
||||
const scene = mv[getSymbol('scene')]
|
||||
scene.visible = false
|
||||
})
|
||||
</script>
|
||||
<!-- Loads <model-viewer> for browsers: -->
|
||||
<script
|
||||
|
|
|
@ -48,6 +48,39 @@ xrf.parseModel = function(model,url){
|
|||
xrf.emit('parseModel',{model,url,file})
|
||||
}
|
||||
|
||||
xrf.loadModel = function(model,url,noadd){
|
||||
let URI = xrfragment.URI.toAbsolute( xrf.navigator.URI, url )
|
||||
let {directory,file,fragment,fileExt} = URI;
|
||||
model.file = URI.file
|
||||
xrf.model = model
|
||||
|
||||
if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model
|
||||
|
||||
if(xrf.debug ) model.animations.map( (a) => console.log("anim: "+a.name) )
|
||||
|
||||
// spec: 1. generate the XRWG
|
||||
xrf.XRWG.generate({model,scene:model.scene})
|
||||
|
||||
// spec: 2. init metadata inside model for non-SRC data
|
||||
if( !model.isSRC ){
|
||||
model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
|
||||
}
|
||||
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
|
||||
const defaultFragment = xrf.frag.defaultPredefinedViews({model,scene:model.scene})
|
||||
// spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
|
||||
let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments
|
||||
|
||||
if( !noadd ) xrf.add( model.scene )
|
||||
|
||||
// only change url when loading *another* file
|
||||
fragment = fragment || defaultFragment || ''
|
||||
xrf.navigator.pushState( URI.external ? URI.URN + URI.file : URI.file, fragment.replace(/^#/,'') )
|
||||
//if( fragment ) xrf.navigator.updateHash(fragment)
|
||||
|
||||
xrf.emit('navigateLoaded',{url,model})
|
||||
}
|
||||
|
||||
|
||||
xrf.parseModel.metadataInMesh = (mesh,model) => {
|
||||
if( mesh.userData ){
|
||||
let frag = {}
|
||||
|
|
|
@ -70,34 +70,7 @@ xrf.navigator.to = (url,flags,loader,data) => {
|
|||
|
||||
loader = loader || new Loader().setPath( URI.URN )
|
||||
const onLoad = (model) => {
|
||||
|
||||
model.file = URI.file
|
||||
xrf.model = model
|
||||
|
||||
if( !model.isXRF ) xrf.parseModel(model,url.replace(directory,"")) // this marks the model as an XRF model
|
||||
|
||||
if(xrf.debug ) model.animations.map( (a) => console.log("anim: "+a.name) )
|
||||
|
||||
// spec: 1. generate the XRWG
|
||||
xrf.XRWG.generate({model,scene:model.scene})
|
||||
|
||||
// spec: 2. init metadata inside model for non-SRC data
|
||||
if( !model.isSRC ){
|
||||
model.scene.traverse( (mesh) => xrf.parseModel.metadataInMesh(mesh,model) )
|
||||
}
|
||||
// spec: 1. execute the default predefined view '#' (if exist) (https://xrfragment.org/#predefined_view)
|
||||
const defaultFragment = xrf.frag.defaultPredefinedViews({model,scene:model.scene})
|
||||
// spec: predefined view(s) & objects-of-interest-in-XRWG from URI (https://xrfragment.org/#predefined_view)
|
||||
let frag = xrf.hashbus.pub( url, model) // and eval URI XR fragments
|
||||
|
||||
xrf.add( model.scene )
|
||||
|
||||
// only change url when loading *another* file
|
||||
fragment = fragment || defaultFragment || ''
|
||||
xrf.navigator.pushState( URI.external ? URI.URN + URI.file : URI.file, fragment.replace(/^#/,'') )
|
||||
//if( fragment ) xrf.navigator.updateHash(fragment)
|
||||
|
||||
xrf.emit('navigateLoaded',{url,model})
|
||||
xrf.loadModel(model,url)
|
||||
resolve(model)
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue