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.
Files changed (39) hide show
  1. package/README.md +97 -0
  2. package/bin/cli.js +370 -0
  3. package/package.json +29 -0
  4. package/template/.claude/skills/threejs-animation/SKILL.md +552 -0
  5. package/template/.claude/skills/threejs-fundamentals/SKILL.md +488 -0
  6. package/template/.claude/skills/threejs-geometry/SKILL.md +548 -0
  7. package/template/.claude/skills/threejs-interaction/SKILL.md +660 -0
  8. package/template/.claude/skills/threejs-lighting/SKILL.md +481 -0
  9. package/template/.claude/skills/threejs-loaders/SKILL.md +623 -0
  10. package/template/.claude/skills/threejs-materials/SKILL.md +520 -0
  11. package/template/.claude/skills/threejs-postprocessing/SKILL.md +602 -0
  12. package/template/.claude/skills/threejs-shaders/SKILL.md +642 -0
  13. package/template/.claude/skills/threejs-textures/SKILL.md +628 -0
  14. package/template/.codex/skills/threejs-animation/SKILL.md +552 -0
  15. package/template/.codex/skills/threejs-fundamentals/SKILL.md +488 -0
  16. package/template/.codex/skills/threejs-geometry/SKILL.md +548 -0
  17. package/template/.codex/skills/threejs-interaction/SKILL.md +660 -0
  18. package/template/.codex/skills/threejs-lighting/SKILL.md +481 -0
  19. package/template/.codex/skills/threejs-loaders/SKILL.md +623 -0
  20. package/template/.codex/skills/threejs-materials/SKILL.md +520 -0
  21. package/template/.codex/skills/threejs-postprocessing/SKILL.md +602 -0
  22. package/template/.codex/skills/threejs-shaders/SKILL.md +642 -0
  23. package/template/.codex/skills/threejs-textures/SKILL.md +628 -0
  24. package/template/README.md +352 -0
  25. package/template/docs/.gitkeep +0 -0
  26. package/template/plans/.gitkeep +0 -0
  27. package/template/prompts/01-mockup-generation.md +44 -0
  28. package/template/prompts/02-prd-generation.md +119 -0
  29. package/template/prompts/03-tdd-generation.md +127 -0
  30. package/template/prompts/04-execution-plan.md +89 -0
  31. package/template/prompts/05-implementation.md +61 -0
  32. package/template/public/assets/.gitkeep +0 -0
  33. package/template/scripts/config.example.json +12 -0
  34. package/template/scripts/generate-assets-json.js +149 -0
  35. package/template/scripts/generate-mockup.js +197 -0
  36. package/template/scripts/generate-plan.js +295 -0
  37. package/template/scripts/generate-prd.js +297 -0
  38. package/template/scripts/generate-tdd.js +283 -0
  39. 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