2024-06-12 16:59:49 +02:00
|
|
|
<!DOCTYPE html>
|
|
|
|
<html lang="en">
|
|
|
|
<head>
|
|
|
|
<title><model-viewer> template</title>
|
|
|
|
<meta charset="utf-8" />
|
|
|
|
<meta name="description" content="<model-viewer> template" />
|
|
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
|
|
<link type="text/css" href="./styles.css" rel="stylesheet" />
|
|
|
|
</head>
|
2024-06-13 11:20:14 +02:00
|
|
|
<body style="overflow:hidden; padding:10%">
|
|
|
|
|
|
|
|
<h1><model-viewer> example</h1>
|
|
|
|
<br><br>
|
2024-06-12 16:59:49 +02:00
|
|
|
|
|
|
|
<model-viewer
|
|
|
|
src="https://xrfragment.org/index.glb"
|
|
|
|
environment-image="https://cdn.glitch.global/8e507517-31ff-4aa5-80c1-10ea6de9483d/white_furnace.hdr"
|
2024-06-13 11:20:14 +02:00
|
|
|
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"
|
2024-06-12 16:59:49 +02:00
|
|
|
>
|
2024-06-13 11:20:14 +02:00
|
|
|
<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>
|
2024-06-12 16:59:49 +02:00
|
|
|
</model-viewer>
|
2024-06-13 11:20:14 +02:00
|
|
|
|
2024-06-12 16:59:49 +02:00
|
|
|
<script type="importmap">
|
|
|
|
{
|
|
|
|
"imports": {
|
|
|
|
"three": "https://unpkg.com/three@0.165.0/build/three.module.js",
|
|
|
|
"three/addons/": "https://unpkg.com/three@0.165.0/examples/jsm/"
|
|
|
|
}
|
|
|
|
}
|
|
|
|
</script>
|
|
|
|
<script type="module">
|
|
|
|
import xrf from "./../../../dist/xrfragment.three.module.js";
|
|
|
|
import * as THREE from 'three'; // *TODO* get three handle from mv
|
|
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
|
|
import { FBXLoader } from 'three/addons/loaders/FBXLoader.js';
|
|
|
|
import { USDZLoader } from 'three/addons/loaders/USDZLoader.js';
|
|
|
|
import { OBJLoader } from 'three/addons/loaders/OBJLoader.js';
|
|
|
|
|
2024-06-13 11:20:14 +02:00
|
|
|
console.dir(xrf)
|
|
|
|
|
2024-06-12 16:59:49 +02:00
|
|
|
const mv = document.querySelector("model-viewer");
|
|
|
|
const $url = document.querySelector('#url')
|
|
|
|
|
|
|
|
function getSymbol(name) {
|
|
|
|
let obj = mv;
|
|
|
|
do {
|
|
|
|
const sym = Object.getOwnPropertySymbols(obj).find(
|
|
|
|
(x) => x.description === name
|
|
|
|
);
|
|
|
|
if (sym) {
|
|
|
|
return sym;
|
|
|
|
}
|
|
|
|
} while ((obj = Object.getPrototypeOf(obj)));
|
|
|
|
}
|
|
|
|
|
2024-06-13 11:20:14 +02:00
|
|
|
const setCamera = (x,y,z,opts) => {
|
|
|
|
opts.controls._options.minimumRadius = 0
|
2024-06-12 18:34:16 +02:00
|
|
|
opts.controls._options.maximumRadius = 100000
|
2024-06-13 11:20:14 +02:00
|
|
|
opts.controls._options.maximumFieldOfView = 360
|
|
|
|
console.dir(opts.controls)
|
2024-06-12 18:34:16 +02:00
|
|
|
let spherical = opts.controls.goalSpherical
|
2024-06-13 11:20:14 +02:00
|
|
|
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()
|
|
|
|
}
|
|
|
|
|
|
|
|
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
|
2024-06-12 16:59:49 +02:00
|
|
|
|
2024-06-13 11:20:14 +02:00
|
|
|
if( camera.parent == null ) scene.add(camera) // xr fragments expects in-scene camera
|
2024-06-12 16:59:49 +02:00
|
|
|
|
|
|
|
// enable XR fragments
|
2024-06-13 11:20:14 +02:00
|
|
|
let xrf = opts.xrf.init({
|
|
|
|
...opts,
|
|
|
|
scene,
|
|
|
|
renderer,
|
|
|
|
camera,
|
|
|
|
loaders: { gltf: GLTFLoader, glb: GLTFLoader, fbx: FBXLoader, obj: OBJLoader, usdz: USDZLoader },
|
|
|
|
controls
|
|
|
|
})
|
|
|
|
window.xrf = xrf
|
|
|
|
|
|
|
|
xrf.addEventListener('pos', function(opts){
|
|
|
|
setTimeout( () => {
|
|
|
|
setCamera( camera.position.x, camera.position.y, camera.position.z ,{camera,controls})
|
|
|
|
},1)
|
2024-06-12 16:59:49 +02:00
|
|
|
})
|
2024-06-13 11:20:14 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
})
|
2024-06-12 16:59:49 +02:00
|
|
|
</script>
|
|
|
|
<!-- Loads <model-viewer> for browsers: -->
|
|
|
|
<script
|
|
|
|
type="module"
|
|
|
|
src="https://ajax.googleapis.com/ajax/libs/model-viewer/3.4.0/model-viewer.min.js"
|
|
|
|
></script>
|
|
|
|
</body>
|
|
|
|
</html>
|