@vgai/engine 0.2.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 +202 -0
- package/README.md +35 -0
- package/package.json +55 -0
- package/src/adapter/authoring.ts +402 -0
- package/src/adapter/colyseus-networking-adapter.ts +72 -0
- package/src/adapter/first-party-systems.ts +103 -0
- package/src/adapter/game-adapter.ts +151 -0
- package/src/adapter/host-context.ts +77 -0
- package/src/adapter/index.ts +85 -0
- package/src/adapter/ingest/game-contract.ts +59 -0
- package/src/adapter/ingest/overlay-applier.ts +207 -0
- package/src/adapter/ingest/overlay-apply.ts +124 -0
- package/src/adapter/ingest/overlay-file.ts +126 -0
- package/src/adapter/ingest/overlay-report.ts +176 -0
- package/src/adapter/ingest/scene-capture.ts +307 -0
- package/src/adapter/ingest/upstream-pin.ts +52 -0
- package/src/adapter/loop-gate-report.ts +54 -0
- package/src/adapter/rapier-physics-adapter.ts +56 -0
- package/src/adapter/system-adapter.ts +154 -0
- package/src/adapter/transform.ts +18 -0
- package/src/adapter/vgai-scene-game-adapter.ts +886 -0
- package/src/adapter/world-kind.ts +34 -0
- package/src/ai/navigation.ts +164 -0
- package/src/animation/anim-graph-types.ts +56 -0
- package/src/animation/anim-graph.ts +406 -0
- package/src/animation/anim-system.ts +28 -0
- package/src/animation/blend-node.ts +119 -0
- package/src/animation/property-track.ts +178 -0
- package/src/animation/schema.ts +204 -0
- package/src/assets.ts +80 -0
- package/src/audio/ambient.ts +300 -0
- package/src/audio/impacts.ts +212 -0
- package/src/audio/index.ts +7 -0
- package/src/audio/movement.ts +140 -0
- package/src/audio/musical.ts +200 -0
- package/src/audio/ui-sounds.ts +171 -0
- package/src/audio/vehicle.ts +235 -0
- package/src/audio/weapons.ts +152 -0
- package/src/core/game-loop.ts +127 -0
- package/src/core/system-runner.ts +298 -0
- package/src/core/types.ts +58 -0
- package/src/dev/console-bridge.ts +83 -0
- package/src/dev/debug-draw.ts +80 -0
- package/src/dev/logger.ts +119 -0
- package/src/ecs/component-manager.ts +748 -0
- package/src/ecs/game-component.ts +147 -0
- package/src/ecs/hmr-swap-report.ts +65 -0
- package/src/input/input-manager.ts +439 -0
- package/src/input/input-types.ts +19 -0
- package/src/input/schema.ts +129 -0
- package/src/loader.ts +70 -0
- package/src/manifest/index.ts +24 -0
- package/src/manifest/load-file.ts +16 -0
- package/src/manifest/load.ts +378 -0
- package/src/manifest/schema.ts +375 -0
- package/src/physics/collision-system.ts +76 -0
- package/src/physics/physics-registry.ts +83 -0
- package/src/physics/transform-writer.ts +41 -0
- package/src/physics/trigger-dispatch.ts +97 -0
- package/src/react/game-state.tsx +172 -0
- package/src/render/auto-batcher.ts +169 -0
- package/src/render/render-batch-system.ts +268 -0
- package/src/render/render-features.ts +146 -0
- package/src/render/render-settings.ts +72 -0
- package/src/runtime/create-runtime.ts +1152 -0
- package/src/runtime/frame-selector-cache.ts +81 -0
- package/src/runtime/game.ts +1003 -0
- package/src/runtime/input-router.ts +213 -0
- package/src/runtime/mount-game.ts +269 -0
- package/src/runtime/mount-manifest.ts +361 -0
- package/src/runtime/scene-ui-bridge.ts +86 -0
- package/src/runtime/scene-ui-data.ts +119 -0
- package/src/runtime/state-bridge.ts +79 -0
- package/src/runtime/types.ts +196 -0
- package/src/scene/asset-loaders.ts +195 -0
- package/src/scene/asset-paths.ts +123 -0
- package/src/scene/asset-registry.ts +67 -0
- package/src/scene/collider-dimensions.ts +125 -0
- package/src/scene/component-registry.ts +40 -0
- package/src/scene/defaults.ts +164 -0
- package/src/scene/geometries/index.ts +7 -0
- package/src/scene/geometries/terrain.ts +42 -0
- package/src/scene/geometry-registry.ts +42 -0
- package/src/scene/instance-registry.ts +84 -0
- package/src/scene/instancers/grid.ts +38 -0
- package/src/scene/instancers/index.ts +7 -0
- package/src/scene/light-camera-factory.ts +97 -0
- package/src/scene/material-factory.ts +211 -0
- package/src/scene/material-registry.ts +73 -0
- package/src/scene/materials/index.ts +7 -0
- package/src/scene/materials/water.ts +56 -0
- package/src/scene/parse.ts +71 -0
- package/src/scene/particles-factory.ts +383 -0
- package/src/scene/scene-apply.ts +356 -0
- package/src/scene/scene-diff-schema.ts +115 -0
- package/src/scene/scene-diff-types.ts +29 -0
- package/src/scene/scene-loader.ts +1533 -0
- package/src/scene/scene-query.ts +63 -0
- package/src/scene/scene-types.ts +34 -0
- package/src/scene/scene-version.ts +40 -0
- package/src/scene/schema/animation.ts +95 -0
- package/src/scene/schema/audio.ts +25 -0
- package/src/scene/schema/camera.ts +21 -0
- package/src/scene/schema/collider.ts +69 -0
- package/src/scene/schema/entity-ref.ts +78 -0
- package/src/scene/schema/entity.ts +169 -0
- package/src/scene/schema/environment.ts +384 -0
- package/src/scene/schema/index.ts +95 -0
- package/src/scene/schema/instances.ts +35 -0
- package/src/scene/schema/joint.ts +26 -0
- package/src/scene/schema/light.ts +38 -0
- package/src/scene/schema/material.ts +113 -0
- package/src/scene/schema/mesh.ts +108 -0
- package/src/scene/schema/particles.ts +398 -0
- package/src/scene/schema/physics.ts +49 -0
- package/src/scene/schema/scene-file.ts +299 -0
- package/src/scene/schema/shadow.ts +24 -0
- package/src/scene/schema/spline.ts +21 -0
- package/src/scene/schema/tuples.ts +21 -0
- package/src/scene/schema/ui.ts +602 -0
- package/src/scene/user-data.ts +203 -0
- package/src/setup/setup-audio.ts +60 -0
- package/src/setup/setup-particles.ts +23 -0
- package/src/setup/setup-physics.ts +67 -0
- package/src/setup/setup-renderer.ts +529 -0
- package/src/types-n8ao.d.ts +37 -0
- package/src/types-realism-effects.d.ts +61 -0
- package/src/world2d/authoring-2d.ts +208 -0
- package/src/world2d/capture-to-scene2d.ts +52 -0
- package/src/world2d/collision-2d.ts +106 -0
- package/src/world2d/components-2d.ts +86 -0
- package/src/world2d/index.ts +66 -0
- package/src/world2d/ingest-iframe-2d.ts +255 -0
- package/src/world2d/ingest2d.ts +131 -0
- package/src/world2d/physics2d-registry.ts +49 -0
- package/src/world2d/pixi-game-adapter.ts +325 -0
- package/src/world2d/pixi-surface.ts +78 -0
- package/src/world2d/scene-capture-2d.ts +117 -0
- package/src/world2d/scene2d-loader.ts +308 -0
- package/src/world2d/schema/entity2d.ts +145 -0
- package/src/world2d/schema/physics2d.ts +53 -0
- package/src/world2d/schema/sprite.ts +71 -0
- package/src/world2d/schema/tilemap.ts +22 -0
- package/src/world2d/schema/tuples2d.ts +25 -0
- package/src/world2d/system-adapters-2d.ts +49 -0
- package/src/world2d/transform-writer-2d.ts +24 -0
- package/src/world2d/types.ts +55 -0
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Typed registry for `Object3D.userData` keys used by the engine and editor.
|
|
3
|
+
*
|
|
4
|
+
* Three.js lets any code stash arbitrary values on `object.userData`, and over
|
|
5
|
+
* time the engine + editor accumulated ~20 stringly-typed keys that together
|
|
6
|
+
* form a *parallel object model* — meaning carried entirely in undocumented
|
|
7
|
+
* string literals. This module is the ONE documented place that lists every
|
|
8
|
+
* such key, its value type, and what it means. All engine/editor-internal
|
|
9
|
+
* `userData` access for these keys must go through {@link getUserData} /
|
|
10
|
+
* {@link setUserData} / {@link hasUserData} / {@link deleteUserData} so the key
|
|
11
|
+
* strings live in exactly one place and are type-checked at every call site.
|
|
12
|
+
*
|
|
13
|
+
* NOTE: This covers engine/editor *internal* keys only. User game code in the
|
|
14
|
+
* example template may stash its own ad-hoc keys; those are out of scope.
|
|
15
|
+
*
|
|
16
|
+
* ## Key reference
|
|
17
|
+
*
|
|
18
|
+
* Serialization / identity (load-bearing — editor identity + scene I/O):
|
|
19
|
+
* - `entity` — the full {@link SceneEntity} descriptor backing this
|
|
20
|
+
* Object3D. Source of truth the editor reads/writes and
|
|
21
|
+
* the serializer walks to rebuild `.vscn.json`.
|
|
22
|
+
* - `entityId` — the entity's stable string id. Presence also marks an
|
|
23
|
+
* Object3D as a "real" entity (vs editor helper/env object).
|
|
24
|
+
*
|
|
25
|
+
* Runtime scene-query overrides (read by `scene-query.ts`):
|
|
26
|
+
* - `tags` — string[] tag override; falls back to `entity.tags`.
|
|
27
|
+
* - `components` — component record or name[] override; falls back to
|
|
28
|
+
* `entity.components`.
|
|
29
|
+
*
|
|
30
|
+
* Runtime gameplay metadata (set by the scene loader):
|
|
31
|
+
* - `navRole` — `'walkable' | 'obstacle'` navmesh role; collected at runtime.
|
|
32
|
+
* - `pivot` — `[x,y,z]` local-space pivot for rotate/scale-around-pivot.
|
|
33
|
+
* - `splineCurve` — resolved THREE curve built from the entity's spline.
|
|
34
|
+
* - `_camera` — THREE.Camera owned by a camera entity (collected on load).
|
|
35
|
+
* - `_particleSystem` — three.quarks ParticleSystem (scene-sync registration + census).
|
|
36
|
+
*
|
|
37
|
+
* Animation (load-bearing — ED5 disposal contract):
|
|
38
|
+
* - `_animMixer` — THREE.AnimationMixer driving this subtree's clips.
|
|
39
|
+
* - `_animGraph` — live AnimGraph instance (state machine) for this entity.
|
|
40
|
+
* - `_animGraphData` — the AnimGraphFile the graph was built from (inspector cache).
|
|
41
|
+
* - `_availableClips` — string[] of clip names discovered on the GLTF.
|
|
42
|
+
*
|
|
43
|
+
* Disposal contract:
|
|
44
|
+
* - `__sharedGeometry` — `true` when a mesh's geometry is shared/cached and MUST
|
|
45
|
+
* NOT be disposed by per-object cleanup (P0.2 contract).
|
|
46
|
+
*
|
|
47
|
+
* Editor shading overrides (editor-only, see `scene-sync.ts`):
|
|
48
|
+
* - `__shadeOrig` — original material(s) stashed before a shading override.
|
|
49
|
+
* - `__shadeUnlit` — generated unlit material(s) for the unlit view mode.
|
|
50
|
+
*
|
|
51
|
+
* Custom-shader materials (see `material-registry.ts`):
|
|
52
|
+
* - `__matDef` — name of the registered custom-material definition.
|
|
53
|
+
* - `__uniforms` — authored uniform values passed to the def's update().
|
|
54
|
+
*
|
|
55
|
+
* Instancing (see `instance-registry.ts`):
|
|
56
|
+
* - `__instancer` — name of the registered instancer that built the mesh.
|
|
57
|
+
*
|
|
58
|
+
* Unmodified-game ingestion (editor-only, see `editor/src/ingest/`):
|
|
59
|
+
* - `__ingest` — `true` on every Object3D minted by ingestion (vs the
|
|
60
|
+
* first-party scene loader); routes edits to live
|
|
61
|
+
* mutation instead of descriptor rebuild.
|
|
62
|
+
* - `__ingestNextId` — monotonic id counter stashed on the captured Scene
|
|
63
|
+
* root so re-reflects keep minting unique ids.
|
|
64
|
+
*
|
|
65
|
+
* Editor scene-graph tagging (editor-only):
|
|
66
|
+
* - `engineInternal` — `true` on engine-owned infrastructure in the game scene
|
|
67
|
+
* (particle BatchedRenderer, debug-draw + its subtree).
|
|
68
|
+
* The editor's play-mode hierarchy skips these so they
|
|
69
|
+
* don't show up as selectable "entities".
|
|
70
|
+
* - `liveObject` — `true` on a runtime/procedural Object3D that the editor
|
|
71
|
+
* surfaced in the play-mode hierarchy with a *synthetic*
|
|
72
|
+
* descriptor (it has no `.vscn` record). Marks it as
|
|
73
|
+
* ephemeral: selectable/movable for testing, never saved.
|
|
74
|
+
* - `editorHelper` — `true` for editor-only helper objects (gizmos, wireframes).
|
|
75
|
+
* - `editorHelperType` — which kind of helper (lights/particles/pivot/navmesh/...).
|
|
76
|
+
* - `editorIcon` — `true` for editor billboard icon sprites.
|
|
77
|
+
* - `envObject` — `true` for environment objects (ambient light, etc.).
|
|
78
|
+
* - `splineControlPoint` — index of a spline control-point drag handle.
|
|
79
|
+
* - `vcDirIdx` — view-cube face direction index (0=+X,1=-X,2=+Y,...).
|
|
80
|
+
*/
|
|
81
|
+
|
|
82
|
+
import type * as THREE from 'three';
|
|
83
|
+
import type { ParticleSystem } from 'three.quarks';
|
|
84
|
+
import type { AnimGraph } from '../animation/anim-graph';
|
|
85
|
+
import type { AnimGraphFile } from '../animation/anim-graph-types';
|
|
86
|
+
import type { SceneEntity } from './scene-types';
|
|
87
|
+
|
|
88
|
+
/** The kinds of editor helper objects tagged via `editorHelperType`. */
|
|
89
|
+
export type EditorHelperType =
|
|
90
|
+
| 'lights'
|
|
91
|
+
| 'cameras'
|
|
92
|
+
| 'audio'
|
|
93
|
+
| 'colliders'
|
|
94
|
+
| 'splines'
|
|
95
|
+
| 'particles'
|
|
96
|
+
| 'pivot'
|
|
97
|
+
| 'navmesh';
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Maps each canonical accessor name to its value type. This is the single
|
|
101
|
+
* source of truth for what every known `userData` key holds.
|
|
102
|
+
*/
|
|
103
|
+
export interface UserDataSchema {
|
|
104
|
+
entity: SceneEntity;
|
|
105
|
+
entityId: string;
|
|
106
|
+
tags: string[];
|
|
107
|
+
components: Record<string, unknown> | string[];
|
|
108
|
+
navRole: 'walkable' | 'obstacle';
|
|
109
|
+
pivot: [number, number, number];
|
|
110
|
+
splineCurve: THREE.Curve<THREE.Vector3>;
|
|
111
|
+
splineControlPoint: number;
|
|
112
|
+
_camera: THREE.Camera;
|
|
113
|
+
_particleSystem: ParticleSystem;
|
|
114
|
+
_animMixer: THREE.AnimationMixer;
|
|
115
|
+
_animGraph: AnimGraph;
|
|
116
|
+
_animGraphData: AnimGraphFile;
|
|
117
|
+
_availableClips: string[];
|
|
118
|
+
__sharedGeometry: boolean;
|
|
119
|
+
__shadeOrig: THREE.Material | THREE.Material[];
|
|
120
|
+
__shadeUnlit: THREE.Material[];
|
|
121
|
+
__matDef: string;
|
|
122
|
+
__uniforms: Record<string, unknown>;
|
|
123
|
+
__instancer: string;
|
|
124
|
+
__ingest: boolean;
|
|
125
|
+
__ingestNextId: number;
|
|
126
|
+
engineInternal: boolean;
|
|
127
|
+
liveObject: boolean;
|
|
128
|
+
editorHelper: boolean;
|
|
129
|
+
editorHelperType: EditorHelperType;
|
|
130
|
+
editorIcon: boolean;
|
|
131
|
+
envObject: boolean;
|
|
132
|
+
vcDirIdx: number;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/** Any key in the typed registry. */
|
|
136
|
+
export type UserDataKey = keyof UserDataSchema;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* Canonical name → literal `userData` string key. The accessor names match the
|
|
140
|
+
* raw string keys 1:1 (this is the documented set referenced by the AC), so the
|
|
141
|
+
* literal strings appear in exactly this one place.
|
|
142
|
+
*/
|
|
143
|
+
export const UserDataKeys = {
|
|
144
|
+
entity: 'entity',
|
|
145
|
+
entityId: 'entityId',
|
|
146
|
+
tags: 'tags',
|
|
147
|
+
components: 'components',
|
|
148
|
+
navRole: 'navRole',
|
|
149
|
+
pivot: 'pivot',
|
|
150
|
+
splineCurve: 'splineCurve',
|
|
151
|
+
splineControlPoint: 'splineControlPoint',
|
|
152
|
+
_camera: '_camera',
|
|
153
|
+
_particleSystem: '_particleSystem',
|
|
154
|
+
_animMixer: '_animMixer',
|
|
155
|
+
_animGraph: '_animGraph',
|
|
156
|
+
_animGraphData: '_animGraphData',
|
|
157
|
+
_availableClips: '_availableClips',
|
|
158
|
+
__sharedGeometry: '__sharedGeometry',
|
|
159
|
+
__shadeOrig: '__shadeOrig',
|
|
160
|
+
__shadeUnlit: '__shadeUnlit',
|
|
161
|
+
__matDef: '__matDef',
|
|
162
|
+
__uniforms: '__uniforms',
|
|
163
|
+
__instancer: '__instancer',
|
|
164
|
+
__ingest: '__ingest',
|
|
165
|
+
__ingestNextId: '__ingestNextId',
|
|
166
|
+
engineInternal: 'engineInternal',
|
|
167
|
+
liveObject: 'liveObject',
|
|
168
|
+
editorHelper: 'editorHelper',
|
|
169
|
+
editorHelperType: 'editorHelperType',
|
|
170
|
+
editorIcon: 'editorIcon',
|
|
171
|
+
envObject: 'envObject',
|
|
172
|
+
vcDirIdx: 'vcDirIdx',
|
|
173
|
+
} as const satisfies Record<UserDataKey, string>;
|
|
174
|
+
|
|
175
|
+
/**
|
|
176
|
+
* Read a typed `userData` value. Returns `undefined` when the key is unset or
|
|
177
|
+
* when `obj` is nullish (so call sites can pass an optionally-resolved object).
|
|
178
|
+
*/
|
|
179
|
+
export function getUserData<K extends UserDataKey>(
|
|
180
|
+
obj: THREE.Object3D | null | undefined,
|
|
181
|
+
key: K,
|
|
182
|
+
): UserDataSchema[K] | undefined {
|
|
183
|
+
return obj ? (obj.userData[UserDataKeys[key]] as UserDataSchema[K] | undefined) : undefined;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/** Write a typed `userData` value. */
|
|
187
|
+
export function setUserData<K extends UserDataKey>(
|
|
188
|
+
obj: THREE.Object3D,
|
|
189
|
+
key: K,
|
|
190
|
+
value: UserDataSchema[K],
|
|
191
|
+
): void {
|
|
192
|
+
obj.userData[UserDataKeys[key]] = value;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/** True when `key` is set (not `undefined`) on the object's `userData`. */
|
|
196
|
+
export function hasUserData(obj: THREE.Object3D | null | undefined, key: UserDataKey): boolean {
|
|
197
|
+
return obj ? obj.userData[UserDataKeys[key]] !== undefined : false;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/** Remove a `userData` key. */
|
|
201
|
+
export function deleteUserData(obj: THREE.Object3D, key: UserDataKey): void {
|
|
202
|
+
delete obj.userData[UserDataKeys[key]];
|
|
203
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import * as THREE from 'three';
|
|
2
|
+
|
|
3
|
+
export interface AudioContext {
|
|
4
|
+
listener: THREE.AudioListener;
|
|
5
|
+
masterGain: GainNode;
|
|
6
|
+
buses: {
|
|
7
|
+
music: GainNode;
|
|
8
|
+
sfx: GainNode;
|
|
9
|
+
voice: GainNode;
|
|
10
|
+
};
|
|
11
|
+
/** Call this on first user interaction (click/key) to resume audio context */
|
|
12
|
+
resume: () => void;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Creates the audio listener and bus hierarchy.
|
|
17
|
+
*
|
|
18
|
+
* Bus routing: music/sfx/voice → master → destination
|
|
19
|
+
* Attach `listener` to the active camera: `camera.add(audioCtx.listener)`.
|
|
20
|
+
*/
|
|
21
|
+
export function setupAudio(camera: THREE.Camera): AudioContext {
|
|
22
|
+
const listener = new THREE.AudioListener();
|
|
23
|
+
camera.add(listener);
|
|
24
|
+
|
|
25
|
+
const ctx = listener.context;
|
|
26
|
+
const masterGain = ctx.createGain();
|
|
27
|
+
masterGain.connect(ctx.destination);
|
|
28
|
+
|
|
29
|
+
// Route the Three.js listener through masterGain so that THREE.Audio /
|
|
30
|
+
// THREE.PositionalAudio objects are also silenced by the mute button.
|
|
31
|
+
listener.gain.disconnect();
|
|
32
|
+
listener.gain.connect(masterGain);
|
|
33
|
+
|
|
34
|
+
const musicGain = ctx.createGain();
|
|
35
|
+
musicGain.gain.value = 0.7;
|
|
36
|
+
musicGain.connect(masterGain);
|
|
37
|
+
|
|
38
|
+
const sfxGain = ctx.createGain();
|
|
39
|
+
sfxGain.gain.value = 1.0;
|
|
40
|
+
sfxGain.connect(masterGain);
|
|
41
|
+
|
|
42
|
+
const voiceGain = ctx.createGain();
|
|
43
|
+
voiceGain.gain.value = 1.0;
|
|
44
|
+
voiceGain.connect(masterGain);
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
listener,
|
|
48
|
+
masterGain,
|
|
49
|
+
buses: {
|
|
50
|
+
music: musicGain,
|
|
51
|
+
sfx: sfxGain,
|
|
52
|
+
voice: voiceGain,
|
|
53
|
+
},
|
|
54
|
+
resume() {
|
|
55
|
+
if (ctx.state === 'suspended') {
|
|
56
|
+
ctx.resume();
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import type * as THREE from 'three';
|
|
2
|
+
import { BatchedRenderer } from 'three.quarks';
|
|
3
|
+
|
|
4
|
+
export interface ParticlesContext {
|
|
5
|
+
batchedRenderer: BatchedRenderer;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Creates the three.quarks BatchedRenderer for GPU particle systems.
|
|
10
|
+
*
|
|
11
|
+
* three.quarks renders all particle systems in a single draw call via instancing.
|
|
12
|
+
* Add particle systems to the batchedRenderer, not directly to the scene.
|
|
13
|
+
*
|
|
14
|
+
* Usage:
|
|
15
|
+
* const particles = setupParticles(scene);
|
|
16
|
+
* // In render loop: particles.batchedRenderer.update(dt);
|
|
17
|
+
*/
|
|
18
|
+
export function setupParticles(scene: THREE.Scene): ParticlesContext {
|
|
19
|
+
const batchedRenderer = new BatchedRenderer();
|
|
20
|
+
scene.add(batchedRenderer);
|
|
21
|
+
|
|
22
|
+
return { batchedRenderer };
|
|
23
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type RAPIER from '@dimforge/rapier3d-compat';
|
|
2
|
+
import * as THREE from 'three';
|
|
3
|
+
|
|
4
|
+
export interface PhysicsContext {
|
|
5
|
+
rapierWorld: RAPIER.World;
|
|
6
|
+
eventQueue: RAPIER.EventQueue;
|
|
7
|
+
debugMesh: THREE.LineSegments;
|
|
8
|
+
debugEnabled: boolean;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Creates the Rapier physics world and debug visualization.
|
|
13
|
+
*
|
|
14
|
+
* Call RAPIER.init() before this function.
|
|
15
|
+
* This is direct Rapier setup — no abstraction. Edit freely.
|
|
16
|
+
*/
|
|
17
|
+
export function setupPhysics(
|
|
18
|
+
rapier: typeof RAPIER,
|
|
19
|
+
scene: THREE.Scene,
|
|
20
|
+
gravity: { x: number; y: number; z: number } = { x: 0, y: -9.81, z: 0 },
|
|
21
|
+
): PhysicsContext {
|
|
22
|
+
const rapierWorld = new rapier.World(gravity);
|
|
23
|
+
const eventQueue = new rapier.EventQueue(true);
|
|
24
|
+
|
|
25
|
+
// Debug visualization (toggled at runtime)
|
|
26
|
+
const debugGeometry = new THREE.BufferGeometry();
|
|
27
|
+
const debugMaterial = new THREE.LineBasicMaterial({ color: 0x00ff00, vertexColors: true });
|
|
28
|
+
const debugMesh = new THREE.LineSegments(debugGeometry, debugMaterial);
|
|
29
|
+
debugMesh.visible = false;
|
|
30
|
+
scene.add(debugMesh);
|
|
31
|
+
|
|
32
|
+
return {
|
|
33
|
+
rapierWorld,
|
|
34
|
+
eventQueue,
|
|
35
|
+
debugMesh,
|
|
36
|
+
debugEnabled: false,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Update the debug line visualization from Rapier's debug render data.
|
|
42
|
+
* Call this each frame if debug is enabled.
|
|
43
|
+
*/
|
|
44
|
+
export function updatePhysicsDebug(rapierWorld: RAPIER.World, debugMesh: THREE.LineSegments) {
|
|
45
|
+
const buffers = rapierWorld.debugRender();
|
|
46
|
+
const geom = debugMesh.geometry;
|
|
47
|
+
|
|
48
|
+
// Reuse the existing BufferAttributes whenever the vertex count is stable
|
|
49
|
+
// (the common case). Allocating a new Float32Array + BufferAttribute every
|
|
50
|
+
// frame orphaned a fresh GPU buffer each time. We only allocate when the
|
|
51
|
+
// size actually changes.
|
|
52
|
+
const posAttr = geom.getAttribute('position') as THREE.BufferAttribute | undefined;
|
|
53
|
+
if (posAttr && posAttr.array.length === buffers.vertices.length) {
|
|
54
|
+
(posAttr.array as Float32Array).set(buffers.vertices);
|
|
55
|
+
posAttr.needsUpdate = true;
|
|
56
|
+
} else {
|
|
57
|
+
geom.setAttribute('position', new THREE.BufferAttribute(new Float32Array(buffers.vertices), 3));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const colAttr = geom.getAttribute('color') as THREE.BufferAttribute | undefined;
|
|
61
|
+
if (colAttr && colAttr.array.length === buffers.colors.length) {
|
|
62
|
+
(colAttr.array as Float32Array).set(buffers.colors);
|
|
63
|
+
colAttr.needsUpdate = true;
|
|
64
|
+
} else {
|
|
65
|
+
geom.setAttribute('color', new THREE.BufferAttribute(new Float32Array(buffers.colors), 4));
|
|
66
|
+
}
|
|
67
|
+
}
|