|
|
@@ -16,8 +16,6 @@
|
|
|
</div>
|
|
|
|
|
|
<small>
|
|
|
- Battle Damaged Sci-fi Helmet by
|
|
|
- <a href="https://sketchfab.com/theblueturtle_" target="_blank" rel="noopener">theblueturtle_</a><br />
|
|
|
<a href="https://hdrihaven.com/hdri/?h=royal_esplanade" target="_blank" rel="noopener">Royal Esplanade</a> by <a href="https://hdrihaven.com/" target="_blank" rel="noopener">HDRI Haven</a>
|
|
|
</small>
|
|
|
</div>
|
|
|
@@ -42,7 +40,10 @@
|
|
|
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
|
|
|
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
|
|
|
|
|
|
- let camera, scene, renderer;
|
|
|
+ import { Inspector } from 'three/addons/inspector/Inspector.js';
|
|
|
+
|
|
|
+ let camera, scene, renderer, controls;
|
|
|
+ let currentModel;
|
|
|
|
|
|
init().then( render );
|
|
|
|
|
|
@@ -67,25 +68,47 @@
|
|
|
scene.background = texture;
|
|
|
scene.environment = texture;
|
|
|
|
|
|
- } );
|
|
|
+ // model
|
|
|
|
|
|
- const loader = new GLTFLoader().setPath( 'models/gltf/DamagedHelmet/glTF/' );
|
|
|
- loader.load( 'DamagedHelmet.gltf', function ( gltf ) {
|
|
|
+ fetch( 'https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/model-index.json' )
|
|
|
+ .then( response => response.json() )
|
|
|
+ .then( models => {
|
|
|
|
|
|
- scene.add( gltf.scene );
|
|
|
+ const gui = renderer.inspector.createParameters( 'Model' );
|
|
|
+ const modelNames = models.map( m => m.name );
|
|
|
+ const params = { model: 'DamagedHelmet' };
|
|
|
|
|
|
- } );
|
|
|
+ if ( ! modelNames.includes( params.model ) && modelNames.length > 0 ) {
|
|
|
+
|
|
|
+ params.model = modelNames[ 0 ];
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ gui.add( params, 'model', modelNames ).onChange( name => {
|
|
|
+
|
|
|
+ const modelInfo = models.find( m => m.name === name );
|
|
|
+ loadModel( modelInfo );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ const initialModel = models.find( m => m.name === params.model );
|
|
|
+ if ( initialModel ) loadModel( initialModel );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ } );
|
|
|
|
|
|
renderer = new THREE.WebGPURenderer( { antialias: true/*, compatibilityMode: true*/ } );
|
|
|
renderer.setPixelRatio( window.devicePixelRatio );
|
|
|
renderer.setSize( window.innerWidth, window.innerHeight );
|
|
|
renderer.setAnimationLoop( render );
|
|
|
renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
|
+ renderer.inspector = new Inspector();
|
|
|
container.appendChild( renderer.domElement );
|
|
|
|
|
|
await renderer.init();
|
|
|
|
|
|
- const controls = new OrbitControls( camera, renderer.domElement );
|
|
|
+ controls = new OrbitControls( camera, renderer.domElement );
|
|
|
controls.minDistance = 2;
|
|
|
controls.maxDistance = 10;
|
|
|
controls.target.set( 0, 0, - 0.2 );
|
|
|
@@ -95,6 +118,72 @@
|
|
|
|
|
|
}
|
|
|
|
|
|
+ function loadModel( modelInfo ) {
|
|
|
+
|
|
|
+ const variants = modelInfo.variants;
|
|
|
+ const variant = variants[ 'glTF-Binary' ] || variants[ 'glTF' ];
|
|
|
+ const url = `https://raw.githubusercontent.com/KhronosGroup/glTF-Sample-Assets/main/Models/${ modelInfo.name }/${ variant.endsWith( '.glb' ) ? 'glTF-Binary' : 'glTF' }/${ variant }`;
|
|
|
+
|
|
|
+ if ( currentModel ) {
|
|
|
+
|
|
|
+ scene.remove( currentModel );
|
|
|
+ currentModel = null;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ const loader = new GLTFLoader();
|
|
|
+ loader.load( url, async function ( gltf ) {
|
|
|
+
|
|
|
+ currentModel = gltf.scene;
|
|
|
+
|
|
|
+ // wait until the model can be added to the scene without blocking due to shader compilation
|
|
|
+
|
|
|
+ await renderer.compileAsync( currentModel, camera, scene );
|
|
|
+
|
|
|
+ scene.add( currentModel );
|
|
|
+
|
|
|
+ // scale to 1.0
|
|
|
+
|
|
|
+ const box = new THREE.Box3().setFromObject( currentModel );
|
|
|
+ const size = box.getSize( new THREE.Vector3() );
|
|
|
+ const maxSize = Math.max( size.x, size.y, size.z );
|
|
|
+ currentModel.scale.multiplyScalar( 1.0 / maxSize );
|
|
|
+
|
|
|
+ fitCameraToSelection( camera, controls, currentModel );
|
|
|
+
|
|
|
+ } );
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ function fitCameraToSelection( camera, controls, selection, fitOffset = 1.3 ) {
|
|
|
+
|
|
|
+ const box = new THREE.Box3();
|
|
|
+ box.setFromObject( selection );
|
|
|
+
|
|
|
+ const size = box.getSize( new THREE.Vector3() );
|
|
|
+ const center = box.getCenter( new THREE.Vector3() );
|
|
|
+
|
|
|
+ const maxSize = Math.max( size.x, size.y, size.z );
|
|
|
+ const fitHeightDistance = maxSize / ( 2 * Math.atan( Math.PI * camera.fov / 360 ) );
|
|
|
+ const fitWidthDistance = fitHeightDistance / camera.aspect;
|
|
|
+ const distance = fitOffset * Math.max( fitHeightDistance, fitWidthDistance );
|
|
|
+
|
|
|
+ const direction = controls.target.clone().sub( camera.position ).normalize().multiplyScalar( distance );
|
|
|
+
|
|
|
+ controls.maxDistance = distance * 10;
|
|
|
+ controls.minDistance = distance / 10;
|
|
|
+ controls.target.copy( center );
|
|
|
+
|
|
|
+ camera.near = distance / 100;
|
|
|
+ camera.far = distance * 100;
|
|
|
+ camera.updateProjectionMatrix();
|
|
|
+
|
|
|
+ camera.position.copy( controls.target ).sub( direction );
|
|
|
+
|
|
|
+ controls.update();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
function onWindowResize() {
|
|
|
|
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|