omgkit 2.2.0 → 2.3.1
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 +3 -3
- package/package.json +1 -1
- package/plugin/skills/databases/database-management/SKILL.md +288 -0
- package/plugin/skills/databases/database-migration/SKILL.md +285 -0
- package/plugin/skills/databases/database-schema-design/SKILL.md +195 -0
- package/plugin/skills/databases/mongodb/SKILL.md +60 -776
- package/plugin/skills/databases/prisma/SKILL.md +53 -744
- package/plugin/skills/databases/redis/SKILL.md +53 -860
- package/plugin/skills/databases/supabase/SKILL.md +283 -0
- package/plugin/skills/devops/aws/SKILL.md +68 -672
- package/plugin/skills/devops/github-actions/SKILL.md +54 -657
- package/plugin/skills/devops/kubernetes/SKILL.md +67 -602
- package/plugin/skills/devops/performance-profiling/SKILL.md +59 -863
- package/plugin/skills/frameworks/django/SKILL.md +87 -853
- package/plugin/skills/frameworks/express/SKILL.md +95 -1301
- package/plugin/skills/frameworks/fastapi/SKILL.md +90 -1198
- package/plugin/skills/frameworks/laravel/SKILL.md +87 -1187
- package/plugin/skills/frameworks/nestjs/SKILL.md +106 -973
- package/plugin/skills/frameworks/react/SKILL.md +94 -962
- package/plugin/skills/frameworks/vue/SKILL.md +95 -1242
- package/plugin/skills/frontend/accessibility/SKILL.md +91 -1056
- package/plugin/skills/frontend/frontend-design/SKILL.md +69 -1262
- package/plugin/skills/frontend/responsive/SKILL.md +76 -799
- package/plugin/skills/frontend/shadcn-ui/SKILL.md +73 -921
- package/plugin/skills/frontend/tailwindcss/SKILL.md +60 -788
- package/plugin/skills/frontend/threejs/SKILL.md +72 -1266
- package/plugin/skills/languages/javascript/SKILL.md +106 -849
- package/plugin/skills/methodology/brainstorming/SKILL.md +70 -576
- package/plugin/skills/methodology/defense-in-depth/SKILL.md +79 -831
- package/plugin/skills/methodology/dispatching-parallel-agents/SKILL.md +81 -654
- package/plugin/skills/methodology/executing-plans/SKILL.md +86 -529
- package/plugin/skills/methodology/finishing-development-branch/SKILL.md +95 -586
- package/plugin/skills/methodology/problem-solving/SKILL.md +67 -681
- package/plugin/skills/methodology/receiving-code-review/SKILL.md +70 -533
- package/plugin/skills/methodology/requesting-code-review/SKILL.md +70 -610
- package/plugin/skills/methodology/root-cause-tracing/SKILL.md +70 -646
- package/plugin/skills/methodology/sequential-thinking/SKILL.md +70 -478
- package/plugin/skills/methodology/systematic-debugging/SKILL.md +66 -559
- package/plugin/skills/methodology/test-driven-development/SKILL.md +91 -752
- package/plugin/skills/methodology/testing-anti-patterns/SKILL.md +78 -687
- package/plugin/skills/methodology/token-optimization/SKILL.md +72 -602
- package/plugin/skills/methodology/verification-before-completion/SKILL.md +108 -529
- package/plugin/skills/methodology/writing-plans/SKILL.md +79 -566
- package/plugin/skills/omega/omega-architecture/SKILL.md +91 -752
- package/plugin/skills/omega/omega-coding/SKILL.md +161 -552
- package/plugin/skills/omega/omega-sprint/SKILL.md +132 -777
- package/plugin/skills/omega/omega-testing/SKILL.md +157 -845
- package/plugin/skills/omega/omega-thinking/SKILL.md +165 -606
- package/plugin/skills/security/better-auth/SKILL.md +46 -1034
- package/plugin/skills/security/oauth/SKILL.md +80 -934
- package/plugin/skills/security/owasp/SKILL.md +78 -862
- package/plugin/skills/testing/playwright/SKILL.md +77 -700
- package/plugin/skills/testing/pytest/SKILL.md +73 -811
- package/plugin/skills/testing/vitest/SKILL.md +60 -920
- package/plugin/skills/tools/document-processing/SKILL.md +111 -838
- package/plugin/skills/tools/image-processing/SKILL.md +126 -659
- package/plugin/skills/tools/mcp-development/SKILL.md +85 -758
- package/plugin/skills/tools/media-processing/SKILL.md +118 -735
- package/plugin/stdrules/SKILL_STANDARDS.md +490 -0
- package/plugin/skills/SKILL_STANDARDS.md +0 -743
|
@@ -1,265 +1,57 @@
|
|
|
1
1
|
---
|
|
2
|
-
name:
|
|
3
|
-
description:
|
|
4
|
-
category: frontend
|
|
5
|
-
triggers:
|
|
6
|
-
- three.js
|
|
7
|
-
- threejs
|
|
8
|
-
- 3d graphics
|
|
9
|
-
- webgl
|
|
10
|
-
- react three fiber
|
|
11
|
-
- r3f
|
|
12
|
-
- 3d animation
|
|
13
|
-
- shaders
|
|
14
|
-
- 3d scenes
|
|
2
|
+
name: building-3d-graphics
|
|
3
|
+
description: Claude builds immersive 3D web experiences with Three.js and React Three Fiber. Use when creating WebGL scenes, 3D animations, shaders, or physics simulations.
|
|
15
4
|
---
|
|
16
5
|
|
|
17
|
-
#
|
|
6
|
+
# Building 3D Graphics
|
|
18
7
|
|
|
19
|
-
|
|
8
|
+
## Quick Start
|
|
20
9
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
- Create interactive 3D scenes with proper camera controls
|
|
26
|
-
- Build declarative 3D with React Three Fiber
|
|
27
|
-
- Implement physically-based rendering (PBR) materials
|
|
28
|
-
- Add realistic lighting and shadows
|
|
29
|
-
- Create smooth animations and transitions
|
|
30
|
-
- Write custom shaders for visual effects
|
|
31
|
-
- Integrate physics simulations
|
|
32
|
-
- Optimize performance for production
|
|
33
|
-
|
|
34
|
-
## Features
|
|
35
|
-
|
|
36
|
-
### 1. Core Three.js Setup
|
|
37
|
-
|
|
38
|
-
```typescript
|
|
39
|
-
// lib/three/SceneManager.ts
|
|
40
|
-
import * as THREE from 'three';
|
|
41
|
-
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls';
|
|
42
|
-
import { EffectComposer } from 'three/examples/jsm/postprocessing/EffectComposer';
|
|
43
|
-
import { RenderPass } from 'three/examples/jsm/postprocessing/RenderPass';
|
|
44
|
-
|
|
45
|
-
interface SceneManagerOptions {
|
|
46
|
-
container: HTMLElement;
|
|
47
|
-
width?: number;
|
|
48
|
-
height?: number;
|
|
49
|
-
antialias?: boolean;
|
|
50
|
-
alpha?: boolean;
|
|
51
|
-
pixelRatio?: number;
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
export class SceneManager {
|
|
55
|
-
private scene: THREE.Scene;
|
|
56
|
-
private camera: THREE.PerspectiveCamera;
|
|
57
|
-
private renderer: THREE.WebGLRenderer;
|
|
58
|
-
private controls: OrbitControls;
|
|
59
|
-
private composer: EffectComposer;
|
|
60
|
-
private clock: THREE.Clock;
|
|
61
|
-
private animationFrameId: number | null = null;
|
|
62
|
-
private updateCallbacks: Set<(delta: number, elapsed: number) => void> = new Set();
|
|
63
|
-
|
|
64
|
-
constructor(options: SceneManagerOptions) {
|
|
65
|
-
const {
|
|
66
|
-
container,
|
|
67
|
-
width = container.clientWidth,
|
|
68
|
-
height = container.clientHeight,
|
|
69
|
-
antialias = true,
|
|
70
|
-
alpha = false,
|
|
71
|
-
pixelRatio = Math.min(window.devicePixelRatio, 2),
|
|
72
|
-
} = options;
|
|
73
|
-
|
|
74
|
-
// Scene
|
|
75
|
-
this.scene = new THREE.Scene();
|
|
76
|
-
this.scene.background = alpha ? null : new THREE.Color(0x111111);
|
|
77
|
-
|
|
78
|
-
// Camera
|
|
79
|
-
this.camera = new THREE.PerspectiveCamera(75, width / height, 0.1, 1000);
|
|
80
|
-
this.camera.position.set(0, 2, 5);
|
|
81
|
-
|
|
82
|
-
// Renderer
|
|
83
|
-
this.renderer = new THREE.WebGLRenderer({
|
|
84
|
-
antialias,
|
|
85
|
-
alpha,
|
|
86
|
-
powerPreference: 'high-performance',
|
|
87
|
-
});
|
|
88
|
-
this.renderer.setSize(width, height);
|
|
89
|
-
this.renderer.setPixelRatio(pixelRatio);
|
|
90
|
-
this.renderer.shadowMap.enabled = true;
|
|
91
|
-
this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;
|
|
92
|
-
this.renderer.outputColorSpace = THREE.SRGBColorSpace;
|
|
93
|
-
this.renderer.toneMapping = THREE.ACESFilmicToneMapping;
|
|
94
|
-
this.renderer.toneMappingExposure = 1.0;
|
|
95
|
-
container.appendChild(this.renderer.domElement);
|
|
96
|
-
|
|
97
|
-
// Controls
|
|
98
|
-
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
|
|
99
|
-
this.controls.enableDamping = true;
|
|
100
|
-
this.controls.dampingFactor = 0.05;
|
|
101
|
-
this.controls.minDistance = 1;
|
|
102
|
-
this.controls.maxDistance = 100;
|
|
103
|
-
|
|
104
|
-
// Post-processing
|
|
105
|
-
this.composer = new EffectComposer(this.renderer);
|
|
106
|
-
this.composer.addPass(new RenderPass(this.scene, this.camera));
|
|
107
|
-
|
|
108
|
-
// Clock
|
|
109
|
-
this.clock = new THREE.Clock();
|
|
110
|
-
|
|
111
|
-
// Handle resize
|
|
112
|
-
this.handleResize = this.handleResize.bind(this);
|
|
113
|
-
window.addEventListener('resize', this.handleResize);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
private handleResize(): void {
|
|
117
|
-
const container = this.renderer.domElement.parentElement;
|
|
118
|
-
if (!container) return;
|
|
119
|
-
|
|
120
|
-
const width = container.clientWidth;
|
|
121
|
-
const height = container.clientHeight;
|
|
122
|
-
|
|
123
|
-
this.camera.aspect = width / height;
|
|
124
|
-
this.camera.updateProjectionMatrix();
|
|
125
|
-
|
|
126
|
-
this.renderer.setSize(width, height);
|
|
127
|
-
this.composer.setSize(width, height);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
public addUpdateCallback(callback: (delta: number, elapsed: number) => void): void {
|
|
131
|
-
this.updateCallbacks.add(callback);
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
public removeUpdateCallback(callback: (delta: number, elapsed: number) => void): void {
|
|
135
|
-
this.updateCallbacks.delete(callback);
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
public start(): void {
|
|
139
|
-
if (this.animationFrameId !== null) return;
|
|
140
|
-
|
|
141
|
-
const animate = () => {
|
|
142
|
-
this.animationFrameId = requestAnimationFrame(animate);
|
|
143
|
-
|
|
144
|
-
const delta = this.clock.getDelta();
|
|
145
|
-
const elapsed = this.clock.getElapsedTime();
|
|
146
|
-
|
|
147
|
-
// Update controls
|
|
148
|
-
this.controls.update();
|
|
149
|
-
|
|
150
|
-
// Run update callbacks
|
|
151
|
-
this.updateCallbacks.forEach((callback) => callback(delta, elapsed));
|
|
152
|
-
|
|
153
|
-
// Render
|
|
154
|
-
this.composer.render();
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
animate();
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
public stop(): void {
|
|
161
|
-
if (this.animationFrameId !== null) {
|
|
162
|
-
cancelAnimationFrame(this.animationFrameId);
|
|
163
|
-
this.animationFrameId = null;
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
|
|
167
|
-
public getScene(): THREE.Scene {
|
|
168
|
-
return this.scene;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
public getCamera(): THREE.PerspectiveCamera {
|
|
172
|
-
return this.camera;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
public getRenderer(): THREE.WebGLRenderer {
|
|
176
|
-
return this.renderer;
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
public dispose(): void {
|
|
180
|
-
this.stop();
|
|
181
|
-
window.removeEventListener('resize', this.handleResize);
|
|
182
|
-
|
|
183
|
-
// Dispose of all objects in scene
|
|
184
|
-
this.scene.traverse((object) => {
|
|
185
|
-
if (object instanceof THREE.Mesh) {
|
|
186
|
-
object.geometry.dispose();
|
|
187
|
-
if (Array.isArray(object.material)) {
|
|
188
|
-
object.material.forEach((m) => m.dispose());
|
|
189
|
-
} else {
|
|
190
|
-
object.material.dispose();
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
});
|
|
10
|
+
```tsx
|
|
11
|
+
import { Canvas } from '@react-three/fiber';
|
|
12
|
+
import { OrbitControls, Environment } from '@react-three/drei';
|
|
194
13
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
14
|
+
export function Scene() {
|
|
15
|
+
return (
|
|
16
|
+
<Canvas shadows camera={{ position: [5, 3, 5], fov: 50 }}>
|
|
17
|
+
<ambientLight intensity={0.5} />
|
|
18
|
+
<directionalLight position={[10, 10, 5]} castShadow />
|
|
19
|
+
<mesh castShadow>
|
|
20
|
+
<boxGeometry args={[1, 1, 1]} />
|
|
21
|
+
<meshStandardMaterial color="#4ecdc4" />
|
|
22
|
+
</mesh>
|
|
23
|
+
<OrbitControls enableDamping />
|
|
24
|
+
<Environment preset="city" />
|
|
25
|
+
</Canvas>
|
|
26
|
+
);
|
|
198
27
|
}
|
|
28
|
+
```
|
|
199
29
|
|
|
200
|
-
|
|
201
|
-
const container = document.getElementById('canvas-container')!;
|
|
202
|
-
const sceneManager = new SceneManager({ container, antialias: true });
|
|
203
|
-
|
|
204
|
-
// Add objects
|
|
205
|
-
const geometry = new THREE.BoxGeometry(1, 1, 1);
|
|
206
|
-
const material = new THREE.MeshStandardMaterial({ color: 0x00ff00 });
|
|
207
|
-
const cube = new THREE.Mesh(geometry, material);
|
|
208
|
-
cube.castShadow = true;
|
|
209
|
-
sceneManager.getScene().add(cube);
|
|
30
|
+
## Features
|
|
210
31
|
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
32
|
+
| Feature | Description | Guide |
|
|
33
|
+
|---------|-------------|-------|
|
|
34
|
+
| Scene Management | Renderer, camera, controls setup with proper disposal | `ref/scene-manager.md` |
|
|
35
|
+
| React Three Fiber | Declarative 3D with React components and hooks | `ref/r3f-patterns.md` |
|
|
36
|
+
| Custom Shaders | GLSL vertex/fragment shaders with uniforms | `ref/shader-materials.md` |
|
|
37
|
+
| Physics | Rapier physics with rigid bodies and colliders | `ref/physics-system.md` |
|
|
38
|
+
| Animation | GSAP and Three.js animation mixer integration | `ref/animation.md` |
|
|
39
|
+
| Performance | LOD, instancing, frustum culling, texture optimization | `ref/optimization.md` |
|
|
217
40
|
|
|
218
|
-
|
|
219
|
-
sceneManager.addUpdateCallback((delta) => {
|
|
220
|
-
cube.rotation.x += delta;
|
|
221
|
-
cube.rotation.y += delta * 0.5;
|
|
222
|
-
});
|
|
41
|
+
## Common Patterns
|
|
223
42
|
|
|
224
|
-
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
### 2. React Three Fiber Integration
|
|
43
|
+
### Animated Component with Interaction
|
|
228
44
|
|
|
229
45
|
```tsx
|
|
230
|
-
// components/Scene.tsx
|
|
231
|
-
import { Canvas, useFrame, useThree } from '@react-three/fiber';
|
|
232
|
-
import {
|
|
233
|
-
OrbitControls,
|
|
234
|
-
Environment,
|
|
235
|
-
ContactShadows,
|
|
236
|
-
PerspectiveCamera,
|
|
237
|
-
useGLTF,
|
|
238
|
-
Float,
|
|
239
|
-
Text3D,
|
|
240
|
-
Center,
|
|
241
|
-
MeshTransmissionMaterial,
|
|
242
|
-
} from '@react-three/drei';
|
|
243
|
-
import { Suspense, useRef, useState } from 'react';
|
|
244
|
-
import * as THREE from 'three';
|
|
245
|
-
|
|
246
|
-
// Animated box component
|
|
247
46
|
function AnimatedBox({ position }: { position: [number, number, number] }) {
|
|
248
47
|
const meshRef = useRef<THREE.Mesh>(null);
|
|
249
48
|
const [hovered, setHovered] = useState(false);
|
|
250
|
-
const [clicked, setClicked] = useState(false);
|
|
251
49
|
|
|
252
50
|
useFrame((state, delta) => {
|
|
253
51
|
if (meshRef.current) {
|
|
254
|
-
meshRef.current.rotation.
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
// Smooth scale animation
|
|
258
|
-
const targetScale = clicked ? 1.5 : hovered ? 1.2 : 1;
|
|
259
|
-
meshRef.current.scale.lerp(
|
|
260
|
-
new THREE.Vector3(targetScale, targetScale, targetScale),
|
|
261
|
-
0.1
|
|
262
|
-
);
|
|
52
|
+
meshRef.current.rotation.y += delta * 0.5;
|
|
53
|
+
const scale = hovered ? 1.2 : 1;
|
|
54
|
+
meshRef.current.scale.lerp(new THREE.Vector3(scale, scale, scale), 0.1);
|
|
263
55
|
}
|
|
264
56
|
});
|
|
265
57
|
|
|
@@ -269,1060 +61,74 @@ function AnimatedBox({ position }: { position: [number, number, number] }) {
|
|
|
269
61
|
position={position}
|
|
270
62
|
onPointerOver={() => setHovered(true)}
|
|
271
63
|
onPointerOut={() => setHovered(false)}
|
|
272
|
-
onClick={() => setClicked(!clicked)}
|
|
273
64
|
>
|
|
274
65
|
<boxGeometry args={[1, 1, 1]} />
|
|
275
|
-
<meshStandardMaterial
|
|
276
|
-
color={hovered ? '#ff6b6b' : '#4ecdc4'}
|
|
277
|
-
metalness={0.5}
|
|
278
|
-
roughness={0.2}
|
|
279
|
-
/>
|
|
280
|
-
</mesh>
|
|
281
|
-
);
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
// GLTF Model loader
|
|
285
|
-
function Model({ url, scale = 1 }: { url: string; scale?: number }) {
|
|
286
|
-
const { scene } = useGLTF(url);
|
|
287
|
-
const modelRef = useRef<THREE.Group>(null);
|
|
288
|
-
|
|
289
|
-
// Clone materials for unique instances
|
|
290
|
-
useEffect(() => {
|
|
291
|
-
scene.traverse((child) => {
|
|
292
|
-
if (child instanceof THREE.Mesh) {
|
|
293
|
-
child.castShadow = true;
|
|
294
|
-
child.receiveShadow = true;
|
|
295
|
-
}
|
|
296
|
-
});
|
|
297
|
-
}, [scene]);
|
|
298
|
-
|
|
299
|
-
return (
|
|
300
|
-
<primitive
|
|
301
|
-
ref={modelRef}
|
|
302
|
-
object={scene}
|
|
303
|
-
scale={scale}
|
|
304
|
-
dispose={null}
|
|
305
|
-
/>
|
|
306
|
-
);
|
|
307
|
-
}
|
|
308
|
-
|
|
309
|
-
// Glass material sphere
|
|
310
|
-
function GlassSphere() {
|
|
311
|
-
return (
|
|
312
|
-
<mesh position={[2, 1, 0]}>
|
|
313
|
-
<sphereGeometry args={[0.8, 64, 64]} />
|
|
314
|
-
<MeshTransmissionMaterial
|
|
315
|
-
backside
|
|
316
|
-
samples={16}
|
|
317
|
-
thickness={0.5}
|
|
318
|
-
chromaticAberration={0.5}
|
|
319
|
-
anisotropy={0.3}
|
|
320
|
-
distortion={0.2}
|
|
321
|
-
distortionScale={0.5}
|
|
322
|
-
temporalDistortion={0.1}
|
|
323
|
-
iridescence={1}
|
|
324
|
-
iridescenceIOR={1}
|
|
325
|
-
iridescenceThicknessRange={[0, 1400]}
|
|
326
|
-
/>
|
|
66
|
+
<meshStandardMaterial color={hovered ? '#ff6b6b' : '#4ecdc4'} />
|
|
327
67
|
</mesh>
|
|
328
68
|
);
|
|
329
69
|
}
|
|
330
|
-
|
|
331
|
-
// 3D Text
|
|
332
|
-
function Text3DComponent({ text }: { text: string }) {
|
|
333
|
-
return (
|
|
334
|
-
<Center position={[0, 2, 0]}>
|
|
335
|
-
<Float speed={2} rotationIntensity={0.5} floatIntensity={1}>
|
|
336
|
-
<Text3D
|
|
337
|
-
font="/fonts/Inter_Bold.json"
|
|
338
|
-
size={0.5}
|
|
339
|
-
height={0.1}
|
|
340
|
-
curveSegments={12}
|
|
341
|
-
>
|
|
342
|
-
{text}
|
|
343
|
-
<meshStandardMaterial color="#ff6b6b" metalness={0.8} roughness={0.2} />
|
|
344
|
-
</Text3D>
|
|
345
|
-
</Float>
|
|
346
|
-
</Center>
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
// Camera rig with smooth following
|
|
351
|
-
function CameraRig({ target }: { target: THREE.Vector3 }) {
|
|
352
|
-
const { camera } = useThree();
|
|
353
|
-
|
|
354
|
-
useFrame(() => {
|
|
355
|
-
camera.position.lerp(
|
|
356
|
-
new THREE.Vector3(target.x + 5, target.y + 3, target.z + 5),
|
|
357
|
-
0.02
|
|
358
|
-
);
|
|
359
|
-
camera.lookAt(target);
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
return null;
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
// Main scene
|
|
366
|
-
export function Scene() {
|
|
367
|
-
return (
|
|
368
|
-
<Canvas
|
|
369
|
-
shadows
|
|
370
|
-
dpr={[1, 2]}
|
|
371
|
-
gl={{
|
|
372
|
-
antialias: true,
|
|
373
|
-
toneMapping: THREE.ACESFilmicToneMapping,
|
|
374
|
-
toneMappingExposure: 1,
|
|
375
|
-
}}
|
|
376
|
-
>
|
|
377
|
-
<PerspectiveCamera makeDefault position={[5, 3, 5]} fov={50} />
|
|
378
|
-
<OrbitControls
|
|
379
|
-
enableDamping
|
|
380
|
-
dampingFactor={0.05}
|
|
381
|
-
minDistance={2}
|
|
382
|
-
maxDistance={20}
|
|
383
|
-
/>
|
|
384
|
-
|
|
385
|
-
{/* Lighting */}
|
|
386
|
-
<ambientLight intensity={0.5} />
|
|
387
|
-
<directionalLight
|
|
388
|
-
position={[10, 10, 5]}
|
|
389
|
-
intensity={1.5}
|
|
390
|
-
castShadow
|
|
391
|
-
shadow-mapSize={[2048, 2048]}
|
|
392
|
-
shadow-camera-far={50}
|
|
393
|
-
shadow-camera-left={-10}
|
|
394
|
-
shadow-camera-right={10}
|
|
395
|
-
shadow-camera-top={10}
|
|
396
|
-
shadow-camera-bottom={-10}
|
|
397
|
-
/>
|
|
398
|
-
|
|
399
|
-
{/* Environment */}
|
|
400
|
-
<Environment preset="city" />
|
|
401
|
-
|
|
402
|
-
{/* Content */}
|
|
403
|
-
<Suspense fallback={null}>
|
|
404
|
-
<AnimatedBox position={[-2, 0.5, 0]} />
|
|
405
|
-
<AnimatedBox position={[0, 0.5, 0]} />
|
|
406
|
-
<GlassSphere />
|
|
407
|
-
<Text3DComponent text="Hello 3D" />
|
|
408
|
-
|
|
409
|
-
{/* Ground */}
|
|
410
|
-
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]} receiveShadow>
|
|
411
|
-
<planeGeometry args={[50, 50]} />
|
|
412
|
-
<meshStandardMaterial color="#1a1a2e" />
|
|
413
|
-
</mesh>
|
|
414
|
-
</Suspense>
|
|
415
|
-
|
|
416
|
-
{/* Contact shadows */}
|
|
417
|
-
<ContactShadows
|
|
418
|
-
position={[0, -0.49, 0]}
|
|
419
|
-
opacity={0.5}
|
|
420
|
-
scale={20}
|
|
421
|
-
blur={2}
|
|
422
|
-
far={10}
|
|
423
|
-
/>
|
|
424
|
-
</Canvas>
|
|
425
|
-
);
|
|
426
|
-
}
|
|
427
|
-
```
|
|
428
|
-
|
|
429
|
-
### 3. Custom Shaders
|
|
430
|
-
|
|
431
|
-
```tsx
|
|
432
|
-
// shaders/GradientMaterial.tsx
|
|
433
|
-
import { shaderMaterial } from '@react-three/drei';
|
|
434
|
-
import { extend, useFrame } from '@react-three/fiber';
|
|
435
|
-
import { useRef } from 'react';
|
|
436
|
-
import * as THREE from 'three';
|
|
437
|
-
|
|
438
|
-
// Vertex shader
|
|
439
|
-
const vertexShader = `
|
|
440
|
-
varying vec2 vUv;
|
|
441
|
-
varying vec3 vPosition;
|
|
442
|
-
varying vec3 vNormal;
|
|
443
|
-
|
|
444
|
-
uniform float uTime;
|
|
445
|
-
uniform float uAmplitude;
|
|
446
|
-
uniform float uFrequency;
|
|
447
|
-
|
|
448
|
-
void main() {
|
|
449
|
-
vUv = uv;
|
|
450
|
-
vPosition = position;
|
|
451
|
-
vNormal = normal;
|
|
452
|
-
|
|
453
|
-
// Wave displacement
|
|
454
|
-
vec3 pos = position;
|
|
455
|
-
float displacement = sin(pos.x * uFrequency + uTime) *
|
|
456
|
-
sin(pos.z * uFrequency + uTime) *
|
|
457
|
-
uAmplitude;
|
|
458
|
-
pos.y += displacement;
|
|
459
|
-
|
|
460
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(pos, 1.0);
|
|
461
|
-
}
|
|
462
|
-
`;
|
|
463
|
-
|
|
464
|
-
// Fragment shader
|
|
465
|
-
const fragmentShader = `
|
|
466
|
-
varying vec2 vUv;
|
|
467
|
-
varying vec3 vPosition;
|
|
468
|
-
varying vec3 vNormal;
|
|
469
|
-
|
|
470
|
-
uniform float uTime;
|
|
471
|
-
uniform vec3 uColorA;
|
|
472
|
-
uniform vec3 uColorB;
|
|
473
|
-
uniform float uNoiseScale;
|
|
474
|
-
|
|
475
|
-
// Simplex noise function
|
|
476
|
-
vec3 mod289(vec3 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
477
|
-
vec4 mod289(vec4 x) { return x - floor(x * (1.0 / 289.0)) * 289.0; }
|
|
478
|
-
vec4 permute(vec4 x) { return mod289(((x*34.0)+1.0)*x); }
|
|
479
|
-
vec4 taylorInvSqrt(vec4 r) { return 1.79284291400159 - 0.85373472095314 * r; }
|
|
480
|
-
|
|
481
|
-
float snoise(vec3 v) {
|
|
482
|
-
const vec2 C = vec2(1.0/6.0, 1.0/3.0);
|
|
483
|
-
const vec4 D = vec4(0.0, 0.5, 1.0, 2.0);
|
|
484
|
-
|
|
485
|
-
vec3 i = floor(v + dot(v, C.yyy));
|
|
486
|
-
vec3 x0 = v - i + dot(i, C.xxx);
|
|
487
|
-
|
|
488
|
-
vec3 g = step(x0.yzx, x0.xyz);
|
|
489
|
-
vec3 l = 1.0 - g;
|
|
490
|
-
vec3 i1 = min(g.xyz, l.zxy);
|
|
491
|
-
vec3 i2 = max(g.xyz, l.zxy);
|
|
492
|
-
|
|
493
|
-
vec3 x1 = x0 - i1 + C.xxx;
|
|
494
|
-
vec3 x2 = x0 - i2 + C.yyy;
|
|
495
|
-
vec3 x3 = x0 - D.yyy;
|
|
496
|
-
|
|
497
|
-
i = mod289(i);
|
|
498
|
-
vec4 p = permute(permute(permute(
|
|
499
|
-
i.z + vec4(0.0, i1.z, i2.z, 1.0))
|
|
500
|
-
+ i.y + vec4(0.0, i1.y, i2.y, 1.0))
|
|
501
|
-
+ i.x + vec4(0.0, i1.x, i2.x, 1.0));
|
|
502
|
-
|
|
503
|
-
float n_ = 0.142857142857;
|
|
504
|
-
vec3 ns = n_ * D.wyz - D.xzx;
|
|
505
|
-
|
|
506
|
-
vec4 j = p - 49.0 * floor(p * ns.z * ns.z);
|
|
507
|
-
|
|
508
|
-
vec4 x_ = floor(j * ns.z);
|
|
509
|
-
vec4 y_ = floor(j - 7.0 * x_);
|
|
510
|
-
|
|
511
|
-
vec4 x = x_ *ns.x + ns.yyyy;
|
|
512
|
-
vec4 y = y_ *ns.x + ns.yyyy;
|
|
513
|
-
vec4 h = 1.0 - abs(x) - abs(y);
|
|
514
|
-
|
|
515
|
-
vec4 b0 = vec4(x.xy, y.xy);
|
|
516
|
-
vec4 b1 = vec4(x.zw, y.zw);
|
|
517
|
-
|
|
518
|
-
vec4 s0 = floor(b0)*2.0 + 1.0;
|
|
519
|
-
vec4 s1 = floor(b1)*2.0 + 1.0;
|
|
520
|
-
vec4 sh = -step(h, vec4(0.0));
|
|
521
|
-
|
|
522
|
-
vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy;
|
|
523
|
-
vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww;
|
|
524
|
-
|
|
525
|
-
vec3 p0 = vec3(a0.xy,h.x);
|
|
526
|
-
vec3 p1 = vec3(a0.zw,h.y);
|
|
527
|
-
vec3 p2 = vec3(a1.xy,h.z);
|
|
528
|
-
vec3 p3 = vec3(a1.zw,h.w);
|
|
529
|
-
|
|
530
|
-
vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2,p2), dot(p3,p3)));
|
|
531
|
-
p0 *= norm.x;
|
|
532
|
-
p1 *= norm.y;
|
|
533
|
-
p2 *= norm.z;
|
|
534
|
-
p3 *= norm.w;
|
|
535
|
-
|
|
536
|
-
vec4 m = max(0.6 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0);
|
|
537
|
-
m = m * m;
|
|
538
|
-
return 42.0 * dot(m*m, vec4(dot(p0,x0), dot(p1,x1), dot(p2,x2), dot(p3,x3)));
|
|
539
|
-
}
|
|
540
|
-
|
|
541
|
-
void main() {
|
|
542
|
-
// Animated noise
|
|
543
|
-
float noise = snoise(vec3(vPosition.xz * uNoiseScale, uTime * 0.3));
|
|
544
|
-
noise = noise * 0.5 + 0.5; // Normalize to 0-1
|
|
545
|
-
|
|
546
|
-
// Gradient based on UV and noise
|
|
547
|
-
float gradient = vUv.y + noise * 0.3;
|
|
548
|
-
|
|
549
|
-
// Mix colors
|
|
550
|
-
vec3 color = mix(uColorA, uColorB, gradient);
|
|
551
|
-
|
|
552
|
-
// Add rim lighting
|
|
553
|
-
vec3 viewDirection = normalize(cameraPosition - vPosition);
|
|
554
|
-
float rimLight = 1.0 - max(0.0, dot(viewDirection, vNormal));
|
|
555
|
-
rimLight = pow(rimLight, 3.0);
|
|
556
|
-
color += rimLight * 0.3;
|
|
557
|
-
|
|
558
|
-
gl_FragColor = vec4(color, 1.0);
|
|
559
|
-
}
|
|
560
|
-
`;
|
|
561
|
-
|
|
562
|
-
// Create shader material
|
|
563
|
-
const GradientMaterial = shaderMaterial(
|
|
564
|
-
{
|
|
565
|
-
uTime: 0,
|
|
566
|
-
uColorA: new THREE.Color('#ff6b6b'),
|
|
567
|
-
uColorB: new THREE.Color('#4ecdc4'),
|
|
568
|
-
uAmplitude: 0.2,
|
|
569
|
-
uFrequency: 2.0,
|
|
570
|
-
uNoiseScale: 1.0,
|
|
571
|
-
},
|
|
572
|
-
vertexShader,
|
|
573
|
-
fragmentShader
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
extend({ GradientMaterial });
|
|
577
|
-
|
|
578
|
-
// TypeScript declaration
|
|
579
|
-
declare global {
|
|
580
|
-
namespace JSX {
|
|
581
|
-
interface IntrinsicElements {
|
|
582
|
-
gradientMaterial: any;
|
|
583
|
-
}
|
|
584
|
-
}
|
|
585
|
-
}
|
|
586
|
-
|
|
587
|
-
// Component using custom shader
|
|
588
|
-
export function ShaderPlane() {
|
|
589
|
-
const materialRef = useRef<THREE.ShaderMaterial>(null);
|
|
590
|
-
|
|
591
|
-
useFrame(({ clock }) => {
|
|
592
|
-
if (materialRef.current) {
|
|
593
|
-
materialRef.current.uniforms.uTime.value = clock.getElapsedTime();
|
|
594
|
-
}
|
|
595
|
-
});
|
|
596
|
-
|
|
597
|
-
return (
|
|
598
|
-
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, 0, 0]}>
|
|
599
|
-
<planeGeometry args={[10, 10, 64, 64]} />
|
|
600
|
-
<gradientMaterial
|
|
601
|
-
ref={materialRef}
|
|
602
|
-
uColorA="#ff6b6b"
|
|
603
|
-
uColorB="#4ecdc4"
|
|
604
|
-
uAmplitude={0.3}
|
|
605
|
-
uFrequency={3.0}
|
|
606
|
-
/>
|
|
607
|
-
</mesh>
|
|
608
|
-
);
|
|
609
|
-
}
|
|
610
|
-
|
|
611
|
-
// Post-processing shader
|
|
612
|
-
const GlitchShader = {
|
|
613
|
-
uniforms: {
|
|
614
|
-
tDiffuse: { value: null },
|
|
615
|
-
uTime: { value: 0 },
|
|
616
|
-
uIntensity: { value: 0.5 },
|
|
617
|
-
},
|
|
618
|
-
vertexShader: `
|
|
619
|
-
varying vec2 vUv;
|
|
620
|
-
void main() {
|
|
621
|
-
vUv = uv;
|
|
622
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
623
|
-
}
|
|
624
|
-
`,
|
|
625
|
-
fragmentShader: `
|
|
626
|
-
uniform sampler2D tDiffuse;
|
|
627
|
-
uniform float uTime;
|
|
628
|
-
uniform float uIntensity;
|
|
629
|
-
varying vec2 vUv;
|
|
630
|
-
|
|
631
|
-
float random(vec2 st) {
|
|
632
|
-
return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123);
|
|
633
|
-
}
|
|
634
|
-
|
|
635
|
-
void main() {
|
|
636
|
-
vec2 uv = vUv;
|
|
637
|
-
|
|
638
|
-
// Random glitch offset
|
|
639
|
-
float glitch = step(0.99, random(vec2(uTime * 0.1, floor(uv.y * 20.0))));
|
|
640
|
-
uv.x += glitch * uIntensity * (random(vec2(uTime)) - 0.5);
|
|
641
|
-
|
|
642
|
-
// RGB split
|
|
643
|
-
float r = texture2D(tDiffuse, uv + vec2(uIntensity * 0.01, 0.0)).r;
|
|
644
|
-
float g = texture2D(tDiffuse, uv).g;
|
|
645
|
-
float b = texture2D(tDiffuse, uv - vec2(uIntensity * 0.01, 0.0)).b;
|
|
646
|
-
|
|
647
|
-
gl_FragColor = vec4(r, g, b, 1.0);
|
|
648
|
-
}
|
|
649
|
-
`,
|
|
650
|
-
};
|
|
651
70
|
```
|
|
652
71
|
|
|
653
|
-
###
|
|
72
|
+
### Instanced Mesh for Performance
|
|
654
73
|
|
|
655
74
|
```tsx
|
|
656
|
-
// components/PhysicsScene.tsx
|
|
657
|
-
import { Canvas } from '@react-three/fiber';
|
|
658
|
-
import { Physics, RigidBody, CuboidCollider, BallCollider } from '@react-three/rapier';
|
|
659
|
-
import { useRef, useState } from 'react';
|
|
660
|
-
import * as THREE from 'three';
|
|
661
|
-
|
|
662
|
-
// Physics-enabled box
|
|
663
|
-
function PhysicsBox({ position }: { position: [number, number, number] }) {
|
|
664
|
-
const [color, setColor] = useState('#ff6b6b');
|
|
665
|
-
|
|
666
|
-
return (
|
|
667
|
-
<RigidBody
|
|
668
|
-
position={position}
|
|
669
|
-
restitution={0.7}
|
|
670
|
-
friction={0.5}
|
|
671
|
-
onCollisionEnter={() => setColor('#4ecdc4')}
|
|
672
|
-
onCollisionExit={() => setColor('#ff6b6b')}
|
|
673
|
-
>
|
|
674
|
-
<mesh castShadow>
|
|
675
|
-
<boxGeometry args={[1, 1, 1]} />
|
|
676
|
-
<meshStandardMaterial color={color} />
|
|
677
|
-
</mesh>
|
|
678
|
-
</RigidBody>
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
|
|
682
|
-
// Bouncing ball
|
|
683
|
-
function BouncingBall({ position }: { position: [number, number, number] }) {
|
|
684
|
-
const rigidBodyRef = useRef(null);
|
|
685
|
-
|
|
686
|
-
const handleClick = () => {
|
|
687
|
-
if (rigidBodyRef.current) {
|
|
688
|
-
// Apply upward impulse on click
|
|
689
|
-
rigidBodyRef.current.applyImpulse({ x: 0, y: 10, z: 0 }, true);
|
|
690
|
-
}
|
|
691
|
-
};
|
|
692
|
-
|
|
693
|
-
return (
|
|
694
|
-
<RigidBody
|
|
695
|
-
ref={rigidBodyRef}
|
|
696
|
-
position={position}
|
|
697
|
-
restitution={0.9}
|
|
698
|
-
friction={0.3}
|
|
699
|
-
colliders="ball"
|
|
700
|
-
>
|
|
701
|
-
<mesh castShadow onClick={handleClick}>
|
|
702
|
-
<sphereGeometry args={[0.5, 32, 32]} />
|
|
703
|
-
<meshStandardMaterial color="#feca57" metalness={0.3} roughness={0.2} />
|
|
704
|
-
</mesh>
|
|
705
|
-
</RigidBody>
|
|
706
|
-
);
|
|
707
|
-
}
|
|
708
|
-
|
|
709
|
-
// Ground plane
|
|
710
|
-
function Ground() {
|
|
711
|
-
return (
|
|
712
|
-
<RigidBody type="fixed" friction={1}>
|
|
713
|
-
<mesh rotation={[-Math.PI / 2, 0, 0]} position={[0, -0.5, 0]} receiveShadow>
|
|
714
|
-
<planeGeometry args={[50, 50]} />
|
|
715
|
-
<meshStandardMaterial color="#1a1a2e" />
|
|
716
|
-
</mesh>
|
|
717
|
-
</RigidBody>
|
|
718
|
-
);
|
|
719
|
-
}
|
|
720
|
-
|
|
721
|
-
// Walls for containment
|
|
722
|
-
function Walls() {
|
|
723
|
-
return (
|
|
724
|
-
<>
|
|
725
|
-
{/* Back wall */}
|
|
726
|
-
<RigidBody type="fixed">
|
|
727
|
-
<CuboidCollider args={[25, 10, 0.5]} position={[0, 5, -25]} />
|
|
728
|
-
</RigidBody>
|
|
729
|
-
{/* Front wall */}
|
|
730
|
-
<RigidBody type="fixed">
|
|
731
|
-
<CuboidCollider args={[25, 10, 0.5]} position={[0, 5, 25]} />
|
|
732
|
-
</RigidBody>
|
|
733
|
-
{/* Left wall */}
|
|
734
|
-
<RigidBody type="fixed">
|
|
735
|
-
<CuboidCollider args={[0.5, 10, 25]} position={[-25, 5, 0]} />
|
|
736
|
-
</RigidBody>
|
|
737
|
-
{/* Right wall */}
|
|
738
|
-
<RigidBody type="fixed">
|
|
739
|
-
<CuboidCollider args={[0.5, 10, 25]} position={[25, 5, 0]} />
|
|
740
|
-
</RigidBody>
|
|
741
|
-
</>
|
|
742
|
-
);
|
|
743
|
-
}
|
|
744
|
-
|
|
745
|
-
// Spawner for dynamic objects
|
|
746
|
-
function ObjectSpawner() {
|
|
747
|
-
const [objects, setObjects] = useState<Array<{ id: number; type: 'box' | 'ball'; position: [number, number, number] }>>([]);
|
|
748
|
-
|
|
749
|
-
const spawnObject = () => {
|
|
750
|
-
const id = Date.now();
|
|
751
|
-
const type = Math.random() > 0.5 ? 'box' : 'ball';
|
|
752
|
-
const position: [number, number, number] = [
|
|
753
|
-
(Math.random() - 0.5) * 10,
|
|
754
|
-
10,
|
|
755
|
-
(Math.random() - 0.5) * 10,
|
|
756
|
-
];
|
|
757
|
-
setObjects((prev) => [...prev, { id, type, position }]);
|
|
758
|
-
};
|
|
759
|
-
|
|
760
|
-
return (
|
|
761
|
-
<>
|
|
762
|
-
{objects.map(({ id, type, position }) =>
|
|
763
|
-
type === 'box' ? (
|
|
764
|
-
<PhysicsBox key={id} position={position} />
|
|
765
|
-
) : (
|
|
766
|
-
<BouncingBall key={id} position={position} />
|
|
767
|
-
)
|
|
768
|
-
)}
|
|
769
|
-
|
|
770
|
-
{/* Spawn trigger - invisible clickable plane */}
|
|
771
|
-
<mesh position={[0, 15, 0]} onClick={spawnObject}>
|
|
772
|
-
<planeGeometry args={[20, 20]} />
|
|
773
|
-
<meshBasicMaterial visible={false} />
|
|
774
|
-
</mesh>
|
|
775
|
-
</>
|
|
776
|
-
);
|
|
777
|
-
}
|
|
778
|
-
|
|
779
|
-
// Main physics scene
|
|
780
|
-
export function PhysicsScene() {
|
|
781
|
-
return (
|
|
782
|
-
<Canvas shadows camera={{ position: [15, 15, 15], fov: 50 }}>
|
|
783
|
-
<ambientLight intensity={0.5} />
|
|
784
|
-
<directionalLight
|
|
785
|
-
position={[10, 20, 10]}
|
|
786
|
-
intensity={1.5}
|
|
787
|
-
castShadow
|
|
788
|
-
shadow-mapSize={[2048, 2048]}
|
|
789
|
-
/>
|
|
790
|
-
|
|
791
|
-
<Physics gravity={[0, -9.81, 0]} debug={false}>
|
|
792
|
-
<Ground />
|
|
793
|
-
<Walls />
|
|
794
|
-
<ObjectSpawner />
|
|
795
|
-
|
|
796
|
-
{/* Initial objects */}
|
|
797
|
-
<PhysicsBox position={[0, 5, 0]} />
|
|
798
|
-
<PhysicsBox position={[0.5, 7, 0.5]} />
|
|
799
|
-
<BouncingBall position={[-2, 5, 0]} />
|
|
800
|
-
<BouncingBall position={[2, 8, 2]} />
|
|
801
|
-
</Physics>
|
|
802
|
-
|
|
803
|
-
<OrbitControls />
|
|
804
|
-
</Canvas>
|
|
805
|
-
);
|
|
806
|
-
}
|
|
807
|
-
```
|
|
808
|
-
|
|
809
|
-
### 5. Animation System
|
|
810
|
-
|
|
811
|
-
```tsx
|
|
812
|
-
// lib/three/AnimationManager.ts
|
|
813
|
-
import * as THREE from 'three';
|
|
814
|
-
import gsap from 'gsap';
|
|
815
|
-
|
|
816
|
-
interface AnimationConfig {
|
|
817
|
-
duration?: number;
|
|
818
|
-
ease?: string;
|
|
819
|
-
delay?: number;
|
|
820
|
-
repeat?: number;
|
|
821
|
-
yoyo?: boolean;
|
|
822
|
-
onComplete?: () => void;
|
|
823
|
-
}
|
|
824
|
-
|
|
825
|
-
export class AnimationManager {
|
|
826
|
-
private mixer: THREE.AnimationMixer | null = null;
|
|
827
|
-
private actions: Map<string, THREE.AnimationAction> = new Map();
|
|
828
|
-
private timelines: Map<string, gsap.core.Timeline> = new Map();
|
|
829
|
-
|
|
830
|
-
// GLTF animation setup
|
|
831
|
-
public setupMixer(model: THREE.Object3D, animations: THREE.AnimationClip[]): void {
|
|
832
|
-
this.mixer = new THREE.AnimationMixer(model);
|
|
833
|
-
|
|
834
|
-
animations.forEach((clip) => {
|
|
835
|
-
const action = this.mixer!.clipAction(clip);
|
|
836
|
-
this.actions.set(clip.name, action);
|
|
837
|
-
});
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
public playAnimation(name: string, options: { loop?: boolean; crossFade?: number } = {}): void {
|
|
841
|
-
const action = this.actions.get(name);
|
|
842
|
-
if (!action) return;
|
|
843
|
-
|
|
844
|
-
const { loop = true, crossFade = 0.3 } = options;
|
|
845
|
-
|
|
846
|
-
// Stop other actions with crossfade
|
|
847
|
-
this.actions.forEach((a, n) => {
|
|
848
|
-
if (n !== name && a.isRunning()) {
|
|
849
|
-
a.fadeOut(crossFade);
|
|
850
|
-
}
|
|
851
|
-
});
|
|
852
|
-
|
|
853
|
-
action.reset();
|
|
854
|
-
action.setLoop(loop ? THREE.LoopRepeat : THREE.LoopOnce, Infinity);
|
|
855
|
-
action.clampWhenFinished = !loop;
|
|
856
|
-
action.fadeIn(crossFade);
|
|
857
|
-
action.play();
|
|
858
|
-
}
|
|
859
|
-
|
|
860
|
-
public update(delta: number): void {
|
|
861
|
-
this.mixer?.update(delta);
|
|
862
|
-
}
|
|
863
|
-
|
|
864
|
-
// GSAP-based object animation
|
|
865
|
-
public animateTo(
|
|
866
|
-
object: THREE.Object3D,
|
|
867
|
-
properties: Partial<{
|
|
868
|
-
position: { x?: number; y?: number; z?: number };
|
|
869
|
-
rotation: { x?: number; y?: number; z?: number };
|
|
870
|
-
scale: { x?: number; y?: number; z?: number };
|
|
871
|
-
}>,
|
|
872
|
-
config: AnimationConfig = {}
|
|
873
|
-
): gsap.core.Tween {
|
|
874
|
-
const { duration = 1, ease = 'power2.out', delay = 0, onComplete } = config;
|
|
875
|
-
|
|
876
|
-
const targets: any[] = [];
|
|
877
|
-
const props: any = {};
|
|
878
|
-
|
|
879
|
-
if (properties.position) {
|
|
880
|
-
targets.push(object.position);
|
|
881
|
-
Object.assign(props, properties.position);
|
|
882
|
-
}
|
|
883
|
-
if (properties.rotation) {
|
|
884
|
-
targets.push(object.rotation);
|
|
885
|
-
Object.assign(props, properties.rotation);
|
|
886
|
-
}
|
|
887
|
-
if (properties.scale) {
|
|
888
|
-
targets.push(object.scale);
|
|
889
|
-
Object.assign(props, properties.scale);
|
|
890
|
-
}
|
|
891
|
-
|
|
892
|
-
return gsap.to(targets, {
|
|
893
|
-
...props,
|
|
894
|
-
duration,
|
|
895
|
-
ease,
|
|
896
|
-
delay,
|
|
897
|
-
onComplete,
|
|
898
|
-
});
|
|
899
|
-
}
|
|
900
|
-
|
|
901
|
-
// Timeline-based complex animations
|
|
902
|
-
public createTimeline(id: string): gsap.core.Timeline {
|
|
903
|
-
const timeline = gsap.timeline({ paused: true });
|
|
904
|
-
this.timelines.set(id, timeline);
|
|
905
|
-
return timeline;
|
|
906
|
-
}
|
|
907
|
-
|
|
908
|
-
public playTimeline(id: string): void {
|
|
909
|
-
this.timelines.get(id)?.play();
|
|
910
|
-
}
|
|
911
|
-
|
|
912
|
-
public dispose(): void {
|
|
913
|
-
this.actions.clear();
|
|
914
|
-
this.timelines.forEach((tl) => tl.kill());
|
|
915
|
-
this.timelines.clear();
|
|
916
|
-
}
|
|
917
|
-
}
|
|
918
|
-
|
|
919
|
-
// React hook for animations
|
|
920
|
-
function useAnimation(meshRef: React.RefObject<THREE.Mesh>) {
|
|
921
|
-
const [isAnimating, setIsAnimating] = useState(false);
|
|
922
|
-
|
|
923
|
-
const animateIn = useCallback(() => {
|
|
924
|
-
if (!meshRef.current || isAnimating) return;
|
|
925
|
-
|
|
926
|
-
setIsAnimating(true);
|
|
927
|
-
|
|
928
|
-
gsap.timeline()
|
|
929
|
-
.fromTo(
|
|
930
|
-
meshRef.current.scale,
|
|
931
|
-
{ x: 0, y: 0, z: 0 },
|
|
932
|
-
{ x: 1, y: 1, z: 1, duration: 0.5, ease: 'back.out(1.7)' }
|
|
933
|
-
)
|
|
934
|
-
.fromTo(
|
|
935
|
-
meshRef.current.rotation,
|
|
936
|
-
{ y: -Math.PI },
|
|
937
|
-
{ y: 0, duration: 0.5, ease: 'power2.out' },
|
|
938
|
-
'<'
|
|
939
|
-
)
|
|
940
|
-
.call(() => setIsAnimating(false));
|
|
941
|
-
}, [isAnimating]);
|
|
942
|
-
|
|
943
|
-
const animateOut = useCallback(() => {
|
|
944
|
-
if (!meshRef.current || isAnimating) return;
|
|
945
|
-
|
|
946
|
-
setIsAnimating(true);
|
|
947
|
-
|
|
948
|
-
gsap.to(meshRef.current.scale, {
|
|
949
|
-
x: 0,
|
|
950
|
-
y: 0,
|
|
951
|
-
z: 0,
|
|
952
|
-
duration: 0.3,
|
|
953
|
-
ease: 'back.in(1.7)',
|
|
954
|
-
onComplete: () => setIsAnimating(false),
|
|
955
|
-
});
|
|
956
|
-
}, [isAnimating]);
|
|
957
|
-
|
|
958
|
-
return { animateIn, animateOut, isAnimating };
|
|
959
|
-
}
|
|
960
|
-
|
|
961
|
-
// Animated component example
|
|
962
|
-
function AnimatedObject() {
|
|
963
|
-
const meshRef = useRef<THREE.Mesh>(null);
|
|
964
|
-
const { animateIn, animateOut, isAnimating } = useAnimation(meshRef);
|
|
965
|
-
|
|
966
|
-
useEffect(() => {
|
|
967
|
-
animateIn();
|
|
968
|
-
}, []);
|
|
969
|
-
|
|
970
|
-
return (
|
|
971
|
-
<mesh
|
|
972
|
-
ref={meshRef}
|
|
973
|
-
onClick={animateOut}
|
|
974
|
-
scale={[0, 0, 0]}
|
|
975
|
-
>
|
|
976
|
-
<boxGeometry />
|
|
977
|
-
<meshStandardMaterial color="#ff6b6b" />
|
|
978
|
-
</mesh>
|
|
979
|
-
);
|
|
980
|
-
}
|
|
981
|
-
```
|
|
982
|
-
|
|
983
|
-
### 6. Performance Optimization
|
|
984
|
-
|
|
985
|
-
```tsx
|
|
986
|
-
// lib/three/PerformanceOptimizer.ts
|
|
987
|
-
import * as THREE from 'three';
|
|
988
|
-
import { useThree, useFrame } from '@react-three/fiber';
|
|
989
|
-
import { useEffect, useMemo, useRef } from 'react';
|
|
990
|
-
|
|
991
|
-
// Level of Detail (LOD) component
|
|
992
|
-
function LODMesh({ position }: { position: [number, number, number] }) {
|
|
993
|
-
const lodRef = useRef<THREE.LOD>(null);
|
|
994
|
-
const { camera } = useThree();
|
|
995
|
-
|
|
996
|
-
const geometries = useMemo(() => ({
|
|
997
|
-
high: new THREE.IcosahedronGeometry(1, 4), // 320 triangles
|
|
998
|
-
medium: new THREE.IcosahedronGeometry(1, 2), // 80 triangles
|
|
999
|
-
low: new THREE.IcosahedronGeometry(1, 1), // 20 triangles
|
|
1000
|
-
}), []);
|
|
1001
|
-
|
|
1002
|
-
const material = useMemo(
|
|
1003
|
-
() => new THREE.MeshStandardMaterial({ color: '#4ecdc4' }),
|
|
1004
|
-
[]
|
|
1005
|
-
);
|
|
1006
|
-
|
|
1007
|
-
useEffect(() => {
|
|
1008
|
-
if (!lodRef.current) return;
|
|
1009
|
-
|
|
1010
|
-
const meshHigh = new THREE.Mesh(geometries.high, material);
|
|
1011
|
-
const meshMedium = new THREE.Mesh(geometries.medium, material);
|
|
1012
|
-
const meshLow = new THREE.Mesh(geometries.low, material);
|
|
1013
|
-
|
|
1014
|
-
lodRef.current.addLevel(meshHigh, 0);
|
|
1015
|
-
lodRef.current.addLevel(meshMedium, 10);
|
|
1016
|
-
lodRef.current.addLevel(meshLow, 20);
|
|
1017
|
-
|
|
1018
|
-
return () => {
|
|
1019
|
-
geometries.high.dispose();
|
|
1020
|
-
geometries.medium.dispose();
|
|
1021
|
-
geometries.low.dispose();
|
|
1022
|
-
material.dispose();
|
|
1023
|
-
};
|
|
1024
|
-
}, [geometries, material]);
|
|
1025
|
-
|
|
1026
|
-
useFrame(() => {
|
|
1027
|
-
lodRef.current?.update(camera);
|
|
1028
|
-
});
|
|
1029
|
-
|
|
1030
|
-
return <lod ref={lodRef} position={position} />;
|
|
1031
|
-
}
|
|
1032
|
-
|
|
1033
|
-
// Instanced mesh for many identical objects
|
|
1034
75
|
function InstancedBoxes({ count = 1000 }: { count?: number }) {
|
|
1035
76
|
const meshRef = useRef<THREE.InstancedMesh>(null);
|
|
1036
|
-
const
|
|
1037
|
-
const tempColor = useMemo(() => new THREE.Color(), []);
|
|
77
|
+
const temp = useMemo(() => new THREE.Object3D(), []);
|
|
1038
78
|
|
|
1039
79
|
useEffect(() => {
|
|
1040
|
-
if (!meshRef.current) return;
|
|
1041
|
-
|
|
1042
|
-
// Set up instance matrices and colors
|
|
1043
80
|
for (let i = 0; i < count; i++) {
|
|
1044
|
-
|
|
81
|
+
temp.position.set(
|
|
1045
82
|
(Math.random() - 0.5) * 50,
|
|
1046
83
|
(Math.random() - 0.5) * 50,
|
|
1047
84
|
(Math.random() - 0.5) * 50
|
|
1048
85
|
);
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
Math.random() * Math.PI,
|
|
1052
|
-
Math.random() * Math.PI
|
|
1053
|
-
);
|
|
1054
|
-
tempObject.scale.setScalar(0.5 + Math.random() * 0.5);
|
|
1055
|
-
tempObject.updateMatrix();
|
|
1056
|
-
|
|
1057
|
-
meshRef.current.setMatrixAt(i, tempObject.matrix);
|
|
1058
|
-
meshRef.current.setColorAt(
|
|
1059
|
-
i,
|
|
1060
|
-
tempColor.setHSL(Math.random(), 0.7, 0.5)
|
|
1061
|
-
);
|
|
1062
|
-
}
|
|
1063
|
-
|
|
1064
|
-
meshRef.current.instanceMatrix.needsUpdate = true;
|
|
1065
|
-
if (meshRef.current.instanceColor) {
|
|
1066
|
-
meshRef.current.instanceColor.needsUpdate = true;
|
|
1067
|
-
}
|
|
1068
|
-
}, [count, tempObject, tempColor]);
|
|
1069
|
-
|
|
1070
|
-
// Animate instances
|
|
1071
|
-
useFrame(({ clock }) => {
|
|
1072
|
-
if (!meshRef.current) return;
|
|
1073
|
-
|
|
1074
|
-
const time = clock.getElapsedTime();
|
|
1075
|
-
|
|
1076
|
-
for (let i = 0; i < count; i++) {
|
|
1077
|
-
meshRef.current.getMatrixAt(i, tempObject.matrix);
|
|
1078
|
-
tempObject.matrix.decompose(
|
|
1079
|
-
tempObject.position,
|
|
1080
|
-
tempObject.quaternion,
|
|
1081
|
-
tempObject.scale
|
|
1082
|
-
);
|
|
1083
|
-
|
|
1084
|
-
tempObject.rotation.x = time * 0.5 + i * 0.01;
|
|
1085
|
-
tempObject.rotation.y = time * 0.3 + i * 0.01;
|
|
1086
|
-
tempObject.updateMatrix();
|
|
1087
|
-
|
|
1088
|
-
meshRef.current.setMatrixAt(i, tempObject.matrix);
|
|
86
|
+
temp.updateMatrix();
|
|
87
|
+
meshRef.current?.setMatrixAt(i, temp.matrix);
|
|
1089
88
|
}
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
});
|
|
89
|
+
meshRef.current!.instanceMatrix.needsUpdate = true;
|
|
90
|
+
}, [count, temp]);
|
|
1093
91
|
|
|
1094
92
|
return (
|
|
1095
|
-
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}
|
|
93
|
+
<instancedMesh ref={meshRef} args={[undefined, undefined, count]}>
|
|
1096
94
|
<boxGeometry args={[1, 1, 1]} />
|
|
1097
95
|
<meshStandardMaterial />
|
|
1098
96
|
</instancedMesh>
|
|
1099
97
|
);
|
|
1100
98
|
}
|
|
1101
|
-
|
|
1102
|
-
// Frustum culling helper
|
|
1103
|
-
function useFrustumCulling(meshRef: React.RefObject<THREE.Mesh>) {
|
|
1104
|
-
const { camera } = useThree();
|
|
1105
|
-
const frustum = useMemo(() => new THREE.Frustum(), []);
|
|
1106
|
-
const projScreenMatrix = useMemo(() => new THREE.Matrix4(), []);
|
|
1107
|
-
|
|
1108
|
-
useFrame(() => {
|
|
1109
|
-
if (!meshRef.current) return;
|
|
1110
|
-
|
|
1111
|
-
projScreenMatrix.multiplyMatrices(
|
|
1112
|
-
camera.projectionMatrix,
|
|
1113
|
-
camera.matrixWorldInverse
|
|
1114
|
-
);
|
|
1115
|
-
frustum.setFromProjectionMatrix(projScreenMatrix);
|
|
1116
|
-
|
|
1117
|
-
// Check if mesh is in frustum
|
|
1118
|
-
const isVisible = frustum.intersectsObject(meshRef.current);
|
|
1119
|
-
meshRef.current.visible = isVisible;
|
|
1120
|
-
});
|
|
1121
|
-
}
|
|
1122
|
-
|
|
1123
|
-
// Texture optimization
|
|
1124
|
-
function useOptimizedTexture(url: string) {
|
|
1125
|
-
const texture = useMemo(() => {
|
|
1126
|
-
const loader = new THREE.TextureLoader();
|
|
1127
|
-
const tex = loader.load(url);
|
|
1128
|
-
|
|
1129
|
-
// Optimize texture settings
|
|
1130
|
-
tex.minFilter = THREE.LinearMipmapLinearFilter;
|
|
1131
|
-
tex.magFilter = THREE.LinearFilter;
|
|
1132
|
-
tex.anisotropy = 4; // Balance quality and performance
|
|
1133
|
-
tex.generateMipmaps = true;
|
|
1134
|
-
|
|
1135
|
-
return tex;
|
|
1136
|
-
}, [url]);
|
|
1137
|
-
|
|
1138
|
-
useEffect(() => {
|
|
1139
|
-
return () => texture.dispose();
|
|
1140
|
-
}, [texture]);
|
|
1141
|
-
|
|
1142
|
-
return texture;
|
|
1143
|
-
}
|
|
1144
|
-
|
|
1145
|
-
// GPU particle system
|
|
1146
|
-
function ParticleSystem({ count = 10000 }: { count?: number }) {
|
|
1147
|
-
const particlesRef = useRef<THREE.Points>(null);
|
|
1148
|
-
|
|
1149
|
-
const [positions, velocities] = useMemo(() => {
|
|
1150
|
-
const positions = new Float32Array(count * 3);
|
|
1151
|
-
const velocities = new Float32Array(count * 3);
|
|
1152
|
-
|
|
1153
|
-
for (let i = 0; i < count; i++) {
|
|
1154
|
-
const i3 = i * 3;
|
|
1155
|
-
positions[i3] = (Math.random() - 0.5) * 20;
|
|
1156
|
-
positions[i3 + 1] = Math.random() * 20;
|
|
1157
|
-
positions[i3 + 2] = (Math.random() - 0.5) * 20;
|
|
1158
|
-
|
|
1159
|
-
velocities[i3] = (Math.random() - 0.5) * 0.02;
|
|
1160
|
-
velocities[i3 + 1] = -Math.random() * 0.05;
|
|
1161
|
-
velocities[i3 + 2] = (Math.random() - 0.5) * 0.02;
|
|
1162
|
-
}
|
|
1163
|
-
|
|
1164
|
-
return [positions, velocities];
|
|
1165
|
-
}, [count]);
|
|
1166
|
-
|
|
1167
|
-
useFrame(() => {
|
|
1168
|
-
if (!particlesRef.current) return;
|
|
1169
|
-
|
|
1170
|
-
const positions = particlesRef.current.geometry.attributes.position.array as Float32Array;
|
|
1171
|
-
|
|
1172
|
-
for (let i = 0; i < count; i++) {
|
|
1173
|
-
const i3 = i * 3;
|
|
1174
|
-
|
|
1175
|
-
positions[i3] += velocities[i3];
|
|
1176
|
-
positions[i3 + 1] += velocities[i3 + 1];
|
|
1177
|
-
positions[i3 + 2] += velocities[i3 + 2];
|
|
1178
|
-
|
|
1179
|
-
// Reset particle when it falls below ground
|
|
1180
|
-
if (positions[i3 + 1] < 0) {
|
|
1181
|
-
positions[i3 + 1] = 20;
|
|
1182
|
-
}
|
|
1183
|
-
}
|
|
1184
|
-
|
|
1185
|
-
particlesRef.current.geometry.attributes.position.needsUpdate = true;
|
|
1186
|
-
});
|
|
1187
|
-
|
|
1188
|
-
return (
|
|
1189
|
-
<points ref={particlesRef}>
|
|
1190
|
-
<bufferGeometry>
|
|
1191
|
-
<bufferAttribute
|
|
1192
|
-
attach="attributes-position"
|
|
1193
|
-
count={count}
|
|
1194
|
-
array={positions}
|
|
1195
|
-
itemSize={3}
|
|
1196
|
-
/>
|
|
1197
|
-
</bufferGeometry>
|
|
1198
|
-
<pointsMaterial
|
|
1199
|
-
size={0.05}
|
|
1200
|
-
color="#ffffff"
|
|
1201
|
-
transparent
|
|
1202
|
-
opacity={0.8}
|
|
1203
|
-
sizeAttenuation
|
|
1204
|
-
/>
|
|
1205
|
-
</points>
|
|
1206
|
-
);
|
|
1207
|
-
}
|
|
1208
99
|
```
|
|
1209
100
|
|
|
1210
|
-
|
|
1211
|
-
|
|
1212
|
-
### Interactive Product Viewer
|
|
101
|
+
### Custom Shader Material
|
|
1213
102
|
|
|
1214
103
|
```tsx
|
|
1215
|
-
|
|
1216
|
-
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
|
|
1220
|
-
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
|
|
1224
|
-
|
|
1225
|
-
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
id: string;
|
|
1235
|
-
position: [number, number, number];
|
|
1236
|
-
label: string;
|
|
1237
|
-
description: string;
|
|
1238
|
-
}>;
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
function Product({ modelUrl, hotspots }: ProductProps) {
|
|
1242
|
-
const { scene } = useGLTF(modelUrl);
|
|
1243
|
-
const [activeHotspot, setActiveHotspot] = useState<string | null>(null);
|
|
1244
|
-
|
|
1245
|
-
return (
|
|
1246
|
-
<group>
|
|
1247
|
-
<primitive object={scene} scale={1} />
|
|
1248
|
-
|
|
1249
|
-
{hotspots.map((hotspot) => (
|
|
1250
|
-
<group key={hotspot.id} position={hotspot.position}>
|
|
1251
|
-
<mesh onClick={() => setActiveHotspot(hotspot.id)}>
|
|
1252
|
-
<sphereGeometry args={[0.1, 16, 16]} />
|
|
1253
|
-
<meshBasicMaterial
|
|
1254
|
-
color={activeHotspot === hotspot.id ? '#ff6b6b' : '#4ecdc4'}
|
|
1255
|
-
/>
|
|
1256
|
-
</mesh>
|
|
1257
|
-
|
|
1258
|
-
{activeHotspot === hotspot.id && (
|
|
1259
|
-
<Html distanceFactor={5}>
|
|
1260
|
-
<div className="bg-white p-4 rounded-lg shadow-lg min-w-[200px]">
|
|
1261
|
-
<h3 className="font-bold">{hotspot.label}</h3>
|
|
1262
|
-
<p className="text-sm text-gray-600">{hotspot.description}</p>
|
|
1263
|
-
</div>
|
|
1264
|
-
</Html>
|
|
1265
|
-
)}
|
|
1266
|
-
</group>
|
|
1267
|
-
))}
|
|
1268
|
-
</group>
|
|
1269
|
-
);
|
|
1270
|
-
}
|
|
1271
|
-
|
|
1272
|
-
export function ProductViewer({ modelUrl, hotspots }: ProductProps) {
|
|
1273
|
-
return (
|
|
1274
|
-
<div className="w-full h-screen">
|
|
1275
|
-
<Canvas camera={{ position: [0, 0, 5], fov: 50 }}>
|
|
1276
|
-
<Suspense fallback={<Loader />}>
|
|
1277
|
-
<Product modelUrl={modelUrl} hotspots={hotspots} />
|
|
1278
|
-
<Environment preset="studio" />
|
|
1279
|
-
</Suspense>
|
|
1280
|
-
|
|
1281
|
-
<OrbitControls
|
|
1282
|
-
enablePan={false}
|
|
1283
|
-
minDistance={2}
|
|
1284
|
-
maxDistance={10}
|
|
1285
|
-
autoRotate
|
|
1286
|
-
autoRotateSpeed={0.5}
|
|
1287
|
-
/>
|
|
1288
|
-
</Canvas>
|
|
1289
|
-
</div>
|
|
1290
|
-
);
|
|
1291
|
-
}
|
|
104
|
+
const GradientMaterial = shaderMaterial(
|
|
105
|
+
{ uTime: 0, uColorA: new THREE.Color('#ff6b6b'), uColorB: new THREE.Color('#4ecdc4') },
|
|
106
|
+
// Vertex shader
|
|
107
|
+
`varying vec2 vUv;
|
|
108
|
+
void main() {
|
|
109
|
+
vUv = uv;
|
|
110
|
+
gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
|
|
111
|
+
}`,
|
|
112
|
+
// Fragment shader
|
|
113
|
+
`uniform float uTime;
|
|
114
|
+
uniform vec3 uColorA;
|
|
115
|
+
uniform vec3 uColorB;
|
|
116
|
+
varying vec2 vUv;
|
|
117
|
+
void main() {
|
|
118
|
+
vec3 color = mix(uColorA, uColorB, vUv.y + sin(uTime) * 0.1);
|
|
119
|
+
gl_FragColor = vec4(color, 1.0);
|
|
120
|
+
}`
|
|
121
|
+
);
|
|
122
|
+
extend({ GradientMaterial });
|
|
1292
123
|
```
|
|
1293
124
|
|
|
1294
125
|
## Best Practices
|
|
1295
126
|
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
- Use object pooling for frequently created/destroyed objects
|
|
1305
|
-
- Optimize geometry with BufferGeometry
|
|
1306
|
-
- Use compressed textures (KTX2, Basis)
|
|
1307
|
-
- Profile performance with Chrome DevTools and Spector.js
|
|
1308
|
-
|
|
1309
|
-
### Don'ts
|
|
1310
|
-
|
|
1311
|
-
- Don't create new geometries/materials in render loops
|
|
1312
|
-
- Don't use too many lights (limit to 3-4 dynamic lights)
|
|
1313
|
-
- Don't skip frustum culling for large scenes
|
|
1314
|
-
- Don't forget to update instanceMatrix when animating instanced meshes
|
|
1315
|
-
- Don't use transparent materials unless necessary
|
|
1316
|
-
- Don't load uncompressed high-resolution textures
|
|
1317
|
-
- Don't forget to set power-of-two texture dimensions
|
|
1318
|
-
- Don't ignore memory leaks from undisposed resources
|
|
1319
|
-
- Don't use complex shaders without fallbacks
|
|
1320
|
-
- Don't skip mobile optimization and testing
|
|
1321
|
-
|
|
1322
|
-
## References
|
|
1323
|
-
|
|
1324
|
-
- [Three.js Documentation](https://threejs.org/docs/)
|
|
1325
|
-
- [React Three Fiber](https://docs.pmnd.rs/react-three-fiber/)
|
|
1326
|
-
- [Drei Helpers](https://github.com/pmndrs/drei)
|
|
1327
|
-
- [Three.js Fundamentals](https://threejs.org/manual/)
|
|
1328
|
-
- [WebGL Best Practices](https://developer.mozilla.org/en-US/docs/Web/API/WebGL_API/WebGL_best_practices)
|
|
127
|
+
| Do | Avoid |
|
|
128
|
+
|----|-------|
|
|
129
|
+
| Use React Three Fiber for React apps | Creating geometries/materials in render loops |
|
|
130
|
+
| Dispose geometries, materials, textures on unmount | Too many dynamic lights (limit to 3-4) |
|
|
131
|
+
| Use instancing for many identical objects | Skipping frustum culling in large scenes |
|
|
132
|
+
| Implement LOD for complex scenes | Uncompressed high-resolution textures |
|
|
133
|
+
| Cap pixel ratio at 2: `Math.min(window.devicePixelRatio, 2)` | Transparent materials unless necessary |
|
|
134
|
+
| Use compressed textures (KTX2, Basis) | Forgetting to update instanceMatrix after changes |
|