@vgai/engine 0.4.1 → 0.5.0-canary.20260719.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/README.md +48 -15
- package/package.json +11 -25
- package/schemas/engine-capabilities.json +10 -10
- package/schemas/entity2d.schema.json +468 -0
- package/schemas/mat.schema.json +2 -33
- package/schemas/prefab.schema.json +16 -172
- package/schemas/scn2d.schema.json +42 -23
- package/schemas/vgai-game.schema.json +34 -0
- package/schemas/vscn.schema.json +16 -172
- package/src/adapter/authoring.ts +152 -2
- package/src/adapter/colyseus-networking-adapter.ts +35 -1
- package/src/adapter/first-party-systems.ts +7 -1
- package/src/adapter/game-adapter.ts +13 -0
- package/src/adapter/index.ts +25 -0
- package/src/adapter/rapier-physics-adapter.ts +55 -2
- package/src/adapter/system-adapter.ts +249 -2
- package/src/adapter/vgai-scene-game-adapter.ts +149 -25
- package/src/ai/navigation.ts +28 -0
- package/src/animation/clip-map.ts +1 -8
- package/src/animation/theatre-director.ts +50 -0
- package/src/animation/xstate-animation-binding.ts +6 -0
- package/src/audio/audio-introspection.ts +290 -0
- package/src/audio/tone-context.ts +46 -0
- package/src/dev/chrome-trace.ts +153 -0
- package/src/dev/performance-profiler.ts +93 -6
- package/src/dev/render-debug-adapter.ts +199 -0
- package/src/dev/render-memory.ts +243 -0
- package/src/dev/webgl-frame-capture.ts +424 -0
- package/src/ecs/component-manager.ts +43 -10
- package/src/ecs/game-component.ts +39 -10
- package/src/input/input-manager.ts +24 -19
- package/src/input/input-types.ts +1 -1
- package/src/loader.ts +7 -0
- package/src/manifest/load.ts +14 -0
- package/src/manifest/schema.ts +65 -0
- package/src/react/game-state.tsx +1 -1
- package/src/render/render-batch-system.ts +26 -12
- package/src/render/spark-renderer-lifecycle.ts +64 -0
- package/src/runtime/create-runtime.ts +33 -5
- package/src/runtime/debug-bridge.ts +5 -5
- package/src/runtime/game.ts +102 -20
- package/src/runtime/mount-manifest.ts +1 -1
- package/src/runtime/render-control.ts +121 -0
- package/src/runtime/types.ts +1 -1
- package/src/scene/asset-loaders.ts +77 -3
- package/src/scene/instance-mesh.ts +25 -0
- package/src/scene/material-factory.ts +4 -15
- package/src/scene/mesh-shadow.ts +18 -0
- package/src/scene/particles-factory.ts +59 -0
- package/src/scene/scene-loader.ts +41 -16
- package/src/scene/schema/instances.ts +1 -2
- package/src/scene/schema/material.ts +83 -94
- package/src/scene/schema/mesh.ts +76 -90
- package/src/scene/schema/scene-file.ts +1 -2
- package/src/scene/user-data.ts +30 -14
- package/src/setup/setup-renderer.ts +6 -1
- package/src/world2d/asset-paths2d.ts +44 -0
- package/src/world2d/collision-2d.ts +7 -14
- package/src/world2d/entity2d-asset.ts +22 -0
- package/src/world2d/index.ts +27 -2
- package/src/world2d/physics2d-transform.ts +173 -0
- package/src/world2d/physics2d-units.ts +10 -0
- package/src/world2d/pixi-game-adapter.ts +148 -36
- package/src/world2d/scene2d-identity.ts +49 -0
- package/src/world2d/scene2d-loader.ts +243 -119
- package/src/world2d/schema/entity2d.ts +51 -33
- package/src/world2d/schema/physics2d.ts +14 -3
- package/src/world2d/schema/sprite.ts +32 -4
- package/src/world2d/schema/tilemap.ts +26 -9
- package/src/world2d/transform-writer-2d.ts +29 -11
- package/src/world2d/types.ts +21 -8
- package/src/world3d-react/behavior.tsx +138 -0
- package/src/world3d-react/engine-bridge.ts +48 -0
- package/src/world3d-react/index.ts +44 -0
- package/src/world3d-react/r3f-adapter.tsx +303 -0
- package/src/world3d-react/world-context.ts +294 -0
- package/vendor/realism-effects/LICENSE.md +21 -0
- package/vendor/realism-effects/UPSTREAM.md +19 -0
- package/vendor/realism-effects/dist/index.cjs +3447 -0
- package/vendor/realism-effects/dist/index.d.ts +59 -0
- package/vendor/realism-effects/dist/index.js +3434 -0
- package/vendor/realism-effects/package.json +23 -0
- package/src/character/cloth-sim.ts +0 -533
- package/src/character/spring-chain.ts +0 -307
- package/src/humanoid/body.ts +0 -663
- package/src/humanoid/clips.ts +0 -149
- package/src/humanoid/compose.ts +0 -209
- package/src/humanoid/generate.ts +0 -189
- package/src/humanoid/index.ts +0 -36
- package/src/humanoid/schema.ts +0 -108
- package/src/humanoid/skeleton.ts +0 -345
- package/src/react/humanoid-bake.document.tsx +0 -337
- package/src/scene/geometries/index.ts +0 -7
- package/src/scene/geometries/terrain.ts +0 -42
- package/src/scene/geometry-registry.ts +0 -42
- package/src/scene/instance-registry.ts +0 -84
- package/src/scene/instancers/grid.ts +0 -38
- package/src/scene/instancers/index.ts +0 -7
- package/src/scene/material-registry.ts +0 -73
- package/src/scene/materials/index.ts +0 -7
- package/src/scene/materials/water.ts +0 -56
- package/src/world2d/components-2d.ts +0 -86
- package/tools/humanoid-bake.tool.ts +0 -274
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import { registerGeometryGenerator } from '../geometry-registry';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Built-in "Terrain" geometry generator — a horizontal heightfield (XZ plane,
|
|
6
|
-
* height in +Y) displaced by a deterministic sum-of-sines, so it pairs with a
|
|
7
|
-
* `trimesh` collider for matching terrain physics. Proof material for roadmap #3.
|
|
8
|
-
*
|
|
9
|
-
* Params: width, depth (world size), segments (grid resolution per axis),
|
|
10
|
-
* amplitude (max height), frequency (wave scale). Deterministic — no Math.random.
|
|
11
|
-
*/
|
|
12
|
-
function heightAt(x: number, z: number, amplitude: number, frequency: number): number {
|
|
13
|
-
return (
|
|
14
|
-
amplitude *
|
|
15
|
-
(Math.sin(x * frequency) * 0.5 +
|
|
16
|
-
Math.cos(z * frequency * 1.3) * 0.3 +
|
|
17
|
-
Math.sin((x + z) * frequency * 0.5) * 0.2)
|
|
18
|
-
);
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
registerGeometryGenerator('Terrain', {
|
|
22
|
-
build(params) {
|
|
23
|
-
const width = (params['width'] as number) ?? 40;
|
|
24
|
-
const depth = (params['depth'] as number) ?? 40;
|
|
25
|
-
const segments = (params['segments'] as number) ?? 64;
|
|
26
|
-
const amplitude = (params['amplitude'] as number) ?? 3;
|
|
27
|
-
const frequency = (params['frequency'] as number) ?? 0.25;
|
|
28
|
-
|
|
29
|
-
const geo = new THREE.PlaneGeometry(width, depth, segments, segments);
|
|
30
|
-
// Lay the plane flat (XZ) with normal +Y, then displace Y by the heightfield.
|
|
31
|
-
geo.rotateX(-Math.PI / 2);
|
|
32
|
-
const pos = geo.attributes['position'] as THREE.BufferAttribute;
|
|
33
|
-
for (let i = 0; i < pos.count; i++) {
|
|
34
|
-
const x = pos.getX(i);
|
|
35
|
-
const z = pos.getZ(i);
|
|
36
|
-
pos.setY(i, heightAt(x, z, amplitude, frequency));
|
|
37
|
-
}
|
|
38
|
-
pos.needsUpdate = true;
|
|
39
|
-
geo.computeVertexNormals();
|
|
40
|
-
return geo;
|
|
41
|
-
},
|
|
42
|
-
});
|
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
import type * as THREE from 'three';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A geometry-generator definition. Registered by name and resolved when a scene
|
|
5
|
-
* mesh declares `{ generator: <name> }`. Mirrors the material/instancer registry
|
|
6
|
-
* pattern: the generation logic lives here in code, the authored params arrive as
|
|
7
|
-
* scene data.
|
|
8
|
-
*
|
|
9
|
-
* `build` returns a real THREE.BufferGeometry. Paired with a `trimesh` collider,
|
|
10
|
-
* the generated geometry also drives physics (e.g. terrain), so the visual and
|
|
11
|
-
* collision surfaces match exactly.
|
|
12
|
-
*/
|
|
13
|
-
export interface GeometryGeneratorDef {
|
|
14
|
-
build(params: Record<string, unknown>): THREE.BufferGeometry;
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const registry = new Map<string, GeometryGeneratorDef>();
|
|
18
|
-
|
|
19
|
-
/** Register a geometry generator under `name`. */
|
|
20
|
-
export function registerGeometryGenerator(name: string, def: GeometryGeneratorDef): void {
|
|
21
|
-
registry.set(name, def);
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
/** Look up a registered geometry generator. */
|
|
25
|
-
export function getGeometryGenerator(name: string): GeometryGeneratorDef | undefined {
|
|
26
|
-
return registry.get(name);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/**
|
|
30
|
-
* Build a procedural geometry by registered generator name. Throws if the
|
|
31
|
-
* generator is unknown (fail loud — same posture as the other registries).
|
|
32
|
-
*/
|
|
33
|
-
export function buildGeneratedGeometry(
|
|
34
|
-
name: string,
|
|
35
|
-
params: Record<string, unknown>,
|
|
36
|
-
): THREE.BufferGeometry {
|
|
37
|
-
const def = registry.get(name);
|
|
38
|
-
if (!def) {
|
|
39
|
-
throw new Error(`Geometry generator "${name}" not found in geometry registry`);
|
|
40
|
-
}
|
|
41
|
-
return def.build(params ?? {});
|
|
42
|
-
}
|
|
@@ -1,84 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import type { InstancesFile } from './schema/instances';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* An instancer definition. Registered by name and resolved when a scene mesh
|
|
6
|
-
* declares `{ instancer: <name> }`. Mirrors the material-registry pattern: the
|
|
7
|
-
* generation logic lives here in code (the "behavior" half), the authored params
|
|
8
|
-
* arrive as scene data (the "data" half).
|
|
9
|
-
*
|
|
10
|
-
* `build` returns per-instance LOCAL transforms; the factory packs them into a
|
|
11
|
-
* single THREE.InstancedMesh, so N instances cost one draw call.
|
|
12
|
-
*/
|
|
13
|
-
export interface InstancerDef {
|
|
14
|
-
/** Produce per-instance local matrices from authored params. */
|
|
15
|
-
build(params: Record<string, unknown>): THREE.Matrix4[];
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
const registry = new Map<string, InstancerDef>();
|
|
19
|
-
|
|
20
|
-
/** Register an instancer definition under `name`. */
|
|
21
|
-
export function registerInstancer(name: string, def: InstancerDef): void {
|
|
22
|
-
registry.set(name, def);
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/** Look up a registered instancer. */
|
|
26
|
-
export function getInstancer(name: string): InstancerDef | undefined {
|
|
27
|
-
return registry.get(name);
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Build a THREE.InstancedMesh from a registered instancer. Throws if the
|
|
32
|
-
* instancer is unknown (fail loud — same posture as the component/material
|
|
33
|
-
* registries). One InstancedMesh = one draw call for all instances.
|
|
34
|
-
*/
|
|
35
|
-
export function buildInstancedMesh(
|
|
36
|
-
geometry: THREE.BufferGeometry,
|
|
37
|
-
material: THREE.Material,
|
|
38
|
-
name: string,
|
|
39
|
-
params: Record<string, unknown>,
|
|
40
|
-
): THREE.InstancedMesh {
|
|
41
|
-
const def = registry.get(name);
|
|
42
|
-
if (!def) {
|
|
43
|
-
throw new Error(`Instancer "${name}" not found in instancer registry`);
|
|
44
|
-
}
|
|
45
|
-
const matrices = def.build(params ?? {});
|
|
46
|
-
const mesh = new THREE.InstancedMesh(geometry, material, matrices.length);
|
|
47
|
-
for (let i = 0; i < matrices.length; i++) {
|
|
48
|
-
mesh.setMatrixAt(i, matrices[i]!);
|
|
49
|
-
}
|
|
50
|
-
mesh.instanceMatrix.needsUpdate = true;
|
|
51
|
-
mesh.userData['__instancer'] = name;
|
|
52
|
-
return mesh;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Build a THREE.InstancedMesh directly from an already-parsed `.instances.json`
|
|
57
|
-
* tuple array (F3, docs/VSCN-STRUCTURAL-GAPS-DESIGN.md — the declarative,
|
|
58
|
-
* authored-data counterpart to `buildInstancedMesh`'s code-registered
|
|
59
|
-
* instancers). Each tuple is
|
|
60
|
-
* `[posX,posY,posZ, quatX,quatY,quatZ,quatW, scaleX,scaleY,scaleZ]`. Pure and
|
|
61
|
-
* synchronous (no fetch) so it's exercisable headlessly by both the scene
|
|
62
|
-
* loader (after it fetches+parses the asset) and unit tests.
|
|
63
|
-
*/
|
|
64
|
-
export function buildInstancedMeshFromTuples(
|
|
65
|
-
geometry: THREE.BufferGeometry,
|
|
66
|
-
material: THREE.Material,
|
|
67
|
-
tuples: InstancesFile,
|
|
68
|
-
): THREE.InstancedMesh {
|
|
69
|
-
const mesh = new THREE.InstancedMesh(geometry, material, tuples.length);
|
|
70
|
-
const pos = new THREE.Vector3();
|
|
71
|
-
const quat = new THREE.Quaternion();
|
|
72
|
-
const scale = new THREE.Vector3();
|
|
73
|
-
const matrix = new THREE.Matrix4();
|
|
74
|
-
for (let i = 0; i < tuples.length; i++) {
|
|
75
|
-
const t = tuples[i]!;
|
|
76
|
-
pos.set(t[0], t[1], t[2]);
|
|
77
|
-
quat.set(t[3], t[4], t[5], t[6]);
|
|
78
|
-
scale.set(t[7], t[8], t[9]);
|
|
79
|
-
matrix.compose(pos, quat, scale);
|
|
80
|
-
mesh.setMatrixAt(i, matrix);
|
|
81
|
-
}
|
|
82
|
-
mesh.instanceMatrix.needsUpdate = true;
|
|
83
|
-
return mesh;
|
|
84
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import { registerInstancer } from '../instance-registry';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Built-in "Grid" instancer — a countX × countZ grid of instances on the XZ
|
|
6
|
-
* plane, centred on the entity origin, with optional deterministic y-jitter.
|
|
7
|
-
* Proof material for roadmap #2 (first-class instancing): a 100×100 grid is
|
|
8
|
-
* 10,000 instances in one draw call.
|
|
9
|
-
*
|
|
10
|
-
* Determinism: uses a seeded LCG (not Math.random) so a scene renders the same
|
|
11
|
-
* layout every load — important for reproducible scenes and tests.
|
|
12
|
-
*/
|
|
13
|
-
registerInstancer('Grid', {
|
|
14
|
-
build(params) {
|
|
15
|
-
const countX = (params['countX'] as number) ?? 100;
|
|
16
|
-
const countZ = (params['countZ'] as number) ?? 100;
|
|
17
|
-
const spacing = (params['spacing'] as number) ?? 2;
|
|
18
|
-
const jitter = (params['jitter'] as number) ?? 0;
|
|
19
|
-
let seed = (params['seed'] as number) ?? 1;
|
|
20
|
-
const rand = () => {
|
|
21
|
-
seed = (seed * 1664525 + 1013904223) % 4294967296;
|
|
22
|
-
return seed / 4294967296;
|
|
23
|
-
};
|
|
24
|
-
|
|
25
|
-
const offX = ((countX - 1) * spacing) / 2;
|
|
26
|
-
const offZ = ((countZ - 1) * spacing) / 2;
|
|
27
|
-
const matrices: THREE.Matrix4[] = [];
|
|
28
|
-
const m = new THREE.Matrix4();
|
|
29
|
-
for (let x = 0; x < countX; x++) {
|
|
30
|
-
for (let z = 0; z < countZ; z++) {
|
|
31
|
-
const y = jitter ? (rand() - 0.5) * jitter : 0;
|
|
32
|
-
m.makeTranslation(x * spacing - offX, y, z * spacing - offZ);
|
|
33
|
-
matrices.push(m.clone());
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
return matrices;
|
|
37
|
-
},
|
|
38
|
-
});
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in instancer definitions. Importing this module for its side effects
|
|
3
|
-
* registers the built-ins into the instancer registry. Both the runtime factory
|
|
4
|
-
* (scene-loader) and the editor factory (entity-factory) import it so instanced
|
|
5
|
-
* meshes resolve on whichever side builds first.
|
|
6
|
-
*/
|
|
7
|
-
import './grid';
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import type * as THREE from 'three';
|
|
2
|
-
|
|
3
|
-
/**
|
|
4
|
-
* A custom-material definition. Registered by name and resolved when a scene
|
|
5
|
-
* material declares `{ type: 'custom', def: <name> }`.
|
|
6
|
-
*
|
|
7
|
-
* The GLSL program lives here in code (the "behavior" half); the per-entity
|
|
8
|
-
* authored uniform values arrive as data on the scene material's `uniforms`
|
|
9
|
-
* (the "data" half) and are passed to `build`. A def may optionally tick its
|
|
10
|
-
* own uniforms each frame (e.g. uTime) via `update`.
|
|
11
|
-
*
|
|
12
|
-
* This is the native extension point of the material system — not a wrapper:
|
|
13
|
-
* `build` returns a real THREE.Material (typically a ShaderMaterial, or a stock
|
|
14
|
-
* material patched via onBeforeCompile), and the engine renders it directly.
|
|
15
|
-
*/
|
|
16
|
-
export interface MaterialDef {
|
|
17
|
-
/** Build a real THREE material from authored uniform values. */
|
|
18
|
-
build(uniforms: Record<string, unknown>): THREE.Material;
|
|
19
|
-
/** Optional per-frame uniform update for self-animating materials. */
|
|
20
|
-
update?(dt: number, material: THREE.Material, uniforms: Record<string, unknown>): void;
|
|
21
|
-
/** Representative flat color (CSS hex) for the editor's "unlit" view mode. */
|
|
22
|
-
previewColor?: string;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
const registry = new Map<string, MaterialDef>();
|
|
26
|
-
|
|
27
|
-
/** Register a custom-material definition under `name`. */
|
|
28
|
-
export function registerMaterial(name: string, def: MaterialDef): void {
|
|
29
|
-
registry.set(name, def);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
/** Look up a registered material definition. */
|
|
33
|
-
export function getMaterialDef(name: string): MaterialDef | undefined {
|
|
34
|
-
return registry.get(name);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Build a custom material by registered def name. Tags the resulting material's
|
|
39
|
-
* `userData` so the per-frame tick and the editor view-mode override can find it.
|
|
40
|
-
* Throws if the def is unknown (fail loud — same posture as the component registry).
|
|
41
|
-
*/
|
|
42
|
-
export function buildCustomMaterial(
|
|
43
|
-
name: string,
|
|
44
|
-
uniforms: Record<string, unknown>,
|
|
45
|
-
): THREE.Material {
|
|
46
|
-
const def = registry.get(name);
|
|
47
|
-
if (!def) {
|
|
48
|
-
throw new Error(`Custom material def "${name}" not found in material registry`);
|
|
49
|
-
}
|
|
50
|
-
const material = def.build(uniforms ?? {});
|
|
51
|
-
material.userData['__matDef'] = name;
|
|
52
|
-
material.userData['__uniforms'] = uniforms ?? {};
|
|
53
|
-
return material;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Tick every custom material under `root` (self-animating uniforms). Called from
|
|
58
|
-
* the runtime `preRender` phase and from the editor's edit-mode RAF — one code
|
|
59
|
-
* path, both modes. Walks the scene graph; materials without a `__matDef` tag are
|
|
60
|
-
* skipped.
|
|
61
|
-
*/
|
|
62
|
-
export function tickCustomMaterials(root: THREE.Object3D, dt: number): void {
|
|
63
|
-
root.traverse((obj) => {
|
|
64
|
-
const material = (obj as THREE.Mesh).material;
|
|
65
|
-
if (!material) return;
|
|
66
|
-
const list = Array.isArray(material) ? material : [material];
|
|
67
|
-
for (const mat of list) {
|
|
68
|
-
const defName = mat.userData?.['__matDef'] as string | undefined;
|
|
69
|
-
if (!defName) continue;
|
|
70
|
-
registry.get(defName)?.update?.(dt, mat, mat.userData['__uniforms'] ?? {});
|
|
71
|
-
}
|
|
72
|
-
});
|
|
73
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Built-in custom-material definitions. Importing this module for its side
|
|
3
|
-
* effects registers the built-ins into the material registry. Both the runtime
|
|
4
|
-
* factory (scene-loader) and the editor factory (entity-factory) import it so
|
|
5
|
-
* custom materials resolve on whichever side builds first.
|
|
6
|
-
*/
|
|
7
|
-
import './water';
|
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
import * as THREE from 'three';
|
|
2
|
-
import { registerMaterial } from '../material-registry';
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Built-in "Water" custom material — the proof-of-concept for first-class custom
|
|
6
|
-
* shaders (roadmap item #1). Vertex shader displaces a plane with travelling sine
|
|
7
|
-
* waves driven by `uTime`; fragment tints by `uColor` with a little wave-crest
|
|
8
|
-
* lightening. Demonstrates: authored uniforms (color, waveHeight), a real
|
|
9
|
-
* ShaderMaterial built from scene JSON, and a self-animating uniform (uTime).
|
|
10
|
-
*/
|
|
11
|
-
const vertexShader = /* glsl */ `
|
|
12
|
-
uniform float uTime;
|
|
13
|
-
uniform float uWaveHeight;
|
|
14
|
-
varying float vWave;
|
|
15
|
-
void main() {
|
|
16
|
-
vec3 p = position;
|
|
17
|
-
// PlaneGeometry lies in local XY with normal +Z; displace along the normal so
|
|
18
|
-
// a plane rotated flat (XZ) ripples upward in world Y.
|
|
19
|
-
float wave =
|
|
20
|
-
sin(p.x * 1.5 + uTime) * 0.6 +
|
|
21
|
-
cos(p.y * 1.2 + uTime * 0.8) * 0.4;
|
|
22
|
-
p.z += wave * uWaveHeight;
|
|
23
|
-
vWave = wave;
|
|
24
|
-
gl_Position = projectionMatrix * modelViewMatrix * vec4(p, 1.0);
|
|
25
|
-
}
|
|
26
|
-
`;
|
|
27
|
-
|
|
28
|
-
const fragmentShader = /* glsl */ `
|
|
29
|
-
uniform vec3 uColor;
|
|
30
|
-
varying float vWave;
|
|
31
|
-
void main() {
|
|
32
|
-
vec3 c = uColor + vWave * 0.12;
|
|
33
|
-
gl_FragColor = vec4(c, 1.0);
|
|
34
|
-
}
|
|
35
|
-
`;
|
|
36
|
-
|
|
37
|
-
registerMaterial('Water', {
|
|
38
|
-
previewColor: '#0a5fa5',
|
|
39
|
-
build(uniforms) {
|
|
40
|
-
const color = (uniforms['color'] as string) ?? '#0a5fa5';
|
|
41
|
-
const waveHeight = (uniforms['waveHeight'] as number) ?? 0.4;
|
|
42
|
-
return new THREE.ShaderMaterial({
|
|
43
|
-
uniforms: {
|
|
44
|
-
uTime: { value: 0 },
|
|
45
|
-
uColor: { value: new THREE.Color(color) },
|
|
46
|
-
uWaveHeight: { value: waveHeight },
|
|
47
|
-
},
|
|
48
|
-
vertexShader,
|
|
49
|
-
fragmentShader,
|
|
50
|
-
});
|
|
51
|
-
},
|
|
52
|
-
update(dt, material) {
|
|
53
|
-
const uTime = (material as THREE.ShaderMaterial).uniforms['uTime'];
|
|
54
|
-
if (uTime) uTime.value += dt;
|
|
55
|
-
},
|
|
56
|
-
});
|
|
@@ -1,86 +0,0 @@
|
|
|
1
|
-
import { z } from 'zod';
|
|
2
|
-
import { GameComponent } from '../ecs/game-component';
|
|
3
|
-
import type { GameContext } from '../runtime/types';
|
|
4
|
-
import type { World2DContext } from './types';
|
|
5
|
-
|
|
6
|
-
/**
|
|
7
|
-
* Camera2DFollow — the canonical 2D camera (spec backend-2d-camera): a 2D camera is
|
|
8
|
-
* a transform on the world Container (`ctx.stage`). Attached to the entity to follow;
|
|
9
|
-
* each frame it pans/zooms `ctx.stage` so the target display stays centered. Mirrors
|
|
10
|
-
* the 3D "camera GameComponent on a camera entity" canonical-way rule.
|
|
11
|
-
*/
|
|
12
|
-
export class Camera2DFollow extends GameComponent<'pixijs'> {
|
|
13
|
-
static declaredKind = 'pixijs' as const;
|
|
14
|
-
static phase = 'preRender' as const;
|
|
15
|
-
static schema = z.object({
|
|
16
|
-
zoom: z.number().default(1).describe('Camera zoom (world Container scale)'),
|
|
17
|
-
smoothing: z.number().default(0.12).describe('Follow lerp factor 0..1 (1 = instant)'),
|
|
18
|
-
});
|
|
19
|
-
zoom = 1;
|
|
20
|
-
smoothing = 0.12;
|
|
21
|
-
|
|
22
|
-
// CONFLICT (bridged, same mechanism the deleted `GameComponent2D` alias
|
|
23
|
-
// used — T7.3 slice 2): `GameComponent<K>`'s lifecycle methods are typed
|
|
24
|
-
// `ctx: GameContext` (D8 §2 — `ctx` always means the game-level context),
|
|
25
|
-
// but this component genuinely needs `World2DContext` fields (`app`/
|
|
26
|
-
// `stage`), which share no structure with `GameContext` (neither is
|
|
27
|
-
// assignable to the other) — a plain `ctx: World2DContext` override would
|
|
28
|
-
// fail even under method bivariance (TS2416). Widening the parameter to
|
|
29
|
-
// the union is a valid (non-bivariant) override on its own — accepting a
|
|
30
|
-
// superset of the base signature is always sound — then narrowing back
|
|
31
|
-
// down via the cast below. At runtime this always receives the real
|
|
32
|
-
// `World2DContext` (`pixi-game-adapter.ts` casts it to `GameContext` only
|
|
33
|
-
// for the unified manager's internal typing).
|
|
34
|
-
update(_dt: number, gameCtx: GameContext | World2DContext): void {
|
|
35
|
-
const ctx = gameCtx as World2DContext;
|
|
36
|
-
const screen = ctx.app.screen;
|
|
37
|
-
const targetX = screen.width / 2 - this.node.x * this.zoom;
|
|
38
|
-
const targetY = screen.height / 2 - this.node.y * this.zoom;
|
|
39
|
-
const stage = ctx.stage;
|
|
40
|
-
stage.scale.set(this.zoom);
|
|
41
|
-
stage.position.x += (targetX - stage.position.x) * this.smoothing;
|
|
42
|
-
stage.position.y += (targetY - stage.position.y) * this.smoothing;
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Clickable2D — federated-events picking (spec backend-2d-input-picking). The sprite
|
|
48
|
-
* must be authored `interactive: true` (loader sets eventMode='static'); on pointerdown
|
|
49
|
-
* the sprite cycles tint, proving Pixi world picking works and is independent of the
|
|
50
|
-
* React/DOM HUD layer.
|
|
51
|
-
*
|
|
52
|
-
* INPUT ROUTING RULE (resolves the headline open design question): the three surfaces
|
|
53
|
-
* are stacked DOM siblings — React/DOM HUD (top, z-index, pointer-events gated) gets the
|
|
54
|
-
* event first; if it doesn't consume it, the Pixi canvas's federated EventSystem hit-tests
|
|
55
|
-
* world2d display objects; Three's raycaster handles the world3d canvas. Because each
|
|
56
|
-
* surface owns its own canvas/DOM node, a click on the HUD does not also pick the Pixi
|
|
57
|
-
* world beneath it, and vice-versa — no cross-surface double-picking.
|
|
58
|
-
*/
|
|
59
|
-
export class Clickable2D extends GameComponent<'pixijs'> {
|
|
60
|
-
static declaredKind = 'pixijs' as const;
|
|
61
|
-
static schema = z.object({
|
|
62
|
-
tints: z
|
|
63
|
-
.array(z.string())
|
|
64
|
-
.default(['#ff6b6b', '#4ecdc4', '#ffd23f', '#a06bff'])
|
|
65
|
-
.describe('Tint cycle'),
|
|
66
|
-
});
|
|
67
|
-
tints: string[] = ['#ff6b6b', '#4ecdc4', '#ffd23f', '#a06bff'];
|
|
68
|
-
private idx = 0;
|
|
69
|
-
private handler = (): void => {
|
|
70
|
-
this.idx = (this.idx + 1) % this.tints.length;
|
|
71
|
-
(this.node as { tint?: number }).tint = Number.parseInt(
|
|
72
|
-
this.tints[this.idx]!.replace('#', ''),
|
|
73
|
-
16,
|
|
74
|
-
);
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
init(): void {
|
|
78
|
-
this.node.eventMode = 'static';
|
|
79
|
-
this.node.cursor = 'pointer';
|
|
80
|
-
this.node.on('pointerdown', this.handler);
|
|
81
|
-
}
|
|
82
|
-
update(): void {}
|
|
83
|
-
dispose(): void {
|
|
84
|
-
this.node.off('pointerdown', this.handler);
|
|
85
|
-
}
|
|
86
|
-
}
|