create-threejs-game 1.0.0
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/README.md +97 -0
- package/bin/cli.js +370 -0
- package/package.json +29 -0
- package/template/.claude/skills/threejs-animation/SKILL.md +552 -0
- package/template/.claude/skills/threejs-fundamentals/SKILL.md +488 -0
- package/template/.claude/skills/threejs-geometry/SKILL.md +548 -0
- package/template/.claude/skills/threejs-interaction/SKILL.md +660 -0
- package/template/.claude/skills/threejs-lighting/SKILL.md +481 -0
- package/template/.claude/skills/threejs-loaders/SKILL.md +623 -0
- package/template/.claude/skills/threejs-materials/SKILL.md +520 -0
- package/template/.claude/skills/threejs-postprocessing/SKILL.md +602 -0
- package/template/.claude/skills/threejs-shaders/SKILL.md +642 -0
- package/template/.claude/skills/threejs-textures/SKILL.md +628 -0
- package/template/.codex/skills/threejs-animation/SKILL.md +552 -0
- package/template/.codex/skills/threejs-fundamentals/SKILL.md +488 -0
- package/template/.codex/skills/threejs-geometry/SKILL.md +548 -0
- package/template/.codex/skills/threejs-interaction/SKILL.md +660 -0
- package/template/.codex/skills/threejs-lighting/SKILL.md +481 -0
- package/template/.codex/skills/threejs-loaders/SKILL.md +623 -0
- package/template/.codex/skills/threejs-materials/SKILL.md +520 -0
- package/template/.codex/skills/threejs-postprocessing/SKILL.md +602 -0
- package/template/.codex/skills/threejs-shaders/SKILL.md +642 -0
- package/template/.codex/skills/threejs-textures/SKILL.md +628 -0
- package/template/README.md +352 -0
- package/template/docs/.gitkeep +0 -0
- package/template/plans/.gitkeep +0 -0
- package/template/prompts/01-mockup-generation.md +44 -0
- package/template/prompts/02-prd-generation.md +119 -0
- package/template/prompts/03-tdd-generation.md +127 -0
- package/template/prompts/04-execution-plan.md +89 -0
- package/template/prompts/05-implementation.md +61 -0
- package/template/public/assets/.gitkeep +0 -0
- package/template/scripts/config.example.json +12 -0
- package/template/scripts/generate-assets-json.js +149 -0
- package/template/scripts/generate-mockup.js +197 -0
- package/template/scripts/generate-plan.js +295 -0
- package/template/scripts/generate-prd.js +297 -0
- package/template/scripts/generate-tdd.js +283 -0
- package/template/scripts/pipeline.js +229 -0
|
@@ -0,0 +1,623 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: threejs-loaders
|
|
3
|
+
description: Three.js asset loading - GLTF, textures, images, models, async patterns. Use when loading 3D models, textures, HDR environments, or managing loading progress.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Three.js Loaders
|
|
7
|
+
|
|
8
|
+
## Quick Start
|
|
9
|
+
|
|
10
|
+
```javascript
|
|
11
|
+
import * as THREE from "three";
|
|
12
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
13
|
+
|
|
14
|
+
// Load GLTF model
|
|
15
|
+
const loader = new GLTFLoader();
|
|
16
|
+
loader.load("model.glb", (gltf) => {
|
|
17
|
+
scene.add(gltf.scene);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
// Load texture
|
|
21
|
+
const textureLoader = new THREE.TextureLoader();
|
|
22
|
+
const texture = textureLoader.load("texture.jpg");
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## LoadingManager
|
|
26
|
+
|
|
27
|
+
Coordinate multiple loaders and track progress.
|
|
28
|
+
|
|
29
|
+
```javascript
|
|
30
|
+
const manager = new THREE.LoadingManager();
|
|
31
|
+
|
|
32
|
+
// Callbacks
|
|
33
|
+
manager.onStart = (url, loaded, total) => {
|
|
34
|
+
console.log(`Started loading: ${url}`);
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
manager.onLoad = () => {
|
|
38
|
+
console.log("All assets loaded!");
|
|
39
|
+
startGame();
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
manager.onProgress = (url, loaded, total) => {
|
|
43
|
+
const progress = (loaded / total) * 100;
|
|
44
|
+
console.log(`Loading: ${progress.toFixed(1)}%`);
|
|
45
|
+
updateProgressBar(progress);
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
manager.onError = (url) => {
|
|
49
|
+
console.error(`Error loading: ${url}`);
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Use manager with loaders
|
|
53
|
+
const textureLoader = new THREE.TextureLoader(manager);
|
|
54
|
+
const gltfLoader = new GLTFLoader(manager);
|
|
55
|
+
|
|
56
|
+
// Load assets
|
|
57
|
+
textureLoader.load("texture1.jpg");
|
|
58
|
+
textureLoader.load("texture2.jpg");
|
|
59
|
+
gltfLoader.load("model.glb");
|
|
60
|
+
// onLoad fires when ALL are complete
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## Texture Loading
|
|
64
|
+
|
|
65
|
+
### TextureLoader
|
|
66
|
+
|
|
67
|
+
```javascript
|
|
68
|
+
const loader = new THREE.TextureLoader();
|
|
69
|
+
|
|
70
|
+
// Callback style
|
|
71
|
+
loader.load(
|
|
72
|
+
"texture.jpg",
|
|
73
|
+
(texture) => {
|
|
74
|
+
// onLoad
|
|
75
|
+
material.map = texture;
|
|
76
|
+
material.needsUpdate = true;
|
|
77
|
+
},
|
|
78
|
+
undefined, // onProgress - not supported for image loading
|
|
79
|
+
(error) => {
|
|
80
|
+
// onError
|
|
81
|
+
console.error("Error loading texture", error);
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
// Synchronous (returns texture, loads async)
|
|
86
|
+
const texture = loader.load("texture.jpg");
|
|
87
|
+
material.map = texture;
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Texture Configuration
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
const texture = loader.load("texture.jpg", (tex) => {
|
|
94
|
+
// Color space (important for color accuracy)
|
|
95
|
+
tex.colorSpace = THREE.SRGBColorSpace; // For color/albedo maps
|
|
96
|
+
// tex.colorSpace = THREE.LinearSRGBColorSpace; // For data maps (normal, roughness)
|
|
97
|
+
|
|
98
|
+
// Wrapping
|
|
99
|
+
tex.wrapS = THREE.RepeatWrapping;
|
|
100
|
+
tex.wrapT = THREE.RepeatWrapping;
|
|
101
|
+
// ClampToEdgeWrapping, RepeatWrapping, MirroredRepeatWrapping
|
|
102
|
+
|
|
103
|
+
// Repeat/offset
|
|
104
|
+
tex.repeat.set(2, 2);
|
|
105
|
+
tex.offset.set(0.5, 0.5);
|
|
106
|
+
tex.rotation = Math.PI / 4;
|
|
107
|
+
tex.center.set(0.5, 0.5);
|
|
108
|
+
|
|
109
|
+
// Filtering
|
|
110
|
+
tex.minFilter = THREE.LinearMipmapLinearFilter; // Default
|
|
111
|
+
tex.magFilter = THREE.LinearFilter; // Default
|
|
112
|
+
// NearestFilter - pixelated
|
|
113
|
+
// LinearFilter - smooth
|
|
114
|
+
// LinearMipmapLinearFilter - smooth with mipmaps
|
|
115
|
+
|
|
116
|
+
// Anisotropic filtering (sharper at angles)
|
|
117
|
+
tex.anisotropy = renderer.capabilities.getMaxAnisotropy();
|
|
118
|
+
|
|
119
|
+
// Flip Y (usually true for standard textures)
|
|
120
|
+
tex.flipY = true;
|
|
121
|
+
|
|
122
|
+
tex.needsUpdate = true;
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
### CubeTextureLoader
|
|
127
|
+
|
|
128
|
+
For environment maps and skyboxes.
|
|
129
|
+
|
|
130
|
+
```javascript
|
|
131
|
+
const loader = new THREE.CubeTextureLoader();
|
|
132
|
+
|
|
133
|
+
// Load 6 faces
|
|
134
|
+
const cubeTexture = loader.load([
|
|
135
|
+
"px.jpg",
|
|
136
|
+
"nx.jpg", // positive/negative X
|
|
137
|
+
"py.jpg",
|
|
138
|
+
"ny.jpg", // positive/negative Y
|
|
139
|
+
"pz.jpg",
|
|
140
|
+
"nz.jpg", // positive/negative Z
|
|
141
|
+
]);
|
|
142
|
+
|
|
143
|
+
// Use as background
|
|
144
|
+
scene.background = cubeTexture;
|
|
145
|
+
|
|
146
|
+
// Use as environment map
|
|
147
|
+
scene.environment = cubeTexture;
|
|
148
|
+
material.envMap = cubeTexture;
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### HDR/EXR Loading
|
|
152
|
+
|
|
153
|
+
```javascript
|
|
154
|
+
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
|
|
155
|
+
import { EXRLoader } from "three/addons/loaders/EXRLoader.js";
|
|
156
|
+
|
|
157
|
+
// HDR
|
|
158
|
+
const rgbeLoader = new RGBELoader();
|
|
159
|
+
rgbeLoader.load("environment.hdr", (texture) => {
|
|
160
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
161
|
+
scene.environment = texture;
|
|
162
|
+
scene.background = texture;
|
|
163
|
+
});
|
|
164
|
+
|
|
165
|
+
// EXR
|
|
166
|
+
const exrLoader = new EXRLoader();
|
|
167
|
+
exrLoader.load("environment.exr", (texture) => {
|
|
168
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
169
|
+
scene.environment = texture;
|
|
170
|
+
});
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### PMREMGenerator
|
|
174
|
+
|
|
175
|
+
Generate prefiltered environment maps for PBR.
|
|
176
|
+
|
|
177
|
+
```javascript
|
|
178
|
+
import { RGBELoader } from "three/addons/loaders/RGBELoader.js";
|
|
179
|
+
|
|
180
|
+
const pmremGenerator = new THREE.PMREMGenerator(renderer);
|
|
181
|
+
pmremGenerator.compileEquirectangularShader();
|
|
182
|
+
|
|
183
|
+
new RGBELoader().load("environment.hdr", (texture) => {
|
|
184
|
+
const envMap = pmremGenerator.fromEquirectangular(texture).texture;
|
|
185
|
+
|
|
186
|
+
scene.environment = envMap;
|
|
187
|
+
scene.background = envMap;
|
|
188
|
+
|
|
189
|
+
texture.dispose();
|
|
190
|
+
pmremGenerator.dispose();
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## GLTF/GLB Loading
|
|
195
|
+
|
|
196
|
+
The most common 3D format for web.
|
|
197
|
+
|
|
198
|
+
```javascript
|
|
199
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
200
|
+
|
|
201
|
+
const loader = new GLTFLoader();
|
|
202
|
+
|
|
203
|
+
loader.load("model.glb", (gltf) => {
|
|
204
|
+
// The loaded scene
|
|
205
|
+
const model = gltf.scene;
|
|
206
|
+
scene.add(model);
|
|
207
|
+
|
|
208
|
+
// Animations
|
|
209
|
+
const animations = gltf.animations;
|
|
210
|
+
if (animations.length > 0) {
|
|
211
|
+
const mixer = new THREE.AnimationMixer(model);
|
|
212
|
+
animations.forEach((clip) => {
|
|
213
|
+
mixer.clipAction(clip).play();
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// Cameras (if any)
|
|
218
|
+
const cameras = gltf.cameras;
|
|
219
|
+
|
|
220
|
+
// Asset info
|
|
221
|
+
console.log(gltf.asset); // Version, generator, etc.
|
|
222
|
+
|
|
223
|
+
// User data from Blender/etc
|
|
224
|
+
console.log(gltf.userData);
|
|
225
|
+
});
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
### GLTF with Draco Compression
|
|
229
|
+
|
|
230
|
+
```javascript
|
|
231
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
232
|
+
import { DRACOLoader } from "three/addons/loaders/DRACOLoader.js";
|
|
233
|
+
|
|
234
|
+
const dracoLoader = new DRACOLoader();
|
|
235
|
+
dracoLoader.setDecoderPath(
|
|
236
|
+
"https://www.gstatic.com/draco/versioned/decoders/1.5.6/",
|
|
237
|
+
);
|
|
238
|
+
dracoLoader.preload();
|
|
239
|
+
|
|
240
|
+
const gltfLoader = new GLTFLoader();
|
|
241
|
+
gltfLoader.setDRACOLoader(dracoLoader);
|
|
242
|
+
|
|
243
|
+
gltfLoader.load("compressed-model.glb", (gltf) => {
|
|
244
|
+
scene.add(gltf.scene);
|
|
245
|
+
});
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
### GLTF with KTX2 Textures
|
|
249
|
+
|
|
250
|
+
```javascript
|
|
251
|
+
import { GLTFLoader } from "three/addons/loaders/GLTFLoader.js";
|
|
252
|
+
import { KTX2Loader } from "three/addons/loaders/KTX2Loader.js";
|
|
253
|
+
|
|
254
|
+
const ktx2Loader = new KTX2Loader();
|
|
255
|
+
ktx2Loader.setTranscoderPath(
|
|
256
|
+
"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/libs/basis/",
|
|
257
|
+
);
|
|
258
|
+
ktx2Loader.detectSupport(renderer);
|
|
259
|
+
|
|
260
|
+
const gltfLoader = new GLTFLoader();
|
|
261
|
+
gltfLoader.setKTX2Loader(ktx2Loader);
|
|
262
|
+
|
|
263
|
+
gltfLoader.load("model-with-ktx2.glb", (gltf) => {
|
|
264
|
+
scene.add(gltf.scene);
|
|
265
|
+
});
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
### Process GLTF Content
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
loader.load("model.glb", (gltf) => {
|
|
272
|
+
const model = gltf.scene;
|
|
273
|
+
|
|
274
|
+
// Enable shadows
|
|
275
|
+
model.traverse((child) => {
|
|
276
|
+
if (child.isMesh) {
|
|
277
|
+
child.castShadow = true;
|
|
278
|
+
child.receiveShadow = true;
|
|
279
|
+
}
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
// Find specific mesh
|
|
283
|
+
const head = model.getObjectByName("Head");
|
|
284
|
+
|
|
285
|
+
// Adjust materials
|
|
286
|
+
model.traverse((child) => {
|
|
287
|
+
if (child.isMesh && child.material) {
|
|
288
|
+
child.material.envMapIntensity = 0.5;
|
|
289
|
+
}
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
// Center and scale
|
|
293
|
+
const box = new THREE.Box3().setFromObject(model);
|
|
294
|
+
const center = box.getCenter(new THREE.Vector3());
|
|
295
|
+
const size = box.getSize(new THREE.Vector3());
|
|
296
|
+
|
|
297
|
+
model.position.sub(center);
|
|
298
|
+
const maxDim = Math.max(size.x, size.y, size.z);
|
|
299
|
+
model.scale.setScalar(1 / maxDim);
|
|
300
|
+
|
|
301
|
+
scene.add(model);
|
|
302
|
+
});
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
## Other Model Formats
|
|
306
|
+
|
|
307
|
+
### OBJ + MTL
|
|
308
|
+
|
|
309
|
+
```javascript
|
|
310
|
+
import { OBJLoader } from "three/addons/loaders/OBJLoader.js";
|
|
311
|
+
import { MTLLoader } from "three/addons/loaders/MTLLoader.js";
|
|
312
|
+
|
|
313
|
+
const mtlLoader = new MTLLoader();
|
|
314
|
+
mtlLoader.load("model.mtl", (materials) => {
|
|
315
|
+
materials.preload();
|
|
316
|
+
|
|
317
|
+
const objLoader = new OBJLoader();
|
|
318
|
+
objLoader.setMaterials(materials);
|
|
319
|
+
objLoader.load("model.obj", (object) => {
|
|
320
|
+
scene.add(object);
|
|
321
|
+
});
|
|
322
|
+
});
|
|
323
|
+
```
|
|
324
|
+
|
|
325
|
+
### FBX
|
|
326
|
+
|
|
327
|
+
```javascript
|
|
328
|
+
import { FBXLoader } from "three/addons/loaders/FBXLoader.js";
|
|
329
|
+
|
|
330
|
+
const loader = new FBXLoader();
|
|
331
|
+
loader.load("model.fbx", (object) => {
|
|
332
|
+
// FBX often has large scale
|
|
333
|
+
object.scale.setScalar(0.01);
|
|
334
|
+
|
|
335
|
+
// Animations
|
|
336
|
+
const mixer = new THREE.AnimationMixer(object);
|
|
337
|
+
object.animations.forEach((clip) => {
|
|
338
|
+
mixer.clipAction(clip).play();
|
|
339
|
+
});
|
|
340
|
+
|
|
341
|
+
scene.add(object);
|
|
342
|
+
});
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
### STL
|
|
346
|
+
|
|
347
|
+
```javascript
|
|
348
|
+
import { STLLoader } from "three/addons/loaders/STLLoader.js";
|
|
349
|
+
|
|
350
|
+
const loader = new STLLoader();
|
|
351
|
+
loader.load("model.stl", (geometry) => {
|
|
352
|
+
const material = new THREE.MeshStandardMaterial({ color: 0x888888 });
|
|
353
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
354
|
+
scene.add(mesh);
|
|
355
|
+
});
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### PLY
|
|
359
|
+
|
|
360
|
+
```javascript
|
|
361
|
+
import { PLYLoader } from "three/addons/loaders/PLYLoader.js";
|
|
362
|
+
|
|
363
|
+
const loader = new PLYLoader();
|
|
364
|
+
loader.load("model.ply", (geometry) => {
|
|
365
|
+
geometry.computeVertexNormals();
|
|
366
|
+
const material = new THREE.MeshStandardMaterial({ vertexColors: true });
|
|
367
|
+
const mesh = new THREE.Mesh(geometry, material);
|
|
368
|
+
scene.add(mesh);
|
|
369
|
+
});
|
|
370
|
+
```
|
|
371
|
+
|
|
372
|
+
## Async/Promise Loading
|
|
373
|
+
|
|
374
|
+
### Promisified Loader
|
|
375
|
+
|
|
376
|
+
```javascript
|
|
377
|
+
function loadModel(url) {
|
|
378
|
+
return new Promise((resolve, reject) => {
|
|
379
|
+
loader.load(url, resolve, undefined, reject);
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
// Usage
|
|
384
|
+
async function init() {
|
|
385
|
+
try {
|
|
386
|
+
const gltf = await loadModel("model.glb");
|
|
387
|
+
scene.add(gltf.scene);
|
|
388
|
+
} catch (error) {
|
|
389
|
+
console.error("Failed to load model:", error);
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
```
|
|
393
|
+
|
|
394
|
+
### Load Multiple Assets
|
|
395
|
+
|
|
396
|
+
```javascript
|
|
397
|
+
async function loadAssets() {
|
|
398
|
+
const [modelGltf, envTexture, colorTexture] = await Promise.all([
|
|
399
|
+
loadGLTF("model.glb"),
|
|
400
|
+
loadRGBE("environment.hdr"),
|
|
401
|
+
loadTexture("color.jpg"),
|
|
402
|
+
]);
|
|
403
|
+
|
|
404
|
+
scene.add(modelGltf.scene);
|
|
405
|
+
scene.environment = envTexture;
|
|
406
|
+
material.map = colorTexture;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
// Helper functions
|
|
410
|
+
function loadGLTF(url) {
|
|
411
|
+
return new Promise((resolve, reject) => {
|
|
412
|
+
new GLTFLoader().load(url, resolve, undefined, reject);
|
|
413
|
+
});
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
function loadRGBE(url) {
|
|
417
|
+
return new Promise((resolve, reject) => {
|
|
418
|
+
new RGBELoader().load(
|
|
419
|
+
url,
|
|
420
|
+
(texture) => {
|
|
421
|
+
texture.mapping = THREE.EquirectangularReflectionMapping;
|
|
422
|
+
resolve(texture);
|
|
423
|
+
},
|
|
424
|
+
undefined,
|
|
425
|
+
reject,
|
|
426
|
+
);
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
function loadTexture(url) {
|
|
431
|
+
return new Promise((resolve, reject) => {
|
|
432
|
+
new THREE.TextureLoader().load(url, resolve, undefined, reject);
|
|
433
|
+
});
|
|
434
|
+
}
|
|
435
|
+
```
|
|
436
|
+
|
|
437
|
+
## Caching
|
|
438
|
+
|
|
439
|
+
### Built-in Cache
|
|
440
|
+
|
|
441
|
+
```javascript
|
|
442
|
+
// Enable cache
|
|
443
|
+
THREE.Cache.enabled = true;
|
|
444
|
+
|
|
445
|
+
// Clear cache
|
|
446
|
+
THREE.Cache.clear();
|
|
447
|
+
|
|
448
|
+
// Manual cache management
|
|
449
|
+
THREE.Cache.add("key", data);
|
|
450
|
+
THREE.Cache.get("key");
|
|
451
|
+
THREE.Cache.remove("key");
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
### Custom Asset Manager
|
|
455
|
+
|
|
456
|
+
```javascript
|
|
457
|
+
class AssetManager {
|
|
458
|
+
constructor() {
|
|
459
|
+
this.textures = new Map();
|
|
460
|
+
this.models = new Map();
|
|
461
|
+
this.gltfLoader = new GLTFLoader();
|
|
462
|
+
this.textureLoader = new THREE.TextureLoader();
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
async loadTexture(key, url) {
|
|
466
|
+
if (this.textures.has(key)) {
|
|
467
|
+
return this.textures.get(key);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
const texture = await new Promise((resolve, reject) => {
|
|
471
|
+
this.textureLoader.load(url, resolve, undefined, reject);
|
|
472
|
+
});
|
|
473
|
+
|
|
474
|
+
this.textures.set(key, texture);
|
|
475
|
+
return texture;
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
async loadModel(key, url) {
|
|
479
|
+
if (this.models.has(key)) {
|
|
480
|
+
return this.models.get(key).clone();
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const gltf = await new Promise((resolve, reject) => {
|
|
484
|
+
this.gltfLoader.load(url, resolve, undefined, reject);
|
|
485
|
+
});
|
|
486
|
+
|
|
487
|
+
this.models.set(key, gltf.scene);
|
|
488
|
+
return gltf.scene.clone();
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
dispose() {
|
|
492
|
+
this.textures.forEach((t) => t.dispose());
|
|
493
|
+
this.textures.clear();
|
|
494
|
+
this.models.clear();
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Usage
|
|
499
|
+
const assets = new AssetManager();
|
|
500
|
+
const texture = await assets.loadTexture("brick", "brick.jpg");
|
|
501
|
+
const model = await assets.loadModel("tree", "tree.glb");
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
## Loading from Different Sources
|
|
505
|
+
|
|
506
|
+
### Data URL / Base64
|
|
507
|
+
|
|
508
|
+
```javascript
|
|
509
|
+
const loader = new THREE.TextureLoader();
|
|
510
|
+
const texture = loader.load("data:image/png;base64,iVBORw0KGgo...");
|
|
511
|
+
```
|
|
512
|
+
|
|
513
|
+
### Blob URL
|
|
514
|
+
|
|
515
|
+
```javascript
|
|
516
|
+
async function loadFromBlob(blob) {
|
|
517
|
+
const url = URL.createObjectURL(blob);
|
|
518
|
+
const texture = await loadTexture(url);
|
|
519
|
+
URL.revokeObjectURL(url);
|
|
520
|
+
return texture;
|
|
521
|
+
}
|
|
522
|
+
```
|
|
523
|
+
|
|
524
|
+
### ArrayBuffer
|
|
525
|
+
|
|
526
|
+
```javascript
|
|
527
|
+
// From fetch
|
|
528
|
+
const response = await fetch("model.glb");
|
|
529
|
+
const buffer = await response.arrayBuffer();
|
|
530
|
+
|
|
531
|
+
// Parse with loader
|
|
532
|
+
const loader = new GLTFLoader();
|
|
533
|
+
loader.parse(buffer, "", (gltf) => {
|
|
534
|
+
scene.add(gltf.scene);
|
|
535
|
+
});
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
### Custom Path/URL
|
|
539
|
+
|
|
540
|
+
```javascript
|
|
541
|
+
// Set base path
|
|
542
|
+
loader.setPath("assets/models/");
|
|
543
|
+
loader.load("model.glb"); // Loads from assets/models/model.glb
|
|
544
|
+
|
|
545
|
+
// Set resource path (for textures referenced in model)
|
|
546
|
+
loader.setResourcePath("assets/textures/");
|
|
547
|
+
|
|
548
|
+
// Custom URL modifier
|
|
549
|
+
manager.setURLModifier((url) => {
|
|
550
|
+
return `https://cdn.example.com/${url}`;
|
|
551
|
+
});
|
|
552
|
+
```
|
|
553
|
+
|
|
554
|
+
## Error Handling
|
|
555
|
+
|
|
556
|
+
```javascript
|
|
557
|
+
// Graceful fallback
|
|
558
|
+
async function loadWithFallback(primaryUrl, fallbackUrl) {
|
|
559
|
+
try {
|
|
560
|
+
return await loadModel(primaryUrl);
|
|
561
|
+
} catch (error) {
|
|
562
|
+
console.warn(`Primary failed, trying fallback: ${error}`);
|
|
563
|
+
return await loadModel(fallbackUrl);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
// Retry logic
|
|
568
|
+
async function loadWithRetry(url, maxRetries = 3) {
|
|
569
|
+
for (let i = 0; i < maxRetries; i++) {
|
|
570
|
+
try {
|
|
571
|
+
return await loadModel(url);
|
|
572
|
+
} catch (error) {
|
|
573
|
+
if (i === maxRetries - 1) throw error;
|
|
574
|
+
await new Promise((r) => setTimeout(r, 1000 * (i + 1)));
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
|
|
579
|
+
// Timeout
|
|
580
|
+
async function loadWithTimeout(url, timeout = 30000) {
|
|
581
|
+
const controller = new AbortController();
|
|
582
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
583
|
+
|
|
584
|
+
try {
|
|
585
|
+
const response = await fetch(url, { signal: controller.signal });
|
|
586
|
+
clearTimeout(timeoutId);
|
|
587
|
+
return response;
|
|
588
|
+
} catch (error) {
|
|
589
|
+
if (error.name === "AbortError") {
|
|
590
|
+
throw new Error("Loading timed out");
|
|
591
|
+
}
|
|
592
|
+
throw error;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## Performance Tips
|
|
598
|
+
|
|
599
|
+
1. **Use compressed formats**: DRACO for geometry, KTX2/Basis for textures
|
|
600
|
+
2. **Load progressively**: Show placeholders while loading
|
|
601
|
+
3. **Lazy load**: Only load what's needed
|
|
602
|
+
4. **Use CDN**: Faster asset delivery
|
|
603
|
+
5. **Enable cache**: `THREE.Cache.enabled = true`
|
|
604
|
+
|
|
605
|
+
```javascript
|
|
606
|
+
// Progressive loading with placeholder
|
|
607
|
+
const placeholder = new THREE.Mesh(
|
|
608
|
+
new THREE.BoxGeometry(1, 1, 1),
|
|
609
|
+
new THREE.MeshBasicMaterial({ wireframe: true }),
|
|
610
|
+
);
|
|
611
|
+
scene.add(placeholder);
|
|
612
|
+
|
|
613
|
+
loadModel("model.glb").then((gltf) => {
|
|
614
|
+
scene.remove(placeholder);
|
|
615
|
+
scene.add(gltf.scene);
|
|
616
|
+
});
|
|
617
|
+
```
|
|
618
|
+
|
|
619
|
+
## See Also
|
|
620
|
+
|
|
621
|
+
- `threejs-textures` - Texture configuration
|
|
622
|
+
- `threejs-animation` - Playing loaded animations
|
|
623
|
+
- `threejs-materials` - Material from loaded models
|