your personal website on atproto - mirror blento.app
at button 161 lines 3.9 kB view raw
1<script lang="ts"> 2 import { T, useTask, useThrelte } from '@threlte/core'; 3 import { GLTF, OrbitControls } from '@threlte/extras'; 4 import type { ThrelteGltf } from '@threlte/extras'; 5 import { onMount } from 'svelte'; 6 import { 7 Box3, 8 Group, 9 Vector3, 10 BufferGeometry, 11 Mesh, 12 MeshStandardMaterial, 13 type Object3D 14 } from 'three'; 15 import { STLLoader } from 'three/addons/loaders/STLLoader.js'; 16 import { FBXLoader } from 'three/addons/loaders/FBXLoader.js'; 17 18 let { 19 path, 20 hover = false, 21 modelType = 'gltf' 22 }: { 23 path: string; 24 hover?: boolean; 25 modelType?: 'gltf' | 'stl' | 'fbx'; 26 } = $props(); 27 28 let rotation = $state(0); 29 let group: Group | undefined = $state(); 30 let stlMesh: Mesh | undefined = $state(); 31 let stlLoaded = $state(false); 32 let fbxGroup: Group | undefined = $state(); 33 let fbxLoaded = $state(false); 34 35 const { start, stop } = useTask((delta: number) => { 36 rotation += delta * 0.5; 37 }); 38 39 $effect(() => { 40 if (hover) { 41 start(); 42 } else { 43 stop(); 44 } 45 }); 46 47 const { renderer } = useThrelte(); 48 49 onMount(() => { 50 renderer.toneMappingExposure = 0.7; 51 }); 52 53 // Load STL file 54 $effect(() => { 55 if (modelType === 'stl' && path) { 56 stlLoaded = false; 57 const loader = new STLLoader(); 58 loader.load( 59 path, 60 (geometry: BufferGeometry) => { 61 // Center and scale the geometry 62 geometry.computeBoundingBox(); 63 const box = geometry.boundingBox; 64 if (box) { 65 const size = new Vector3(); 66 box.getSize(size); 67 const center = new Vector3(); 68 box.getCenter(center); 69 70 const maxSize = Math.max(size.x, size.y, size.z); 71 const scale = 1.2 / maxSize; 72 73 geometry.translate(-center.x, -center.y, -center.z); 74 geometry.scale(scale, scale, scale); 75 } 76 77 // Create mesh with a nice material 78 const material = new MeshStandardMaterial({ 79 color: 0x808080, 80 metalness: 0.3, 81 roughness: 0.6 82 }); 83 84 stlMesh = new Mesh(geometry, material); 85 stlLoaded = true; 86 }, 87 undefined, 88 (error) => { 89 console.error('Error loading STL:', error); 90 } 91 ); 92 } 93 }); 94 95 // Load FBX file 96 $effect(() => { 97 if (modelType === 'fbx' && path) { 98 fbxLoaded = false; 99 const loader = new FBXLoader(); 100 loader.load( 101 path, 102 (object: Group) => { 103 // Center and scale the model 104 const box = new Box3().setFromObject(object); 105 const size = box.getSize(new Vector3()); 106 const center = box.getCenter(new Vector3()); 107 108 const maxSize = Math.max(size.x, size.y, size.z); 109 const scale = 1.2 / maxSize; 110 111 object.scale.set(scale, scale, scale); 112 object.position.set(-center.x * scale, -center.y * scale, -center.z * scale); 113 114 fbxGroup = object; 115 fbxLoaded = true; 116 }, 117 undefined, 118 (error) => { 119 console.error('Error loading FBX:', error); 120 } 121 ); 122 } 123 }); 124 125 function handleGltfLoad(gltf: ThrelteGltf) { 126 if (!group) return; 127 128 const box = new Box3().setFromObject(gltf.scene as Object3D); 129 const size = box.getSize(new Vector3()); 130 const center = box.getCenter(new Vector3()); 131 132 let maxSize = Math.max(size.x, size.y, size.z); 133 let scale = 1.2 / maxSize; 134 135 group.scale.set(scale, scale, scale); 136 group.position.set(-center.x * scale, -center.y * scale, -center.z * scale); 137 } 138</script> 139 140<T.PerspectiveCamera makeDefault position={[0, 0, 2.5]} fov={50} near={0.1} far={100}> 141 <OrbitControls enableZoom={false} enablePan={false} /> 142</T.PerspectiveCamera> 143 144<T.DirectionalLight args={[0xffffff, 2]} position={[-1, 1, 1]} /> 145<T.AmbientLight args={[0xffffff, 0.7]} /> 146 147<T.Group rotation={[0.3, rotation + 0.5, 0]}> 148 {#if modelType === 'stl'} 149 {#if stlLoaded && stlMesh} 150 <T is={stlMesh} /> 151 {/if} 152 {:else if modelType === 'fbx'} 153 {#if fbxLoaded && fbxGroup} 154 <T is={fbxGroup} /> 155 {/if} 156 {:else} 157 <T.Group bind:ref={group}> 158 <GLTF url={path} onload={handleGltfLoad} /> 159 </T.Group> 160 {/if} 161</T.Group>