@jarrodmedrano/claude-skills 1.0.3 → 1.0.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.
Files changed (26) hide show
  1. package/.claude/skills/bevy/SKILL.md +406 -0
  2. package/.claude/skills/bevy/references/bevy_specific_tips.md +385 -0
  3. package/.claude/skills/bevy/references/common_pitfalls.md +217 -0
  4. package/.claude/skills/bevy/references/ecs_patterns.md +277 -0
  5. package/.claude/skills/bevy/references/project_structure.md +116 -0
  6. package/.claude/skills/bevy/references/ui_development.md +147 -0
  7. package/.claude/skills/domain-driven-design/SKILL.md +459 -0
  8. package/.claude/skills/domain-driven-design/references/ddd_foundations_and_patterns.md +664 -0
  9. package/.claude/skills/domain-driven-design/references/rich_hickey_principles.md +406 -0
  10. package/.claude/skills/domain-driven-design/references/visualization_examples.md +790 -0
  11. package/.claude/skills/domain-driven-design/references/wlaschin_patterns.md +639 -0
  12. package/.claude/skills/godot/SKILL.md +728 -0
  13. package/.claude/skills/godot/assets/templates/attribute_template.gd +109 -0
  14. package/.claude/skills/godot/assets/templates/component_template.gd +76 -0
  15. package/.claude/skills/godot/assets/templates/interaction_template.gd +108 -0
  16. package/.claude/skills/godot/assets/templates/item_resource.tres +11 -0
  17. package/.claude/skills/godot/assets/templates/spell_resource.tres +20 -0
  18. package/.claude/skills/godot/references/architecture-patterns.md +608 -0
  19. package/.claude/skills/godot/references/common-pitfalls.md +518 -0
  20. package/.claude/skills/godot/references/file-formats.md +491 -0
  21. package/.claude/skills/godot/references/godot4-physics-api.md +302 -0
  22. package/.claude/skills/godot/scripts/validate_tres.py +145 -0
  23. package/.claude/skills/godot/scripts/validate_tscn.py +170 -0
  24. package/.claude/skills/react-three-fiber/SKILL.md +2055 -0
  25. package/.claude/skills/react-three-fiber/scripts/build-scene.ts +171 -0
  26. package/package.json +1 -1
