@rune-kit/rune 2.10.0 → 2.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +8 -6
- package/commands/rune.md +168 -168
- package/contexts/dev.md +34 -34
- package/contexts/research.md +43 -43
- package/contexts/review.md +55 -55
- package/extensions/ai-ml/PACK.md +88 -88
- package/extensions/ai-ml/skills/ai-agents.md +172 -172
- package/extensions/ai-ml/skills/code-sandbox.md +187 -187
- package/extensions/ai-ml/skills/deep-research.md +146 -146
- package/extensions/ai-ml/skills/embedding-search.md +66 -66
- package/extensions/ai-ml/skills/fine-tuning-guide.md +74 -74
- package/extensions/ai-ml/skills/llm-architect.md +125 -125
- package/extensions/ai-ml/skills/llm-integration.md +64 -64
- package/extensions/ai-ml/skills/prompt-patterns.md +72 -72
- package/extensions/ai-ml/skills/rag-patterns.md +66 -66
- package/extensions/ai-ml/skills/web-extraction.md +114 -114
- package/extensions/analytics/PACK.md +92 -92
- package/extensions/analytics/skills/ab-testing.md +72 -72
- package/extensions/analytics/skills/dashboard-patterns.md +83 -83
- package/extensions/analytics/skills/data-validation.md +68 -68
- package/extensions/analytics/skills/funnel-analysis.md +81 -81
- package/extensions/analytics/skills/sql-patterns.md +57 -57
- package/extensions/analytics/skills/statistical-analysis.md +79 -79
- package/extensions/analytics/skills/tracking-setup.md +71 -71
- package/extensions/backend/PACK.md +104 -104
- package/extensions/backend/skills/api-patterns.md +84 -84
- package/extensions/backend/skills/async-pipeline.md +193 -193
- package/extensions/backend/skills/auth-patterns.md +97 -97
- package/extensions/backend/skills/background-jobs.md +133 -133
- package/extensions/backend/skills/caching-patterns.md +108 -108
- package/extensions/backend/skills/cli-generation.md +133 -133
- package/extensions/backend/skills/database-patterns.md +87 -87
- package/extensions/backend/skills/middleware-patterns.md +104 -104
- package/extensions/chrome-ext/PACK.md +93 -93
- package/extensions/chrome-ext/skills/cws-preflight.md +143 -143
- package/extensions/chrome-ext/skills/cws-publish.md +104 -104
- package/extensions/chrome-ext/skills/ext-ai-integration.md +251 -251
- package/extensions/chrome-ext/skills/ext-messaging.md +139 -139
- package/extensions/chrome-ext/skills/ext-storage.md +133 -133
- package/extensions/chrome-ext/skills/mv3-scaffold.md +164 -164
- package/extensions/content/PACK.md +96 -96
- package/extensions/content/skills/blog-patterns.md +88 -88
- package/extensions/content/skills/cms-integration.md +131 -131
- package/extensions/content/skills/content-scoring.md +107 -107
- package/extensions/content/skills/i18n.md +83 -83
- package/extensions/content/skills/mdx-authoring.md +137 -137
- package/extensions/content/skills/reference.md +1014 -1014
- package/extensions/content/skills/seo-patterns.md +67 -67
- package/extensions/content/skills/video-repurpose.md +153 -153
- package/extensions/devops/PACK.md +101 -101
- package/extensions/devops/skills/chaos-testing.md +67 -67
- package/extensions/devops/skills/ci-cd.md +75 -75
- package/extensions/devops/skills/docker.md +58 -58
- package/extensions/devops/skills/edge-serverless.md +163 -163
- package/extensions/devops/skills/infra-as-code.md +158 -158
- package/extensions/devops/skills/kubernetes.md +110 -110
- package/extensions/devops/skills/monitoring.md +57 -57
- package/extensions/devops/skills/server-setup.md +64 -64
- package/extensions/devops/skills/ssl-domain.md +42 -42
- package/extensions/ecommerce/PACK.md +116 -116
- package/extensions/ecommerce/skills/cart-system.md +79 -79
- package/extensions/ecommerce/skills/inventory-mgmt.md +102 -102
- package/extensions/ecommerce/skills/order-management.md +126 -126
- package/extensions/ecommerce/skills/payment-integration.md +472 -472
- package/extensions/ecommerce/skills/shopify-dev.md +69 -69
- package/extensions/ecommerce/skills/subscription-billing.md +93 -93
- package/extensions/ecommerce/skills/tax-compliance.md +117 -117
- package/extensions/gamedev/PACK.md +142 -142
- package/extensions/gamedev/skills/asset-pipeline.md +74 -74
- package/extensions/gamedev/skills/audio-system.md +129 -129
- package/extensions/gamedev/skills/camera-system.md +87 -87
- package/extensions/gamedev/skills/ecs.md +98 -98
- package/extensions/gamedev/skills/game-loops.md +72 -72
- package/extensions/gamedev/skills/input-system.md +199 -199
- package/extensions/gamedev/skills/multiplayer.md +180 -180
- package/extensions/gamedev/skills/particles.md +105 -105
- package/extensions/gamedev/skills/physics-engine.md +89 -89
- package/extensions/gamedev/skills/scene-management.md +146 -146
- package/extensions/gamedev/skills/threejs-patterns.md +90 -90
- package/extensions/gamedev/skills/webgl.md +71 -71
- package/extensions/mobile/PACK.md +106 -106
- package/extensions/mobile/skills/app-store-connect.md +152 -152
- package/extensions/mobile/skills/app-store-prep.md +66 -66
- package/extensions/mobile/skills/deep-linking.md +109 -109
- package/extensions/mobile/skills/flutter.md +60 -60
- package/extensions/mobile/skills/ios-build-pipeline.md +142 -142
- package/extensions/mobile/skills/native-bridge.md +66 -66
- package/extensions/mobile/skills/ota-updates.md +97 -97
- package/extensions/mobile/skills/push-notifications.md +111 -111
- package/extensions/mobile/skills/react-native.md +82 -82
- package/extensions/saas/PACK.md +116 -116
- package/extensions/saas/skills/billing-integration.md +200 -200
- package/extensions/saas/skills/feature-flags.md +130 -130
- package/extensions/saas/skills/multi-tenant.md +103 -103
- package/extensions/saas/skills/onboarding-flow.md +139 -139
- package/extensions/saas/skills/subscription-flow.md +95 -95
- package/extensions/saas/skills/team-management.md +144 -144
- package/extensions/security/PACK.md +99 -99
- package/extensions/security/skills/api-security.md +140 -140
- package/extensions/security/skills/compliance.md +68 -68
- package/extensions/security/skills/owasp-audit.md +64 -64
- package/extensions/security/skills/pentest-patterns.md +77 -77
- package/extensions/security/skills/secret-mgmt.md +65 -65
- package/extensions/security/skills/supply-chain.md +65 -65
- package/extensions/trading/PACK.md +80 -80
- package/extensions/trading/skills/chart-components.md +55 -55
- package/extensions/trading/skills/experiment-loop.md +125 -125
- package/extensions/trading/skills/fintech-patterns.md +47 -47
- package/extensions/trading/skills/indicator-library.md +58 -58
- package/extensions/trading/skills/quant-analysis.md +111 -111
- package/extensions/trading/skills/realtime-data.md +58 -58
- package/extensions/trading/skills/trade-logic.md +104 -104
- package/extensions/ui/PACK.md +130 -130
- package/extensions/ui/skills/a11y-audit.md +91 -91
- package/extensions/ui/skills/animation-patterns.md +127 -127
- package/extensions/ui/skills/component-patterns.md +100 -100
- package/extensions/ui/skills/design-decision.md +108 -108
- package/extensions/ui/skills/design-system.md +68 -68
- package/extensions/ui/skills/landing-patterns.md +155 -155
- package/extensions/ui/skills/palette-picker.md +173 -173
- package/extensions/ui/skills/react-health.md +90 -90
- package/extensions/ui/skills/type-system.md +125 -125
- package/extensions/ui/skills/web-vitals.md +153 -153
- package/extensions/zalo/PACK.md +145 -145
- package/extensions/zalo/skills/zalo-oa-mcp.md +317 -317
- package/extensions/zalo/skills/zalo-oa-messaging.md +429 -429
- package/extensions/zalo/skills/zalo-oa-setup.md +236 -236
- package/extensions/zalo/skills/zalo-oa-webhook.md +189 -189
- package/extensions/zalo/skills/zalo-personal-messaging.md +194 -194
- package/extensions/zalo/skills/zalo-personal-setup.md +153 -153
- package/extensions/zalo/skills/zalo-rate-guard.md +219 -219
- package/hooks/auto-format/index.cjs +48 -48
- package/hooks/hooks.json +111 -111
- package/hooks/post-session-reflect/index.cjs +189 -189
- package/hooks/pre-compact/index.cjs +95 -95
- package/hooks/run-hook.cmd +1 -1
- package/hooks/secrets-scan/index.cjs +100 -100
- package/hooks/session-start/index.cjs +71 -71
- package/hooks/typecheck/index.cjs +65 -65
- package/package.json +63 -63
- package/references/ui-pro-max-data/LICENSE-UI-PRO-MAX +21 -21
- package/references/ui-pro-max-data/charts.csv +26 -26
- package/references/ui-pro-max-data/colors.csv +161 -161
- package/references/ui-pro-max-data/styles.csv +68 -68
- package/references/ui-pro-max-data/typography.csv +74 -74
- package/references/ui-pro-max-data/ui-reasoning.csv +162 -162
- package/references/ui-pro-max-data/ux-guidelines.csv +99 -99
- package/skills/adversary/SKILL.md +283 -283
- package/skills/asset-creator/SKILL.md +157 -157
- package/skills/audit/SKILL.md +147 -2
- package/skills/autopsy/SKILL.md +335 -335
- package/skills/brainstorm/SKILL.md +342 -342
- package/skills/browser-pilot/SKILL.md +168 -168
- package/skills/constraint-check/SKILL.md +165 -165
- package/skills/context-engine/SKILL.md +404 -404
- package/skills/cook/SKILL.md +917 -863
- package/skills/db/SKILL.md +273 -273
- package/skills/debug/SKILL.md +465 -465
- package/skills/dependency-doctor/SKILL.md +265 -235
- package/skills/deploy/SKILL.md +274 -231
- package/skills/design/DESIGN-REFERENCE.md +365 -365
- package/skills/design/SKILL.md +589 -589
- package/skills/doc-processor/SKILL.md +254 -254
- package/skills/docs/SKILL.md +374 -374
- package/skills/docs-seeker/SKILL.md +177 -177
- package/skills/fix/SKILL.md +330 -330
- package/skills/git/SKILL.md +339 -339
- package/skills/hallucination-guard/SKILL.md +219 -219
- package/skills/incident/SKILL.md +254 -253
- package/skills/integrity-check/SKILL.md +169 -169
- package/skills/journal/SKILL.md +240 -240
- package/skills/launch/SKILL.md +344 -344
- package/skills/logic-guardian/SKILL.md +251 -251
- package/skills/marketing/SKILL.md +290 -289
- package/skills/mcp-builder/SKILL.md +425 -425
- package/skills/neural-memory/SKILL.md +362 -362
- package/skills/onboard/SKILL.md +404 -403
- package/skills/perf/SKILL.md +346 -346
- package/skills/plan/SKILL.md +433 -428
- package/skills/preflight/SKILL.md +415 -415
- package/skills/problem-solver/SKILL.md +380 -284
- package/skills/rescue/SKILL.md +474 -474
- package/skills/retro/SKILL.md +3 -1
- package/skills/review/SKILL.md +612 -588
- package/skills/review-intake/SKILL.md +249 -249
- package/skills/safeguard/SKILL.md +200 -200
- package/skills/sast/SKILL.md +190 -190
- package/skills/scaffold/SKILL.md +328 -287
- package/skills/scope-guard/SKILL.md +180 -180
- package/skills/scout/SKILL.md +263 -263
- package/skills/sentinel/SKILL.md +382 -381
- package/skills/sentinel-env/SKILL.md +254 -254
- package/skills/sequential-thinking/SKILL.md +234 -234
- package/skills/session-bridge/SKILL.md +543 -543
- package/skills/skill-forge/SKILL.md +581 -581
- package/skills/skill-router/SKILL.md +3 -0
- package/skills/surgeon/SKILL.md +215 -215
- package/skills/team/SKILL.md +556 -537
- package/skills/test/SKILL.md +614 -614
- package/skills/trend-scout/SKILL.md +145 -145
- package/skills/verification/SKILL.md +326 -326
- package/skills/video-creator/SKILL.md +201 -201
- package/skills/watchdog/SKILL.md +168 -168
- package/skills/worktree/SKILL.md +140 -140
|
@@ -1,89 +1,89 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "physics-engine"
|
|
3
|
-
pack: "@rune/gamedev"
|
|
4
|
-
description: "Physics integration — Rapier.js, rigid bodies, constraints, raycasting, collision callbacks, deterministic simulation."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# physics-engine
|
|
10
|
-
|
|
11
|
-
Physics integration — Rapier.js, rigid bodies, constraints, raycasting, collision callbacks, deterministic simulation.
|
|
12
|
-
|
|
13
|
-
#### Workflow
|
|
14
|
-
|
|
15
|
-
**Step 1 — Detect physics setup**
|
|
16
|
-
Use Grep to find physics libraries: `rapier`, `cannon`, `ammo`, `@dimforge/rapier3d`, `RigidBody`, `Collider`. Read physics initialization and body creation to understand: engine choice, world configuration, and collision handling.
|
|
17
|
-
|
|
18
|
-
**Step 2 — Audit physics configuration**
|
|
19
|
-
Check for: physics step tied to render frame (non-deterministic), missing collision groups (everything collides with everything), no sleep threshold (wasted CPU on static objects), raycasts without max distance (expensive), and missing body cleanup on entity destroy.
|
|
20
|
-
|
|
21
|
-
**Step 3 — Emit optimized physics**
|
|
22
|
-
Emit: Rapier.js (WASM, deterministic) setup with proper collision groups, sleep thresholds, event-driven collision callbacks, and raycasting utility.
|
|
23
|
-
|
|
24
|
-
#### Example
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// Rapier.js (WASM) — setup with collision groups and raycasting
|
|
28
|
-
import RAPIER from '@dimforge/rapier3d-compat';
|
|
29
|
-
|
|
30
|
-
await RAPIER.init();
|
|
31
|
-
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
|
|
32
|
-
|
|
33
|
-
// Collision groups: player=0x0001, enemy=0x0002, ground=0x0004, projectile=0x0008
|
|
34
|
-
const GROUPS = { PLAYER: 0x0001, ENEMY: 0x0002, GROUND: 0x0004, PROJECTILE: 0x0008 };
|
|
35
|
-
|
|
36
|
-
// Ground — static, collides with everything
|
|
37
|
-
const groundBody = world.createRigidBody(RAPIER.RigidBodyDesc.fixed().setTranslation(0, 0, 0));
|
|
38
|
-
world.createCollider(
|
|
39
|
-
RAPIER.ColliderDesc.cuboid(50, 0.1, 50)
|
|
40
|
-
.setCollisionGroups((GROUPS.GROUND << 16) | 0xFFFF),
|
|
41
|
-
groundBody,
|
|
42
|
-
);
|
|
43
|
-
|
|
44
|
-
// Player — dynamic, collides with ground + enemy (not own projectiles)
|
|
45
|
-
const playerBody = world.createRigidBody(RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 5, 0));
|
|
46
|
-
world.createCollider(
|
|
47
|
-
RAPIER.ColliderDesc.capsule(0.5, 0.3)
|
|
48
|
-
.setCollisionGroups((GROUPS.PLAYER << 16) | (GROUPS.GROUND | GROUPS.ENEMY))
|
|
49
|
-
.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS),
|
|
50
|
-
playerBody,
|
|
51
|
-
);
|
|
52
|
-
|
|
53
|
-
// Raycast utility
|
|
54
|
-
function raycast(origin: RAPIER.Vector3, direction: RAPIER.Vector3, maxDist = 100) {
|
|
55
|
-
const ray = new RAPIER.Ray(origin, direction);
|
|
56
|
-
const hit = world.castRay(ray, maxDist, true);
|
|
57
|
-
if (hit) {
|
|
58
|
-
const point = ray.pointAt(hit.timeOfImpact);
|
|
59
|
-
return { point, normal: hit.normal, collider: hit.collider };
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
#### Collision Event Handling
|
|
66
|
-
|
|
67
|
-
```typescript
|
|
68
|
-
// Event-driven collision callbacks — no polling
|
|
69
|
-
const eventQueue = new RAPIER.EventQueue(true);
|
|
70
|
-
|
|
71
|
-
// In fixed update step:
|
|
72
|
-
world.step(eventQueue);
|
|
73
|
-
|
|
74
|
-
eventQueue.drainCollisionEvents((handle1, handle2, started) => {
|
|
75
|
-
const body1 = world.getRigidBody(world.getCollider(handle1).parent()!);
|
|
76
|
-
const body2 = world.getRigidBody(world.getCollider(handle2).parent()!);
|
|
77
|
-
|
|
78
|
-
const entity1 = entityMap.get(body1.handle);
|
|
79
|
-
const entity2 = entityMap.get(body2.handle);
|
|
80
|
-
|
|
81
|
-
if (started) {
|
|
82
|
-
entity1?.onCollisionEnter(entity2);
|
|
83
|
-
entity2?.onCollisionEnter(entity1);
|
|
84
|
-
} else {
|
|
85
|
-
entity1?.onCollisionExit(entity2);
|
|
86
|
-
entity2?.onCollisionExit(entity1);
|
|
87
|
-
}
|
|
88
|
-
});
|
|
89
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "physics-engine"
|
|
3
|
+
pack: "@rune/gamedev"
|
|
4
|
+
description: "Physics integration — Rapier.js, rigid bodies, constraints, raycasting, collision callbacks, deterministic simulation."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# physics-engine
|
|
10
|
+
|
|
11
|
+
Physics integration — Rapier.js, rigid bodies, constraints, raycasting, collision callbacks, deterministic simulation.
|
|
12
|
+
|
|
13
|
+
#### Workflow
|
|
14
|
+
|
|
15
|
+
**Step 1 — Detect physics setup**
|
|
16
|
+
Use Grep to find physics libraries: `rapier`, `cannon`, `ammo`, `@dimforge/rapier3d`, `RigidBody`, `Collider`. Read physics initialization and body creation to understand: engine choice, world configuration, and collision handling.
|
|
17
|
+
|
|
18
|
+
**Step 2 — Audit physics configuration**
|
|
19
|
+
Check for: physics step tied to render frame (non-deterministic), missing collision groups (everything collides with everything), no sleep threshold (wasted CPU on static objects), raycasts without max distance (expensive), and missing body cleanup on entity destroy.
|
|
20
|
+
|
|
21
|
+
**Step 3 — Emit optimized physics**
|
|
22
|
+
Emit: Rapier.js (WASM, deterministic) setup with proper collision groups, sleep thresholds, event-driven collision callbacks, and raycasting utility.
|
|
23
|
+
|
|
24
|
+
#### Example
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Rapier.js (WASM) — setup with collision groups and raycasting
|
|
28
|
+
import RAPIER from '@dimforge/rapier3d-compat';
|
|
29
|
+
|
|
30
|
+
await RAPIER.init();
|
|
31
|
+
const world = new RAPIER.World({ x: 0, y: -9.81, z: 0 });
|
|
32
|
+
|
|
33
|
+
// Collision groups: player=0x0001, enemy=0x0002, ground=0x0004, projectile=0x0008
|
|
34
|
+
const GROUPS = { PLAYER: 0x0001, ENEMY: 0x0002, GROUND: 0x0004, PROJECTILE: 0x0008 };
|
|
35
|
+
|
|
36
|
+
// Ground — static, collides with everything
|
|
37
|
+
const groundBody = world.createRigidBody(RAPIER.RigidBodyDesc.fixed().setTranslation(0, 0, 0));
|
|
38
|
+
world.createCollider(
|
|
39
|
+
RAPIER.ColliderDesc.cuboid(50, 0.1, 50)
|
|
40
|
+
.setCollisionGroups((GROUPS.GROUND << 16) | 0xFFFF),
|
|
41
|
+
groundBody,
|
|
42
|
+
);
|
|
43
|
+
|
|
44
|
+
// Player — dynamic, collides with ground + enemy (not own projectiles)
|
|
45
|
+
const playerBody = world.createRigidBody(RAPIER.RigidBodyDesc.dynamic().setTranslation(0, 5, 0));
|
|
46
|
+
world.createCollider(
|
|
47
|
+
RAPIER.ColliderDesc.capsule(0.5, 0.3)
|
|
48
|
+
.setCollisionGroups((GROUPS.PLAYER << 16) | (GROUPS.GROUND | GROUPS.ENEMY))
|
|
49
|
+
.setActiveEvents(RAPIER.ActiveEvents.COLLISION_EVENTS),
|
|
50
|
+
playerBody,
|
|
51
|
+
);
|
|
52
|
+
|
|
53
|
+
// Raycast utility
|
|
54
|
+
function raycast(origin: RAPIER.Vector3, direction: RAPIER.Vector3, maxDist = 100) {
|
|
55
|
+
const ray = new RAPIER.Ray(origin, direction);
|
|
56
|
+
const hit = world.castRay(ray, maxDist, true);
|
|
57
|
+
if (hit) {
|
|
58
|
+
const point = ray.pointAt(hit.timeOfImpact);
|
|
59
|
+
return { point, normal: hit.normal, collider: hit.collider };
|
|
60
|
+
}
|
|
61
|
+
return null;
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
#### Collision Event Handling
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// Event-driven collision callbacks — no polling
|
|
69
|
+
const eventQueue = new RAPIER.EventQueue(true);
|
|
70
|
+
|
|
71
|
+
// In fixed update step:
|
|
72
|
+
world.step(eventQueue);
|
|
73
|
+
|
|
74
|
+
eventQueue.drainCollisionEvents((handle1, handle2, started) => {
|
|
75
|
+
const body1 = world.getRigidBody(world.getCollider(handle1).parent()!);
|
|
76
|
+
const body2 = world.getRigidBody(world.getCollider(handle2).parent()!);
|
|
77
|
+
|
|
78
|
+
const entity1 = entityMap.get(body1.handle);
|
|
79
|
+
const entity2 = entityMap.get(body2.handle);
|
|
80
|
+
|
|
81
|
+
if (started) {
|
|
82
|
+
entity1?.onCollisionEnter(entity2);
|
|
83
|
+
entity2?.onCollisionEnter(entity1);
|
|
84
|
+
} else {
|
|
85
|
+
entity1?.onCollisionExit(entity2);
|
|
86
|
+
entity2?.onCollisionExit(entity1);
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
```
|
|
@@ -1,146 +1,146 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "scene-management"
|
|
3
|
-
pack: "@rune/gamedev"
|
|
4
|
-
description: "Scene transitions, preloading, serialization — stack-based SceneManager with fade transitions, asset preloading before enter, and level JSON serialization."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# scene-management
|
|
10
|
-
|
|
11
|
-
Scene transitions, preloading, serialization — stack-based SceneManager with fade transitions, asset preloading before enter, and level JSON serialization.
|
|
12
|
-
|
|
13
|
-
#### Workflow
|
|
14
|
-
|
|
15
|
-
**Step 1 — Detect scene structure**
|
|
16
|
-
Use Grep to find scene code: `Scene`, `GameState`, `StateMachine`, `push`, `pop`, `replace`. Read to understand: how scenes transition, whether assets are preloaded, and how scene data is persisted.
|
|
17
|
-
|
|
18
|
-
**Step 2 — Audit scene management**
|
|
19
|
-
Check for: abrupt scene switches with no transition (jarring UX), assets loaded mid-scene causing stutters, no scene stack (can't return to previous state), missing cleanup on exit.
|
|
20
|
-
|
|
21
|
-
**Step 3 — Emit SceneManager**
|
|
22
|
-
Emit: stack-based SceneManager with fade in/out, asset preloading tied to scene.assets[], push/pop/replace operations, and level serialization/deserialization.
|
|
23
|
-
|
|
24
|
-
#### Scene Stack with Transitions and Preloading
|
|
25
|
-
|
|
26
|
-
```typescript
|
|
27
|
-
// Scene manager — stack-based, asset preloading, fade transitions
|
|
28
|
-
interface Scene {
|
|
29
|
-
name: string;
|
|
30
|
-
assets: string[]; // asset keys to preload before entering
|
|
31
|
-
onEnter(data?: unknown): void;
|
|
32
|
-
onExit(): void;
|
|
33
|
-
update(dt: number): void;
|
|
34
|
-
render(ctx: CanvasRenderingContext2D): void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
class SceneManager {
|
|
38
|
-
private stack: Scene[] = [];
|
|
39
|
-
private loader: AssetLoader;
|
|
40
|
-
private transitioning = false;
|
|
41
|
-
private fadeAlpha = 0;
|
|
42
|
-
private fadeDir: 1 | -1 = 1;
|
|
43
|
-
|
|
44
|
-
constructor(loader: AssetLoader) { this.loader = loader; }
|
|
45
|
-
|
|
46
|
-
get current(): Scene | undefined { return this.stack[this.stack.length - 1]; }
|
|
47
|
-
|
|
48
|
-
async push(scene: Scene, data?: unknown) {
|
|
49
|
-
if (this.transitioning) return;
|
|
50
|
-
this.transitioning = true;
|
|
51
|
-
|
|
52
|
-
await this.fadeOut();
|
|
53
|
-
await this.preloadScene(scene);
|
|
54
|
-
this.stack.push(scene);
|
|
55
|
-
scene.onEnter(data);
|
|
56
|
-
await this.fadeIn();
|
|
57
|
-
|
|
58
|
-
this.transitioning = false;
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
async pop() {
|
|
62
|
-
if (this.transitioning || this.stack.length <= 1) return;
|
|
63
|
-
this.transitioning = true;
|
|
64
|
-
|
|
65
|
-
await this.fadeOut();
|
|
66
|
-
this.current?.onExit();
|
|
67
|
-
this.stack.pop();
|
|
68
|
-
await this.fadeIn();
|
|
69
|
-
|
|
70
|
-
this.transitioning = false;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
async replace(scene: Scene, data?: unknown) {
|
|
74
|
-
if (this.transitioning) return;
|
|
75
|
-
this.transitioning = true;
|
|
76
|
-
|
|
77
|
-
await this.fadeOut();
|
|
78
|
-
this.current?.onExit();
|
|
79
|
-
this.stack.pop();
|
|
80
|
-
await this.preloadScene(scene);
|
|
81
|
-
this.stack.push(scene);
|
|
82
|
-
scene.onEnter(data);
|
|
83
|
-
await this.fadeIn();
|
|
84
|
-
|
|
85
|
-
this.transitioning = false;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
private async preloadScene(scene: Scene) {
|
|
89
|
-
// Preload only assets not already cached
|
|
90
|
-
await Promise.all(scene.assets.map(key => this.loader.load(key, `/assets/${key}`)));
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
renderTransition(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
|
94
|
-
if (this.fadeAlpha > 0) {
|
|
95
|
-
ctx.save();
|
|
96
|
-
ctx.globalAlpha = this.fadeAlpha;
|
|
97
|
-
ctx.fillStyle = '#000';
|
|
98
|
-
ctx.fillRect(0, 0, w, h);
|
|
99
|
-
ctx.restore();
|
|
100
|
-
}
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
private fadeOut(): Promise<void> {
|
|
104
|
-
return new Promise(resolve => {
|
|
105
|
-
this.fadeDir = 1;
|
|
106
|
-
const interval = setInterval(() => {
|
|
107
|
-
this.fadeAlpha = Math.min(1, this.fadeAlpha + 0.05);
|
|
108
|
-
if (this.fadeAlpha >= 1) { clearInterval(interval); resolve(); }
|
|
109
|
-
}, 16);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
private fadeIn(): Promise<void> {
|
|
114
|
-
return new Promise(resolve => {
|
|
115
|
-
this.fadeDir = -1;
|
|
116
|
-
const interval = setInterval(() => {
|
|
117
|
-
this.fadeAlpha = Math.max(0, this.fadeAlpha - 0.05);
|
|
118
|
-
if (this.fadeAlpha <= 0) { clearInterval(interval); resolve(); }
|
|
119
|
-
}, 16);
|
|
120
|
-
});
|
|
121
|
-
}
|
|
122
|
-
}
|
|
123
|
-
|
|
124
|
-
// Level serialization — save/load level state as JSON
|
|
125
|
-
interface LevelData {
|
|
126
|
-
name: string;
|
|
127
|
-
entities: Array<{ type: string; x: number; y: number; props: Record<string, unknown> }>;
|
|
128
|
-
tilemap: number[][];
|
|
129
|
-
}
|
|
130
|
-
|
|
131
|
-
function serializeLevel(world: World): LevelData {
|
|
132
|
-
const entities: LevelData['entities'] = [];
|
|
133
|
-
for (const id of world.query(Position)) {
|
|
134
|
-
const pos = world.getComponent(id, Position)!;
|
|
135
|
-
entities.push({ type: 'generic', x: pos.x, y: pos.y, props: {} });
|
|
136
|
-
}
|
|
137
|
-
return { name: 'level1', entities, tilemap: [] };
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
function deserializeLevel(data: LevelData, world: World) {
|
|
141
|
-
for (const e of data.entities) {
|
|
142
|
-
const id = world.createEntity();
|
|
143
|
-
world.addComponent(id, new Position(e.x, e.y));
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "scene-management"
|
|
3
|
+
pack: "@rune/gamedev"
|
|
4
|
+
description: "Scene transitions, preloading, serialization — stack-based SceneManager with fade transitions, asset preloading before enter, and level JSON serialization."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# scene-management
|
|
10
|
+
|
|
11
|
+
Scene transitions, preloading, serialization — stack-based SceneManager with fade transitions, asset preloading before enter, and level JSON serialization.
|
|
12
|
+
|
|
13
|
+
#### Workflow
|
|
14
|
+
|
|
15
|
+
**Step 1 — Detect scene structure**
|
|
16
|
+
Use Grep to find scene code: `Scene`, `GameState`, `StateMachine`, `push`, `pop`, `replace`. Read to understand: how scenes transition, whether assets are preloaded, and how scene data is persisted.
|
|
17
|
+
|
|
18
|
+
**Step 2 — Audit scene management**
|
|
19
|
+
Check for: abrupt scene switches with no transition (jarring UX), assets loaded mid-scene causing stutters, no scene stack (can't return to previous state), missing cleanup on exit.
|
|
20
|
+
|
|
21
|
+
**Step 3 — Emit SceneManager**
|
|
22
|
+
Emit: stack-based SceneManager with fade in/out, asset preloading tied to scene.assets[], push/pop/replace operations, and level serialization/deserialization.
|
|
23
|
+
|
|
24
|
+
#### Scene Stack with Transitions and Preloading
|
|
25
|
+
|
|
26
|
+
```typescript
|
|
27
|
+
// Scene manager — stack-based, asset preloading, fade transitions
|
|
28
|
+
interface Scene {
|
|
29
|
+
name: string;
|
|
30
|
+
assets: string[]; // asset keys to preload before entering
|
|
31
|
+
onEnter(data?: unknown): void;
|
|
32
|
+
onExit(): void;
|
|
33
|
+
update(dt: number): void;
|
|
34
|
+
render(ctx: CanvasRenderingContext2D): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
class SceneManager {
|
|
38
|
+
private stack: Scene[] = [];
|
|
39
|
+
private loader: AssetLoader;
|
|
40
|
+
private transitioning = false;
|
|
41
|
+
private fadeAlpha = 0;
|
|
42
|
+
private fadeDir: 1 | -1 = 1;
|
|
43
|
+
|
|
44
|
+
constructor(loader: AssetLoader) { this.loader = loader; }
|
|
45
|
+
|
|
46
|
+
get current(): Scene | undefined { return this.stack[this.stack.length - 1]; }
|
|
47
|
+
|
|
48
|
+
async push(scene: Scene, data?: unknown) {
|
|
49
|
+
if (this.transitioning) return;
|
|
50
|
+
this.transitioning = true;
|
|
51
|
+
|
|
52
|
+
await this.fadeOut();
|
|
53
|
+
await this.preloadScene(scene);
|
|
54
|
+
this.stack.push(scene);
|
|
55
|
+
scene.onEnter(data);
|
|
56
|
+
await this.fadeIn();
|
|
57
|
+
|
|
58
|
+
this.transitioning = false;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
async pop() {
|
|
62
|
+
if (this.transitioning || this.stack.length <= 1) return;
|
|
63
|
+
this.transitioning = true;
|
|
64
|
+
|
|
65
|
+
await this.fadeOut();
|
|
66
|
+
this.current?.onExit();
|
|
67
|
+
this.stack.pop();
|
|
68
|
+
await this.fadeIn();
|
|
69
|
+
|
|
70
|
+
this.transitioning = false;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async replace(scene: Scene, data?: unknown) {
|
|
74
|
+
if (this.transitioning) return;
|
|
75
|
+
this.transitioning = true;
|
|
76
|
+
|
|
77
|
+
await this.fadeOut();
|
|
78
|
+
this.current?.onExit();
|
|
79
|
+
this.stack.pop();
|
|
80
|
+
await this.preloadScene(scene);
|
|
81
|
+
this.stack.push(scene);
|
|
82
|
+
scene.onEnter(data);
|
|
83
|
+
await this.fadeIn();
|
|
84
|
+
|
|
85
|
+
this.transitioning = false;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
private async preloadScene(scene: Scene) {
|
|
89
|
+
// Preload only assets not already cached
|
|
90
|
+
await Promise.all(scene.assets.map(key => this.loader.load(key, `/assets/${key}`)));
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
renderTransition(ctx: CanvasRenderingContext2D, w: number, h: number) {
|
|
94
|
+
if (this.fadeAlpha > 0) {
|
|
95
|
+
ctx.save();
|
|
96
|
+
ctx.globalAlpha = this.fadeAlpha;
|
|
97
|
+
ctx.fillStyle = '#000';
|
|
98
|
+
ctx.fillRect(0, 0, w, h);
|
|
99
|
+
ctx.restore();
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
private fadeOut(): Promise<void> {
|
|
104
|
+
return new Promise(resolve => {
|
|
105
|
+
this.fadeDir = 1;
|
|
106
|
+
const interval = setInterval(() => {
|
|
107
|
+
this.fadeAlpha = Math.min(1, this.fadeAlpha + 0.05);
|
|
108
|
+
if (this.fadeAlpha >= 1) { clearInterval(interval); resolve(); }
|
|
109
|
+
}, 16);
|
|
110
|
+
});
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
private fadeIn(): Promise<void> {
|
|
114
|
+
return new Promise(resolve => {
|
|
115
|
+
this.fadeDir = -1;
|
|
116
|
+
const interval = setInterval(() => {
|
|
117
|
+
this.fadeAlpha = Math.max(0, this.fadeAlpha - 0.05);
|
|
118
|
+
if (this.fadeAlpha <= 0) { clearInterval(interval); resolve(); }
|
|
119
|
+
}, 16);
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// Level serialization — save/load level state as JSON
|
|
125
|
+
interface LevelData {
|
|
126
|
+
name: string;
|
|
127
|
+
entities: Array<{ type: string; x: number; y: number; props: Record<string, unknown> }>;
|
|
128
|
+
tilemap: number[][];
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function serializeLevel(world: World): LevelData {
|
|
132
|
+
const entities: LevelData['entities'] = [];
|
|
133
|
+
for (const id of world.query(Position)) {
|
|
134
|
+
const pos = world.getComponent(id, Position)!;
|
|
135
|
+
entities.push({ type: 'generic', x: pos.x, y: pos.y, props: {} });
|
|
136
|
+
}
|
|
137
|
+
return { name: 'level1', entities, tilemap: [] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
function deserializeLevel(data: LevelData, world: World) {
|
|
141
|
+
for (const e of data.entities) {
|
|
142
|
+
const id = world.createEntity();
|
|
143
|
+
world.addComponent(id, new Position(e.x, e.y));
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
```
|
|
@@ -1,90 +1,90 @@
|
|
|
1
|
-
---
|
|
2
|
-
name: "threejs-patterns"
|
|
3
|
-
pack: "@rune/gamedev"
|
|
4
|
-
description: "Three.js patterns — scene setup, React Three Fiber integration, PBR materials, post-processing, performance optimization."
|
|
5
|
-
model: sonnet
|
|
6
|
-
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
-
---
|
|
8
|
-
|
|
9
|
-
# threejs-patterns
|
|
10
|
-
|
|
11
|
-
Three.js patterns — scene setup, React Three Fiber integration, PBR materials, post-processing, performance optimization.
|
|
12
|
-
|
|
13
|
-
#### Workflow
|
|
14
|
-
|
|
15
|
-
**Step 1 — Detect Three.js setup**
|
|
16
|
-
Use Grep to find Three.js usage: `THREE.`, `useThree`, `useFrame`, `Canvas`, `@react-three/fiber`, `@react-three/drei`. Read the main scene file to understand: renderer setup, scene graph structure, camera type, and lighting model.
|
|
17
|
-
|
|
18
|
-
**Step 2 — Audit performance**
|
|
19
|
-
Check for: objects created inside `useFrame` (GC pressure), missing `dispose()` on unmount (memory leak), no frustum culling on large scenes, textures without power-of-two dimensions, unoptimized geometry (too many draw calls), and missing LOD for distant objects.
|
|
20
|
-
|
|
21
|
-
**Step 3 — Emit optimized scene**
|
|
22
|
-
Emit: properly structured R3F scene with declarative lights, memoized geometries, disposal on unmount, instanced meshes for repeated objects, and post-processing pipeline.
|
|
23
|
-
|
|
24
|
-
#### Example
|
|
25
|
-
|
|
26
|
-
```tsx
|
|
27
|
-
// React Three Fiber — optimized scene with instancing and post-processing
|
|
28
|
-
import { Canvas, useFrame } from '@react-three/fiber';
|
|
29
|
-
import { OrbitControls, Environment, useGLTF, Instances, Instance } from '@react-three/drei';
|
|
30
|
-
import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing';
|
|
31
|
-
import { useRef, useMemo } from 'react';
|
|
32
|
-
|
|
33
|
-
function InstancedTrees({ count = 500 }) {
|
|
34
|
-
const positions = useMemo(() =>
|
|
35
|
-
Array.from({ length: count }, () => [
|
|
36
|
-
(Math.random() - 0.5) * 100,
|
|
37
|
-
0,
|
|
38
|
-
(Math.random() - 0.5) * 100,
|
|
39
|
-
] as [number, number, number]),
|
|
40
|
-
[count]);
|
|
41
|
-
|
|
42
|
-
return (
|
|
43
|
-
<Instances limit={count}>
|
|
44
|
-
<cylinderGeometry args={[0.2, 0.4, 3]} />
|
|
45
|
-
<meshStandardMaterial color="#4a7c59" />
|
|
46
|
-
{positions.map((pos, i) => <Instance key={i} position={pos} />)}
|
|
47
|
-
</Instances>
|
|
48
|
-
);
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
function GameScene() {
|
|
52
|
-
return (
|
|
53
|
-
<Canvas camera={{ position: [0, 10, 20], fov: 60 }} gl={{ antialias: true }}>
|
|
54
|
-
<ambientLight intensity={0.3} />
|
|
55
|
-
<directionalLight position={[10, 20, 10]} intensity={1} castShadow />
|
|
56
|
-
<Environment preset="sunset" />
|
|
57
|
-
<InstancedTrees count={500} />
|
|
58
|
-
<OrbitControls maxPolarAngle={Math.PI / 2.2} />
|
|
59
|
-
<EffectComposer>
|
|
60
|
-
<Bloom intensity={0.3} luminanceThreshold={0.8} />
|
|
61
|
-
<Vignette offset={0.3} darkness={0.6} />
|
|
62
|
-
</EffectComposer>
|
|
63
|
-
</Canvas>
|
|
64
|
-
);
|
|
65
|
-
}
|
|
66
|
-
```
|
|
67
|
-
|
|
68
|
-
#### LOD (Level of Detail) Pattern
|
|
69
|
-
|
|
70
|
-
```typescript
|
|
71
|
-
import * as THREE from 'three';
|
|
72
|
-
|
|
73
|
-
// Swap geometry based on camera distance
|
|
74
|
-
function buildLODMesh(
|
|
75
|
-
highGeo: THREE.BufferGeometry,
|
|
76
|
-
midGeo: THREE.BufferGeometry,
|
|
77
|
-
lowGeo: THREE.BufferGeometry,
|
|
78
|
-
material: THREE.Material,
|
|
79
|
-
): THREE.LOD {
|
|
80
|
-
const lod = new THREE.LOD();
|
|
81
|
-
lod.addLevel(new THREE.Mesh(highGeo, material), 0); // < 20 units
|
|
82
|
-
lod.addLevel(new THREE.Mesh(midGeo, material), 20); // 20–80 units
|
|
83
|
-
lod.addLevel(new THREE.Mesh(lowGeo, material), 80); // > 80 units
|
|
84
|
-
return lod;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
// In render loop — LOD auto-updates based on camera distance
|
|
88
|
-
// scene.add(buildLODMesh(highGeo, midGeo, lowGeo, mat));
|
|
89
|
-
// lod.update(camera); // call once per frame
|
|
90
|
-
```
|
|
1
|
+
---
|
|
2
|
+
name: "threejs-patterns"
|
|
3
|
+
pack: "@rune/gamedev"
|
|
4
|
+
description: "Three.js patterns — scene setup, React Three Fiber integration, PBR materials, post-processing, performance optimization."
|
|
5
|
+
model: sonnet
|
|
6
|
+
tools: [Read, Edit, Write, Grep, Glob, Bash]
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# threejs-patterns
|
|
10
|
+
|
|
11
|
+
Three.js patterns — scene setup, React Three Fiber integration, PBR materials, post-processing, performance optimization.
|
|
12
|
+
|
|
13
|
+
#### Workflow
|
|
14
|
+
|
|
15
|
+
**Step 1 — Detect Three.js setup**
|
|
16
|
+
Use Grep to find Three.js usage: `THREE.`, `useThree`, `useFrame`, `Canvas`, `@react-three/fiber`, `@react-three/drei`. Read the main scene file to understand: renderer setup, scene graph structure, camera type, and lighting model.
|
|
17
|
+
|
|
18
|
+
**Step 2 — Audit performance**
|
|
19
|
+
Check for: objects created inside `useFrame` (GC pressure), missing `dispose()` on unmount (memory leak), no frustum culling on large scenes, textures without power-of-two dimensions, unoptimized geometry (too many draw calls), and missing LOD for distant objects.
|
|
20
|
+
|
|
21
|
+
**Step 3 — Emit optimized scene**
|
|
22
|
+
Emit: properly structured R3F scene with declarative lights, memoized geometries, disposal on unmount, instanced meshes for repeated objects, and post-processing pipeline.
|
|
23
|
+
|
|
24
|
+
#### Example
|
|
25
|
+
|
|
26
|
+
```tsx
|
|
27
|
+
// React Three Fiber — optimized scene with instancing and post-processing
|
|
28
|
+
import { Canvas, useFrame } from '@react-three/fiber';
|
|
29
|
+
import { OrbitControls, Environment, useGLTF, Instances, Instance } from '@react-three/drei';
|
|
30
|
+
import { EffectComposer, Bloom, Vignette } from '@react-three/postprocessing';
|
|
31
|
+
import { useRef, useMemo } from 'react';
|
|
32
|
+
|
|
33
|
+
function InstancedTrees({ count = 500 }) {
|
|
34
|
+
const positions = useMemo(() =>
|
|
35
|
+
Array.from({ length: count }, () => [
|
|
36
|
+
(Math.random() - 0.5) * 100,
|
|
37
|
+
0,
|
|
38
|
+
(Math.random() - 0.5) * 100,
|
|
39
|
+
] as [number, number, number]),
|
|
40
|
+
[count]);
|
|
41
|
+
|
|
42
|
+
return (
|
|
43
|
+
<Instances limit={count}>
|
|
44
|
+
<cylinderGeometry args={[0.2, 0.4, 3]} />
|
|
45
|
+
<meshStandardMaterial color="#4a7c59" />
|
|
46
|
+
{positions.map((pos, i) => <Instance key={i} position={pos} />)}
|
|
47
|
+
</Instances>
|
|
48
|
+
);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function GameScene() {
|
|
52
|
+
return (
|
|
53
|
+
<Canvas camera={{ position: [0, 10, 20], fov: 60 }} gl={{ antialias: true }}>
|
|
54
|
+
<ambientLight intensity={0.3} />
|
|
55
|
+
<directionalLight position={[10, 20, 10]} intensity={1} castShadow />
|
|
56
|
+
<Environment preset="sunset" />
|
|
57
|
+
<InstancedTrees count={500} />
|
|
58
|
+
<OrbitControls maxPolarAngle={Math.PI / 2.2} />
|
|
59
|
+
<EffectComposer>
|
|
60
|
+
<Bloom intensity={0.3} luminanceThreshold={0.8} />
|
|
61
|
+
<Vignette offset={0.3} darkness={0.6} />
|
|
62
|
+
</EffectComposer>
|
|
63
|
+
</Canvas>
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
#### LOD (Level of Detail) Pattern
|
|
69
|
+
|
|
70
|
+
```typescript
|
|
71
|
+
import * as THREE from 'three';
|
|
72
|
+
|
|
73
|
+
// Swap geometry based on camera distance
|
|
74
|
+
function buildLODMesh(
|
|
75
|
+
highGeo: THREE.BufferGeometry,
|
|
76
|
+
midGeo: THREE.BufferGeometry,
|
|
77
|
+
lowGeo: THREE.BufferGeometry,
|
|
78
|
+
material: THREE.Material,
|
|
79
|
+
): THREE.LOD {
|
|
80
|
+
const lod = new THREE.LOD();
|
|
81
|
+
lod.addLevel(new THREE.Mesh(highGeo, material), 0); // < 20 units
|
|
82
|
+
lod.addLevel(new THREE.Mesh(midGeo, material), 20); // 20–80 units
|
|
83
|
+
lod.addLevel(new THREE.Mesh(lowGeo, material), 80); // > 80 units
|
|
84
|
+
return lod;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// In render loop — LOD auto-updates based on camera distance
|
|
88
|
+
// scene.add(buildLODMesh(highGeo, midGeo, lowGeo, mat));
|
|
89
|
+
// lod.update(camera); // call once per frame
|
|
90
|
+
```
|