@vulfram/engine 0.17.1-alpha → 0.19.3-alpha
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 +3 -3
- package/package.json +10 -5
- package/src/engine/api.ts +12 -25
- package/src/engine/bridge/dispatch.ts +3 -8
- package/src/engine/ecs/components.ts +340 -0
- package/src/engine/ecs/index.ts +3 -661
- package/src/engine/ecs/intents.ts +184 -0
- package/src/engine/ecs/systems.ts +26 -0
- package/src/engine/intents/store.ts +72 -0
- package/src/engine/state.ts +3 -3
- package/src/engine/systems/command-intent.ts +11 -16
- package/src/engine/systems/diagnostics.ts +3 -13
- package/src/engine/systems/input-mirror.ts +156 -18
- package/src/engine/systems/resource-upload.ts +12 -14
- package/src/engine/systems/response-decode.ts +17 -0
- package/src/engine/systems/scene-sync.ts +12 -13
- package/src/engine/systems/ui-bridge.ts +31 -10
- package/src/engine/systems/utils.ts +46 -3
- package/src/engine/systems/world-lifecycle.ts +9 -15
- package/src/engine/world/entities.ts +201 -37
- package/src/engine/world/mount.ts +27 -6
- package/src/engine/world/world-ui.ts +77 -30
- package/src/engine/world/world3d.ts +282 -33
- package/src/helpers/collision.ts +487 -0
- package/src/helpers/index.ts +2 -0
- package/src/helpers/raycast.ts +442 -0
- package/src/types/cmds/geometry.ts +2 -2
- package/src/types/cmds/index.ts +42 -0
- package/src/types/cmds/input.ts +39 -0
- package/src/types/cmds/material.ts +10 -10
- package/src/types/cmds/realm.ts +0 -2
- package/src/types/cmds/system.ts +10 -0
- package/src/types/cmds/target.ts +14 -0
- package/src/types/events/keyboard.ts +2 -2
- package/src/types/events/pointer.ts +43 -0
- package/src/types/events/system.ts +44 -0
|
@@ -0,0 +1,487 @@
|
|
|
1
|
+
import { mat4, vec3, vec4 } from 'gl-matrix';
|
|
2
|
+
import { getWorldOrThrow, requireInitialized } from '../engine/bridge/guards';
|
|
3
|
+
import { getResolvedEntityTransformMatrix } from '../engine/systems/utils';
|
|
4
|
+
import {
|
|
5
|
+
create3DEntity,
|
|
6
|
+
create3DTag,
|
|
7
|
+
draw3DGizmoAabb,
|
|
8
|
+
draw3DGizmoLine,
|
|
9
|
+
remove3DEntity,
|
|
10
|
+
set3DParent,
|
|
11
|
+
update3DTransform,
|
|
12
|
+
} from '../engine/world/world3d';
|
|
13
|
+
import type { EntityId, World3DId } from '../engine/world/types';
|
|
14
|
+
import {
|
|
15
|
+
createPointerRayFromEvent,
|
|
16
|
+
intersectRayAabb,
|
|
17
|
+
type PointerEventRaycastInput,
|
|
18
|
+
type Ray3,
|
|
19
|
+
type RayHit,
|
|
20
|
+
} from './raycast';
|
|
21
|
+
|
|
22
|
+
export interface CollisionRelativeTransform {
|
|
23
|
+
position?: [number, number, number];
|
|
24
|
+
rotation?: [number, number, number, number];
|
|
25
|
+
scale?: [number, number, number];
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface CollisionEntityBase {
|
|
29
|
+
kind: 'aabb' | 'sphere' | 'plane';
|
|
30
|
+
worldId: World3DId;
|
|
31
|
+
entityId: EntityId;
|
|
32
|
+
modelEntityId: EntityId;
|
|
33
|
+
relativeTransform: Required<CollisionRelativeTransform>;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface CollisionAabbEntity extends CollisionEntityBase {
|
|
37
|
+
kind: 'aabb';
|
|
38
|
+
halfExtents: [number, number, number];
|
|
39
|
+
debugGizmoAabb: boolean;
|
|
40
|
+
debugGizmoColor: [number, number, number, number];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface CollisionAabbWorldBounds {
|
|
44
|
+
min: [number, number, number];
|
|
45
|
+
max: [number, number, number];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
type CollisionAabbWorldCorners = [
|
|
49
|
+
[number, number, number],
|
|
50
|
+
[number, number, number],
|
|
51
|
+
[number, number, number],
|
|
52
|
+
[number, number, number],
|
|
53
|
+
[number, number, number],
|
|
54
|
+
[number, number, number],
|
|
55
|
+
[number, number, number],
|
|
56
|
+
[number, number, number],
|
|
57
|
+
];
|
|
58
|
+
|
|
59
|
+
export interface CollisionSphereEntity extends CollisionEntityBase {
|
|
60
|
+
kind: 'sphere';
|
|
61
|
+
radius: number;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface CollisionPlaneEntity extends CollisionEntityBase {
|
|
65
|
+
kind: 'plane';
|
|
66
|
+
normal: [number, number, number];
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type CollisionEntity =
|
|
70
|
+
| CollisionAabbEntity
|
|
71
|
+
| CollisionSphereEntity
|
|
72
|
+
| CollisionPlaneEntity;
|
|
73
|
+
|
|
74
|
+
export interface CollisionAttachBaseArgs {
|
|
75
|
+
worldId: World3DId;
|
|
76
|
+
modelEntityId: EntityId;
|
|
77
|
+
relativeTransform?: CollisionRelativeTransform;
|
|
78
|
+
name?: string;
|
|
79
|
+
labels?: string[];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export interface AttachCollisionAabbArgs extends CollisionAttachBaseArgs {
|
|
83
|
+
halfExtents: [number, number, number];
|
|
84
|
+
debugGizmoAabb?: boolean;
|
|
85
|
+
debugGizmoColor?: [number, number, number, number];
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export interface AttachCollisionSphereArgs extends CollisionAttachBaseArgs {
|
|
89
|
+
radius: number;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
export interface AttachCollisionPlaneArgs extends CollisionAttachBaseArgs {
|
|
93
|
+
normal?: [number, number, number];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function normalizeTransform(
|
|
97
|
+
transform?: CollisionRelativeTransform,
|
|
98
|
+
): Required<CollisionRelativeTransform> {
|
|
99
|
+
return {
|
|
100
|
+
position: transform?.position ?? [0, 0, 0],
|
|
101
|
+
rotation: transform?.rotation ?? [0, 0, 0, 1],
|
|
102
|
+
scale: transform?.scale ?? [1, 1, 1],
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function buildLabels(
|
|
107
|
+
kind: CollisionEntity['kind'],
|
|
108
|
+
labels?: string[],
|
|
109
|
+
): string[] {
|
|
110
|
+
const base = [`collision`, `collision:${kind}`];
|
|
111
|
+
if (!labels || labels.length === 0) {
|
|
112
|
+
return base;
|
|
113
|
+
}
|
|
114
|
+
return [...base, ...labels];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function attachCollisionEntityBase(
|
|
118
|
+
kind: CollisionEntity['kind'],
|
|
119
|
+
args: CollisionAttachBaseArgs,
|
|
120
|
+
): Pick<CollisionEntityBase, 'entityId' | 'relativeTransform'> {
|
|
121
|
+
const entityId = create3DEntity(args.worldId);
|
|
122
|
+
const relativeTransform = normalizeTransform(args.relativeTransform);
|
|
123
|
+
|
|
124
|
+
set3DParent(args.worldId, entityId, args.modelEntityId);
|
|
125
|
+
update3DTransform(args.worldId, entityId, relativeTransform);
|
|
126
|
+
create3DTag(args.worldId, entityId, {
|
|
127
|
+
name: args.name ?? `collision:${kind}`,
|
|
128
|
+
labels: buildLabels(kind, args.labels),
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
return { entityId, relativeTransform };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Creates an AABB collider entity parented to a model entity.
|
|
136
|
+
*/
|
|
137
|
+
export function attachCollisionAabb(
|
|
138
|
+
args: AttachCollisionAabbArgs,
|
|
139
|
+
): CollisionAabbEntity {
|
|
140
|
+
const { entityId, relativeTransform } = attachCollisionEntityBase(
|
|
141
|
+
'aabb',
|
|
142
|
+
args,
|
|
143
|
+
);
|
|
144
|
+
return {
|
|
145
|
+
kind: 'aabb',
|
|
146
|
+
worldId: args.worldId,
|
|
147
|
+
entityId,
|
|
148
|
+
modelEntityId: args.modelEntityId,
|
|
149
|
+
relativeTransform,
|
|
150
|
+
halfExtents: args.halfExtents,
|
|
151
|
+
debugGizmoAabb: args.debugGizmoAabb ?? false,
|
|
152
|
+
debugGizmoColor: args.debugGizmoColor ?? [0.2, 0.9, 0.2, 1],
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Emits one frame of debug AABB gizmo for the collision entity.
|
|
158
|
+
*
|
|
159
|
+
* Note:
|
|
160
|
+
* This uses the collision relative transform center and half extents.
|
|
161
|
+
* If parent rotation is significant, this stays axis-aligned.
|
|
162
|
+
*/
|
|
163
|
+
export function drawCollisionAabbGizmo(collision: CollisionAabbEntity): void {
|
|
164
|
+
if (!collision.debugGizmoAabb) return;
|
|
165
|
+
const corners = getCollisionAabbWorldCorners(collision);
|
|
166
|
+
const edges: [number, number][] = [
|
|
167
|
+
[0, 1],
|
|
168
|
+
[1, 3],
|
|
169
|
+
[3, 2],
|
|
170
|
+
[2, 0],
|
|
171
|
+
[4, 5],
|
|
172
|
+
[5, 7],
|
|
173
|
+
[7, 6],
|
|
174
|
+
[6, 4],
|
|
175
|
+
[0, 4],
|
|
176
|
+
[1, 5],
|
|
177
|
+
[2, 6],
|
|
178
|
+
[3, 7],
|
|
179
|
+
];
|
|
180
|
+
for (let i = 0; i < edges.length; i++) {
|
|
181
|
+
const [a, b] = edges[i]!;
|
|
182
|
+
draw3DGizmoLine(collision.worldId, {
|
|
183
|
+
start: corners[a]!,
|
|
184
|
+
end: corners[b]!,
|
|
185
|
+
color: collision.debugGizmoColor,
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Computes world-space AABB bounds from the collision entity resolved transform.
|
|
192
|
+
*
|
|
193
|
+
* This includes parent constraints (hierarchy) and runtime transform updates.
|
|
194
|
+
*/
|
|
195
|
+
export function getCollisionAabbWorldBounds(
|
|
196
|
+
collision: CollisionAabbEntity,
|
|
197
|
+
): CollisionAabbWorldBounds {
|
|
198
|
+
const corners = getCollisionAabbWorldCorners(collision);
|
|
199
|
+
let minX = Number.POSITIVE_INFINITY;
|
|
200
|
+
let minY = Number.POSITIVE_INFINITY;
|
|
201
|
+
let minZ = Number.POSITIVE_INFINITY;
|
|
202
|
+
let maxX = Number.NEGATIVE_INFINITY;
|
|
203
|
+
let maxY = Number.NEGATIVE_INFINITY;
|
|
204
|
+
let maxZ = Number.NEGATIVE_INFINITY;
|
|
205
|
+
|
|
206
|
+
for (let i = 0; i < corners.length; i++) {
|
|
207
|
+
const [x, y, z] = corners[i]!;
|
|
208
|
+
if (x < minX) minX = x;
|
|
209
|
+
if (y < minY) minY = y;
|
|
210
|
+
if (z < minZ) minZ = z;
|
|
211
|
+
if (x > maxX) maxX = x;
|
|
212
|
+
if (y > maxY) maxY = y;
|
|
213
|
+
if (z > maxZ) maxZ = z;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
return {
|
|
217
|
+
min: [minX, minY, minZ],
|
|
218
|
+
max: [maxX, maxY, maxZ],
|
|
219
|
+
};
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Returns world transform matrix for the collision entity (with hierarchy solved).
|
|
224
|
+
*/
|
|
225
|
+
export function getCollisionWorldTransformMatrix(
|
|
226
|
+
collision: CollisionEntity,
|
|
227
|
+
): mat4 {
|
|
228
|
+
requireInitialized();
|
|
229
|
+
const world = getWorldOrThrow(collision.worldId as number);
|
|
230
|
+
return getResolvedEntityTransformMatrix(
|
|
231
|
+
world,
|
|
232
|
+
collision.entityId as number,
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Returns all 8 world-space corners for an AABB collider oriented by entity transform.
|
|
238
|
+
*/
|
|
239
|
+
export function getCollisionAabbWorldCorners(
|
|
240
|
+
collision: CollisionAabbEntity,
|
|
241
|
+
): CollisionAabbWorldCorners {
|
|
242
|
+
const worldTransform = getCollisionWorldTransformMatrix(collision);
|
|
243
|
+
const [hx, hy, hz] = collision.halfExtents;
|
|
244
|
+
|
|
245
|
+
const localCorners: [number, number, number][] = [
|
|
246
|
+
[-hx, -hy, -hz],
|
|
247
|
+
[-hx, -hy, hz],
|
|
248
|
+
[-hx, hy, -hz],
|
|
249
|
+
[-hx, hy, hz],
|
|
250
|
+
[hx, -hy, -hz],
|
|
251
|
+
[hx, -hy, hz],
|
|
252
|
+
[hx, hy, -hz],
|
|
253
|
+
[hx, hy, hz],
|
|
254
|
+
];
|
|
255
|
+
|
|
256
|
+
const corners: [number, number, number][] = [];
|
|
257
|
+
for (let i = 0; i < localCorners.length; i++) {
|
|
258
|
+
const [x, y, z] = localCorners[i]!;
|
|
259
|
+
const worldCorner = vec4.transformMat4(
|
|
260
|
+
vec4.create(),
|
|
261
|
+
vec4.fromValues(x, y, z, 1),
|
|
262
|
+
worldTransform as mat4,
|
|
263
|
+
);
|
|
264
|
+
corners.push([worldCorner[0]!, worldCorner[1]!, worldCorner[2]!]);
|
|
265
|
+
}
|
|
266
|
+
return corners as CollisionAabbWorldCorners;
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
/**
|
|
270
|
+
* Raycasts an oriented AABB collider (local AABB transformed by entity world matrix).
|
|
271
|
+
*/
|
|
272
|
+
export function raycastCollisionAabb(
|
|
273
|
+
ray: Ray3,
|
|
274
|
+
collision: CollisionAabbEntity,
|
|
275
|
+
localPadding = 0,
|
|
276
|
+
): RayHit | null {
|
|
277
|
+
const world = getCollisionWorldTransformMatrix(collision);
|
|
278
|
+
const inverse = mat4.invert(mat4.create(), world);
|
|
279
|
+
if (!inverse) return null;
|
|
280
|
+
|
|
281
|
+
const localOrigin4 = vec4.transformMat4(
|
|
282
|
+
vec4.create(),
|
|
283
|
+
vec4.fromValues(ray.origin[0]!, ray.origin[1]!, ray.origin[2]!, 1),
|
|
284
|
+
inverse,
|
|
285
|
+
);
|
|
286
|
+
const localDirection4 = vec4.transformMat4(
|
|
287
|
+
vec4.create(),
|
|
288
|
+
vec4.fromValues(ray.direction[0]!, ray.direction[1]!, ray.direction[2]!, 0),
|
|
289
|
+
inverse,
|
|
290
|
+
);
|
|
291
|
+
const localDirection = vec3.normalize(
|
|
292
|
+
vec3.create(),
|
|
293
|
+
vec3.fromValues(localDirection4[0]!, localDirection4[1]!, localDirection4[2]!),
|
|
294
|
+
);
|
|
295
|
+
const localRay: Ray3 = {
|
|
296
|
+
origin: vec3.fromValues(
|
|
297
|
+
localOrigin4[0]!,
|
|
298
|
+
localOrigin4[1]!,
|
|
299
|
+
localOrigin4[2]!,
|
|
300
|
+
),
|
|
301
|
+
direction: localDirection,
|
|
302
|
+
};
|
|
303
|
+
|
|
304
|
+
const [hx, hy, hz] = collision.halfExtents;
|
|
305
|
+
const px = Math.max(0, localPadding);
|
|
306
|
+
const py = Math.max(0, localPadding);
|
|
307
|
+
const pz = Math.max(0, localPadding);
|
|
308
|
+
const localHit = intersectRayAabb(
|
|
309
|
+
localRay,
|
|
310
|
+
[-hx - px, -hy - py, -hz - pz],
|
|
311
|
+
[hx + px, hy + py, hz + pz],
|
|
312
|
+
);
|
|
313
|
+
if (!localHit) return null;
|
|
314
|
+
|
|
315
|
+
const worldPoint4 = vec4.transformMat4(
|
|
316
|
+
vec4.create(),
|
|
317
|
+
vec4.fromValues(
|
|
318
|
+
localHit.point[0]!,
|
|
319
|
+
localHit.point[1]!,
|
|
320
|
+
localHit.point[2]!,
|
|
321
|
+
1,
|
|
322
|
+
),
|
|
323
|
+
world,
|
|
324
|
+
);
|
|
325
|
+
const worldPoint = vec3.fromValues(
|
|
326
|
+
worldPoint4[0]!,
|
|
327
|
+
worldPoint4[1]!,
|
|
328
|
+
worldPoint4[2]!,
|
|
329
|
+
);
|
|
330
|
+
|
|
331
|
+
return {
|
|
332
|
+
distance: vec3.distance(worldPoint, ray.origin),
|
|
333
|
+
point: worldPoint,
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
export interface PointerCollisionAabbInput extends PointerEventRaycastInput {
|
|
338
|
+
collision: CollisionAabbEntity;
|
|
339
|
+
/**
|
|
340
|
+
* Optional border tolerance in screen pixels (default: 0.5).
|
|
341
|
+
* Helps avoid subpixel edge misses where the rendered cube appears hit,
|
|
342
|
+
* but strict math falls just outside due precision/rasterization boundaries.
|
|
343
|
+
*/
|
|
344
|
+
edgePaddingPixels?: number;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
function resolvePointerViewportSize(
|
|
348
|
+
input: PointerEventRaycastInput,
|
|
349
|
+
): [number, number] | null {
|
|
350
|
+
const event = input.pointerEvent;
|
|
351
|
+
if (
|
|
352
|
+
event.positionTarget &&
|
|
353
|
+
typeof event.targetWidth === 'number' &&
|
|
354
|
+
typeof event.targetHeight === 'number' &&
|
|
355
|
+
event.targetWidth > 0 &&
|
|
356
|
+
event.targetHeight > 0
|
|
357
|
+
) {
|
|
358
|
+
return [event.targetWidth, event.targetHeight];
|
|
359
|
+
}
|
|
360
|
+
if (
|
|
361
|
+
typeof event.windowWidth === 'number' &&
|
|
362
|
+
typeof event.windowHeight === 'number' &&
|
|
363
|
+
event.windowWidth > 0 &&
|
|
364
|
+
event.windowHeight > 0
|
|
365
|
+
) {
|
|
366
|
+
return [event.windowWidth, event.windowHeight];
|
|
367
|
+
}
|
|
368
|
+
if (input.fallbackViewportSize) {
|
|
369
|
+
const w = input.fallbackViewportSize[0] ?? 0;
|
|
370
|
+
const h = input.fallbackViewportSize[1] ?? 0;
|
|
371
|
+
if (w > 0 && h > 0) return [w, h];
|
|
372
|
+
}
|
|
373
|
+
return null;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
function computeLocalEdgePadding(
|
|
377
|
+
input: PointerCollisionAabbInput,
|
|
378
|
+
ray: Ray3,
|
|
379
|
+
): number {
|
|
380
|
+
const viewport = resolvePointerViewportSize(input);
|
|
381
|
+
if (!viewport) return 0;
|
|
382
|
+
const [, viewportHeight] = viewport;
|
|
383
|
+
if (viewportHeight <= 0) return 0;
|
|
384
|
+
|
|
385
|
+
const projection = input.projectionMatrix;
|
|
386
|
+
const focalY = Math.abs(projection[5] ?? 0);
|
|
387
|
+
if (focalY <= 1e-8) return 0;
|
|
388
|
+
|
|
389
|
+
const world = getCollisionWorldTransformMatrix(input.collision);
|
|
390
|
+
const center = vec3.fromValues(world[12]!, world[13]!, world[14]!);
|
|
391
|
+
const depth = vec3.distance(center, ray.origin);
|
|
392
|
+
if (!Number.isFinite(depth) || depth <= 0) return 0;
|
|
393
|
+
|
|
394
|
+
const pixelPadding = Math.max(0, input.edgePaddingPixels ?? 0.5);
|
|
395
|
+
const worldPadding = ((2 * depth) / (focalY * viewportHeight)) * pixelPadding;
|
|
396
|
+
if (!Number.isFinite(worldPadding) || worldPadding <= 0) return 0;
|
|
397
|
+
|
|
398
|
+
const sx = Math.hypot(world[0]!, world[1]!, world[2]!);
|
|
399
|
+
const sy = Math.hypot(world[4]!, world[5]!, world[6]!);
|
|
400
|
+
const sz = Math.hypot(world[8]!, world[9]!, world[10]!);
|
|
401
|
+
const minScale = Math.max(1e-8, Math.min(sx, sy, sz));
|
|
402
|
+
|
|
403
|
+
return worldPadding / minScale;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
/**
|
|
407
|
+
* Raycasts pointer event coordinates against an AABB collision entity.
|
|
408
|
+
*
|
|
409
|
+
* This uses target/window dimensions carried by pointer events to normalize
|
|
410
|
+
* coordinates with the real drawn area.
|
|
411
|
+
*/
|
|
412
|
+
export function raycastPointerCollisionAabb(
|
|
413
|
+
input: PointerCollisionAabbInput,
|
|
414
|
+
): RayHit | null {
|
|
415
|
+
const ray = createPointerRayFromEvent(input);
|
|
416
|
+
if (!ray) return null;
|
|
417
|
+
const localPadding = computeLocalEdgePadding(input, ray);
|
|
418
|
+
return raycastCollisionAabb(ray, input.collision, localPadding);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
/**
|
|
422
|
+
* Fast boolean check for pointer collision against an AABB entity.
|
|
423
|
+
*/
|
|
424
|
+
export function isPointerCollidingAabb(
|
|
425
|
+
input: PointerCollisionAabbInput,
|
|
426
|
+
): boolean {
|
|
427
|
+
return raycastPointerCollisionAabb(input) !== null;
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
/**
|
|
431
|
+
* Creates a sphere collider entity parented to a model entity.
|
|
432
|
+
*/
|
|
433
|
+
export function attachCollisionSphere(
|
|
434
|
+
args: AttachCollisionSphereArgs,
|
|
435
|
+
): CollisionSphereEntity {
|
|
436
|
+
const { entityId, relativeTransform } = attachCollisionEntityBase(
|
|
437
|
+
'sphere',
|
|
438
|
+
args,
|
|
439
|
+
);
|
|
440
|
+
return {
|
|
441
|
+
kind: 'sphere',
|
|
442
|
+
worldId: args.worldId,
|
|
443
|
+
entityId,
|
|
444
|
+
modelEntityId: args.modelEntityId,
|
|
445
|
+
relativeTransform,
|
|
446
|
+
radius: args.radius,
|
|
447
|
+
};
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Creates a plane collider entity parented to a model entity.
|
|
452
|
+
*/
|
|
453
|
+
export function attachCollisionPlane(
|
|
454
|
+
args: AttachCollisionPlaneArgs,
|
|
455
|
+
): CollisionPlaneEntity {
|
|
456
|
+
const { entityId, relativeTransform } = attachCollisionEntityBase(
|
|
457
|
+
'plane',
|
|
458
|
+
args,
|
|
459
|
+
);
|
|
460
|
+
return {
|
|
461
|
+
kind: 'plane',
|
|
462
|
+
worldId: args.worldId,
|
|
463
|
+
entityId,
|
|
464
|
+
modelEntityId: args.modelEntityId,
|
|
465
|
+
relativeTransform,
|
|
466
|
+
normal: args.normal ?? [0, 1, 0],
|
|
467
|
+
};
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
/**
|
|
471
|
+
* Updates only the relative transform of an attached collision entity.
|
|
472
|
+
*/
|
|
473
|
+
export function updateCollisionRelativeTransform(
|
|
474
|
+
collision: CollisionEntity,
|
|
475
|
+
relativeTransform: CollisionRelativeTransform,
|
|
476
|
+
): void {
|
|
477
|
+
const normalized = normalizeTransform(relativeTransform);
|
|
478
|
+
update3DTransform(collision.worldId, collision.entityId, normalized);
|
|
479
|
+
collision.relativeTransform = normalized;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
/**
|
|
483
|
+
* Removes an attached collision entity.
|
|
484
|
+
*/
|
|
485
|
+
export function disposeCollisionEntity(collision: CollisionEntity): void {
|
|
486
|
+
remove3DEntity(collision.worldId, collision.entityId);
|
|
487
|
+
}
|