@@ -0,0 +1,2055 @@
1
+ ---
2
+ name: threejs-scene-builder
3
+ description: Comprehensive Three.js and React Three Fiber skill for creating 3D scenes, characters, NPCs, procedural generation, animation retargeting, and interactive experiences. Use when user asks to "create Three.js scene", "setup React Three Fiber", "add 3D character", "create NPC AI", "procedural 3D generation", "retarget animation", "setup avatar system", or "create 3D game".
4
+ allowed-tools: [Write, Read]
5
+ ---
6
+
7
+ # Three.js & React Three Fiber - Complete 3D Development Skill
8
+
9
+ Comprehensive guide for creating advanced 3D web experiences using Three.js and React Three Fiber (R3F), covering scene setup, character systems, NPCs, procedural generation, animation retargeting, physics, and interactive gameplay.
10
+
11
+ ## When to Use
12
+
13
+ - "Create Three.js scene" / "Setup 3D scene"
14
+ - "Setup React Three Fiber" / "Create R3F app"
15
+ - "Generate WebGL visualization" / "Create interactive 3D graphics"
16
+ - "Add 3D character" / "Setup avatar system"
17
+ - "Create NPC AI" / "Add game characters"
18
+ - "Procedural 3D generation" / "Generate 3D content"
19
+ - "Retarget animation" / "Mixamo to Three.js"
20
+ - "Setup character controller" / "Add physics"
21
+ - "Ready Player Me integration"
22
+ - "Create 3D game with Three.js"
23
+
24
+ ## Instructions
25
+
26
+ ### 1. Install Three.js
27
+
28
+ ```bash
29
+ npm install three
30
+ # TypeScript types
31
+ npm install --save-dev @types/three
32
+ ```
33
+
34
+ ### 2. Basic Scene Setup
35
+
36
+ **TypeScript:**
37
+ ```typescript
38
+ // scene.ts
39
+ import * as THREE from 'three';
40
+ import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
41
+
42
+ export class SceneManager {
43
+ private scene: THREE.Scene;
44
+ private camera: THREE.PerspectiveCamera;
45
+ private renderer: THREE.WebGLRenderer;
46
+ private controls: OrbitControls;
47
+ private animationId: number | null = null;
48
+
49
+ constructor(container: HTMLElement) {
50
+ // Scene
51
+ this.scene = new THREE.Scene();
52
+ this.scene.background = new THREE.Color(0x1a1a1a);
53
+ this.scene.fog = new THREE.Fog(0x1a1a1a, 10, 50);
54
+
55
+ // Camera
56
+ this.camera = new THREE.PerspectiveCamera(
57
+ 75,
58
+ window.innerWidth / window.innerHeight,
59
+ 0.1,
60
+ 1000
61
+ );
62
+ this.camera.position.set(5, 5, 5);
63
+ this.camera.lookAt(0, 0, 0);
64
+
65
+ // Renderer
66
+ this.renderer = new THREE.WebGLRenderer({
67
+ antialias: true,
68
+ alpha: true,
69
+ });
70
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
71
+ this.renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
72
+ this.renderer.shadowMap.enabled = true;
73
+ this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
74
+ container.appendChild(this.renderer.domElement);
75
+
76
+ // Controls
77
+ this.controls = new OrbitControls(this.camera, this.renderer.domElement);
78
+ this.controls.enableDamping = true;
79
+ this.controls.dampingFactor = 0.05;
80
+ this.controls.minDistance = 2;
81
+ this.controls.maxDistance = 20;
82
+
83
+ // Lights
84
+ this.setupLights();
85
+
86
+ // Handle resize
87
+ window.addEventListener('resize', () => this.onWindowResize());
88
+
89
+ // Start animation
90
+ this.animate();
91
+ }
92
+
93
+ private setupLights() {
94
+ // Ambient light
95
+ const ambient = new THREE.AmbientLight(0xffffff, 0.4);
96
+ this.scene.add(ambient);
97
+
98
+ // Directional light (sun)
99
+ const directional = new THREE.DirectionalLight(0xffffff, 0.8);
100
+ directional.position.set(5, 10, 5);
101
+ directional.castShadow = true;
102
+ directional.shadow.camera.left = -10;
103
+ directional.shadow.camera.right = 10;
104
+ directional.shadow.camera.top = 10;
105
+ directional.shadow.camera.bottom = -10;
106
+ directional.shadow.mapSize.width = 2048;
107
+ directional.shadow.mapSize.height = 2048;
108
+ this.scene.add(directional);
109
+
110
+ // Hemisphere light
111
+ const hemisphere = new THREE.HemisphereLight(0xffffbb, 0x080820, 0.3);
112
+ this.scene.add(hemisphere);
113
+ }
114
+
115
+ private onWindowResize() {
116
+ this.camera.aspect = window.innerWidth / window.innerHeight;
117
+ this.camera.updateProjectionMatrix();
118
+ this.renderer.setSize(window.innerWidth, window.innerHeight);
119
+ }
120
+
121
+ private animate() {
122
+ this.animationId = requestAnimationFrame(() => this.animate());
123
+ this.controls.update();
124
+ this.renderer.render(this.scene, this.camera);
125
+ }
126
+
127
+ public addObject(object: THREE.Object3D) {
128
+ this.scene.add(object);
129
+ }
130
+
131
+ public dispose() {
132
+ if (this.animationId !== null) {
133
+ cancelAnimationFrame(this.animationId);
134
+ }
135
+ this.renderer.dispose();
136
+ this.controls.dispose();
137
+ }
138
+ }
139
+
140
+ // Usage
141
+ const container = document.getElementById('canvas-container')!;
142
+ const sceneManager = new SceneManager(container);
143
+
144
+ // Add a cube
145
+ const geometry = new THREE.BoxGeometry();
146
+ const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
147
+ const cube = new THREE.Mesh(geometry, material);
148
+ cube.castShadow = true;
149
+ cube.receiveShadow = true;
150
+ sceneManager.addObject(cube);
151
+ ```
152
+
153
+ ### 3. Advanced Material Setup
154
+
155
+ ```typescript
156
+ // materials.ts
157
+ import * as THREE from 'three';
158
+
159
+ export class MaterialLibrary {
160
+ // PBR Material
161
+ static createPBRMaterial(options: {
162
+ color?: number;
163
+ roughness?: number;
164
+ metalness?: number;
165
+ normalMap?: THREE.Texture;
166
+ roughnessMap?: THREE.Texture;
167
+ }) {
168
+ return new THREE.MeshStandardMaterial({
169
+ color: options.color ?? 0xffffff,
170
+ roughness: options.roughness ?? 0.5,
171
+ metalness: options.metalness ?? 0.5,
172
+ normalMap: options.normalMap,
173
+ roughnessMap: options.roughnessMap,
174
+ });
175
+ }
176
+
177
+ // Glass Material
178
+ static createGlassMaterial() {
179
+ return new THREE.MeshPhysicalMaterial({
180
+ color: 0xffffff,
181
+ metalness: 0,
182
+ roughness: 0,
183
+ transmission: 1,
184
+ thickness: 0.5,
185
+ });
186
+ }
187
+
188
+ // Glowing Material
189
+ static createGlowMaterial(color: number = 0x00ff00) {
190
+ return new THREE.MeshBasicMaterial({
191
+ color,
192
+ transparent: true,
193
+ opacity: 0.8,
194
+ blending: THREE.AdditiveBlending,
195
+ });
196
+ }
197
+
198
+ // Toon Material
199
+ static createToonMaterial(color: number = 0x00ff00) {
200
+ return new THREE.MeshToonMaterial({
201
+ color,
202
+ gradientMap: this.createGradientTexture(),
203
+ });
204
+ }
205
+
206
+ private static createGradientTexture() {
207
+ const canvas = document.createElement('canvas');
208
+ canvas.width = 256;
209
+ canvas.height = 1;
210
+ const ctx = canvas.getContext('2d')!;
211
+ const gradient = ctx.createLinearGradient(0, 0, 256, 0);
212
+ gradient.addColorStop(0, '#000000');
213
+ gradient.addColorStop(0.5, '#808080');
214
+ gradient.addColorStop(1, '#ffffff');
215
+ ctx.fillStyle = gradient;
216
+ ctx.fillRect(0, 0, 256, 1);
217
+ return new THREE.CanvasTexture(canvas);
218
+ }
219
+ }
220
+ ```
221
+
222
+ ### 4. Geometry Helpers
223
+
224
+ ```typescript
225
+ // geometries.ts
226
+ import * as THREE from 'three';
227
+
228
+ export class GeometryHelpers {
229
+ static createGroundPlane(size: number = 20) {
230
+ const geometry = new THREE.PlaneGeometry(size, size);
231
+ const material = new THREE.MeshStandardMaterial({
232
+ color: 0x808080,
233
+ roughness: 0.8,
234
+ metalness: 0.2,
235
+ });
236
+ const plane = new THREE.Mesh(geometry, material);
237
+ plane.rotation.x = -Math.PI / 2;
238
+ plane.receiveShadow = true;
239
+ return plane;
240
+ }
241
+
242
+ static createSkybox(textureLoader: THREE.TextureLoader, path: string) {
243
+ const loader = new THREE.CubeTextureLoader();
244
+ const texture = loader.load([
245
+ `${path}/px.jpg`, `${path}/nx.jpg`,
246
+ `${path}/py.jpg`, `${path}/ny.jpg`,
247
+ `${path}/pz.jpg`, `${path}/nz.jpg`,
248
+ ]);
249
+ return texture;
250
+ }
251
+
252
+ static createParticles(count: number = 1000) {
253
+ const geometry = new THREE.BufferGeometry();
254
+ const positions = new Float32Array(count * 3);
255
+
256
+ for (let i = 0; i < count * 3; i++) {
257
+ positions[i] = (Math.random() - 0.5) * 20;
258
+ }
259
+
260
+ geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
261
+
262
+ const material = new THREE.PointsMaterial({
263
+ size: 0.05,
264
+ color: 0xffffff,
265
+ transparent: true,
266
+ opacity: 0.8,
267
+ blending: THREE.AdditiveBlending,
268
+ });
269
+
270
+ return new THREE.Points(geometry, material);
271
+ }
272
+ }
273
+ ```
274
+
275
+ ### 5. Animation System
276
+
277
+ ```typescript
278
+ // animation.ts
279
+ import * as THREE from 'three';
280
+
281
+ export class AnimationController {
282
+ private mixer: THREE.AnimationMixer;
283
+ private actions: Map<string, THREE.AnimationAction> = new Map();
284
+
285
+ constructor(model: THREE.Object3D, animations: THREE.AnimationClip[]) {
286
+ this.mixer = new THREE.AnimationMixer(model);
287
+
288
+ animations.forEach((clip, index) => {
289
+ const action = this.mixer.clipAction(clip);
290
+ this.actions.set(clip.name || `animation_${index}`, action);
291
+ });
292
+ }
293
+
294
+ play(name: string, fadeIn: number = 0.5) {
295
+ const action = this.actions.get(name);
296
+ if (action) {
297
+ action.reset().fadeIn(fadeIn).play();
298
+ }
299
+ }
300
+
301
+ stop(name: string, fadeOut: number = 0.5) {
302
+ const action = this.actions.get(name);
303
+ if (action) {
304
+ action.fadeOut(fadeOut);
305
+ }
306
+ }
307
+
308
+ update(deltaTime: number) {
309
+ this.mixer.update(deltaTime);
310
+ }
311
+ }
312
+ ```
313
+
314
+ ### 6. Model Loading
315
+
316
+ ```typescript
317
+ // loader.ts
318
+ import * as THREE from 'three';
319
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
320
+ import { DRACOLoader } from 'three/examples/jsm/loaders/DRACOLoader';
321
+
322
+ export class ModelLoader {
323
+ private gltfLoader: GLTFLoader;
324
+ private textureLoader: THREE.TextureLoader;
325
+ private loadingManager: THREE.LoadingManager;
326
+
327
+ constructor(onProgress?: (progress: number) => void) {
328
+ this.loadingManager = new THREE.LoadingManager();
329
+
330
+ if (onProgress) {
331
+ this.loadingManager.onProgress = (url, loaded, total) => {
332
+ onProgress((loaded / total) * 100);
333
+ };
334
+ }
335
+
336
+ // Setup DRACO loader for compressed models
337
+ const dracoLoader = new DRACOLoader(this.loadingManager);
338
+ dracoLoader.setDecoderPath('/draco/');
339
+
340
+ this.gltfLoader = new GLTFLoader(this.loadingManager);
341
+ this.gltfLoader.setDRACOLoader(dracoLoader);
342
+
343
+ this.textureLoader = new THREE.TextureLoader(this.loadingManager);
344
+ }
345
+
346
+ async loadGLTF(url: string): Promise<THREE.Group> {
347
+ return new Promise((resolve, reject) => {
348
+ this.gltfLoader.load(
349
+ url,
350
+ (gltf) => {
351
+ gltf.scene.traverse((child) => {
352
+ if (child instanceof THREE.Mesh) {
353
+ child.castShadow = true;
354
+ child.receiveShadow = true;
355
+ }
356
+ });
357
+ resolve(gltf.scene);
358
+ },
359
+ undefined,
360
+ reject
361
+ );
362
+ });
363
+ }
364
+
365
+ async loadTexture(url: string): Promise<THREE.Texture> {
366
+ return new Promise((resolve, reject) => {
367
+ this.textureLoader.load(url, resolve, undefined, reject);
368
+ });
369
+ }
370
+ }
371
+ ```
372
+
373
+ ### 7. Post-Processing
374
+
375
+ ```typescript
376
+ // post-processing.ts
377
+ import * as THREE from 'three';
378
+ import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
379
+ import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
380
+ import { UnrealBloomPass } from 'three/examples/jsm/postprocessing/UnrealBloomPass';
381
+ import { SSAOPass } from 'three/examples/jsm/postprocessing/SSAOPass';
382
+
383
+ export class PostProcessing {
384
+ private composer: EffectComposer;
385
+
386
+ constructor(
387
+ renderer: THREE.WebGLRenderer,
388
+ scene: THREE.Scene,
389
+ camera: THREE.Camera
390
+ ) {
391
+ this.composer = new EffectComposer(renderer);
392
+
393
+ // Render pass
394
+ const renderPass = new RenderPass(scene, camera);
395
+ this.composer.addPass(renderPass);
396
+
397
+ // Bloom pass
398
+ const bloomPass = new UnrealBloomPass(
399
+ new THREE.Vector2(window.innerWidth, window.innerHeight),
400
+ 1.5, // strength
401
+ 0.4, // radius
402
+ 0.85 // threshold
403
+ );
404
+ this.composer.addPass(bloomPass);
405
+
406
+ // SSAO pass
407
+ const ssaoPass = new SSAOPass(scene, camera);
408
+ ssaoPass.kernelRadius = 16;
409
+ this.composer.addPass(ssaoPass);
410
+ }
411
+
412
+ render() {
413
+ this.composer.render();
414
+ }
415
+
416
+ resize(width: number, height: number) {
417
+ this.composer.setSize(width, height);
418
+ }
419
+ }
420
+ ```
421
+
422
+ ### 8. Performance Optimization
423
+
424
+ ```typescript
425
+ // optimization.ts
426
+ import * as THREE from 'three';
427
+
428
+ export class PerformanceOptimizer {
429
+ static optimizeGeometry(geometry: THREE.BufferGeometry) {
430
+ // Merge vertices
431
+ geometry.computeVertexNormals();
432
+ geometry.normalizeNormals();
433
+ return geometry;
434
+ }
435
+
436
+ static createLOD(geometries: THREE.BufferGeometry[], distances: number[]) {
437
+ const lod = new THREE.LOD();
438
+
439
+ geometries.forEach((geometry, index) => {
440
+ const material = new THREE.MeshStandardMaterial();
441
+ const mesh = new THREE.Mesh(geometry, material);
442
+ lod.addLevel(mesh, distances[index]);
443
+ });
444
+
445
+ return lod;
446
+ }
447
+
448
+ static enableInstancing(
449
+ geometry: THREE.BufferGeometry,
450
+ material: THREE.Material,
451
+ count: number,
452
+ positions: THREE.Vector3[]
453
+ ) {
454
+ const mesh = new THREE.InstancedMesh(geometry, material, count);
455
+
456
+ const matrix = new THREE.Matrix4();
457
+ positions.forEach((pos, i) => {
458
+ matrix.setPosition(pos);
459
+ mesh.setMatrixAt(i, matrix);
460
+ });
461
+
462
+ mesh.instanceMatrix.needsUpdate = true;
463
+ return mesh;
464
+ }
465
+
466
+ static setupFrustumCulling(camera: THREE.Camera, objects: THREE.Object3D[]) {
467
+ const frustum = new THREE.Frustum();
468
+ const projScreenMatrix = new THREE.Matrix4();
469
+
470
+ return () => {
471
+ camera.updateMatrixWorld();
472
+ projScreenMatrix.multiplyMatrices(
473
+ camera.projectionMatrix,
474
+ camera.matrixWorldInverse
475
+ );
476
+ frustum.setFromProjectionMatrix(projScreenMatrix);
477
+
478
+ objects.forEach((obj) => {
479
+ obj.visible = frustum.intersectsObject(obj);
480
+ });
481
+ };
482
+ }
483
+ }
484
+ ```
485
+
486
+ ### 9. React Integration
487
+
488
+ ```typescript
489
+ // ThreeCanvas.tsx
490
+ import React, { useEffect, useRef } from 'react';
491
+ import { SceneManager } from './scene';
492
+
493
+ export const ThreeCanvas: React.FC = () => {
494
+ const containerRef = useRef<HTMLDivElement>(null);
495
+ const sceneManagerRef = useRef<SceneManager | null>(null);
496
+
497
+ useEffect(() => {
498
+ if (!containerRef.current) return;
499
+
500
+ // Initialize scene
501
+ sceneManagerRef.current = new SceneManager(containerRef.current);
502
+
503
+ // Cleanup
504
+ return () => {
505
+ sceneManagerRef.current?.dispose();
506
+ };
507
+ }, []);
508
+
509
+ return (
510
+ <div
511
+ ref={containerRef}
512
+ style={{ width: '100vw', height: '100vh' }}
513
+ />
514
+ );
515
+ };
516
+ ```
517
+
518
+ ### 10. Complete Example
519
+
520
+ ```typescript
521
+ // main.ts
522
+ import * as THREE from 'three';
523
+ import { SceneManager } from './scene';
524
+ import { GeometryHelpers } from './geometries';
525
+ import { MaterialLibrary } from './materials';
526
+ import { ModelLoader } from './loader';
527
+
528
+ async function main() {
529
+ const container = document.getElementById('app')!;
530
+ const scene = new SceneManager(container);
531
+
532
+ // Add ground plane
533
+ const ground = GeometryHelpers.createGroundPlane();
534
+ scene.addObject(ground);
535
+
536
+ // Add particles
537
+ const particles = GeometryHelpers.createParticles(5000);
538
+ scene.addObject(particles);
539
+
540
+ // Load model
541
+ const loader = new ModelLoader((progress) => {
542
+ console.log(`Loading: ${progress.toFixed(0)}%`);
543
+ });
544
+
545
+ try {
546
+ const model = await loader.loadGLTF('/models/scene.gltf');
547
+ model.scale.set(2, 2, 2);
548
+ scene.addObject(model);
549
+ } catch (error) {
550
+ console.error('Failed to load model:', error);
551
+ }
552
+
553
+ // Add rotating cube with PBR material
554
+ const cubeGeo = new THREE.BoxGeometry();
555
+ const cubeMat = MaterialLibrary.createPBRMaterial({
556
+ color: 0x2194ce,
557
+ roughness: 0.3,
558
+ metalness: 0.8,
559
+ });
560
+ const cube = new THREE.Mesh(cubeGeo, cubeMat);
561
+ cube.position.y = 1;
562
+ scene.addObject(cube);
563
+
564
+ // Animation loop
565
+ function animate() {
566
+ requestAnimationFrame(animate);
567
+ cube.rotation.y += 0.01;
568
+ }
569
+ animate();
570
+ }
571
+
572
+ main();
573
+ ```
574
+
575
+ ### Best Practices
576
+
577
+ **DO:**
578
+ - Use BufferGeometry
579
+ - Enable frustum culling
580
+ - Use instanced meshes for many objects
581
+ - Optimize textures
582
+ - Use LOD for distant objects
583
+ - Dispose geometries and materials
584
+ - Limit shadow maps
585
+ - Use post-processing sparingly
586
+
587
+ **DON'T:**
588
+ - Create objects in render loop
589
+ - Use high-poly models without LOD
590
+ - Forget to dispose resources
591
+ - Use too many lights
592
+ - Skip texture compression
593
+ - Render at native device pixel ratio on mobile
594
+ - Update uniforms every frame unnecessarily
595
+
596
+ ---
597
+
598
+ ## 🚀 PART 2: React Three Fiber (R3F)
599
+
600
+ React Three Fiber is a React renderer for Three.js that enables declarative, component-based 3D development.
601
+
602
+ ### Installation
603
+
604
+ ```bash
605
+ npm install three @react-three/fiber
606
+ # Ecosystem libraries
607
+ npm install @react-three/drei @react-three/postprocessing
608
+ npm install postprocessing
609
+ npm install --save-dev @types/three
610
+ ```
611
+
612
+ ### Basic R3F Scene
613
+
614
+ ```tsx
615
+ // App.tsx
616
+ import { useRef, useState } from 'react';
617
+ import { Canvas, useFrame, MeshProps } from '@react-three/fiber';
618
+ import { OrbitControls, Environment, Sky } from '@react-three/drei';
619
+ import * as THREE from 'three';
620
+
621
+ function Box(props: MeshProps) {
622
+ const meshRef = useRef<THREE.Mesh>(null);
623
+ const [hovered, setHover] = useState(false);
624
+ const [active, setActive] = useState(false);
625
+
626
+ useFrame((state, delta) => {
627
+ if (meshRef.current) {
628
+ meshRef.current.rotation.x += delta;
629
+ }
630
+ });
631
+
632
+ return (
633
+ <mesh
634
+ {...props}
635
+ ref={meshRef}
636
+ scale={active ? 1.5 : 1}
637
+ onClick={() => setActive(!active)}
638
+ onPointerOver={() => setHover(true)}
639
+ onPointerOut={() => setHover(false)}
640
+ >
641
+ <boxGeometry args={[1, 1, 1]} />
642
+ <meshStandardMaterial color={hovered ? 'hotpink' : 'orange'} />
643
+ </mesh>
644
+ );
645
+ }
646
+
647
+ export default function App() {
648
+ return (
649
+ <Canvas camera={{ position: [0, 0, 5], fov: 75 }}>
650
+ <ambientLight intensity={0.5} />
651
+ <directionalLight position={[10, 10, 5]} intensity={1} />
652
+ <Box position={[0, 0, 0]} />
653
+ <OrbitControls />
654
+ <Sky sunPosition={[100, 20, 100]} />
655
+ <Environment preset="sunset" />
656
+ </Canvas>
657
+ );
658
+ }
659
+ ```
660
+
661
+ ### Advanced R3F Scene Manager
662
+
663
+ ```tsx
664
+ // Scene.tsx
665
+ import { useRef, useMemo } from 'react';
666
+ import { Canvas, useFrame, useThree } from '@react-three/fiber';
667
+ import {
668
+ OrbitControls,
669
+ PerspectiveCamera,
670
+ Environment,
671
+ ContactShadows,
672
+ useGLTF,
673
+ useTexture,
674
+ Html,
675
+ Stats,
676
+ } from '@react-three/drei';
677
+ import {
678
+ EffectComposer,
679
+ Bloom,
680
+ DepthOfField,
681
+ Vignette,
682
+ SSAO,
683
+ } from '@react-three/postprocessing';
684
+ import * as THREE from 'three';
685
+
686
+ // Scene configuration
687
+ interface SceneConfig {
688
+ enablePostProcessing?: boolean;
689
+ enableShadows?: boolean;
690
+ enableStats?: boolean;
691
+ toneMappingExposure?: number;
692
+ }
693
+
694
+ // Main scene component
695
+ function Scene({ config }: { config: SceneConfig }) {
696
+ const { gl, scene } = useThree();
697
+
698
+ // Configure renderer
699
+ useMemo(() => {
700
+ gl.shadowMap.enabled = config.enableShadows ?? true;
701
+ gl.shadowMap.type = THREE.PCFSoftShadowMap;
702
+ gl.toneMapping = THREE.ACESFilmicToneMapping;
703
+ gl.toneMappingExposure = config.toneMappingExposure ?? 1;
704
+ scene.background = new THREE.Color(0x1a1a1a);
705
+ }, [gl, scene, config]);
706
+
707
+ return (
708
+ <>
709
+ {config.enableStats && <Stats />}
710
+
711
+ {/* Lighting */}
712
+ <ambientLight intensity={0.3} />
713
+ <directionalLight
714
+ position={[10, 10, 5]}
715
+ intensity={1}
716
+ castShadow
717
+ shadow-mapSize={[2048, 2048]}
718
+ shadow-camera-left={-10}
719
+ shadow-camera-right={10}
720
+ shadow-camera-top={10}
721
+ shadow-camera-bottom={-10}
722
+ />
723
+ <hemisphereLight args={[0xffffbb, 0x080820, 0.2]} />
724
+
725
+ {/* Environment */}
726
+ <Environment preset="city" background={false} />
727
+
728
+ {/* Your 3D content here */}
729
+ </>
730
+ );
731
+ }
732
+
733
+ // App with Canvas wrapper
734
+ export default function App() {
735
+ return (
736
+ <Canvas shadows dpr={[1, 2]}>
737
+ <Scene config={{ enablePostProcessing: true, enableShadows: true }} />
738
+ <OrbitControls makeDefault />
739
+
740
+ {/* Post-processing */}
741
+ <EffectComposer>
742
+ <Bloom luminanceThreshold={1} intensity={0.5} />
743
+ <DepthOfField focusDistance={0.01} focalLength={0.2} bokehScale={3} />
744
+ <SSAO />
745
+ <Vignette opacity={0.5} />
746
+ </EffectComposer>
747
+ </Canvas>
748
+ );
749
+ }
750
+ ```
751
+
752
+ ### R3F Hooks & Best Practices
753
+
754
+ ```tsx
755
+ // hooks/useAnimatedModel.ts
756
+ import { useRef, useEffect } from 'react';
757
+ import { useGLTF, useAnimations } from '@react-three/drei';
758
+ import * as THREE from 'three';
759
+
760
+ export function useAnimatedModel(modelPath: string, animationName?: string) {
761
+ const group = useRef<THREE.Group>(null);
762
+ const { scene, animations } = useGLTF(modelPath);
763
+ const { actions, names } = useAnimations(animations, group);
764
+
765
+ useEffect(() => {
766
+ if (animationName && actions[animationName]) {
767
+ actions[animationName]?.play();
768
+ } else if (names.length > 0 && actions[names[0]]) {
769
+ actions[names[0]]?.play();
770
+ }
771
+ }, [actions, animationName, names]);
772
+
773
+ return { group, scene, actions, names };
774
+ }
775
+
776
+ // Usage
777
+ function AnimatedCharacter() {
778
+ const { group, scene } = useAnimatedModel('/models/character.glb', 'Idle');
779
+
780
+ return (
781
+ <group ref={group}>
782
+ <primitive object={scene} />
783
+ </group>
784
+ );
785
+ }
786
+ ```
787
+
788
+ ### Drei Library - Essential Helpers
789
+
790
+ ```tsx
791
+ import {
792
+ // Cameras & Controls
793
+ PerspectiveCamera, OrthographicCamera, OrbitControls,
794
+ FlyControls, PointerLockControls, TrackballControls,
795
+
796
+ // Staging
797
+ Environment, Sky, Stars, Cloud, ContactShadows,
798
+ BakeShadows, AccumulativeShadows, RandomizedLight,
799
+
800
+ // Abstractions
801
+ Box, Sphere, Plane, Cone, Cylinder, Torus,
802
+ Text, Text3D, Html, Billboard,
803
+
804
+ // Loaders
805
+ useGLTF, useFBX, useTexture, useCubeTexture,
806
+
807
+ // Performance
808
+ Instances, Instance, Merged, Detailed,
809
+
810
+ // Misc
811
+ useHelper, Grid, GizmoHelper, Stats, PerformanceMonitor,
812
+ Center, Bounds, Shadow, Reflector
813
+ } from '@react-three/drei';
814
+
815
+ // Example: Performance-optimized instances
816
+ function Trees({ count = 100 }) {
817
+ const { nodes } = useGLTF('/models/tree.glb');
818
+
819
+ return (
820
+ <Instances limit={count} geometry={nodes.tree.geometry}>
821
+ <meshStandardMaterial color="green" />
822
+ {Array.from({ length: count }, (_, i) => (
823
+ <Instance
824
+ key={i}
825
+ position={[
826
+ (Math.random() - 0.5) * 50,
827
+ 0,
828
+ (Math.random() - 0.5) * 50,
829
+ ]}
830
+ rotation={[0, Math.random() * Math.PI * 2, 0]}
831
+ scale={0.5 + Math.random() * 0.5}
832
+ />
833
+ ))}
834
+ </Instances>
835
+ );
836
+ }
837
+ ```
838
+
839
+ ---
840
+
841
+ ## 🎮 PART 3: Character Systems & Animation
842
+
843
+ ### Character Animation System
844
+
845
+ ```typescript
846
+ // characterAnimation.ts
847
+ import * as THREE from 'three';
848
+
849
+ export class CharacterAnimationController {
850
+ private mixer: THREE.AnimationMixer;
851
+ private actions: Map<string, THREE.AnimationAction> = new Map();
852
+ private currentAction: THREE.AnimationAction | null = null;
853
+
854
+ constructor(model: THREE.Object3D, animations: THREE.AnimationClip[]) {
855
+ this.mixer = new THREE.AnimationMixer(model);
856
+
857
+ animations.forEach((clip) => {
858
+ const action = this.mixer.clipAction(clip);
859
+ this.actions.set(clip.name, action);
860
+ });
861
+ }
862
+
863
+ play(name: string, fadeTime: number = 0.3) {
864
+ const nextAction = this.actions.get(name);
865
+ if (!nextAction) return;
866
+
867
+ if (this.currentAction && this.currentAction !== nextAction) {
868
+ this.currentAction.fadeOut(fadeTime);
869
+ }
870
+
871
+ nextAction.reset().fadeIn(fadeTime).play();
872
+ this.currentAction = nextAction;
873
+ }
874
+
875
+ crossFade(fromName: string, toName: string, duration: number = 0.3) {
876
+ const fromAction = this.actions.get(fromName);
877
+ const toAction = this.actions.get(toName);
878
+
879
+ if (fromAction && toAction) {
880
+ fromAction.fadeOut(duration);
881
+ toAction.reset().fadeIn(duration).play();
882
+ this.currentAction = toAction;
883
+ }
884
+ }
885
+
886
+ setWeight(name: string, weight: number) {
887
+ const action = this.actions.get(name);
888
+ if (action) action.setEffectiveWeight(weight);
889
+ }
890
+
891
+ update(deltaTime: number) {
892
+ this.mixer.update(deltaTime);
893
+ }
894
+
895
+ getAction(name: string) {
896
+ return this.actions.get(name);
897
+ }
898
+ }
899
+ ```
900
+
901
+ ### Animation Retargeting with SkeletonUtils
902
+
903
+ ```typescript
904
+ // retargeting.ts
905
+ import * as THREE from 'three';
906
+ import { SkeletonUtils } from 'three/examples/jsm/utils/SkeletonUtils';
907
+
908
+ export class AnimationRetargeter {
909
+ /**
910
+ * Retarget animation from one skeleton to another
911
+ * Based on: https://github.com/upf-gti/retargeting-threejs
912
+ */
913
+ static retargetAnimation(
914
+ sourceClip: THREE.AnimationClip,
915
+ targetSkeleton: THREE.Skeleton,
916
+ sourceSkeleton: THREE.Skeleton
917
+ ): THREE.AnimationClip {
918
+ // Use SkeletonUtils for retargeting
919
+ const retargetedClip = SkeletonUtils.retargetClip(
920
+ targetSkeleton.bones[0],
921
+ sourceSkeleton.bones[0],
922
+ sourceClip,
923
+ {}
924
+ );
925
+
926
+ return retargetedClip;
927
+ }
928
+
929
+ /**
930
+ * Load Mixamo animation and apply to custom character
931
+ */
932
+ static async loadMixamoAnimation(
933
+ fbxPath: string,
934
+ targetModel: THREE.Object3D,
935
+ loader: any
936
+ ): Promise<THREE.AnimationClip[]> {
937
+ return new Promise((resolve, reject) => {
938
+ loader.load(
939
+ fbxPath,
940
+ (fbx: any) => {
941
+ // Extract animations
942
+ const animations = fbx.animations;
943
+
944
+ // Find skeletons
945
+ const targetSkeleton = this.findSkeleton(targetModel);
946
+ const sourceSkeleton = this.findSkeleton(fbx);
947
+
948
+ if (!targetSkeleton || !sourceSkeleton) {
949
+ reject(new Error('Skeleton not found'));
950
+ return;
951
+ }
952
+
953
+ // Retarget all animations
954
+ const retargetedClips = animations.map((clip: THREE.AnimationClip) =>
955
+ this.retargetAnimation(clip, targetSkeleton, sourceSkeleton)
956
+ );
957
+
958
+ resolve(retargetedClips);
959
+ },
960
+ undefined,
961
+ reject
962
+ );
963
+ });
964
+ }
965
+
966
+ private static findSkeleton(object: THREE.Object3D): THREE.Skeleton | null {
967
+ let skeleton: THREE.Skeleton | null = null;
968
+
969
+ object.traverse((child) => {
970
+ if (child instanceof THREE.SkinnedMesh && child.skeleton) {
971
+ skeleton = child.skeleton;
972
+ }
973
+ });
974
+
975
+ return skeleton;
976
+ }
977
+ }
978
+ ```
979
+
980
+ ### Mixamo Integration Workflow
981
+
982
+ ```typescript
983
+ // mixamoIntegration.ts
984
+ import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
985
+ import { FBXLoader } from 'three/examples/jsm/loaders/FBXLoader';
986
+ import * as THREE from 'three';
987
+
988
+ export class MixamoCharacter {
989
+ private model: THREE.Group;
990
+ private mixer: THREE.AnimationMixer;
991
+ private animations: Map<string, THREE.AnimationClip> = new Map();
992
+
993
+ constructor(model: THREE.Group) {
994
+ this.model = model;
995
+ this.mixer = new THREE.AnimationMixer(model);
996
+ }
997
+
998
+ /**
999
+ * Load character with embedded animations (GLB from Mixamo)
1000
+ */
1001
+ static async loadWithAnimations(url: string): Promise<MixamoCharacter> {
1002
+ const loader = new GLTFLoader();
1003
+
1004
+ return new Promise((resolve, reject) => {
1005
+ loader.load(
1006
+ url,
1007
+ (gltf) => {
1008
+ const character = new MixamoCharacter(gltf.scene);
1009
+
1010
+ // Add animations
1011
+ gltf.animations.forEach((clip) => {
1012
+ character.animations.set(clip.name, clip);
1013
+ });
1014
+
1015
+ resolve(character);
1016
+ },
1017
+ undefined,
1018
+ reject
1019
+ );
1020
+ });
1021
+ }
1022
+
1023
+ /**
1024
+ * Add separate animation file (FBX without skin)
1025
+ */
1026
+ async addAnimation(name: string, fbxPath: string): Promise<void> {
1027
+ const loader = new FBXLoader();
1028
+
1029
+ return new Promise((resolve, reject) => {
1030
+ loader.load(
1031
+ fbxPath,
1032
+ (fbx) => {
1033
+ if (fbx.animations.length > 0) {
1034
+ this.animations.set(name, fbx.animations[0]);
1035
+ }
1036
+ resolve();
1037
+ },
1038
+ undefined,
1039
+ reject
1040
+ );
1041
+ });
1042
+ }
1043
+
1044
+ play(name: string, loop: boolean = true) {
1045
+ const clip = this.animations.get(name);
1046
+ if (!clip) return;
1047
+
1048
+ const action = this.mixer.clipAction(clip);
1049
+ action.loop = loop ? THREE.LoopRepeat : THREE.LoopOnce;
1050
+ action.play();
1051
+
1052
+ return action;
1053
+ }
1054
+
1055
+ update(deltaTime: number) {
1056
+ this.mixer.update(deltaTime);
1057
+ }
1058
+
1059
+ getModel() {
1060
+ return this.model;
1061
+ }
1062
+ }
1063
+
1064
+ // Usage Example
1065
+ async function setupMixamoCharacter() {
1066
+ // Method 1: Load character with animations
1067
+ const character = await MixamoCharacter.loadWithAnimations(
1068
+ '/models/character-with-animations.glb'
1069
+ );
1070
+
1071
+ // Method 2: Load character and add animations separately
1072
+ const character2 = await MixamoCharacter.loadWithAnimations(
1073
+ '/models/character.glb'
1074
+ );
1075
+ await character2.addAnimation('walk', '/animations/walking.fbx');
1076
+ await character2.addAnimation('run', '/animations/running.fbx');
1077
+
1078
+ character2.play('walk');
1079
+ }
1080
+ ```
1081
+
1082
+ ### Ready Player Me Integration
1083
+
1084
+ ```tsx
1085
+ // ReadyPlayerMeAvatar.tsx
1086
+ import { useEffect, useRef } from 'react';
1087
+ import { useGLTF, useAnimations } from '@react-three/drei';
1088
+ import * as THREE from 'three';
1089
+
1090
+ interface AvatarProps {
1091
+ avatarId: string;
1092
+ animationUrl?: string;
1093
+ position?: [number, number, number];
1094
+ }
1095
+
1096
+ export function ReadyPlayerMeAvatar({
1097
+ avatarId,
1098
+ animationUrl,
1099
+ position = [0, 0, 0]
1100
+ }: AvatarProps) {
1101
+ const group = useRef<THREE.Group>(null);
1102
+ const avatarUrl = `https://models.readyplayer.me/${avatarId}.glb`;
1103
+
1104
+ const { scene, animations } = useGLTF(avatarUrl);
1105
+ const { actions, names } = useAnimations(animations, group);
1106
+
1107
+ useEffect(() => {
1108
+ // Apply better lighting to RPM avatars
1109
+ scene.traverse((child) => {
1110
+ if (child instanceof THREE.Mesh) {
1111
+ child.castShadow = true;
1112
+ child.receiveShadow = true;
1113
+
1114
+ // Fix material issues
1115
+ if (child.material) {
1116
+ child.material.envMapIntensity = 1;
1117
+ }
1118
+ }
1119
+ });
1120
+ }, [scene]);
1121
+
1122
+ useEffect(() => {
1123
+ // Play first animation or idle
1124
+ const actionName = names.includes('Idle') ? 'Idle' : names[0];
1125
+ if (actionName && actions[actionName]) {
1126
+ actions[actionName]?.play();
1127
+ }
1128
+ }, [actions, names]);
1129
+
1130
+ return (
1131
+ <group ref={group} position={position}>
1132
+ <primitive object={scene} />
1133
+ </group>
1134
+ );
1135
+ }
1136
+
1137
+ // Preload avatars for better performance
1138
+ useGLTF.preload('https://models.readyplayer.me/your-avatar-id.glb');
1139
+ ```
1140
+
1141
+ ---
1142
+
1143
+ ## 🤖 PART 4: NPC AI & Game Behaviors
1144
+
1145
+ ### Yuka.js Integration for Game AI
1146
+
1147
+ ```bash
1148
+ npm install yuka
1149
+ ```
1150
+
1151
+ ```typescript
1152
+ // npcAI.ts
1153
+ import * as YUKA from 'yuka';
1154
+ import * as THREE from 'three';
1155
+
1156
+ export class NPCController {
1157
+ private entityManager: YUKA.EntityManager;
1158
+ private time: YUKA.Time;
1159
+ private vehicle: YUKA.Vehicle;
1160
+ private mesh: THREE.Mesh;
1161
+
1162
+ constructor(mesh: THREE.Mesh) {
1163
+ this.mesh = mesh;
1164
+ this.entityManager = new YUKA.EntityManager();
1165
+ this.time = new YUKA.Time();
1166
+
1167
+ // Create vehicle (NPC)
1168
+ this.vehicle = new YUKA.Vehicle();
1169
+ this.vehicle.maxSpeed = 3;
1170
+ this.vehicle.maxForce = 10;
1171
+ this.vehicle.setRenderComponent(mesh, this.sync);
1172
+
1173
+ this.entityManager.add(this.vehicle);
1174
+ }
1175
+
1176
+ // Sync Three.js mesh with Yuka entity
1177
+ private sync(entity: YUKA.GameEntity, renderComponent: THREE.Object3D) {
1178
+ renderComponent.position.copy(entity.position as any);
1179
+ renderComponent.quaternion.copy(entity.rotation as any);
1180
+ }
1181
+
1182
+ // Seek behavior - move towards target
1183
+ seekTarget(targetPosition: THREE.Vector3) {
1184
+ const seekBehavior = new YUKA.SeekBehavior(
1185
+ new YUKA.Vector3(targetPosition.x, targetPosition.y, targetPosition.z)
1186
+ );
1187
+ this.vehicle.steering.add(seekBehavior);
1188
+ }
1189
+
1190
+ // Flee behavior - run away from threat
1191
+ fleeFrom(threatPosition: THREE.Vector3) {
1192
+ const fleeBehavior = new YUKA.FleeBehavior(
1193
+ new YUKA.Vector3(threatPosition.x, threatPosition.y, threatPosition.z)
1194
+ );
1195
+ this.vehicle.steering.add(fleeBehavior);
1196
+ }
1197
+
1198
+ // Wander behavior - random movement
1199
+ enableWander() {
1200
+ const wanderBehavior = new YUKA.WanderBehavior();
1201
+ wanderBehavior.radius = 5;
1202
+ wanderBehavior.distance = 10;
1203
+ wanderBehavior.jitter = 2;
1204
+ this.vehicle.steering.add(wanderBehavior);
1205
+ }
1206
+
1207
+ // Follow path behavior
1208
+ followPath(waypoints: THREE.Vector3[]) {
1209
+ const path = new YUKA.Path();
1210
+ waypoints.forEach((wp) => {
1211
+ path.add(new YUKA.Vector3(wp.x, wp.y, wp.z));
1212
+ });
1213
+ path.loop = true;
1214
+
1215
+ const followPathBehavior = new YUKA.FollowPathBehavior(path, 0.5);
1216
+ this.vehicle.steering.add(followPathBehavior);
1217
+ }
1218
+
1219
+ // Obstacle avoidance
1220
+ enableObstacleAvoidance(obstacles: YUKA.GameEntity[]) {
1221
+ const obstacleAvoidance = new YUKA.ObstacleAvoidanceBehavior(obstacles);
1222
+ this.vehicle.steering.add(obstacleAvoidance);
1223
+ }
1224
+
1225
+ update() {
1226
+ const delta = this.time.update().getDelta();
1227
+ this.entityManager.update(delta);
1228
+ }
1229
+
1230
+ getVehicle() {
1231
+ return this.vehicle;
1232
+ }
1233
+ }
1234
+ ```
1235
+
1236
+ ### Advanced NPC with State Machine
1237
+
1238
+ ```typescript
1239
+ // npcStateMachine.ts
1240
+ import * as YUKA from 'yuka';
1241
+
1242
+ export class NPCWithStates extends YUKA.GameEntity {
1243
+ private stateMachine: YUKA.StateMachine<NPCWithStates>;
1244
+ private states: {
1245
+ idle: YUKA.State<NPCWithStates>;
1246
+ patrol: YUKA.State<NPCWithStates>;
1247
+ chase: YUKA.State<NPCWithStates>;
1248
+ attack: YUKA.State<NPCWithStates>;
1249
+ flee: YUKA.State<NPCWithStates>;
1250
+ };
1251
+
1252
+ public target: YUKA.GameEntity | null = null;
1253
+ public health: number = 100;
1254
+ public detectionRadius: number = 10;
1255
+ public attackRange: number = 2;
1256
+
1257
+ constructor() {
1258
+ super();
1259
+ this.stateMachine = new YUKA.StateMachine(this);
1260
+ this.states = this.setupStates();
1261
+ this.stateMachine.currentState = this.states.idle;
1262
+ }
1263
+
1264
+ private setupStates() {
1265
+ // Idle State
1266
+ const idleState = new YUKA.State('idle');
1267
+ idleState.enter = () => console.log('NPC is idle');
1268
+ idleState.execute = (npc: NPCWithStates) => {
1269
+ // Check for nearby targets
1270
+ if (npc.detectTarget()) {
1271
+ npc.stateMachine.changeState(npc.states.chase);
1272
+ }
1273
+ };
1274
+
1275
+ // Patrol State
1276
+ const patrolState = new YUKA.State('patrol');
1277
+ patrolState.execute = (npc: NPCWithStates) => {
1278
+ // Patrol logic
1279
+ if (npc.detectTarget()) {
1280
+ npc.stateMachine.changeState(npc.states.chase);
1281
+ }
1282
+ };
1283
+
1284
+ // Chase State
1285
+ const chaseState = new YUKA.State('chase');
1286
+ chaseState.enter = () => console.log('NPC is chasing');
1287
+ chaseState.execute = (npc: NPCWithStates) => {
1288
+ if (!npc.target) {
1289
+ npc.stateMachine.changeState(npc.states.idle);
1290
+ return;
1291
+ }
1292
+
1293
+ const distance = npc.position.distanceTo(npc.target.position);
1294
+
1295
+ if (distance <= npc.attackRange) {
1296
+ npc.stateMachine.changeState(npc.states.attack);
1297
+ } else if (distance > npc.detectionRadius) {
1298
+ npc.stateMachine.changeState(npc.states.idle);
1299
+ }
1300
+ };
1301
+
1302
+ // Attack State
1303
+ const attackState = new YUKA.State('attack');
1304
+ attackState.execute = (npc: NPCWithStates) => {
1305
+ if (!npc.target) {
1306
+ npc.stateMachine.changeState(npc.states.idle);
1307
+ return;
1308
+ }
1309
+
1310
+ const distance = npc.position.distanceTo(npc.target.position);
1311
+ if (distance > npc.attackRange) {
1312
+ npc.stateMachine.changeState(npc.states.chase);
1313
+ }
1314
+ };
1315
+
1316
+ // Flee State
1317
+ const fleeState = new YUKA.State('flee');
1318
+ fleeState.enter = () => console.log('NPC is fleeing');
1319
+ fleeState.execute = (npc: NPCWithStates) => {
1320
+ if (npc.health > 30) {
1321
+ npc.stateMachine.changeState(npc.states.idle);
1322
+ }
1323
+ };
1324
+
1325
+ return {
1326
+ idle: idleState,
1327
+ patrol: patrolState,
1328
+ chase: chaseState,
1329
+ attack: attackState,
1330
+ flee: fleeState,
1331
+ };
1332
+ }
1333
+
1334
+ detectTarget(): boolean {
1335
+ // Implement target detection logic
1336
+ return false; // Placeholder
1337
+ }
1338
+
1339
+ update(delta: number): this {
1340
+ this.stateMachine.update();
1341
+ return this;
1342
+ }
1343
+ }
1344
+ ```
1345
+
1346
+ ### React Three Fiber NPC Component
1347
+
1348
+ ```tsx
1349
+ // NPCComponent.tsx
1350
+ import { useRef, useEffect } from 'react';
1351
+ import { useFrame } from '@react-three/fiber';
1352
+ import { useGLTF } from '@react-three/drei';
1353
+ import { NPCController } from './npcAI';
1354
+ import * as THREE from 'three';
1355
+
1356
+ interface NPCProps {
1357
+ modelPath: string;
1358
+ initialPosition: [number, number, number];
1359
+ behavior: 'wander' | 'patrol' | 'chase';
1360
+ waypoints?: [number, number, number][];
1361
+ target?: THREE.Vector3;
1362
+ }
1363
+
1364
+ export function NPC({
1365
+ modelPath,
1366
+ initialPosition,
1367
+ behavior,
1368
+ waypoints,
1369
+ target
1370
+ }: NPCProps) {
1371
+ const { scene } = useGLTF(modelPath);
1372
+ const groupRef = useRef<THREE.Group>(null);
1373
+ const npcController = useRef<NPCController | null>(null);
1374
+
1375
+ useEffect(() => {
1376
+ if (!groupRef.current) return;
1377
+
1378
+ // Find the first mesh in the scene hierarchy
1379
+ // For production, use scene.getObjectByName() or traverse to find specific mesh
1380
+ let mesh: THREE.Mesh | undefined;
1381
+ scene.traverse((child) => {
1382
+ if (!mesh && child instanceof THREE.Mesh) {
1383
+ mesh = child;
1384
+ }
1385
+ });
1386
+
1387
+ if (!mesh) return;
1388
+
1389
+ npcController.current = new NPCController(mesh);
1390
+
1391
+ // Setup behavior
1392
+ switch (behavior) {
1393
+ case 'wander':
1394
+ npcController.current.enableWander();
1395
+ break;
1396
+ case 'patrol':
1397
+ if (waypoints) {
1398
+ const points = waypoints.map(wp => new THREE.Vector3(...wp));
1399
+ npcController.current.followPath(points);
1400
+ }
1401
+ break;
1402
+ case 'chase':
1403
+ if (target) {
1404
+ npcController.current.seekTarget(target);
1405
+ }
1406
+ break;
1407
+ }
1408
+ }, [behavior, waypoints, target, scene]);
1409
+
1410
+ useFrame(() => {
1411
+ npcController.current?.update();
1412
+ });
1413
+
1414
+ return (
1415
+ <group ref={groupRef} position={initialPosition}>
1416
+ <primitive object={scene} />
1417
+ </group>
1418
+ );
1419
+ }
1420
+ ```
1421
+
1422
+ ---
1423
+
1424
+ ## 🎲 PART 5: Procedural Generation
1425
+
1426
+ ### Procedural Character Generation
1427
+
1428
+ ```typescript
1429
+ // proceduralCharacter.ts
1430
+ import * as THREE from 'three';
1431
+
1432
+ interface CharacterParameters {
1433
+ height: number; // 0.5 - 1.5 (multiplier)
1434
+ bodyWidth: number; // 0.7 - 1.3
1435
+ headSize: number; // 0.8 - 1.2
1436
+ armLength: number; // 0.8 - 1.2
1437
+ legLength: number; // 0.9 - 1.1
1438
+ skinColor: THREE.Color;
1439
+ hairColor: THREE.Color;
1440
+ }
1441
+
1442
+ export class ProceduralCharacterGenerator {
1443
+ /**
1444
+ * Generate a procedural humanoid character
1445
+ * Inspired by parametric models like SMPL
1446
+ */
1447
+ static generate(params: CharacterParameters): THREE.Group {
1448
+ const character = new THREE.Group();
1449
+
1450
+ // Base measurements
1451
+ const baseHeight = 1.7 * params.height;
1452
+ const headHeight = 0.25 * params.headSize;
1453
+ const torsoHeight = 0.45 * baseHeight;
1454
+ const legHeight = 0.45 * baseHeight * params.legLength;
1455
+ const armLength = 0.35 * baseHeight * params.armLength;
1456
+
1457
+ // Head
1458
+ const headGeo = new THREE.SphereGeometry(
1459
+ headHeight / 2,
1460
+ 32,
1461
+ 32
1462
+ );
1463
+ const headMat = new THREE.MeshStandardMaterial({
1464
+ color: params.skinColor
1465
+ });
1466
+ const head = new THREE.Mesh(headGeo, headMat);
1467
+ head.position.y = baseHeight - headHeight / 2;
1468
+ head.castShadow = true;
1469
+ character.add(head);
1470
+
1471
+ // Torso
1472
+ const torsoGeo = new THREE.BoxGeometry(
1473
+ 0.3 * params.bodyWidth,
1474
+ torsoHeight,
1475
+ 0.2
1476
+ );
1477
+ const torso = new THREE.Mesh(torsoGeo, headMat);
1478
+ torso.position.y = baseHeight - headHeight - torsoHeight / 2;
1479
+ torso.castShadow = true;
1480
+ character.add(torso);
1481
+
1482
+ // Arms
1483
+ const armGeo = new THREE.CylinderGeometry(0.04, 0.04, armLength, 8);
1484
+ const leftArm = new THREE.Mesh(armGeo, headMat);
1485
+ leftArm.position.set(
1486
+ -0.15 * params.bodyWidth - 0.04,
1487
+ baseHeight - headHeight - armLength / 2 - 0.05,
1488
+ 0
1489
+ );
1490
+ leftArm.castShadow = true;
1491
+ character.add(leftArm);
1492
+
1493
+ const rightArm = leftArm.clone();
1494
+ rightArm.position.x *= -1;
1495
+ character.add(rightArm);
1496
+
1497
+ // Legs
1498
+ const legGeo = new THREE.CylinderGeometry(0.06, 0.05, legHeight, 8);
1499
+ const leftLeg = new THREE.Mesh(legGeo, headMat);
1500
+ leftLeg.position.set(
1501
+ -0.08,
1502
+ legHeight / 2,
1503
+ 0
1504
+ );
1505
+ leftLeg.castShadow = true;
1506
+ character.add(leftLeg);
1507
+
1508
+ const rightLeg = leftLeg.clone();
1509
+ rightLeg.position.x *= -1;
1510
+ character.add(rightLeg);
1511
+
1512
+ // Hair (simple)
1513
+ const hairGeo = new THREE.SphereGeometry(headHeight / 2 + 0.02, 32, 32);
1514
+ const hairMat = new THREE.MeshStandardMaterial({
1515
+ color: params.hairColor
1516
+ });
1517
+ const hair = new THREE.Mesh(hairGeo, hairMat);
1518
+ hair.position.copy(head.position);
1519
+ hair.position.y += 0.02;
1520
+ hair.scale.set(1, 0.6, 1);
1521
+ character.add(hair);
1522
+
1523
+ return character;
1524
+ }
1525
+
1526
+ /**
1527
+ * Generate random character parameters
1528
+ */
1529
+ static randomParams(): CharacterParameters {
1530
+ return {
1531
+ height: 0.8 + Math.random() * 0.4,
1532
+ bodyWidth: 0.85 + Math.random() * 0.3,
1533
+ headSize: 0.9 + Math.random() * 0.2,
1534
+ armLength: 0.9 + Math.random() * 0.2,
1535
+ legLength: 0.95 + Math.random() * 0.1,
1536
+ skinColor: new THREE.Color().setHSL(
1537
+ 0.08 + Math.random() * 0.05,
1538
+ 0.3 + Math.random() * 0.3,
1539
+ 0.4 + Math.random() * 0.3
1540
+ ),
1541
+ hairColor: new THREE.Color().setHSL(
1542
+ Math.random(),
1543
+ 0.5 + Math.random() * 0.5,
1544
+ 0.2 + Math.random() * 0.3
1545
+ ),
1546
+ };
1547
+ }
1548
+ }
1549
+
1550
+ // Usage
1551
+ const params = ProceduralCharacterGenerator.randomParams();
1552
+ const character = ProceduralCharacterGenerator.generate(params);
1553
+ scene.add(character);
1554
+ ```
1555
+
1556
+ ### Procedural Terrain Generation
1557
+
1558
+ ```bash
1559
+ npm install simplex-noise
1560
+ ```
1561
+
1562
+ ```typescript
1563
+ // proceduralTerrain.ts
1564
+ import * as THREE from 'three';
1565
+ import { SimplexNoise } from 'simplex-noise';
1566
+
1567
+ export class TerrainGenerator {
1568
+ static generate(
1569
+ width: number,
1570
+ depth: number,
1571
+ segments: number,
1572
+ heightScale: number = 10
1573
+ ): THREE.Mesh {
1574
+ const geometry = new THREE.PlaneGeometry(
1575
+ width,
1576
+ depth,
1577
+ segments,
1578
+ segments
1579
+ );
1580
+
1581
+ const noise = new SimplexNoise();
1582
+ const vertices = geometry.attributes.position.array;
1583
+
1584
+ for (let i = 0; i < vertices.length; i += 3) {
1585
+ const x = vertices[i];
1586
+ const z = vertices[i + 1];
1587
+
1588
+ // Multi-octave noise for realistic terrain
1589
+ let height = 0;
1590
+ height += noise.noise2D(x * 0.01, z * 0.01) * heightScale;
1591
+ height += noise.noise2D(x * 0.05, z * 0.05) * (heightScale * 0.25);
1592
+ height += noise.noise2D(x * 0.1, z * 0.1) * (heightScale * 0.125);
1593
+
1594
+ vertices[i + 2] = height;
1595
+ }
1596
+
1597
+ geometry.computeVertexNormals();
1598
+ geometry.rotateX(-Math.PI / 2);
1599
+
1600
+ // Vertex coloring based on height
1601
+ const colors: number[] = [];
1602
+ for (let i = 0; i < vertices.length; i += 3) {
1603
+ const height = vertices[i + 2];
1604
+ const color = new THREE.Color();
1605
+
1606
+ if (height < 0) {
1607
+ color.setHex(0x3a7bc8); // Water
1608
+ } else if (height < 2) {
1609
+ color.setHex(0xc2b280); // Sand
1610
+ } else if (height < 5) {
1611
+ color.setHex(0x567d46); // Grass
1612
+ } else {
1613
+ color.setHex(0x8b7355); // Mountain
1614
+ }
1615
+
1616
+ colors.push(color.r, color.g, color.b);
1617
+ }
1618
+
1619
+ geometry.setAttribute(
1620
+ 'color',
1621
+ new THREE.Float32BufferAttribute(colors, 3)
1622
+ );
1623
+
1624
+ const material = new THREE.MeshStandardMaterial({
1625
+ vertexColors: true,
1626
+ flatShading: true,
1627
+ });
1628
+
1629
+ return new THREE.Mesh(geometry, material);
1630
+ }
1631
+ }
1632
+ ```
1633
+
1634
+ ### Procedural Buildings
1635
+
1636
+ ```typescript
1637
+ // proceduralBuilding.ts
1638
+ import * as THREE from 'three';
1639
+
1640
+ export class BuildingGenerator {
1641
+ static generateBuilding(
1642
+ floors: number = 3 + Math.floor(Math.random() * 7),
1643
+ floorHeight: number = 3,
1644
+ width: number = 5 + Math.random() * 5,
1645
+ depth: number = 5 + Math.random() * 5
1646
+ ): THREE.Group {
1647
+ const building = new THREE.Group();
1648
+
1649
+ // Building material
1650
+ const wallMaterial = new THREE.MeshStandardMaterial({
1651
+ color: new THREE.Color().setHSL(0, 0, 0.6 + Math.random() * 0.2),
1652
+ roughness: 0.8,
1653
+ });
1654
+
1655
+ // Create floors
1656
+ for (let i = 0; i < floors; i++) {
1657
+ const floorGeo = new THREE.BoxGeometry(width, floorHeight, depth);
1658
+ const floor = new THREE.Mesh(floorGeo, wallMaterial);
1659
+ floor.position.y = i * floorHeight + floorHeight / 2;
1660
+ floor.castShadow = true;
1661
+ floor.receiveShadow = true;
1662
+ building.add(floor);
1663
+
1664
+ // Add windows
1665
+ BuildingGenerator.addWindows(floor, width, depth, floorHeight);
1666
+ }
1667
+
1668
+ // Roof
1669
+ const roofGeo = new THREE.ConeGeometry(
1670
+ Math.max(width, depth) * 0.7,
1671
+ floorHeight,
1672
+ 4
1673
+ );
1674
+ const roofMat = new THREE.MeshStandardMaterial({ color: 0x8b4513 });
1675
+ const roof = new THREE.Mesh(roofGeo, roofMat);
1676
+ roof.position.y = floors * floorHeight + floorHeight / 2;
1677
+ roof.rotation.y = Math.PI / 4;
1678
+ roof.castShadow = true;
1679
+ building.add(roof);
1680
+
1681
+ return building;
1682
+ }
1683
+
1684
+ private static addWindows(
1685
+ floor: THREE.Mesh,
1686
+ width: number,
1687
+ depth: number,
1688
+ height: number
1689
+ ) {
1690
+ const windowMaterial = new THREE.MeshStandardMaterial({
1691
+ color: 0x87ceeb,
1692
+ emissive: 0x87ceeb,
1693
+ emissiveIntensity: 0.3,
1694
+ });
1695
+
1696
+ const windowSize = 0.8;
1697
+ const windowDepth = 0.1;
1698
+
1699
+ // Front and back windows
1700
+ for (let i = -1; i <= 1; i += 2) {
1701
+ const windowGeo = new THREE.BoxGeometry(
1702
+ windowSize,
1703
+ windowSize,
1704
+ windowDepth
1705
+ );
1706
+ const window = new THREE.Mesh(windowGeo, windowMaterial);
1707
+ window.position.set(0, 0, (depth / 2 + windowDepth / 2) * i);
1708
+ floor.add(window);
1709
+ }
1710
+ }
1711
+ }
1712
+ ```
1713
+
1714
+ ---
1715
+
1716
+ ## ⚡ PART 6: Physics & Character Controllers
1717
+
1718
+ ### Rapier Physics Integration
1719
+
1720
+ ```bash
1721
+ npm install @react-three/rapier
1722
+ ```
1723
+
1724
+ ```tsx
1725
+ // PhysicsScene.tsx
1726
+ import { Physics, RigidBody, CuboidCollider } from '@react-three/rapier';
1727
+ import { Canvas } from '@react-three/fiber';
1728
+
1729
+ function PhysicsScene() {
1730
+ return (
1731
+ <Canvas>
1732
+ <Physics gravity={[0, -9.81, 0]}>
1733
+ {/* Ground */}
1734
+ <RigidBody type="fixed">
1735
+ <mesh rotation={[-Math.PI / 2, 0, 0]}>
1736
+ <planeGeometry args={[100, 100]} />
1737
+ <meshStandardMaterial color="gray" />
1738
+ </mesh>
1739
+ </RigidBody>
1740
+
1741
+ {/* Dynamic objects */}
1742
+ <RigidBody position={[0, 5, 0]}>
1743
+ <mesh castShadow>
1744
+ <boxGeometry />
1745
+ <meshStandardMaterial color="red" />
1746
+ </mesh>
1747
+ </RigidBody>
1748
+ </Physics>
1749
+
1750
+ <ambientLight intensity={0.5} />
1751
+ <directionalLight position={[10, 10, 5]} castShadow />
1752
+ </Canvas>
1753
+ );
1754
+ }
1755
+ ```
1756
+
1757
+ ### Character Controller with Physics
1758
+
1759
+ ```tsx
1760
+ // CharacterController.tsx
1761
+ import { useRef, useEffect } from 'react';
1762
+ import { useFrame } from '@react-three/fiber';
1763
+ import { RigidBody, CapsuleCollider, RapierRigidBody } from '@react-three/rapier';
1764
+ import { useKeyboardControls } from '@react-three/drei';
1765
+ import * as THREE from 'three';
1766
+
1767
+ interface CharacterControllerProps {
1768
+ position?: [number, number, number];
1769
+ }
1770
+
1771
+ export function CharacterController({ position = [0, 2, 0] }: CharacterControllerProps) {
1772
+ const rigidBodyRef = useRef<RapierRigidBody>(null);
1773
+ const [, get] = useKeyboardControls();
1774
+
1775
+ const velocity = useRef(new THREE.Vector3());
1776
+ const direction = useRef(new THREE.Vector3());
1777
+
1778
+ const moveSpeed = 5;
1779
+ const jumpForce = 5;
1780
+
1781
+ useFrame((state, delta) => {
1782
+ if (!rigidBodyRef.current) return;
1783
+
1784
+ const { forward, backward, left, right, jump } = get();
1785
+
1786
+ // Get current velocity
1787
+ const currentVel = rigidBodyRef.current.linvel();
1788
+ velocity.current.set(currentVel.x, currentVel.y, currentVel.z);
1789
+
1790
+ // Calculate movement direction
1791
+ direction.current.set(0, 0, 0);
1792
+ if (forward) direction.current.z -= 1;
1793
+ if (backward) direction.current.z += 1;
1794
+ if (left) direction.current.x -= 1;
1795
+ if (right) direction.current.x += 1;
1796
+
1797
+ direction.current.normalize();
1798
+
1799
+ // Apply movement
1800
+ velocity.current.x = direction.current.x * moveSpeed;
1801
+ velocity.current.z = direction.current.z * moveSpeed;
1802
+
1803
+ // Jump
1804
+ if (jump && Math.abs(currentVel.y) < 0.1) {
1805
+ velocity.current.y = jumpForce;
1806
+ }
1807
+
1808
+ rigidBodyRef.current.setLinvel(velocity.current, true);
1809
+ });
1810
+
1811
+ return (
1812
+ <RigidBody
1813
+ ref={rigidBodyRef}
1814
+ position={position}
1815
+ lockRotations={true}
1816
+ >
1817
+ <CapsuleCollider args={[0.5, 0.5]} />
1818
+ <mesh castShadow>
1819
+ <capsuleGeometry args={[0.5, 1]} />
1820
+ <meshStandardMaterial color="blue" />
1821
+ </mesh>
1822
+ </RigidBody>
1823
+ );
1824
+ }
1825
+
1826
+ // Keyboard controls setup
1827
+ import { KeyboardControls } from '@react-three/drei';
1828
+
1829
+ const keyboardMap = [
1830
+ { name: 'forward', keys: ['ArrowUp', 'KeyW'] },
1831
+ { name: 'backward', keys: ['ArrowDown', 'KeyS'] },
1832
+ { name: 'left', keys: ['ArrowLeft', 'KeyA'] },
1833
+ { name: 'right', keys: ['ArrowRight', 'KeyD'] },
1834
+ { name: 'jump', keys: ['Space'] },
1835
+ ];
1836
+
1837
+ function App() {
1838
+ return (
1839
+ <KeyboardControls map={keyboardMap}>
1840
+ <Canvas>
1841
+ {/* Your scene */}
1842
+ <CharacterController />
1843
+ </Canvas>
1844
+ </KeyboardControls>
1845
+ );
1846
+ }
1847
+ ```
1848
+
1849
+ ---
1850
+
1851
+ ## 📚 PART 7: Academic References & Research
1852
+
1853
+ ### Character Animation & Retargeting
1854
+
1855
+ **Key Concepts:**
1856
+ - **IK (Inverse Kinematics)**: Calculate joint angles to reach target positions (e.g., hand placement)
1857
+ - **FK (Forward Kinematics)**: Calculate end position from joint angles
1858
+ - **Retargeting**: Transfer animations between different skeletal structures
1859
+
1860
+ **Research Areas:**
1861
+ 1. **Motion Retargeting Techniques**
1862
+ - Skeleton correspondence construction
1863
+ - Preserving motion characteristics during transfer
1864
+ - IK-based vs FK-based approaches
1865
+
1866
+ 2. **Implementation References:**
1867
+ - SkeletonUtils (Three.js) for basic retargeting
1868
+ - upf-gti/retargeting-threejs - Open source retargeting solver
1869
+ - Mixamo workflow with Blender for manual retargeting
1870
+
1871
+ ### Parametric Body Models
1872
+
1873
+ #### SMPL (Skinned Multi-Person Linear)
1874
+
1875
+ - Parametric model encoding body shape and pose with ~100 parameters
1876
+ - Applications: avatar creation, virtual try-on, motion capture
1877
+ - Web implementation possible with SMPL-X JavaScript libraries
1878
+
1879
+ **Modern Approaches:**
1880
+ - **AvatarForge**: AI-driven multimodal avatar generation
1881
+ - **SmartAvatar**: VLM-based parametric avatar creation
1882
+ - **FaceMaker**: Procedural facial feature generation
1883
+
1884
+ **Academic Papers:**
1885
+ 1. "FaceMaker—A Procedural Face Generator to Foster Character Design Research"
1886
+ 2. "Parametric 3D human modeling with biharmonic SMPL"
1887
+ 3. "AvatarCraft: Transforming Text into Neural Human Avatars"
1888
+
1889
+ ### Game AI & Pathfinding
1890
+
1891
+ **Steering Behaviors (Craig Reynolds):**
1892
+ - Seek, Flee, Pursue, Evade
1893
+ - Wander, Arrive, Obstacle Avoidance
1894
+ - Path Following, Leader Following
1895
+
1896
+ **Pathfinding Algorithms:**
1897
+ - A* (A-Star) for optimal paths
1898
+ - Navigation meshes for 3D environments
1899
+ - Dynamic pathfinding for moving obstacles
1900
+
1901
+ **State Machines:**
1902
+ - Finite State Machines (FSM) for behavior control
1903
+ - Hierarchical State Machines for complex NPCs
1904
+ - Goal-Oriented Action Planning (GOAP)
1905
+
1906
+ **Implementation:**
1907
+ - Yuka.js - Complete game AI library for web
1908
+ - Integration with Three.js and React Three Fiber
1909
+
1910
+ ### Procedural Generation
1911
+
1912
+ **Noise Functions:**
1913
+ - Perlin Noise - Smooth, natural-looking randomness
1914
+ - Simplex Noise - Improved performance, no directional artifacts
1915
+ - Multi-octave noise for realistic terrain
1916
+
1917
+ **Procedural Techniques:**
1918
+ - L-Systems for organic structures (trees, plants)
1919
+ - Wave Function Collapse for level generation
1920
+ - Marching Cubes for volumetric terrain
1921
+ - Parametric models for character variation
1922
+
1923
+ ---
1924
+
1925
+ ## 🎯 Best Practices Summary
1926
+
1927
+ ### React Three Fiber
1928
+
1929
+ **DO:**
1930
+ - Use `useFrame` for animations, not `setInterval`
1931
+ - Leverage drei helpers for common tasks
1932
+ - Use `<Instances>` for repeated geometries
1933
+ - Implement proper cleanup in useEffect
1934
+ - Use Suspense for lazy loading models
1935
+ - Enable `concurrent` mode for better performance
1936
+
1937
+ **DON'T:**
1938
+ - Create Three.js objects in render (use useMemo)
1939
+ - Forget to dispose of geometries/materials
1940
+ - Use too many lights (performance impact)
1941
+ - Skip `dpr` limiting on mobile devices
1942
+
1943
+ ### Character Systems
1944
+
1945
+ **DO:**
1946
+ - Use GLTFLoader for web (not FBX when possible)
1947
+ - Implement animation blending with fadeIn/fadeOut
1948
+ - Cache loaded models with useGLTF.preload()
1949
+ - Use Mixamo for quick prototyping
1950
+ - Implement proper skeleton hierarchy
1951
+
1952
+ **DON'T:**
1953
+ - Mix incompatible skeletal structures
1954
+ - Skip bone name verification in retargeting
1955
+ - Forget to update animation mixer each frame
1956
+ - Load uncompressed models in production
1957
+
1958
+ ### NPC & Game AI
1959
+
1960
+ **DO:**
1961
+ - Use Yuka.js for production-ready AI
1962
+ - Implement state machines for complex behaviors
1963
+ - Optimize AI update frequency (not every frame)
1964
+ - Use spatial partitioning for large NPC counts
1965
+ - Add perception systems (vision, hearing)
1966
+
1967
+ **DON'T:**
1968
+ - Run pathfinding every frame
1969
+ - Skip behavior priority systems
1970
+ - Forget performance budgets for AI
1971
+ - Hardcode behavior values (use configuration)
1972
+
1973
+ ### Physics
1974
+
1975
+ **DO:**
1976
+ - Use Rapier for best web performance
1977
+ - Implement CCD for fast-moving objects
1978
+ - Use compound colliders for complex shapes
1979
+ - Limit physics updates to 60Hz
1980
+ - Implement sleep states for static objects
1981
+
1982
+ **DON'T:**
1983
+ - Use mesh colliders for everything
1984
+ - Skip collision layers/masks
1985
+ - Run physics at render framerate
1986
+ - Forget to cleanup physics objects
1987
+
1988
+ ---
1989
+
1990
+ ## 🔗 Essential Resources
1991
+
1992
+ ### Documentation
1993
+ - [Three.js](https://threejs.org/docs/)
1994
+ - [React Three Fiber](https://docs.pmnd.rs/react-three-fiber)
1995
+ - [Drei](https://github.com/pmndrs/drei)
1996
+ - [Yuka.js](https://mugen87.github.io/yuka/)
1997
+ - [Rapier](https://rapier.rs/)
1998
+
1999
+ ### Model Resources
2000
+ - [Ready Player Me](https://readyplayer.me/)
2001
+ - [Mixamo](https://www.mixamo.com/)
2002
+ - [Sketchfab](https://sketchfab.com/)
2003
+ - [Poly Haven](https://polyhaven.com/)
2004
+
2005
+ ### Learning
2006
+ - [Three.js Journey](https://threejs-journey.com/)
2007
+ - [Discover Three.js](https://discoverthreejs.com/)
2008
+ - [Bruno Simon's Portfolio](https://bruno-simon.com/)
2009
+
2010
+ ---
2011
+
2012
+ ## ✅ Complete Checklist
2013
+
2014
+ ### Basic Setup
2015
+ - [ ] Three.js or R3F installed
2016
+ - [ ] Scene, camera, renderer configured
2017
+ - [ ] Lighting system implemented
2018
+ - [ ] Controls added (OrbitControls/PointerLock)
2019
+ - [ ] Responsive canvas sizing
2020
+
2021
+ ### Character System
2022
+ - [ ] GLTF/GLB loader implemented
2023
+ - [ ] Animation system working
2024
+ - [ ] Character controller (if needed)
2025
+ - [ ] Ready Player Me or Mixamo integration
2026
+ - [ ] Animation blending functional
2027
+
2028
+ ### Game Features (if applicable)
2029
+ - [ ] NPC AI with Yuka.js
2030
+ - [ ] Physics with Rapier
2031
+ - [ ] Collision detection
2032
+ - [ ] State management
2033
+ - [ ] Input handling
2034
+
2035
+ ### Procedural Generation (if applicable)
2036
+ - [ ] Terrain generation
2037
+ - [ ] Character variations
2038
+ - [ ] Procedural buildings/props
2039
+ - [ ] Noise-based systems
2040
+
2041
+ ### Performance
2042
+ - [ ] Instancing for repeated objects
2043
+ - [ ] LOD system implemented
2044
+ - [ ] Frustum culling enabled
2045
+ - [ ] Texture optimization
2046
+ - [ ] Shadow map optimization
2047
+ - [ ] Post-processing optimized
2048
+
2049
+ ### Production Ready
2050
+ - [ ] Error boundaries added
2051
+ - [ ] Loading states implemented
2052
+ - [ ] Mobile optimization
2053
+ - [ ] Proper cleanup/disposal
2054
+ - [ ] Asset preloading
2055
+ - [ ] Performance monitoring