@lark-apaas/coding-steering 0.1.2 → 0.1.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +8 -13
- package/steering/html/skills/rich-interactive-design/SKILL.md +5 -2
- package/steering/html/skills/rich-interactive-design/references/tasks/interactive-scene.md +1 -0
- package/steering/vite-react/skills/react-three-fiber/SKILL.md +214 -0
- package/steering/vite-react/skills/react-three-fiber/references/animation.md +1001 -0
- package/steering/vite-react/skills/react-three-fiber/references/fundamentals.md +877 -0
- package/steering/vite-react/skills/react-three-fiber/references/geometry.md +717 -0
- package/steering/vite-react/skills/react-three-fiber/references/interaction.md +880 -0
- package/steering/vite-react/skills/react-three-fiber/references/lighting.md +668 -0
- package/steering/vite-react/skills/react-three-fiber/references/loaders.md +607 -0
- package/steering/vite-react/skills/react-three-fiber/references/materials.md +601 -0
- package/steering/vite-react/skills/react-three-fiber/references/physics.md +820 -0
- package/steering/vite-react/skills/react-three-fiber/references/postprocessing.md +754 -0
- package/steering/vite-react/skills/react-three-fiber/references/shaders.md +874 -0
- package/steering/vite-react/skills/react-three-fiber/references/textures.md +635 -0
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
# React Three Fiber Loaders
|
|
2
|
+
|
|
3
|
+
## Quick Start
|
|
4
|
+
|
|
5
|
+
```tsx
|
|
6
|
+
import { Canvas } from '@react-three/fiber'
|
|
7
|
+
import { useGLTF, OrbitControls } from '@react-three/drei'
|
|
8
|
+
import { Suspense } from 'react'
|
|
9
|
+
|
|
10
|
+
function Model() {
|
|
11
|
+
const { scene } = useGLTF('/models/robot.glb')
|
|
12
|
+
return <primitive object={scene} />
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export default function App() {
|
|
16
|
+
return (
|
|
17
|
+
<Canvas>
|
|
18
|
+
<ambientLight />
|
|
19
|
+
<Suspense fallback={null}>
|
|
20
|
+
<Model />
|
|
21
|
+
</Suspense>
|
|
22
|
+
<OrbitControls />
|
|
23
|
+
</Canvas>
|
|
24
|
+
)
|
|
25
|
+
}
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## useGLTF (Drei)
|
|
29
|
+
|
|
30
|
+
The recommended way to load GLTF/GLB models.
|
|
31
|
+
|
|
32
|
+
### Basic Usage
|
|
33
|
+
|
|
34
|
+
```tsx
|
|
35
|
+
import { useGLTF } from '@react-three/drei'
|
|
36
|
+
|
|
37
|
+
function Model() {
|
|
38
|
+
const gltf = useGLTF('/models/robot.glb')
|
|
39
|
+
|
|
40
|
+
// gltf contains:
|
|
41
|
+
// - scene: THREE.Group (the main scene)
|
|
42
|
+
// - nodes: Object of named meshes
|
|
43
|
+
// - materials: Object of named materials
|
|
44
|
+
// - animations: Array of AnimationClip
|
|
45
|
+
|
|
46
|
+
return <primitive object={gltf.scene} />
|
|
47
|
+
}
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
### Using Nodes and Materials
|
|
51
|
+
|
|
52
|
+
```tsx
|
|
53
|
+
function Model() {
|
|
54
|
+
const { nodes, materials } = useGLTF('/models/robot.glb')
|
|
55
|
+
|
|
56
|
+
return (
|
|
57
|
+
<group>
|
|
58
|
+
{/* Use specific meshes */}
|
|
59
|
+
<mesh
|
|
60
|
+
geometry={nodes.Body.geometry}
|
|
61
|
+
material={materials.Metal}
|
|
62
|
+
position={[0, 0, 0]}
|
|
63
|
+
/>
|
|
64
|
+
<mesh
|
|
65
|
+
geometry={nodes.Head.geometry}
|
|
66
|
+
material={materials.Plastic}
|
|
67
|
+
position={[0, 1, 0]}
|
|
68
|
+
/>
|
|
69
|
+
</group>
|
|
70
|
+
)
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### With TypeScript (gltfjsx)
|
|
75
|
+
|
|
76
|
+
Generate typed components using gltfjsx:
|
|
77
|
+
|
|
78
|
+
```bash
|
|
79
|
+
npx gltfjsx model.glb --types
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
// Generated component
|
|
84
|
+
import { useGLTF } from '@react-three/drei'
|
|
85
|
+
import { GLTF } from 'three-stdlib'
|
|
86
|
+
|
|
87
|
+
type GLTFResult = GLTF & {
|
|
88
|
+
nodes: {
|
|
89
|
+
Body: THREE.Mesh
|
|
90
|
+
Head: THREE.Mesh
|
|
91
|
+
}
|
|
92
|
+
materials: {
|
|
93
|
+
Metal: THREE.MeshStandardMaterial
|
|
94
|
+
Plastic: THREE.MeshStandardMaterial
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
export function Model(props: JSX.IntrinsicElements['group']) {
|
|
99
|
+
const { nodes, materials } = useGLTF('/model.glb') as GLTFResult
|
|
100
|
+
|
|
101
|
+
return (
|
|
102
|
+
<group {...props} dispose={null}>
|
|
103
|
+
<mesh geometry={nodes.Body.geometry} material={materials.Metal} />
|
|
104
|
+
<mesh geometry={nodes.Head.geometry} material={materials.Plastic} />
|
|
105
|
+
</group>
|
|
106
|
+
)
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
useGLTF.preload('/model.glb')
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Draco Compression
|
|
113
|
+
|
|
114
|
+
```tsx
|
|
115
|
+
import { useGLTF } from '@react-three/drei'
|
|
116
|
+
|
|
117
|
+
function Model() {
|
|
118
|
+
// Drei automatically handles Draco if the file is Draco-compressed
|
|
119
|
+
const { scene } = useGLTF('/models/compressed.glb')
|
|
120
|
+
return <primitive object={scene} />
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
// Or specify Draco decoder path
|
|
124
|
+
useGLTF.setDecoderPath('https://www.gstatic.com/draco/versioned/decoders/1.5.6/')
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
### Preloading
|
|
128
|
+
|
|
129
|
+
```tsx
|
|
130
|
+
import { useGLTF } from '@react-three/drei'
|
|
131
|
+
|
|
132
|
+
// Preload at module level
|
|
133
|
+
useGLTF.preload('/models/robot.glb')
|
|
134
|
+
useGLTF.preload(['/model1.glb', '/model2.glb'])
|
|
135
|
+
|
|
136
|
+
function Model() {
|
|
137
|
+
// Will be instant if preloaded
|
|
138
|
+
const { scene } = useGLTF('/models/robot.glb')
|
|
139
|
+
return <primitive object={scene} />
|
|
140
|
+
}
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### Processing Loaded Model
|
|
144
|
+
|
|
145
|
+
```tsx
|
|
146
|
+
function Model() {
|
|
147
|
+
const { scene } = useGLTF('/models/robot.glb')
|
|
148
|
+
|
|
149
|
+
useEffect(() => {
|
|
150
|
+
// Enable shadows on all meshes
|
|
151
|
+
scene.traverse((child) => {
|
|
152
|
+
if (child.isMesh) {
|
|
153
|
+
child.castShadow = true
|
|
154
|
+
child.receiveShadow = true
|
|
155
|
+
}
|
|
156
|
+
})
|
|
157
|
+
}, [scene])
|
|
158
|
+
|
|
159
|
+
return <primitive object={scene} />
|
|
160
|
+
}
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## useLoader (Core R3F)
|
|
164
|
+
|
|
165
|
+
For loading any Three.js asset.
|
|
166
|
+
|
|
167
|
+
### Basic Texture Loading
|
|
168
|
+
|
|
169
|
+
```tsx
|
|
170
|
+
import { useLoader } from '@react-three/fiber'
|
|
171
|
+
import { TextureLoader } from 'three'
|
|
172
|
+
|
|
173
|
+
function TexturedMesh() {
|
|
174
|
+
const texture = useLoader(TextureLoader, '/textures/color.jpg')
|
|
175
|
+
|
|
176
|
+
return (
|
|
177
|
+
<mesh>
|
|
178
|
+
<boxGeometry />
|
|
179
|
+
<meshStandardMaterial map={texture} />
|
|
180
|
+
</mesh>
|
|
181
|
+
)
|
|
182
|
+
}
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Multiple Assets
|
|
186
|
+
|
|
187
|
+
```tsx
|
|
188
|
+
function MultiTexture() {
|
|
189
|
+
const [colorMap, normalMap, roughnessMap] = useLoader(TextureLoader, [
|
|
190
|
+
'/textures/color.jpg',
|
|
191
|
+
'/textures/normal.jpg',
|
|
192
|
+
'/textures/roughness.jpg',
|
|
193
|
+
])
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<mesh>
|
|
197
|
+
<sphereGeometry args={[1, 64, 64]} />
|
|
198
|
+
<meshStandardMaterial
|
|
199
|
+
map={colorMap}
|
|
200
|
+
normalMap={normalMap}
|
|
201
|
+
roughnessMap={roughnessMap}
|
|
202
|
+
/>
|
|
203
|
+
</mesh>
|
|
204
|
+
)
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
### With Extensions (Draco)
|
|
209
|
+
|
|
210
|
+
```tsx
|
|
211
|
+
import { useLoader } from '@react-three/fiber'
|
|
212
|
+
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
|
|
213
|
+
import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader'
|
|
214
|
+
|
|
215
|
+
function Model() {
|
|
216
|
+
const gltf = useLoader(GLTFLoader, '/model.glb', (loader) => {
|
|
217
|
+
const dracoLoader = new DRACOLoader()
|
|
218
|
+
dracoLoader.setDecoderPath('https://www.gstatic.com/draco/v1/decoders/')
|
|
219
|
+
loader.setDRACOLoader(dracoLoader)
|
|
220
|
+
})
|
|
221
|
+
|
|
222
|
+
return <primitive object={gltf.scene} />
|
|
223
|
+
}
|
|
224
|
+
```
|
|
225
|
+
|
|
226
|
+
### Progress Callback
|
|
227
|
+
|
|
228
|
+
```tsx
|
|
229
|
+
function Model() {
|
|
230
|
+
const gltf = useLoader(
|
|
231
|
+
GLTFLoader,
|
|
232
|
+
'/model.glb',
|
|
233
|
+
undefined, // extensions
|
|
234
|
+
(progress) => {
|
|
235
|
+
console.log(`Loading: ${(progress.loaded / progress.total) * 100}%`)
|
|
236
|
+
}
|
|
237
|
+
)
|
|
238
|
+
|
|
239
|
+
return <primitive object={gltf.scene} />
|
|
240
|
+
}
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
### Preloading (useLoader)
|
|
244
|
+
|
|
245
|
+
```tsx
|
|
246
|
+
import { useLoader } from '@react-three/fiber'
|
|
247
|
+
import { TextureLoader } from 'three'
|
|
248
|
+
|
|
249
|
+
// Preload
|
|
250
|
+
useLoader.preload(TextureLoader, '/textures/color.jpg')
|
|
251
|
+
useLoader.preload(TextureLoader, ['/tex1.jpg', '/tex2.jpg'])
|
|
252
|
+
|
|
253
|
+
// Clear cache
|
|
254
|
+
useLoader.clear(TextureLoader, '/textures/color.jpg')
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
## Drei Loader Hooks
|
|
258
|
+
|
|
259
|
+
### useTexture
|
|
260
|
+
|
|
261
|
+
```tsx
|
|
262
|
+
import { useTexture } from '@react-three/drei'
|
|
263
|
+
|
|
264
|
+
// Single
|
|
265
|
+
const texture = useTexture('/texture.jpg')
|
|
266
|
+
|
|
267
|
+
// Array
|
|
268
|
+
const [color, normal] = useTexture(['/color.jpg', '/normal.jpg'])
|
|
269
|
+
|
|
270
|
+
// Named object (spreads directly to material)
|
|
271
|
+
const textures = useTexture({
|
|
272
|
+
map: '/color.jpg',
|
|
273
|
+
normalMap: '/normal.jpg',
|
|
274
|
+
roughnessMap: '/roughness.jpg',
|
|
275
|
+
})
|
|
276
|
+
<meshStandardMaterial {...textures} />
|
|
277
|
+
|
|
278
|
+
// With callback for configuration
|
|
279
|
+
const texture = useTexture('/texture.jpg', (tex) => {
|
|
280
|
+
tex.wrapS = tex.wrapT = THREE.RepeatWrapping
|
|
281
|
+
tex.repeat.set(4, 4)
|
|
282
|
+
})
|
|
283
|
+
|
|
284
|
+
// Preload
|
|
285
|
+
useTexture.preload('/texture.jpg')
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### useCubeTexture
|
|
289
|
+
|
|
290
|
+
```tsx
|
|
291
|
+
import { useCubeTexture } from '@react-three/drei'
|
|
292
|
+
|
|
293
|
+
function EnvMap() {
|
|
294
|
+
const envMap = useCubeTexture(
|
|
295
|
+
['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
|
|
296
|
+
{ path: '/textures/cube/' }
|
|
297
|
+
)
|
|
298
|
+
|
|
299
|
+
return (
|
|
300
|
+
<mesh>
|
|
301
|
+
<sphereGeometry />
|
|
302
|
+
<meshStandardMaterial envMap={envMap} metalness={1} roughness={0} />
|
|
303
|
+
</mesh>
|
|
304
|
+
)
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
### useEnvironment
|
|
309
|
+
|
|
310
|
+
```tsx
|
|
311
|
+
import { useEnvironment } from '@react-three/drei'
|
|
312
|
+
|
|
313
|
+
// Preset
|
|
314
|
+
const envMap = useEnvironment({ preset: 'sunset' })
|
|
315
|
+
// Presets: apartment, city, dawn, forest, lobby, night, park, studio, sunset, warehouse
|
|
316
|
+
|
|
317
|
+
// Custom HDR file
|
|
318
|
+
const envMap = useEnvironment({ files: '/hdri/studio.hdr' })
|
|
319
|
+
|
|
320
|
+
// Cube map
|
|
321
|
+
const envMap = useEnvironment({
|
|
322
|
+
files: ['px.jpg', 'nx.jpg', 'py.jpg', 'ny.jpg', 'pz.jpg', 'nz.jpg'],
|
|
323
|
+
path: '/textures/',
|
|
324
|
+
})
|
|
325
|
+
```
|
|
326
|
+
|
|
327
|
+
### useVideoTexture
|
|
328
|
+
|
|
329
|
+
```tsx
|
|
330
|
+
import { useVideoTexture } from '@react-three/drei'
|
|
331
|
+
|
|
332
|
+
function VideoPlane() {
|
|
333
|
+
const texture = useVideoTexture('/video.mp4', {
|
|
334
|
+
start: true,
|
|
335
|
+
loop: true,
|
|
336
|
+
muted: true,
|
|
337
|
+
crossOrigin: 'anonymous',
|
|
338
|
+
})
|
|
339
|
+
|
|
340
|
+
return (
|
|
341
|
+
<mesh>
|
|
342
|
+
<planeGeometry args={[16/9 * 2, 2]} />
|
|
343
|
+
<meshBasicMaterial map={texture} toneMapped={false} />
|
|
344
|
+
</mesh>
|
|
345
|
+
)
|
|
346
|
+
}
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### useFont
|
|
350
|
+
|
|
351
|
+
```tsx
|
|
352
|
+
import { useFont, Text3D } from '@react-three/drei'
|
|
353
|
+
|
|
354
|
+
// Preload font
|
|
355
|
+
useFont.preload('/fonts/helvetiker.json')
|
|
356
|
+
|
|
357
|
+
function Text() {
|
|
358
|
+
return (
|
|
359
|
+
<Text3D font="/fonts/helvetiker.json" size={1} height={0.2}>
|
|
360
|
+
Hello
|
|
361
|
+
<meshStandardMaterial color="gold" />
|
|
362
|
+
</Text3D>
|
|
363
|
+
)
|
|
364
|
+
}
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Suspense Patterns
|
|
368
|
+
|
|
369
|
+
### Basic Suspense
|
|
370
|
+
|
|
371
|
+
```tsx
|
|
372
|
+
import { Suspense } from 'react'
|
|
373
|
+
|
|
374
|
+
function Scene() {
|
|
375
|
+
return (
|
|
376
|
+
<Canvas>
|
|
377
|
+
<Suspense fallback={<Loader />}>
|
|
378
|
+
<Model />
|
|
379
|
+
</Suspense>
|
|
380
|
+
</Canvas>
|
|
381
|
+
)
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function Loader() {
|
|
385
|
+
return (
|
|
386
|
+
<mesh>
|
|
387
|
+
<boxGeometry />
|
|
388
|
+
<meshBasicMaterial color="gray" wireframe />
|
|
389
|
+
</mesh>
|
|
390
|
+
)
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Loading Progress UI
|
|
395
|
+
|
|
396
|
+
```tsx
|
|
397
|
+
import { useProgress, Html } from '@react-three/drei'
|
|
398
|
+
|
|
399
|
+
function Loader() {
|
|
400
|
+
const { active, progress, errors, item, loaded, total } = useProgress()
|
|
401
|
+
|
|
402
|
+
return (
|
|
403
|
+
<Html center>
|
|
404
|
+
<div className="loader">
|
|
405
|
+
<div className="progress-bar">
|
|
406
|
+
<div style={{ width: `${progress}%` }} />
|
|
407
|
+
</div>
|
|
408
|
+
<p>{Math.round(progress)}% loaded</p>
|
|
409
|
+
<p>Loading: {item}</p>
|
|
410
|
+
</div>
|
|
411
|
+
</Html>
|
|
412
|
+
)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
function App() {
|
|
416
|
+
return (
|
|
417
|
+
<Canvas>
|
|
418
|
+
<Suspense fallback={<Loader />}>
|
|
419
|
+
<Scene />
|
|
420
|
+
</Suspense>
|
|
421
|
+
</Canvas>
|
|
422
|
+
)
|
|
423
|
+
}
|
|
424
|
+
```
|
|
425
|
+
|
|
426
|
+
### Drei Loader Component
|
|
427
|
+
|
|
428
|
+
```tsx
|
|
429
|
+
import { Loader } from '@react-three/drei'
|
|
430
|
+
|
|
431
|
+
function App() {
|
|
432
|
+
return (
|
|
433
|
+
<>
|
|
434
|
+
<Canvas>
|
|
435
|
+
<Suspense fallback={null}>
|
|
436
|
+
<Scene />
|
|
437
|
+
</Suspense>
|
|
438
|
+
</Canvas>
|
|
439
|
+
{/* HTML loading overlay */}
|
|
440
|
+
<Loader />
|
|
441
|
+
</>
|
|
442
|
+
)
|
|
443
|
+
}
|
|
444
|
+
```
|
|
445
|
+
|
|
446
|
+
## Other Model Formats
|
|
447
|
+
|
|
448
|
+
### OBJ + MTL
|
|
449
|
+
|
|
450
|
+
```tsx
|
|
451
|
+
import { useLoader } from '@react-three/fiber'
|
|
452
|
+
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'
|
|
453
|
+
import { MTLLoader } from 'three/examples/jsm/loaders/MTLLoader'
|
|
454
|
+
|
|
455
|
+
function OBJModel() {
|
|
456
|
+
const materials = useLoader(MTLLoader, '/model.mtl')
|
|
457
|
+
const obj = useLoader(OBJLoader, '/model.obj', (loader) => {
|
|
458
|
+
materials.preload()
|
|
459
|
+
loader.setMaterials(materials)
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
return <primitive object={obj} />
|
|
463
|
+
}
|
|
464
|
+
```
|
|
465
|
+
|
|
466
|
+
### FBX
|
|
467
|
+
|
|
468
|
+
```tsx
|
|
469
|
+
import { useFBX } from '@react-three/drei'
|
|
470
|
+
|
|
471
|
+
function FBXModel() {
|
|
472
|
+
const fbx = useFBX('/model.fbx')
|
|
473
|
+
|
|
474
|
+
return <primitive object={fbx} scale={0.01} />
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
// Preload
|
|
478
|
+
useFBX.preload('/model.fbx')
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### STL
|
|
482
|
+
|
|
483
|
+
```tsx
|
|
484
|
+
import { useLoader } from '@react-three/fiber'
|
|
485
|
+
import { STLLoader } from 'three/examples/jsm/loaders/STLLoader'
|
|
486
|
+
|
|
487
|
+
function STLModel() {
|
|
488
|
+
const geometry = useLoader(STLLoader, '/model.stl')
|
|
489
|
+
|
|
490
|
+
return (
|
|
491
|
+
<mesh geometry={geometry}>
|
|
492
|
+
<meshStandardMaterial color="gray" />
|
|
493
|
+
</mesh>
|
|
494
|
+
)
|
|
495
|
+
}
|
|
496
|
+
```
|
|
497
|
+
|
|
498
|
+
### PLY
|
|
499
|
+
|
|
500
|
+
```tsx
|
|
501
|
+
import { useLoader } from '@react-three/fiber'
|
|
502
|
+
import { PLYLoader } from 'three/examples/jsm/loaders/PLYLoader'
|
|
503
|
+
|
|
504
|
+
function PLYModel() {
|
|
505
|
+
const geometry = useLoader(PLYLoader, '/model.ply')
|
|
506
|
+
|
|
507
|
+
useEffect(() => {
|
|
508
|
+
geometry.computeVertexNormals()
|
|
509
|
+
}, [geometry])
|
|
510
|
+
|
|
511
|
+
return (
|
|
512
|
+
<mesh geometry={geometry}>
|
|
513
|
+
<meshStandardMaterial vertexColors />
|
|
514
|
+
</mesh>
|
|
515
|
+
)
|
|
516
|
+
}
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
## Clone for Multiple Instances
|
|
520
|
+
|
|
521
|
+
```tsx
|
|
522
|
+
import { useGLTF, Clone } from '@react-three/drei'
|
|
523
|
+
|
|
524
|
+
function Trees() {
|
|
525
|
+
const { scene } = useGLTF('/models/tree.glb')
|
|
526
|
+
|
|
527
|
+
return (
|
|
528
|
+
<>
|
|
529
|
+
<Clone object={scene} position={[0, 0, 0]} />
|
|
530
|
+
<Clone object={scene} position={[5, 0, 0]} />
|
|
531
|
+
<Clone object={scene} position={[-5, 0, 0]} />
|
|
532
|
+
<Clone object={scene} position={[0, 0, 5]} scale={1.5} />
|
|
533
|
+
</>
|
|
534
|
+
)
|
|
535
|
+
}
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
## Error Handling
|
|
539
|
+
|
|
540
|
+
```tsx
|
|
541
|
+
import { useGLTF } from '@react-three/drei'
|
|
542
|
+
import { ErrorBoundary } from 'react-error-boundary'
|
|
543
|
+
|
|
544
|
+
function ModelWithErrorHandling() {
|
|
545
|
+
return (
|
|
546
|
+
<ErrorBoundary fallback={<FallbackModel />}>
|
|
547
|
+
<Suspense fallback={<Loader />}>
|
|
548
|
+
<Model />
|
|
549
|
+
</Suspense>
|
|
550
|
+
</ErrorBoundary>
|
|
551
|
+
)
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
function FallbackModel() {
|
|
555
|
+
return (
|
|
556
|
+
<mesh>
|
|
557
|
+
<boxGeometry />
|
|
558
|
+
<meshBasicMaterial color="red" wireframe />
|
|
559
|
+
</mesh>
|
|
560
|
+
)
|
|
561
|
+
}
|
|
562
|
+
```
|
|
563
|
+
|
|
564
|
+
## Asset Caching
|
|
565
|
+
|
|
566
|
+
```tsx
|
|
567
|
+
import { useGLTF, useTexture } from '@react-three/drei'
|
|
568
|
+
|
|
569
|
+
// Assets are automatically cached by URL
|
|
570
|
+
// Same URL = same asset instance
|
|
571
|
+
|
|
572
|
+
function Scene() {
|
|
573
|
+
// These all reference the same cached asset
|
|
574
|
+
const model1 = useGLTF('/model.glb')
|
|
575
|
+
const model2 = useGLTF('/model.glb')
|
|
576
|
+
const model3 = useGLTF('/model.glb')
|
|
577
|
+
|
|
578
|
+
// Clear cache if needed
|
|
579
|
+
useGLTF.clear('/model.glb')
|
|
580
|
+
}
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
## Performance Tips
|
|
584
|
+
|
|
585
|
+
1. **Preload critical assets**: Avoid loading during interaction
|
|
586
|
+
2. **Use Draco compression**: Smaller file sizes
|
|
587
|
+
3. **Use LOD models**: Different detail levels for distance
|
|
588
|
+
4. **Clone instead of reload**: For multiple instances
|
|
589
|
+
5. **Lazy load non-critical**: Load on demand
|
|
590
|
+
|
|
591
|
+
```tsx
|
|
592
|
+
// Preload strategy
|
|
593
|
+
useGLTF.preload('/models/hero.glb') // Critical
|
|
594
|
+
useTexture.preload('/textures/main.jpg') // Critical
|
|
595
|
+
|
|
596
|
+
function LazyModel({ visible }) {
|
|
597
|
+
// Only load when visible
|
|
598
|
+
const { scene } = useGLTF(visible ? '/models/detail.glb' : null)
|
|
599
|
+
return scene ? <primitive object={scene} /> : null
|
|
600
|
+
}
|
|
601
|
+
```
|
|
602
|
+
|
|
603
|
+
## See Also
|
|
604
|
+
|
|
605
|
+
- `r3f-animation` - Playing loaded animations
|
|
606
|
+
- `r3f-textures` - Texture configuration
|
|
607
|
+
- `r3f-materials` - Materials from loaded models
|