/ concept-collection / mesh-converter
Sign in
concept-collection / mesh-converter
mesh-converter / src / MeshView.tsx
115 lines · 3.6 KBCodeBlameHistory
bd1359eShare meshes as self-contained URLsJeremy Magland 1import { useEffect, useMemo } from 'react'
edd4e73Mesh converter: upload, view, and export meshes across formatsJeremy Magland 2import { Canvas } from '@react-three/fiber'
3import { OrbitControls } from '@react-three/drei'
4import * as THREE from 'three'
5import type { MeshData } from './mesh/types'
bd1359eShare meshes as self-contained URLsJeremy Magland 6import { VIEW_MODES } from './viewModes'
7import type { ViewMode } from './viewModes'
9const PLAIN_COLOR = '#8fb4d9'
11function MeshObject({ mesh, mode }: { mesh: MeshData; mode: ViewMode }) {
edd4e73Mesh converter: upload, view, and export meshes across formatsJeremy Magland 12 const geometry = useMemo(() => {
13 const g = new THREE.BufferGeometry()
14 g.setAttribute('position', new THREE.Float32BufferAttribute(mesh.positions, 3))
50876cdConvert real mesh formats with meshio via PyodideJeremy Magland 15 g.setIndex(new THREE.Uint32BufferAttribute(mesh.indices, 1))
17 g.setAttribute('normal', new THREE.Float32BufferAttribute(mesh.normals, 3))
18 } else {
19 g.computeVertexNormals()
20 }
21 if (mesh.colors) {
22 g.setAttribute('color', new THREE.Float32BufferAttribute(mesh.colors, 3))
23 }
24 // Center and scale to a consistent size so the fixed camera always frames it
25 g.center()
26 g.computeBoundingSphere()
27 return g
28 }, [mesh])
30 useEffect(() => () => geometry.dispose(), [geometry])
32 const scale = 1.6 / (geometry.boundingSphere?.radius || 1)
50876cdConvert real mesh formats with meshio via PyodideJeremy Magland 33 const useVertexColors = !!mesh.colors
34 // material settings are baked into the compiled shader; remount materials
35 // when they change so three.js rebuilds the program
36 const matKey = `${mode}-${useVertexColors ? 'vc' : 'plain'}`
38 return (
50876cdConvert real mesh formats with meshio via PyodideJeremy Magland 39 <group scale={scale}>
40 {(mode === 'shaded' || mode === 'both') && (
41 <mesh geometry={geometry}>
42 <meshStandardMaterial
43 key={matKey}
44 vertexColors={useVertexColors}
45 color={useVertexColors ? 'white' : PLAIN_COLOR}
46 roughness={0.55}
47 metalness={0.1}
48 side={THREE.DoubleSide}
49 polygonOffset={mode === 'both'}
50 polygonOffsetFactor={1}
51 polygonOffsetUnits={1}
52 />
53 </mesh>
54 )}
55 {(mode === 'wire' || mode === 'both') && (
56 <mesh geometry={geometry}>
57 <meshBasicMaterial
58 key={matKey}
59 wireframe
60 // over the shaded surface use thin dark lines; standalone
61 // wireframe keeps the mesh's own coloring
62 vertexColors={mode === 'wire' && useVertexColors}
63 color={mode === 'both' ? '#10161f' : useVertexColors ? 'white' : PLAIN_COLOR}
64 transparent={mode === 'both'}
65 opacity={mode === 'both' ? 0.35 : 1}
66 />
67 </mesh>
68 )}
69 {mode === 'points' && (
70 <points geometry={geometry}>
71 <pointsMaterial
72 key={matKey}
73 vertexColors={useVertexColors}
74 color={useVertexColors ? 'white' : PLAIN_COLOR}
75 size={0.02}
76 />
77 </points>
78 )}
79 </group>
bd1359eShare meshes as self-contained URLsJeremy Magland 83export function MeshView({
84 mesh,
85 mode,
86 onModeChange,
87}: {
88 mesh: MeshData
89 mode: ViewMode
90 onModeChange: (mode: ViewMode) => void
91}) {
94 <div className="view-toolbar">
95 {VIEW_MODES.map((m) => (
96 <button
97 key={m.id}
98 className={mode === m.id ? 'active' : ''}
bd1359eShare meshes as self-contained URLsJeremy Magland 99 onClick={() => onModeChange(m.id)}
101 {m.label}
102 </button>
103 ))}
104 </div>
105 <Canvas camera={{ position: [2.6, 1.8, 2.6], fov: 45 }}>
106 <color attach="background" args={['#16181d']} />
107 <ambientLight intensity={0.5} />
108 <directionalLight position={[5, 8, 4]} intensity={1.6} />
109 <directionalLight position={[-4, -3, -6]} intensity={0.4} />
110 <MeshObject mesh={mesh} mode={mode} />
111 <OrbitControls makeDefault />
112 </Canvas>
113 </>
moveopenescclose