@waica/engine 0.25.0 → 0.26.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 +55 -1
- package/dist/anchored-pieces.d.ts +3 -2
- package/dist/anchored-pieces.js +12 -8
- package/dist/archetype.d.ts +2 -2
- package/dist/assets/asset-loader.d.ts +28 -5
- package/dist/assets/asset-loader.js +63 -7
- package/dist/assets/model-backend.d.ts +21 -0
- package/dist/assets/model-backend.js +21 -0
- package/dist/assets/model-cache.d.ts +47 -0
- package/dist/assets/model-cache.js +117 -0
- package/dist/audio/audio-subsystem.d.ts +6 -1
- package/dist/audio/audio-subsystem.js +8 -15
- package/dist/audio/placement.d.ts +41 -0
- package/dist/audio/placement.js +21 -0
- package/dist/audio/spatial.d.ts +20 -0
- package/dist/audio/spatial.js +26 -0
- package/dist/audio/types.d.ts +7 -1
- package/dist/camera-projection.d.ts +38 -0
- package/dist/camera-projection.js +44 -0
- package/dist/camera-rig.d.ts +48 -0
- package/dist/camera-rig.js +98 -0
- package/dist/camera.d.ts +20 -3
- package/dist/camera.js +3 -1
- package/dist/component.d.ts +38 -2
- package/dist/component.js +3 -0
- package/dist/components/animated-sprite.d.ts +2 -1
- package/dist/components/animated-sprite.js +1 -0
- package/dist/components/collider.d.ts +89 -0
- package/dist/components/collider.js +57 -0
- package/dist/components/dynamic-body.d.ts +2 -1
- package/dist/components/dynamic-body.js +1 -0
- package/dist/components/hitbox.d.ts +2 -1
- package/dist/components/hitbox.js +1 -0
- package/dist/components/light.d.ts +2 -1
- package/dist/components/light.js +1 -0
- package/dist/components/model.d.ts +63 -0
- package/dist/components/model.js +117 -0
- package/dist/components/particle-emitter.d.ts +2 -1
- package/dist/components/particle-emitter.js +1 -0
- package/dist/components/point-light.d.ts +75 -0
- package/dist/components/point-light.js +102 -0
- package/dist/components/rigid-body.d.ts +114 -0
- package/dist/components/rigid-body.js +83 -0
- package/dist/components/solid.d.ts +2 -1
- package/dist/components/solid.js +1 -0
- package/dist/components/sprite.d.ts +2 -1
- package/dist/components/sprite.js +1 -0
- package/dist/components/sun.d.ts +46 -0
- package/dist/components/sun.js +74 -0
- package/dist/components/tilemap.d.ts +2 -1
- package/dist/components/tilemap.js +1 -0
- package/dist/entity-transform.d.ts +18 -0
- package/dist/entity-transform.js +17 -0
- package/dist/game.d.ts +51 -15
- package/dist/game.js +83 -70
- package/dist/index.d.ts +29 -4
- package/dist/index.js +12 -1
- package/dist/lit-frame.js +10 -3
- package/dist/physics-3d/body-contacts.d.ts +10 -0
- package/dist/physics-3d/body-contacts.js +59 -0
- package/dist/physics-3d/body-sync.d.ts +52 -0
- package/dist/physics-3d/body-sync.js +153 -0
- package/dist/physics-3d/character-controller.d.ts +44 -0
- package/dist/physics-3d/character-controller.js +126 -0
- package/dist/physics-3d/physics-host.d.ts +86 -0
- package/dist/physics-3d/physics-host.js +174 -0
- package/dist/physics-3d/physics-world.d.ts +65 -0
- package/dist/physics-3d/physics-world.js +158 -0
- package/dist/physics-3d/rapier-module.d.ts +17 -0
- package/dist/physics-3d/rapier-module.js +19 -0
- package/dist/physics-3d/sensor-dispatch.d.ts +10 -0
- package/dist/physics-3d/sensor-dispatch.js +77 -0
- package/dist/physics-3d/world-queries.d.ts +39 -0
- package/dist/physics-3d/world-queries.js +63 -0
- package/dist/pointer-raycast.d.ts +17 -0
- package/dist/pointer-raycast.js +48 -0
- package/dist/pointer.d.ts +27 -6
- package/dist/pointer.js +43 -17
- package/dist/runtime-bridge.d.ts +1 -1
- package/dist/runtime-inspection.d.ts +9 -0
- package/dist/runtime-inspection.js +8 -2
- package/dist/runtime-lighting-snapshot.d.ts +27 -1
- package/dist/runtime-lighting-snapshot.js +31 -0
- package/dist/runtime-physics-snapshot.d.ts +41 -0
- package/dist/runtime-physics-snapshot.js +45 -0
- package/dist/runtime-view-snapshot.d.ts +25 -0
- package/dist/runtime-view-snapshot.js +30 -0
- package/dist/scene-camera-3d.d.ts +66 -0
- package/dist/scene-camera-3d.js +117 -0
- package/dist/scene-lights-3d.d.ts +17 -0
- package/dist/scene-lights-3d.js +27 -0
- package/dist/scene-simulation.d.ts +22 -0
- package/dist/scene-simulation.js +29 -0
- package/dist/scene-space.d.ts +50 -0
- package/dist/scene-space.js +103 -0
- package/dist/scene.d.ts +12 -2
- package/dist/scene.js +11 -4
- package/dist/simulation-phases.d.ts +9 -0
- package/dist/simulation-phases.js +19 -0
- package/dist/spatial-query-3d.d.ts +48 -0
- package/dist/spatial-query-3d.js +96 -0
- package/dist/spatial-query.d.ts +18 -4
- package/dist/spatial-query.js +57 -1
- package/package.json +2 -1
|
@@ -1,6 +1,8 @@
|
|
|
1
|
+
import { cameraEffectsSnapshot, viewSnapshot } from './runtime-view-snapshot.js';
|
|
1
2
|
import { anchoredPiecesOf } from './ui.js';
|
|
2
3
|
import { componentClassOf } from './component-registry.js';
|
|
3
4
|
import { capLights, lightingSnapshot, postSnapshot, } from './runtime-lighting-snapshot.js';
|
|
5
|
+
import { capBodies, physicsSection } from './runtime-physics-snapshot.js';
|
|
4
6
|
export const RUNTIME_PROJECTION_LIMITS = {
|
|
5
7
|
depth: 5,
|
|
6
8
|
entries: 100,
|
|
@@ -293,9 +295,12 @@ export class RuntimeInspector {
|
|
|
293
295
|
audio: this.audioSnapshot(),
|
|
294
296
|
time: this.timeSnapshot(),
|
|
295
297
|
ui: this.uiSnapshot(projectionIssues),
|
|
296
|
-
camera: this.game
|
|
298
|
+
camera: cameraEffectsSnapshot(this.game),
|
|
299
|
+
space: this.game.space,
|
|
300
|
+
view: viewSnapshot(this.game),
|
|
297
301
|
lighting: lightingSnapshot(this.game, (entity) => this.idFor(entity)),
|
|
298
302
|
post: postSnapshot(this.game),
|
|
303
|
+
...physicsSection(this.game, (entity) => this.idFor(entity)),
|
|
299
304
|
});
|
|
300
305
|
}
|
|
301
306
|
audioSnapshot() {
|
|
@@ -345,7 +350,8 @@ export class RuntimeInspector {
|
|
|
345
350
|
return capped;
|
|
346
351
|
}
|
|
347
352
|
const anchoredCapped = capAnchored(capped);
|
|
348
|
-
|
|
353
|
+
const bodiesCapped = capBodies(anchoredCapped, fitsSnapshot);
|
|
354
|
+
return fitsSnapshot(bodiesCapped) ? bodiesCapped : capLights(bodiesCapped, fitsSnapshot);
|
|
349
355
|
}
|
|
350
356
|
idFor(entity) {
|
|
351
357
|
const existing = this.ids.get(entity);
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { Entity } from './entity.js';
|
|
2
2
|
import type { Game } from './game.js';
|
|
3
3
|
import type { ProjectionIssue } from './runtime-inspection.js';
|
|
4
|
+
import type { Vec3Json } from './scene-camera-3d.js';
|
|
4
5
|
import type { AmbientLight, PostEffectsState } from './scene-render-options.js';
|
|
5
6
|
/** One live Light as a Runtime Snapshot reports it: its entity's name, logical position and params. */
|
|
6
7
|
export interface RuntimeSnapshotLight {
|
|
@@ -18,10 +19,35 @@ export interface RuntimeSnapshotLight {
|
|
|
18
19
|
softness: number;
|
|
19
20
|
castShadows: boolean;
|
|
20
21
|
}
|
|
21
|
-
/**
|
|
22
|
+
/** One live Sun in a 3D scene (issue #154 CA-16): its entity's name and id, the unit direction it shines along, color and intensity. */
|
|
23
|
+
export interface RuntimeSnapshotSun {
|
|
24
|
+
entity: string;
|
|
25
|
+
id: string;
|
|
26
|
+
direction: Vec3Json;
|
|
27
|
+
/** `#rrggbb`. */
|
|
28
|
+
color: string;
|
|
29
|
+
intensity: number;
|
|
30
|
+
}
|
|
31
|
+
/** One live Point Light in a 3D scene (issue #154 CA-16): its world position and look; `distance` 0 is unlimited. */
|
|
32
|
+
export interface RuntimeSnapshotPointLight {
|
|
33
|
+
entity: string;
|
|
34
|
+
id: string;
|
|
35
|
+
position: Vec3Json;
|
|
36
|
+
/** `#rrggbb`. */
|
|
37
|
+
color: string;
|
|
38
|
+
intensity: number;
|
|
39
|
+
distance: number;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* `game.lighting` (issue #78 CA-12): the Ambient Light now and every live
|
|
43
|
+
* Light, in logical coordinates. A 3D scene (issue #154 CA-16) adds its
|
|
44
|
+
* Suns and Point Lights; a 2D scene's snapshot has neither key.
|
|
45
|
+
*/
|
|
22
46
|
export interface RuntimeSnapshotLighting {
|
|
23
47
|
ambient: AmbientLight;
|
|
24
48
|
lights: RuntimeSnapshotLight[];
|
|
49
|
+
sun?: RuntimeSnapshotSun[];
|
|
50
|
+
pointLights?: RuntimeSnapshotPointLight[];
|
|
25
51
|
}
|
|
26
52
|
/** `game.post` (issue #78 CA-12): each Post Effect, null when off. */
|
|
27
53
|
export type RuntimeSnapshotPost = PostEffectsState;
|
|
@@ -1,6 +1,37 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import { PointLight } from './components/point-light.js';
|
|
3
|
+
import { Sun } from './components/sun.js';
|
|
1
4
|
const hex = (channel) => Math.round(channel * 255).toString(16).padStart(2, '0');
|
|
5
|
+
const rounded = (value) => Math.round(value * 1e6) / 1e6 || 0;
|
|
6
|
+
const hexOf = (color) => `#${(color & 0xffffff).toString(16).padStart(6, '0')}`;
|
|
7
|
+
function unit(direction) {
|
|
8
|
+
const [x, y, z] = new THREE.Vector3(...direction).normalize().toArray();
|
|
9
|
+
return [rounded(x), rounded(y), rounded(z)];
|
|
10
|
+
}
|
|
11
|
+
/** The Suns and Point Lights of a 3D scene's entities, in spawn order. */
|
|
12
|
+
function lights3dSnapshot(game, idFor) {
|
|
13
|
+
const sun = [];
|
|
14
|
+
const pointLights = [];
|
|
15
|
+
for (const entity of game.entities) {
|
|
16
|
+
for (const component of entity.components) {
|
|
17
|
+
if (component instanceof Sun) {
|
|
18
|
+
const { direction, color, intensity } = component;
|
|
19
|
+
sun.push({ entity: entity.name, id: idFor(entity), direction: unit(direction), color: hexOf(color), intensity });
|
|
20
|
+
}
|
|
21
|
+
else if (component instanceof PointLight && component.light) {
|
|
22
|
+
const { color, intensity, distance, light } = component;
|
|
23
|
+
light.updateWorldMatrix(true, false);
|
|
24
|
+
const at = light.getWorldPosition(new THREE.Vector3());
|
|
25
|
+
const position = [rounded(at.x), rounded(at.y), rounded(at.z)];
|
|
26
|
+
pointLights.push({ entity: entity.name, id: idFor(entity), position, color: hexOf(color), intensity, distance });
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
return { sun, pointLights };
|
|
31
|
+
}
|
|
2
32
|
export function lightingSnapshot(game, idFor) {
|
|
3
33
|
return {
|
|
34
|
+
...(game.space === '3d' ? lights3dSnapshot(game, idFor) : {}),
|
|
4
35
|
ambient: game.lighting.ambient,
|
|
5
36
|
lights: game.lighting.lights.map((light) => {
|
|
6
37
|
const field = light.field();
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Entity } from './entity.js';
|
|
2
|
+
import type { Game } from './game.js';
|
|
3
|
+
import { type BodyKind } from './physics-3d/body-sync.js';
|
|
4
|
+
import type { PhysicsState } from './physics-3d/physics-host.js';
|
|
5
|
+
import type { ProjectionIssue } from './runtime-inspection.js';
|
|
6
|
+
import type { Vec3Json } from './scene-camera-3d.js';
|
|
7
|
+
/** One live body of a 3D scene (issue #159 CA-17): its entity's name and snapshot id, its Rapier type, velocity and whether it stands on something. */
|
|
8
|
+
export interface RuntimeSnapshotBody {
|
|
9
|
+
entity: string;
|
|
10
|
+
id: string;
|
|
11
|
+
type: BodyKind;
|
|
12
|
+
velocity: Vec3Json;
|
|
13
|
+
grounded: boolean;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* `game.physics` of a 3D scene (issue #159 CA-17), beside `lighting`: the
|
|
17
|
+
* world's state (`loading` until the Rapier module arrives), its gravity and
|
|
18
|
+
* every body in spawn order, rounded to 1e-6. Never filtered by entity or
|
|
19
|
+
* component filters; a 2D scene's snapshot has no `physics` key.
|
|
20
|
+
*/
|
|
21
|
+
export interface RuntimeSnapshotPhysics {
|
|
22
|
+
state: PhysicsState;
|
|
23
|
+
gravity: Vec3Json;
|
|
24
|
+
bodies: RuntimeSnapshotBody[];
|
|
25
|
+
}
|
|
26
|
+
/** The `physics` section of a 3D scene's snapshot, as the spread that adds it; nothing in a 2D scene. */
|
|
27
|
+
export declare function physicsSection(game: Game, idFor: (entity: Entity) => string): {
|
|
28
|
+
physics?: RuntimeSnapshotPhysics;
|
|
29
|
+
};
|
|
30
|
+
/** What capping the bodies needs from a snapshot: its physics section and its projection issues. */
|
|
31
|
+
interface BodiesCappable {
|
|
32
|
+
physics?: RuntimeSnapshotPhysics;
|
|
33
|
+
projectionIssues: ProjectionIssue[];
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The global cap's stage for bodies, beside the lights' (`capLights`): drops
|
|
37
|
+
* bodies from the end until the snapshot fits, recording how many went with a
|
|
38
|
+
* `truncated` marker at `physics.bodies[n]`.
|
|
39
|
+
*/
|
|
40
|
+
export declare function capBodies<T extends BodiesCappable>(snapshot: T, fits: (candidate: T) => boolean): T;
|
|
41
|
+
export {};
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { bodyVelocity } from './physics-3d/body-sync.js';
|
|
2
|
+
const rounded = (value) => Math.round(value * 1e6) / 1e6 || 0;
|
|
3
|
+
const roundedVector = (x, y, z) => [rounded(x), rounded(y), rounded(z)];
|
|
4
|
+
/** The `physics` section of a 3D scene's snapshot, as the spread that adds it; nothing in a 2D scene. */
|
|
5
|
+
export function physicsSection(game, idFor) {
|
|
6
|
+
if (game.space !== '3d')
|
|
7
|
+
return {};
|
|
8
|
+
const world = game.physics.world;
|
|
9
|
+
const [gx, gy, gz] = game.physics.sceneGravity;
|
|
10
|
+
const bodies = (world?.bodiesOf(game.entities) ?? []).map((record) => {
|
|
11
|
+
const { x, y, z } = bodyVelocity(record);
|
|
12
|
+
return {
|
|
13
|
+
entity: record.entity.name,
|
|
14
|
+
id: idFor(record.entity),
|
|
15
|
+
type: record.kind,
|
|
16
|
+
velocity: roundedVector(x, y, z),
|
|
17
|
+
grounded: record.grounded,
|
|
18
|
+
};
|
|
19
|
+
});
|
|
20
|
+
return { physics: { state: game.physics.state, gravity: roundedVector(gx, gy, gz), bodies } };
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* The global cap's stage for bodies, beside the lights' (`capLights`): drops
|
|
24
|
+
* bodies from the end until the snapshot fits, recording how many went with a
|
|
25
|
+
* `truncated` marker at `physics.bodies[n]`.
|
|
26
|
+
*/
|
|
27
|
+
export function capBodies(snapshot, fits) {
|
|
28
|
+
const physics = snapshot.physics;
|
|
29
|
+
if (!physics || fits(snapshot))
|
|
30
|
+
return snapshot;
|
|
31
|
+
let capped = snapshot;
|
|
32
|
+
const retained = [...physics.bodies];
|
|
33
|
+
while (retained.length > 0) {
|
|
34
|
+
retained.pop();
|
|
35
|
+
const omitted = physics.bodies.length - retained.length;
|
|
36
|
+
capped = {
|
|
37
|
+
...snapshot,
|
|
38
|
+
physics: { ...physics, bodies: [...retained] },
|
|
39
|
+
projectionIssues: [...snapshot.projectionIssues, { path: `physics.bodies[${retained.length}]`, marker: 'truncated', omitted }],
|
|
40
|
+
};
|
|
41
|
+
if (fits(capped))
|
|
42
|
+
return capped;
|
|
43
|
+
}
|
|
44
|
+
return capped;
|
|
45
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CameraEffectsState } from './camera-effects.js';
|
|
2
|
+
import type { Game } from './game.js';
|
|
3
|
+
import type { Vec3Json } from './scene-camera-3d.js';
|
|
4
|
+
/**
|
|
5
|
+
* The view the scene is drawn from (issue #154 CA-16), beside the Camera
|
|
6
|
+
* Effects `camera`: the orthographic camera's centre and visible height, or
|
|
7
|
+
* the perspective camera's pose. Always reflects the live camera.
|
|
8
|
+
*/
|
|
9
|
+
export type RuntimeSnapshotView = {
|
|
10
|
+
kind: 'orthographic';
|
|
11
|
+
position: [number, number];
|
|
12
|
+
zoom: number;
|
|
13
|
+
} | {
|
|
14
|
+
kind: 'perspective';
|
|
15
|
+
position: Vec3Json;
|
|
16
|
+
target: Vec3Json;
|
|
17
|
+
fov: number;
|
|
18
|
+
};
|
|
19
|
+
export declare function viewSnapshot(game: Game): RuntimeSnapshotView;
|
|
20
|
+
/**
|
|
21
|
+
* `game.cameraEffects.state` as the snapshot reports it. A perspective camera
|
|
22
|
+
* does not shake (spec decision 24: shake is a no-op in a 3D scene), so a 3D
|
|
23
|
+
* scene reports no offset even though the effect's own state keeps decaying.
|
|
24
|
+
*/
|
|
25
|
+
export declare function cameraEffectsSnapshot(game: Game): CameraEffectsState;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import { isPerspectiveCamera } from './camera-projection.js';
|
|
3
|
+
/** Float noise off a pose, so a camera that did not move reports the numbers it was declared with. */
|
|
4
|
+
const clean = (value) => Math.round(value * 1e6) / 1e6 || 0;
|
|
5
|
+
function vector(value) {
|
|
6
|
+
return [clean(value.x), clean(value.y), clean(value.z)];
|
|
7
|
+
}
|
|
8
|
+
export function viewSnapshot(game) {
|
|
9
|
+
const camera = game.camera;
|
|
10
|
+
if (!isPerspectiveCamera(camera)) {
|
|
11
|
+
return { kind: 'orthographic', position: [clean(camera.position.x), clean(camera.position.y)], zoom: game.view };
|
|
12
|
+
}
|
|
13
|
+
camera.updateMatrixWorld();
|
|
14
|
+
const ahead = camera.getWorldDirection(new THREE.Vector3()).multiplyScalar(game.cameraRig.lookDistance);
|
|
15
|
+
return {
|
|
16
|
+
kind: 'perspective',
|
|
17
|
+
position: vector(camera.position),
|
|
18
|
+
target: vector(camera.position.clone().add(ahead)),
|
|
19
|
+
fov: camera.fov,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* `game.cameraEffects.state` as the snapshot reports it. A perspective camera
|
|
24
|
+
* does not shake (spec decision 24: shake is a no-op in a 3D scene), so a 3D
|
|
25
|
+
* scene reports no offset even though the effect's own state keeps decaying.
|
|
26
|
+
*/
|
|
27
|
+
export function cameraEffectsSnapshot(game) {
|
|
28
|
+
const state = game.cameraEffects.state;
|
|
29
|
+
return game.space === '3d' ? { ...state, shake: { x: 0, y: 0 } } : state;
|
|
30
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import type * as THREE from 'three/webgpu';
|
|
2
|
+
/** A world-space point or direction as scene JSON stores it. */
|
|
3
|
+
export type Vec3Json = [number, number, number];
|
|
4
|
+
/**
|
|
5
|
+
* A scene's perspective camera (3D scenes, ADR 0027). Fixed by the scene; game
|
|
6
|
+
* code may move it. Y is up and the camera looks from `position` at `target`.
|
|
7
|
+
*/
|
|
8
|
+
export interface PerspectiveSceneCameraJson {
|
|
9
|
+
kind: 'perspective';
|
|
10
|
+
/** Where the camera sits. Default `[0, 5, 10]`. */
|
|
11
|
+
position?: Vec3Json;
|
|
12
|
+
/** The point it looks at. Default `[0, 0, 0]`. */
|
|
13
|
+
target?: Vec3Json;
|
|
14
|
+
/** Vertical field of view in degrees, in (0, 180). Default 60. */
|
|
15
|
+
fov?: number;
|
|
16
|
+
/** Near clipping plane, above 0. Default 0.1. */
|
|
17
|
+
near?: number;
|
|
18
|
+
/** Far clipping plane, above `near`. Default 1000. */
|
|
19
|
+
far?: number;
|
|
20
|
+
zoom?: never;
|
|
21
|
+
follow?: never;
|
|
22
|
+
deadzoneWidth?: never;
|
|
23
|
+
deadzoneHeight?: never;
|
|
24
|
+
lookahead?: never;
|
|
25
|
+
lookaheadY?: never;
|
|
26
|
+
smoothing?: never;
|
|
27
|
+
limits?: never;
|
|
28
|
+
}
|
|
29
|
+
export interface ResolvedPerspectiveCamera {
|
|
30
|
+
kind: 'perspective';
|
|
31
|
+
position: Vec3Json;
|
|
32
|
+
target: Vec3Json;
|
|
33
|
+
fov: number;
|
|
34
|
+
near: number;
|
|
35
|
+
far: number;
|
|
36
|
+
}
|
|
37
|
+
export declare const PERSPECTIVE_DEFAULTS: {
|
|
38
|
+
readonly position: Vec3Json;
|
|
39
|
+
readonly target: Vec3Json;
|
|
40
|
+
readonly fov: 60;
|
|
41
|
+
readonly near: 0.1;
|
|
42
|
+
readonly far: 1000;
|
|
43
|
+
};
|
|
44
|
+
/** True for a perspective camera block; everything else (or none) is the orthographic one. */
|
|
45
|
+
export declare function isPerspectiveCameraJson(json: {
|
|
46
|
+
kind?: string;
|
|
47
|
+
} | undefined): json is PerspectiveSceneCameraJson;
|
|
48
|
+
/** Fills a perspective camera block with the engine defaults. */
|
|
49
|
+
export declare function resolvePerspectiveCamera(json?: PerspectiveSceneCameraJson): ResolvedPerspectiveCamera;
|
|
50
|
+
/** Points `camera` as the resolved block says, at the given aspect ratio. */
|
|
51
|
+
export declare function placePerspectiveCamera(camera: THREE.PerspectiveCamera, resolved: ResolvedPerspectiveCamera, aspect: number): void;
|
|
52
|
+
/** One field of a scene's render, camera or entity transform that is invalid, for validate_project. */
|
|
53
|
+
export interface SceneFieldIssue {
|
|
54
|
+
field: string;
|
|
55
|
+
message: string;
|
|
56
|
+
}
|
|
57
|
+
/** A JSON value as a message quotes it: strings in quotes, everything else as `String()` shows it. */
|
|
58
|
+
export declare const shown: (value: unknown) => string;
|
|
59
|
+
export declare function isFiniteNumber(value: unknown): value is number;
|
|
60
|
+
/**
|
|
61
|
+
* Every field of a perspective camera block outside its range: `fov` in
|
|
62
|
+
* (0, 180), `near` above 0, `far` above `near`, and `position` and `target`
|
|
63
|
+
* three finite numbers, and none of the orthographic block's fields. Anything
|
|
64
|
+
* that is not a perspective block reports none.
|
|
65
|
+
*/
|
|
66
|
+
export declare function perspectiveCameraIssues(camera: unknown): SceneFieldIssue[];
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
export const PERSPECTIVE_DEFAULTS = {
|
|
2
|
+
position: [0, 5, 10],
|
|
3
|
+
target: [0, 0, 0],
|
|
4
|
+
fov: 60,
|
|
5
|
+
near: 0.1,
|
|
6
|
+
far: 1000,
|
|
7
|
+
};
|
|
8
|
+
/** True for a perspective camera block; everything else (or none) is the orthographic one. */
|
|
9
|
+
export function isPerspectiveCameraJson(json) {
|
|
10
|
+
return json?.kind === 'perspective';
|
|
11
|
+
}
|
|
12
|
+
/** Fills a perspective camera block with the engine defaults. */
|
|
13
|
+
export function resolvePerspectiveCamera(json = { kind: 'perspective' }) {
|
|
14
|
+
return {
|
|
15
|
+
kind: 'perspective',
|
|
16
|
+
position: json.position ?? PERSPECTIVE_DEFAULTS.position,
|
|
17
|
+
target: json.target ?? PERSPECTIVE_DEFAULTS.target,
|
|
18
|
+
fov: json.fov ?? PERSPECTIVE_DEFAULTS.fov,
|
|
19
|
+
near: json.near ?? PERSPECTIVE_DEFAULTS.near,
|
|
20
|
+
far: json.far ?? PERSPECTIVE_DEFAULTS.far,
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Points `camera` as the resolved block says, at the given aspect ratio. */
|
|
24
|
+
export function placePerspectiveCamera(camera, resolved, aspect) {
|
|
25
|
+
camera.fov = resolved.fov;
|
|
26
|
+
camera.near = resolved.near;
|
|
27
|
+
camera.far = resolved.far;
|
|
28
|
+
camera.aspect = aspect;
|
|
29
|
+
camera.position.set(...resolved.position);
|
|
30
|
+
camera.lookAt(...resolved.target);
|
|
31
|
+
camera.updateProjectionMatrix();
|
|
32
|
+
camera.updateMatrixWorld();
|
|
33
|
+
}
|
|
34
|
+
/** A JSON value as a message quotes it: strings in quotes, everything else as `String()` shows it. */
|
|
35
|
+
export const shown = (value) => (typeof value === 'string' ? JSON.stringify(value) : String(value));
|
|
36
|
+
export function isFiniteNumber(value) {
|
|
37
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
38
|
+
}
|
|
39
|
+
function vectorIssue(field, value, issues) {
|
|
40
|
+
if (value === undefined)
|
|
41
|
+
return;
|
|
42
|
+
if (Array.isArray(value) && value.length === 3 && value.every(isFiniteNumber))
|
|
43
|
+
return;
|
|
44
|
+
issues.push({ field, message: `${field} must be three finite numbers [x, y, z]; got ${JSON.stringify(value)}.` });
|
|
45
|
+
}
|
|
46
|
+
function fovIssue(value, issues) {
|
|
47
|
+
if (value === undefined)
|
|
48
|
+
return;
|
|
49
|
+
if (isFiniteNumber(value) && value > 0 && value < 180)
|
|
50
|
+
return;
|
|
51
|
+
issues.push({ field: 'camera.fov', message: `camera.fov must be a number above 0 and below 180; got ${shown(value)}.` });
|
|
52
|
+
}
|
|
53
|
+
/** `position` and `target` as the camera would use them, when both are well-formed (declared or default). */
|
|
54
|
+
function resolvedAim(block) {
|
|
55
|
+
const position = block.position ?? PERSPECTIVE_DEFAULTS.position;
|
|
56
|
+
const target = block.target ?? PERSPECTIVE_DEFAULTS.target;
|
|
57
|
+
const wellFormed = (value) => Array.isArray(value) && value.length === 3 && value.every(isFiniteNumber);
|
|
58
|
+
return wellFormed(position) && wellFormed(target) ? { position, target } : null;
|
|
59
|
+
}
|
|
60
|
+
function aimIssue(block, issues) {
|
|
61
|
+
const aim = resolvedAim(block);
|
|
62
|
+
if (!aim || aim.position.some((value, axis) => value !== aim.target[axis]))
|
|
63
|
+
return;
|
|
64
|
+
issues.push({
|
|
65
|
+
field: 'camera.target',
|
|
66
|
+
message: `camera.target must differ from camera.position (${JSON.stringify(aim.position)}): a camera needs a direction to look in.`,
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
function planeIssues(near, far, issues) {
|
|
70
|
+
const nearIsValid = isFiniteNumber(near) && near > 0;
|
|
71
|
+
if (near !== undefined && !nearIsValid) {
|
|
72
|
+
issues.push({ field: 'camera.near', message: `camera.near must be a number above 0; got ${shown(near)}.` });
|
|
73
|
+
}
|
|
74
|
+
const nearValue = nearIsValid ? near : PERSPECTIVE_DEFAULTS.near;
|
|
75
|
+
if (far !== undefined && !(isFiniteNumber(far) && far > nearValue)) {
|
|
76
|
+
issues.push({ field: 'camera.far', message: `camera.far must be a number above near (${nearValue}); got ${shown(far)}.` });
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
/** The orthographic block's fields, which a perspective block does not accept. */
|
|
80
|
+
const ORTHOGRAPHIC_ONLY_FIELDS = [
|
|
81
|
+
'follow',
|
|
82
|
+
'zoom',
|
|
83
|
+
'deadzoneWidth',
|
|
84
|
+
'deadzoneHeight',
|
|
85
|
+
'lookahead',
|
|
86
|
+
'lookaheadY',
|
|
87
|
+
'smoothing',
|
|
88
|
+
'limits',
|
|
89
|
+
];
|
|
90
|
+
function orthographicFieldIssues(block, issues) {
|
|
91
|
+
for (const field of ORTHOGRAPHIC_ONLY_FIELDS) {
|
|
92
|
+
if (block[field] === undefined)
|
|
93
|
+
continue;
|
|
94
|
+
issues.push({ field: `camera.${field}`, message: `camera.${field} belongs to an orthographic camera; a perspective camera does not accept it.` });
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Every field of a perspective camera block outside its range: `fov` in
|
|
99
|
+
* (0, 180), `near` above 0, `far` above `near`, and `position` and `target`
|
|
100
|
+
* three finite numbers, and none of the orthographic block's fields. Anything
|
|
101
|
+
* that is not a perspective block reports none.
|
|
102
|
+
*/
|
|
103
|
+
export function perspectiveCameraIssues(camera) {
|
|
104
|
+
if (typeof camera !== 'object' || camera === null)
|
|
105
|
+
return [];
|
|
106
|
+
const block = { ...camera };
|
|
107
|
+
if (block.kind !== 'perspective')
|
|
108
|
+
return [];
|
|
109
|
+
const issues = [];
|
|
110
|
+
vectorIssue('camera.position', block.position, issues);
|
|
111
|
+
vectorIssue('camera.target', block.target, issues);
|
|
112
|
+
fovIssue(block.fov, issues);
|
|
113
|
+
aimIssue(block, issues);
|
|
114
|
+
planeIssues(block.near, block.far, issues);
|
|
115
|
+
orthographicFieldIssues(block, issues);
|
|
116
|
+
return issues;
|
|
117
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
import type { SceneSpace } from './scene-space.js';
|
|
3
|
+
import type { AmbientLight } from './scene-render-options.js';
|
|
4
|
+
/**
|
|
5
|
+
* The Ambient Light of a 3D scene (ADR 0027): one `THREE.AmbientLight` the
|
|
6
|
+
* Game owns, fed by `game.lighting.ambient` (`render.lighting.ambient` and the
|
|
7
|
+
* runtime setter), color × intensity, full white when nothing says otherwise.
|
|
8
|
+
* It exists only while the live scene is 3D; a 2D scene lights through the
|
|
9
|
+
* light-map instead (ADR 0026).
|
|
10
|
+
*/
|
|
11
|
+
export declare class SceneAmbientLight3d {
|
|
12
|
+
private readonly scene;
|
|
13
|
+
private light;
|
|
14
|
+
constructor(scene: THREE.Scene);
|
|
15
|
+
/** Adds, updates or removes the light for the live scene's space; called once per frame before it is drawn. */
|
|
16
|
+
sync(space: SceneSpace, ambient: AmbientLight): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import * as THREE from 'three/webgpu';
|
|
2
|
+
/**
|
|
3
|
+
* The Ambient Light of a 3D scene (ADR 0027): one `THREE.AmbientLight` the
|
|
4
|
+
* Game owns, fed by `game.lighting.ambient` (`render.lighting.ambient` and the
|
|
5
|
+
* runtime setter), color × intensity, full white when nothing says otherwise.
|
|
6
|
+
* It exists only while the live scene is 3D; a 2D scene lights through the
|
|
7
|
+
* light-map instead (ADR 0026).
|
|
8
|
+
*/
|
|
9
|
+
export class SceneAmbientLight3d {
|
|
10
|
+
scene;
|
|
11
|
+
light = null;
|
|
12
|
+
constructor(scene) {
|
|
13
|
+
this.scene = scene;
|
|
14
|
+
}
|
|
15
|
+
/** Adds, updates or removes the light for the live scene's space; called once per frame before it is drawn. */
|
|
16
|
+
sync(space, ambient) {
|
|
17
|
+
if (space !== '3d') {
|
|
18
|
+
this.light?.removeFromParent();
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
this.light ??= new THREE.AmbientLight();
|
|
22
|
+
if (this.light.parent !== this.scene)
|
|
23
|
+
this.scene.add(this.light);
|
|
24
|
+
this.light.color.set(ambient.color);
|
|
25
|
+
this.light.intensity = ambient.intensity;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { type SceneFieldIssue, type Vec3Json } from './scene-camera-3d.js';
|
|
2
|
+
import type { SceneSpace } from './scene-space.js';
|
|
3
|
+
/**
|
|
4
|
+
* A 3D scene's `simulation` block (issue #159): the physics world's settings.
|
|
5
|
+
* Absent, or with `gravity` absent, the world pulls along -y at 9.81.
|
|
6
|
+
*/
|
|
7
|
+
export interface SceneSimulationJson {
|
|
8
|
+
/** The world's gravity vector in units per second squared. Default `[0, -9.81, 0]`. */
|
|
9
|
+
gravity?: Vec3Json;
|
|
10
|
+
}
|
|
11
|
+
export interface ResolvedSceneSimulation {
|
|
12
|
+
gravity: Vec3Json;
|
|
13
|
+
}
|
|
14
|
+
export declare const DEFAULT_GRAVITY: Readonly<Vec3Json>;
|
|
15
|
+
/** The simulation a scene runs with: its `simulation` block, defaults filling the rest. A malformed gravity reads as the default (`validate_project` reports it). */
|
|
16
|
+
export declare function resolveSceneSimulation(json: SceneSimulationJson | undefined): ResolvedSceneSimulation;
|
|
17
|
+
/**
|
|
18
|
+
* Every invalid field of a scene's `simulation` block: a gravity that is not
|
|
19
|
+
* three finite numbers in a 3d scene, and any block at all in a 2d one, where
|
|
20
|
+
* nothing simulates.
|
|
21
|
+
*/
|
|
22
|
+
export declare function sceneSimulationIssues(simulation: unknown, space: SceneSpace): SceneFieldIssue[];
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isFiniteNumber } from './scene-camera-3d.js';
|
|
2
|
+
export const DEFAULT_GRAVITY = [0, -9.81, 0];
|
|
3
|
+
function isVec3(value) {
|
|
4
|
+
return Array.isArray(value) && value.length === 3 && value.every(isFiniteNumber);
|
|
5
|
+
}
|
|
6
|
+
/** The simulation a scene runs with: its `simulation` block, defaults filling the rest. A malformed gravity reads as the default (`validate_project` reports it). */
|
|
7
|
+
export function resolveSceneSimulation(json) {
|
|
8
|
+
const gravity = json?.gravity;
|
|
9
|
+
return { gravity: isVec3(gravity) ? [...gravity] : [...DEFAULT_GRAVITY] };
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Every invalid field of a scene's `simulation` block: a gravity that is not
|
|
13
|
+
* three finite numbers in a 3d scene, and any block at all in a 2d one, where
|
|
14
|
+
* nothing simulates.
|
|
15
|
+
*/
|
|
16
|
+
export function sceneSimulationIssues(simulation, space) {
|
|
17
|
+
if (simulation === undefined)
|
|
18
|
+
return [];
|
|
19
|
+
if (space === '2d') {
|
|
20
|
+
return [{ field: 'simulation', message: `A simulation block needs render.space '3d'; this scene is 2d, where nothing simulates.` }];
|
|
21
|
+
}
|
|
22
|
+
if (typeof simulation !== 'object' || simulation === null || Array.isArray(simulation)) {
|
|
23
|
+
return [{ field: 'simulation', message: `simulation must be an object; got ${JSON.stringify(simulation)}.` }];
|
|
24
|
+
}
|
|
25
|
+
const gravity = Reflect.get(simulation, 'gravity');
|
|
26
|
+
if (gravity === undefined || isVec3(gravity))
|
|
27
|
+
return [];
|
|
28
|
+
return [{ field: 'simulation.gravity', message: `simulation.gravity must be three finite numbers [x, y, z]; got ${JSON.stringify(gravity)}.` }];
|
|
29
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import type { ComponentClass, ComponentSpace } from './component.js';
|
|
2
|
+
import { type SceneFieldIssue } from './scene-camera-3d.js';
|
|
3
|
+
/**
|
|
4
|
+
* A scene's space (ADR 0027): `'2d'` is the engine's orthographic world,
|
|
5
|
+
* where `z` is draw order; `'3d'` makes `z` a world axis under a perspective
|
|
6
|
+
* camera and a real depth buffer. A scene declares it as `render.space`.
|
|
7
|
+
*/
|
|
8
|
+
export type SceneSpace = '2d' | '3d';
|
|
9
|
+
export declare const SCENE_SPACES: readonly SceneSpace[];
|
|
10
|
+
/** The scene's space: 3d only when `render.space` says exactly so. */
|
|
11
|
+
export declare function resolveSceneSpace(render: unknown): SceneSpace;
|
|
12
|
+
/**
|
|
13
|
+
* What a scene's render block makes of the draw-order options at runtime. A
|
|
14
|
+
* 3D scene has no y-sort and no isometric projection whatever the block
|
|
15
|
+
* says (`validate_project` reports them); a 2D scene reads them as always.
|
|
16
|
+
*/
|
|
17
|
+
export declare function resolveRenderPolicy(render: unknown): {
|
|
18
|
+
space: SceneSpace;
|
|
19
|
+
sort: 'y' | null;
|
|
20
|
+
projection: 'isometric' | null;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* What a component class says about the space it belongs to: its `static
|
|
24
|
+
* space` marker, or nothing. Only that slice of `ComponentClass`, so the
|
|
25
|
+
* abstract `Component` and a class the caller builds by hand qualify too.
|
|
26
|
+
*/
|
|
27
|
+
export type SpaceMarked = Pick<ComponentClass, 'space'>;
|
|
28
|
+
/** The space a component class belongs to; a class that declares none is `'both'`. */
|
|
29
|
+
export declare function componentSpaceOf(Class: SpaceMarked): ComponentSpace;
|
|
30
|
+
/**
|
|
31
|
+
* True when a component of this class does not belong in a scene of this
|
|
32
|
+
* space: a `'2d'` component in 3d, a `'3d'` one in 2d. Reads the class's
|
|
33
|
+
* `static space` marker, so engine, behavior and project-owned components
|
|
34
|
+
* answer the same way. A class the caller does not know (undefined) is
|
|
35
|
+
* space-neutral.
|
|
36
|
+
*/
|
|
37
|
+
export declare function componentSpaceMismatch(space: SceneSpace, Class: SpaceMarked | undefined): boolean;
|
|
38
|
+
/**
|
|
39
|
+
* Every invalid field of a scene's `render.space`, its camera block and the
|
|
40
|
+
* render options a 3d scene cannot use: a space outside `'2d' | '3d'`, a
|
|
41
|
+
* camera that does not match the space, a perspective camera out of range
|
|
42
|
+
* and `render.sort`, `render.projection` or `render.batch` under 3d. Other
|
|
43
|
+
* render fields are `sceneRenderIssues`'s.
|
|
44
|
+
*/
|
|
45
|
+
export declare function sceneSpaceIssues(render: unknown, camera: unknown): SceneFieldIssue[];
|
|
46
|
+
/**
|
|
47
|
+
* An entity's `position` (2 or 3 finite numbers), `rotation` (degrees) and
|
|
48
|
+
* `scale` (3 finite numbers each) that are anything else.
|
|
49
|
+
*/
|
|
50
|
+
export declare function entityTransformIssues(entity: unknown): SceneFieldIssue[];
|