@waica/engine 0.28.0 → 0.29.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 +22 -8
- package/dist/anchored-pieces.js +21 -5
- package/dist/archetype.d.ts +15 -0
- package/dist/archetype.js +41 -1
- package/dist/camera-projection.d.ts +6 -5
- package/dist/camera-projection.js +8 -7
- package/dist/camera-rig.d.ts +32 -3
- package/dist/camera-rig.js +75 -1
- package/dist/components/animator.d.ts +6 -0
- package/dist/components/animator.js +12 -0
- package/dist/entity-update-schedules.d.ts +21 -0
- package/dist/entity-update-schedules.js +67 -0
- package/dist/game.d.ts +13 -11
- package/dist/game.js +40 -74
- package/dist/index.d.ts +7 -2
- package/dist/index.js +2 -0
- package/dist/input-mouse.d.ts +83 -0
- package/dist/input-mouse.js +205 -0
- package/dist/input.d.ts +26 -5
- package/dist/input.js +58 -10
- package/dist/orbit-camera.d.ts +68 -0
- package/dist/orbit-camera.js +92 -0
- package/dist/physics-3d/world-queries.d.ts +10 -1
- package/dist/physics-3d/world-queries.js +6 -2
- package/dist/runtime-bridge.d.ts +12 -2
- package/dist/runtime-bridge.js +12 -1
- package/dist/runtime-inspection.js +3 -2
- package/dist/runtime-view-snapshot.d.ts +5 -8
- package/dist/runtime-view-snapshot.js +13 -10
- package/dist/scene-camera-3d.d.ts +14 -15
- package/dist/scene-camera-3d.js +14 -10
- package/dist/scene-camera-orbit.d.ts +72 -0
- package/dist/scene-camera-orbit.js +155 -0
- package/dist/scene-field-issues.d.ts +16 -0
- package/dist/scene-field-issues.js +11 -0
- package/dist/scene.js +5 -3
- package/dist/spatial-query-3d.d.ts +2 -2
- package/dist/spatial-query-3d.js +13 -4
- package/package.json +1 -1
package/dist/scene-camera-3d.js
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { orbitCameraIssues, resolveOrbit } from './scene-camera-orbit.js';
|
|
2
|
+
import { isFiniteNumber, shown } from './scene-field-issues.js';
|
|
1
3
|
export const PERSPECTIVE_DEFAULTS = {
|
|
2
4
|
position: [0, 5, 10],
|
|
3
5
|
target: [0, 0, 0],
|
|
@@ -18,8 +20,13 @@ export function resolvePerspectiveCamera(json = { kind: 'perspective' }) {
|
|
|
18
20
|
fov: json.fov ?? PERSPECTIVE_DEFAULTS.fov,
|
|
19
21
|
near: json.near ?? PERSPECTIVE_DEFAULTS.near,
|
|
20
22
|
far: json.far ?? PERSPECTIVE_DEFAULTS.far,
|
|
23
|
+
...orbitOf(json),
|
|
21
24
|
};
|
|
22
25
|
}
|
|
26
|
+
function orbitOf(json) {
|
|
27
|
+
const orbit = resolveOrbit(json);
|
|
28
|
+
return orbit ? { orbit } : {};
|
|
29
|
+
}
|
|
23
30
|
/** Points `camera` as the resolved block says, at the given aspect ratio. */
|
|
24
31
|
export function placePerspectiveCamera(camera, resolved, aspect) {
|
|
25
32
|
camera.fov = resolved.fov;
|
|
@@ -31,11 +38,7 @@ export function placePerspectiveCamera(camera, resolved, aspect) {
|
|
|
31
38
|
camera.updateProjectionMatrix();
|
|
32
39
|
camera.updateMatrixWorld();
|
|
33
40
|
}
|
|
34
|
-
|
|
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
|
-
}
|
|
41
|
+
export { isFiniteNumber, shown } from './scene-field-issues.js';
|
|
39
42
|
function vectorIssue(field, value, issues) {
|
|
40
43
|
if (value === undefined)
|
|
41
44
|
return;
|
|
@@ -76,9 +79,8 @@ function planeIssues(near, far, issues) {
|
|
|
76
79
|
issues.push({ field: 'camera.far', message: `camera.far must be a number above near (${nearValue}); got ${shown(far)}.` });
|
|
77
80
|
}
|
|
78
81
|
}
|
|
79
|
-
/** The orthographic block's fields, which a perspective block does not accept. */
|
|
82
|
+
/** The orthographic block's fields, which a perspective block does not accept (`follow` it does, issue #181). */
|
|
80
83
|
const ORTHOGRAPHIC_ONLY_FIELDS = [
|
|
81
|
-
'follow',
|
|
82
84
|
'zoom',
|
|
83
85
|
'deadzoneWidth',
|
|
84
86
|
'deadzoneHeight',
|
|
@@ -96,9 +98,10 @@ function orthographicFieldIssues(block, issues) {
|
|
|
96
98
|
}
|
|
97
99
|
/**
|
|
98
100
|
* Every field of a perspective camera block outside its range: `fov` in
|
|
99
|
-
* (0, 180), `near` above 0, `far` above `near`,
|
|
100
|
-
* three finite numbers, and none of the
|
|
101
|
-
* that is not a perspective block
|
|
101
|
+
* (0, 180), `near` above 0, `far` above `near`, `position` and `target`
|
|
102
|
+
* three finite numbers, the orbit fields (orbitCameraIssues), and none of the
|
|
103
|
+
* orthographic block's other fields. Anything that is not a perspective block
|
|
104
|
+
* reports none.
|
|
102
105
|
*/
|
|
103
106
|
export function perspectiveCameraIssues(camera) {
|
|
104
107
|
if (typeof camera !== 'object' || camera === null)
|
|
@@ -113,5 +116,6 @@ export function perspectiveCameraIssues(camera) {
|
|
|
113
116
|
aimIssue(block, issues);
|
|
114
117
|
planeIssues(block.near, block.far, issues);
|
|
115
118
|
orthographicFieldIssues(block, issues);
|
|
119
|
+
issues.push(...orbitCameraIssues(block));
|
|
116
120
|
return issues;
|
|
117
121
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { type SceneFieldIssue } from './scene-field-issues.js';
|
|
2
|
+
/**
|
|
3
|
+
* The orbit fields of a scene's perspective camera block (issue #181, ADR
|
|
4
|
+
* 0031): with `follow`, the camera orbits that entity instead of staying
|
|
5
|
+
* where `position` and `target` put it. Angles are degrees; `yaw` 0 looks
|
|
6
|
+
* toward -z (the camera sits on +z), 90 toward +x; `pitch` is the camera's
|
|
7
|
+
* height above the look point, positive looking down.
|
|
8
|
+
*/
|
|
9
|
+
export interface OrbitCameraJson {
|
|
10
|
+
/** The entity to orbit; absent or empty keeps the camera fixed. */
|
|
11
|
+
follow?: string;
|
|
12
|
+
/** The chosen distance from the look point. Default 6. */
|
|
13
|
+
distance?: number;
|
|
14
|
+
/** The wheel's closest distance. Default 2. */
|
|
15
|
+
minDistance?: number;
|
|
16
|
+
/** The wheel's farthest distance. Default 12. */
|
|
17
|
+
maxDistance?: number;
|
|
18
|
+
/** Initial yaw, in degrees. Default 0. */
|
|
19
|
+
yaw?: number;
|
|
20
|
+
/** Initial pitch, in degrees, in (-89, 89). Default 20. */
|
|
21
|
+
pitch?: number;
|
|
22
|
+
/** Lowest pitch, in (-89, 89). Default -10. */
|
|
23
|
+
minPitch?: number;
|
|
24
|
+
/** Highest pitch, in (-89, 89). Default 70. */
|
|
25
|
+
maxPitch?: number;
|
|
26
|
+
/** How far above the followed entity's position the camera looks. Default 1.2. */
|
|
27
|
+
lookHeight?: number;
|
|
28
|
+
/** Degrees of orbit per CSS pixel of locked mouse movement. Default 0.2. */
|
|
29
|
+
sensitivity?: number;
|
|
30
|
+
/** Degrees per second the orbit Actions turn at full value. Default 120. */
|
|
31
|
+
orbitSpeed?: number;
|
|
32
|
+
/** Flips the mouse's vertical orbit. Default false. */
|
|
33
|
+
invertY?: boolean;
|
|
34
|
+
/** The Action that orbits left (yaw down). Default `look_left`. */
|
|
35
|
+
orbitLeftAction?: string;
|
|
36
|
+
/** The Action that orbits right (yaw up). Default `look_right`. */
|
|
37
|
+
orbitRightAction?: string;
|
|
38
|
+
/** The Action that looks up (pitch down). Default `look_up`. */
|
|
39
|
+
orbitUpAction?: string;
|
|
40
|
+
/** The Action that looks down (pitch up). Default `look_down`. */
|
|
41
|
+
orbitDownAction?: string;
|
|
42
|
+
}
|
|
43
|
+
/** Every orbit field resolved; `follow` names the entity. */
|
|
44
|
+
export type ResolvedOrbit = Required<OrbitCameraJson>;
|
|
45
|
+
export declare const ORBIT_DEFAULTS: {
|
|
46
|
+
readonly distance: 6;
|
|
47
|
+
readonly minDistance: 2;
|
|
48
|
+
readonly maxDistance: 12;
|
|
49
|
+
readonly yaw: 0;
|
|
50
|
+
readonly pitch: 20;
|
|
51
|
+
readonly minPitch: -10;
|
|
52
|
+
readonly maxPitch: 70;
|
|
53
|
+
readonly lookHeight: 1.2;
|
|
54
|
+
readonly sensitivity: 0.2;
|
|
55
|
+
readonly orbitSpeed: 120;
|
|
56
|
+
readonly invertY: false;
|
|
57
|
+
readonly orbitLeftAction: 'look_left';
|
|
58
|
+
readonly orbitRightAction: 'look_right';
|
|
59
|
+
readonly orbitUpAction: 'look_up';
|
|
60
|
+
readonly orbitDownAction: 'look_down';
|
|
61
|
+
};
|
|
62
|
+
/** The orbit a block declares with its defaults filled, or null for a fixed camera (no `follow`). */
|
|
63
|
+
export declare function resolveOrbit(json: OrbitCameraJson): ResolvedOrbit | null;
|
|
64
|
+
/**
|
|
65
|
+
* Every orbit field of a perspective block outside its range: the numbers
|
|
66
|
+
* (distances above 0, pitches in (-89, 89), speeds at least 0), `invertY`,
|
|
67
|
+
* the Action names, and a `minDistance` or `minPitch` above its maximum
|
|
68
|
+
* (errors); and a following camera's initial `distance` or `pitch` outside
|
|
69
|
+
* its own range (warnings). Whether `follow` names an entity of the scene is
|
|
70
|
+
* the scene validator's.
|
|
71
|
+
*/
|
|
72
|
+
export declare function orbitCameraIssues(block: Record<string, unknown>): SceneFieldIssue[];
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
import { isFiniteNumber, shown } from './scene-field-issues.js';
|
|
2
|
+
const ACTION_FIELDS = ['orbitLeftAction', 'orbitRightAction', 'orbitUpAction', 'orbitDownAction'];
|
|
3
|
+
export const ORBIT_DEFAULTS = {
|
|
4
|
+
distance: 6,
|
|
5
|
+
minDistance: 2,
|
|
6
|
+
maxDistance: 12,
|
|
7
|
+
yaw: 0,
|
|
8
|
+
pitch: 20,
|
|
9
|
+
minPitch: -10,
|
|
10
|
+
maxPitch: 70,
|
|
11
|
+
lookHeight: 1.2,
|
|
12
|
+
sensitivity: 0.2,
|
|
13
|
+
orbitSpeed: 120,
|
|
14
|
+
invertY: false,
|
|
15
|
+
orbitLeftAction: 'look_left',
|
|
16
|
+
orbitRightAction: 'look_right',
|
|
17
|
+
orbitUpAction: 'look_up',
|
|
18
|
+
orbitDownAction: 'look_down',
|
|
19
|
+
};
|
|
20
|
+
/** The orbit a block declares with its defaults filled, or null for a fixed camera (no `follow`). */
|
|
21
|
+
export function resolveOrbit(json) {
|
|
22
|
+
if (!json.follow)
|
|
23
|
+
return null;
|
|
24
|
+
return { follow: json.follow, ...resolveFraming(json), ...resolveControls(json) };
|
|
25
|
+
}
|
|
26
|
+
/** Where the camera sits: distances, angles and the look height. */
|
|
27
|
+
function resolveFraming(json) {
|
|
28
|
+
return {
|
|
29
|
+
distance: json.distance ?? ORBIT_DEFAULTS.distance,
|
|
30
|
+
minDistance: json.minDistance ?? ORBIT_DEFAULTS.minDistance,
|
|
31
|
+
maxDistance: json.maxDistance ?? ORBIT_DEFAULTS.maxDistance,
|
|
32
|
+
yaw: json.yaw ?? ORBIT_DEFAULTS.yaw,
|
|
33
|
+
pitch: json.pitch ?? ORBIT_DEFAULTS.pitch,
|
|
34
|
+
minPitch: json.minPitch ?? ORBIT_DEFAULTS.minPitch,
|
|
35
|
+
maxPitch: json.maxPitch ?? ORBIT_DEFAULTS.maxPitch,
|
|
36
|
+
lookHeight: json.lookHeight ?? ORBIT_DEFAULTS.lookHeight,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
/** How input turns it: the mouse, the orbit Actions and their speed. */
|
|
40
|
+
function resolveControls(json) {
|
|
41
|
+
return {
|
|
42
|
+
sensitivity: json.sensitivity ?? ORBIT_DEFAULTS.sensitivity,
|
|
43
|
+
orbitSpeed: json.orbitSpeed ?? ORBIT_DEFAULTS.orbitSpeed,
|
|
44
|
+
invertY: json.invertY ?? ORBIT_DEFAULTS.invertY,
|
|
45
|
+
orbitLeftAction: json.orbitLeftAction ?? ORBIT_DEFAULTS.orbitLeftAction,
|
|
46
|
+
orbitRightAction: json.orbitRightAction ?? ORBIT_DEFAULTS.orbitRightAction,
|
|
47
|
+
orbitUpAction: json.orbitUpAction ?? ORBIT_DEFAULTS.orbitUpAction,
|
|
48
|
+
orbitDownAction: json.orbitDownAction ?? ORBIT_DEFAULTS.orbitDownAction,
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/** The rule each numeric orbit field must meet, and how its message says it. */
|
|
52
|
+
const NUMBER_RULES = [
|
|
53
|
+
{ field: 'distance', accepts: (value) => value > 0, rule: 'a number above 0' },
|
|
54
|
+
{ field: 'minDistance', accepts: (value) => value > 0, rule: 'a number above 0' },
|
|
55
|
+
{ field: 'maxDistance', accepts: (value) => value > 0, rule: 'a number above 0' },
|
|
56
|
+
{ field: 'yaw', accepts: () => true, rule: 'a finite number' },
|
|
57
|
+
{ field: 'pitch', accepts: (value) => Math.abs(value) < 89, rule: 'a number above -89 and below 89' },
|
|
58
|
+
{ field: 'minPitch', accepts: (value) => Math.abs(value) < 89, rule: 'a number above -89 and below 89' },
|
|
59
|
+
{ field: 'maxPitch', accepts: (value) => Math.abs(value) < 89, rule: 'a number above -89 and below 89' },
|
|
60
|
+
{ field: 'lookHeight', accepts: () => true, rule: 'a finite number' },
|
|
61
|
+
{ field: 'sensitivity', accepts: (value) => value >= 0, rule: 'a number of at least 0' },
|
|
62
|
+
{ field: 'orbitSpeed', accepts: (value) => value >= 0, rule: 'a number of at least 0' },
|
|
63
|
+
];
|
|
64
|
+
function numberIssues(block, issues) {
|
|
65
|
+
for (const { field, accepts, rule } of NUMBER_RULES) {
|
|
66
|
+
const value = block[field];
|
|
67
|
+
if (value === undefined || (isFiniteNumber(value) && accepts(value)))
|
|
68
|
+
continue;
|
|
69
|
+
issues.push({ field: `camera.${field}`, message: `camera.${field} must be ${rule}; got ${shown(value)}.` });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
function actionIssues(block, issues) {
|
|
73
|
+
for (const field of ACTION_FIELDS) {
|
|
74
|
+
const value = block[field];
|
|
75
|
+
if (value === undefined || (typeof value === 'string' && value.length > 0))
|
|
76
|
+
continue;
|
|
77
|
+
issues.push({ field: `camera.${field}`, message: `camera.${field} must be an Action name; got ${shown(value)}.` });
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function fieldIssues(block, issues) {
|
|
81
|
+
if (block.follow !== undefined && typeof block.follow !== 'string') {
|
|
82
|
+
issues.push({ field: 'camera.follow', message: `camera.follow must be an entity name; got ${shown(block.follow)}.` });
|
|
83
|
+
}
|
|
84
|
+
numberIssues(block, issues);
|
|
85
|
+
if (block.invertY !== undefined && typeof block.invertY !== 'boolean') {
|
|
86
|
+
issues.push({ field: 'camera.invertY', message: `camera.invertY must be true or false; got ${shown(block.invertY)}.` });
|
|
87
|
+
}
|
|
88
|
+
actionIssues(block, issues);
|
|
89
|
+
}
|
|
90
|
+
function rangeIssue(block, [low, high], issues) {
|
|
91
|
+
const min = block[low];
|
|
92
|
+
const max = block[high];
|
|
93
|
+
const fallback = ORBIT_DEFAULTS;
|
|
94
|
+
const lowValue = min ?? fallback[low];
|
|
95
|
+
const highValue = max ?? fallback[high];
|
|
96
|
+
if (!isFiniteNumber(lowValue) || !isFiniteNumber(highValue) || lowValue <= highValue)
|
|
97
|
+
return;
|
|
98
|
+
issues.push({
|
|
99
|
+
field: `camera.${low}`,
|
|
100
|
+
message: `camera.${low} (${lowValue}) must not exceed camera.${high} (${highValue}).`,
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
/** A declared value or its default, when it is a number its rule accepts (else the field is already an error). */
|
|
104
|
+
function numberOr(block, field) {
|
|
105
|
+
const fallback = ORBIT_DEFAULTS;
|
|
106
|
+
const value = block[field] ?? fallback[field];
|
|
107
|
+
const rule = NUMBER_RULES.find((entry) => entry.field === field);
|
|
108
|
+
return isFiniteNumber(value) && (rule?.accepts(value) ?? true) ? value : null;
|
|
109
|
+
}
|
|
110
|
+
/** The range each initial orbit number starts inside. */
|
|
111
|
+
const START_RANGES = {
|
|
112
|
+
distance: ['minDistance', 'maxDistance'],
|
|
113
|
+
pitch: ['minPitch', 'maxPitch'],
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* A following camera's declared initial `distance` or `pitch` outside its
|
|
117
|
+
* valid range [low, high]: the camera clamps it, so it starts elsewhere than
|
|
118
|
+
* the scene says, a warning (issue #181, maintainer decision). An invalid
|
|
119
|
+
* value or an inverted range is already an error and is not repeated.
|
|
120
|
+
*/
|
|
121
|
+
function startIssue(block, field) {
|
|
122
|
+
const [low, high] = START_RANGES[field];
|
|
123
|
+
const value = block[field] === undefined ? null : numberOr(block, field);
|
|
124
|
+
const min = numberOr(block, low);
|
|
125
|
+
const max = numberOr(block, high);
|
|
126
|
+
if (!block.follow || value === null || min === null || max === null || min > max)
|
|
127
|
+
return null;
|
|
128
|
+
if (value >= min && value <= max)
|
|
129
|
+
return null;
|
|
130
|
+
return {
|
|
131
|
+
field: `camera.${field}`,
|
|
132
|
+
message: `camera.${field} (${value}) is outside [camera.${low} (${min}), camera.${high} (${max})]; the camera starts at ${Math.min(Math.max(value, min), max)}.`,
|
|
133
|
+
severity: 'warning',
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Every orbit field of a perspective block outside its range: the numbers
|
|
138
|
+
* (distances above 0, pitches in (-89, 89), speeds at least 0), `invertY`,
|
|
139
|
+
* the Action names, and a `minDistance` or `minPitch` above its maximum
|
|
140
|
+
* (errors); and a following camera's initial `distance` or `pitch` outside
|
|
141
|
+
* its own range (warnings). Whether `follow` names an entity of the scene is
|
|
142
|
+
* the scene validator's.
|
|
143
|
+
*/
|
|
144
|
+
export function orbitCameraIssues(block) {
|
|
145
|
+
const issues = [];
|
|
146
|
+
fieldIssues(block, issues);
|
|
147
|
+
rangeIssue(block, ['minDistance', 'maxDistance'], issues);
|
|
148
|
+
rangeIssue(block, ['minPitch', 'maxPitch'], issues);
|
|
149
|
+
for (const field of ['distance', 'pitch']) {
|
|
150
|
+
const start = startIssue(block, field);
|
|
151
|
+
if (start)
|
|
152
|
+
issues.push(start);
|
|
153
|
+
}
|
|
154
|
+
return issues;
|
|
155
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the scene validators share (the camera, orbit, render and simulation
|
|
3
|
+
* blocks): the issue they report and how a message quotes a JSON value. A
|
|
4
|
+
* leaf module, so the orbit fields can use them without importing the
|
|
5
|
+
* perspective camera that imports them.
|
|
6
|
+
*/
|
|
7
|
+
/** One field of a scene's render, camera or entity transform that is invalid, for validate_project: an error unless it is a `'warning'`. */
|
|
8
|
+
export interface SceneFieldIssue {
|
|
9
|
+
field: string;
|
|
10
|
+
message: string;
|
|
11
|
+
/** A field the runtime accepts as written but adjusts (a clamp): reported as a warning. */
|
|
12
|
+
severity?: 'warning';
|
|
13
|
+
}
|
|
14
|
+
/** A JSON value as a message quotes it: strings in quotes, everything else as `String()` shows it. */
|
|
15
|
+
export declare const shown: (value: unknown) => string;
|
|
16
|
+
export declare function isFiniteNumber(value: unknown): value is number;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* What the scene validators share (the camera, orbit, render and simulation
|
|
3
|
+
* blocks): the issue they report and how a message quotes a JSON value. A
|
|
4
|
+
* leaf module, so the orbit fields can use them without importing the
|
|
5
|
+
* perspective camera that imports them.
|
|
6
|
+
*/
|
|
7
|
+
/** A JSON value as a message quotes it: strings in quotes, everything else as `String()` shows it. */
|
|
8
|
+
export const shown = (value) => (typeof value === 'string' ? JSON.stringify(value) : String(value));
|
|
9
|
+
export function isFiniteNumber(value) {
|
|
10
|
+
return typeof value === 'number' && Number.isFinite(value);
|
|
11
|
+
}
|
package/dist/scene.js
CHANGED
|
@@ -133,14 +133,16 @@ export function loadScene(game, scene, registry) {
|
|
|
133
133
|
game.registry = registry;
|
|
134
134
|
game.setSceneRender(scene.render);
|
|
135
135
|
game.setSceneSimulation(scene.simulation);
|
|
136
|
-
// A perspective camera
|
|
136
|
+
// A perspective camera is adopted first, so components see it from their first onReady.
|
|
137
137
|
const adoptsBeforeSpawns = game.space === '3d';
|
|
138
138
|
if (adoptsBeforeSpawns)
|
|
139
139
|
game.setSceneCamera(scene.camera);
|
|
140
140
|
for (const entityJson of scene.entities)
|
|
141
141
|
spawnFromJson(game, entityJson, registry);
|
|
142
|
-
// After the spawns
|
|
143
|
-
if (
|
|
142
|
+
// After the spawns, with a follow target: the orthographic camera starts centered on it, an orbit camera on its orbit.
|
|
143
|
+
if (adoptsBeforeSpawns)
|
|
144
|
+
game.placeSceneCamera();
|
|
145
|
+
else
|
|
144
146
|
game.setSceneCamera(scene.camera);
|
|
145
147
|
if (registry.ui)
|
|
146
148
|
game.ui.defineAll(registry.ui);
|
|
@@ -3,7 +3,7 @@ import type { Entity } from './entity.js';
|
|
|
3
3
|
import type { Game } from './game.js';
|
|
4
4
|
import { type Point3d, type QueryVolume3d } from './physics-3d/world-queries.js';
|
|
5
5
|
import type { ComponentClasses, NearestSpatialQueryFilter, SpatialQueryFilter } from './spatial-query.js';
|
|
6
|
-
export type { Point3d, QueryVolume3d } from './physics-3d/world-queries.js';
|
|
6
|
+
export type { Point3d, QueryColliders3d, QueryVolume3d } from './physics-3d/world-queries.js';
|
|
7
7
|
/** The first solid collider a 3D ray reached, with its entity and the crossing (issue #159 CA-21). */
|
|
8
8
|
export interface RayHit3d<T extends Entity = Entity> {
|
|
9
9
|
readonly entity: T;
|
|
@@ -18,7 +18,7 @@ type NearestFilter = NearestSpatialQueryFilter<ComponentClasses> | undefined;
|
|
|
18
18
|
export interface EntityRules {
|
|
19
19
|
matchesCommon(entity: Entity, filter: NearestFilter): boolean;
|
|
20
20
|
}
|
|
21
|
-
/** The 3D forms of `game.query` (ADR 0015's domains kept): `ray` reads solids, `area` and `point` read sensors, `nearest` reads transforms. */
|
|
21
|
+
/** The 3D forms of `game.query` (ADR 0015's domains kept): `ray` reads solids, `area` and `point` read sensors (`area` every collider on request), `nearest` reads transforms. */
|
|
22
22
|
export declare class SpatialQuery3d {
|
|
23
23
|
private readonly game;
|
|
24
24
|
private readonly rules;
|
package/dist/spatial-query-3d.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
import { recordsAtPoint, recordsInVolume, worldRayHits } from './physics-3d/world-queries.js';
|
|
2
2
|
const isPoint3d = (value) => typeof value === 'object' && value !== null && ['x', 'y', 'z'].every((axis) => Number.isFinite(Reflect.get(value, axis)));
|
|
3
3
|
const isExtent = (value) => typeof value === 'number' && Number.isFinite(value) && value > 0;
|
|
4
|
+
/** An absent or known collider domain, and an absent or finite box yaw: the optional parts of a volume. */
|
|
5
|
+
function hasValidOptions(value) {
|
|
6
|
+
const colliders = Reflect.get(value, 'colliders');
|
|
7
|
+
const yaw = Reflect.get(value, 'yaw');
|
|
8
|
+
const knownDomain = colliders === undefined || colliders === 'sensors' || colliders === 'all';
|
|
9
|
+
return knownDomain && (yaw === undefined || (typeof yaw === 'number' && Number.isFinite(yaw)));
|
|
10
|
+
}
|
|
4
11
|
/** A finite box or sphere with a place: anything else is a volume Rapier cannot test. */
|
|
5
12
|
function isVolume3d(value) {
|
|
6
|
-
if (typeof value !== 'object' || value === null || !isPoint3d(Reflect.get(value, 'center')))
|
|
13
|
+
if (typeof value !== 'object' || value === null || !isPoint3d(Reflect.get(value, 'center')) || !hasValidOptions(value))
|
|
7
14
|
return false;
|
|
8
15
|
const shape = Reflect.get(value, 'shape');
|
|
9
16
|
if (shape === 'sphere')
|
|
@@ -13,7 +20,7 @@ function isVolume3d(value) {
|
|
|
13
20
|
}
|
|
14
21
|
/** The farthest a nearest query looks: its `maxDistance`, or no limit. */
|
|
15
22
|
const rangeOf = (filter) => filter?.maxDistance ?? Infinity;
|
|
16
|
-
/** The 3D forms of `game.query` (ADR 0015's domains kept): `ray` reads solids, `area` and `point` read sensors, `nearest` reads transforms. */
|
|
23
|
+
/** The 3D forms of `game.query` (ADR 0015's domains kept): `ray` reads solids, `area` and `point` read sensors (`area` every collider on request), `nearest` reads transforms. */
|
|
17
24
|
export class SpatialQuery3d {
|
|
18
25
|
game;
|
|
19
26
|
rules;
|
|
@@ -40,7 +47,8 @@ export class SpatialQuery3d {
|
|
|
40
47
|
const world = this.worldNow();
|
|
41
48
|
if (!world || !isVolume3d(volume))
|
|
42
49
|
return [];
|
|
43
|
-
|
|
50
|
+
const domain = volume.colliders === 'all' ? 'all' : true;
|
|
51
|
+
return this.inEntityOrder(recordsInVolume(world, volume, (record) => this.eligible(record, filter, domain)), filter);
|
|
44
52
|
}
|
|
45
53
|
point(at, filter) {
|
|
46
54
|
const world = this.worldNow();
|
|
@@ -82,7 +90,8 @@ export class SpatialQuery3d {
|
|
|
82
90
|
* Rapier's callback, so it reads the record and nothing else.
|
|
83
91
|
*/
|
|
84
92
|
eligible(record, filter, sensors) {
|
|
85
|
-
|
|
93
|
+
const inDomain = sensors === 'all' || record.sensor === sensors;
|
|
94
|
+
return inDomain && record.entity.alive && this.rules.matchesCommon(record.entity, filter);
|
|
86
95
|
}
|
|
87
96
|
/** The user's `where`, run after Rapier has answered: it may throw, spawn or destroy without the world mid-query. */
|
|
88
97
|
wanted(record, filter) {
|