@adobe/data-gpu 0.9.87 → 0.9.88
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/dist/graphics/rendering/bone-collider-plugin.d.ts +2724 -331
- package/dist/graphics/rendering/interpolation-plugin.d.ts +2532 -138
- package/dist/graphics/rendering/jolt-ragdoll-plugin.d.ts +2398 -5
- package/dist/graphics/rendering/model-collider-plugin.d.ts +2675 -282
- package/dist/graphics/rendering/pbr-render/physics-bridge-plugin.d.ts +2396 -3
- package/dist/physics/physics-data-plugin.d.ts +2398 -2
- package/dist/physics/physics-data-plugin.js +26 -0
- package/dist/physics/physics-data-plugin.js.map +1 -1
- package/dist/physics/physics-hit.d.ts +18 -0
- package/dist/physics/physics-hit.js +3 -0
- package/dist/physics/physics-hit.js.map +1 -0
- package/dist/physics/physics-query.d.ts +43 -0
- package/dist/physics/physics-query.js +3 -0
- package/dist/physics/physics-query.js.map +1 -0
- package/dist/physics/public.d.ts +2 -0
- package/dist/physics/public.js +2 -0
- package/dist/physics/public.js.map +1 -1
- package/dist/physics/solvers/jolt-solver-plugin.d.ts +2394 -4
- package/dist/physics/solvers/jolt-solver-plugin.js +149 -0
- package/dist/physics/solvers/jolt-solver-plugin.js.map +1 -1
- package/dist/physics/solvers/mutable-hit.d.ts +14 -0
- package/dist/physics/solvers/mutable-hit.js +3 -0
- package/dist/physics/solvers/mutable-hit.js.map +1 -0
- package/dist/physics/solvers/orient-normal-against-ray.d.ts +11 -0
- package/dist/physics/solvers/orient-normal-against-ray.js +25 -0
- package/dist/physics/solvers/orient-normal-against-ray.js.map +1 -0
- package/dist/physics/solvers/orient-normal-against-ray.test.d.ts +1 -0
- package/dist/physics/solvers/orient-normal-against-ray.test.js +27 -0
- package/dist/physics/solvers/orient-normal-against-ray.test.js.map +1 -0
- package/dist/physics/solvers/pick-ray-conformance.test.d.ts +1 -0
- package/dist/physics/solvers/pick-ray-conformance.test.js +159 -0
- package/dist/physics/solvers/pick-ray-conformance.test.js.map +1 -0
- package/dist/physics/solvers/rapier-solver-plugin.d.ts +2394 -4
- package/dist/physics/solvers/rapier-solver-plugin.js +110 -1
- package/dist/physics/solvers/rapier-solver-plugin.js.map +1 -1
- package/package.json +2 -2
- package/src/physics/README.md +15 -2
- package/src/physics/physics-data-plugin.ts +29 -1
- package/src/physics/physics-hit.ts +21 -0
- package/src/physics/physics-query.ts +42 -0
- package/src/physics/public.ts +2 -0
- package/src/physics/solvers/jolt-solver-plugin.ts +119 -0
- package/src/physics/solvers/mutable-hit.ts +18 -0
- package/src/physics/solvers/orient-normal-against-ray.test.ts +31 -0
- package/src/physics/solvers/orient-normal-against-ray.ts +18 -0
- package/src/physics/solvers/pick-ray-conformance.test.ts +200 -0
- package/src/physics/solvers/rapier-solver-plugin.ts +92 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
// © 2026 Adobe. MIT License. See /LICENSE for details.
|
|
2
|
+
|
|
3
|
+
import { describe, it, expect, beforeAll } from "vitest";
|
|
4
|
+
import { Database } from "@adobe/data/ecs";
|
|
5
|
+
import type { Line3 } from "@adobe/data/math";
|
|
6
|
+
import { rapierSolver } from "./rapier-solver-plugin.js";
|
|
7
|
+
import { joltSolver } from "./jolt-solver-plugin.js";
|
|
8
|
+
import type { PhysicsHit } from "../physics-hit.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Abstract picking conformance suite: the SAME expected outcomes are asserted
|
|
12
|
+
* against every solver behind the `physicsData` seam. Building a fixed static
|
|
13
|
+
* scene and running identical `pickRay` calls proves both engines fulfil the
|
|
14
|
+
* `PhysicsQuery` contract interchangeably (entity, parametric α, hit point,
|
|
15
|
+
* and a ray-opposing normal), including the with/without-radius variants.
|
|
16
|
+
*
|
|
17
|
+
* npx vitest --run src/physics/solvers/pick-ray-conformance.test.ts
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
// The solver plugin extends `physicsData`, so the StaticCollider archetype, the
|
|
21
|
+
// `frameTime` resource, and the `pickRay` action all exist on the created db.
|
|
22
|
+
// The scene is driven dynamically, so the db is used through a loose shape
|
|
23
|
+
// (runtime invariant: these members exist on any physicsData solver).
|
|
24
|
+
interface LooseDb {
|
|
25
|
+
store: {
|
|
26
|
+
archetypes: Record<string, { insert(v: unknown): number }>;
|
|
27
|
+
resources: Record<string, unknown>;
|
|
28
|
+
};
|
|
29
|
+
system: { functions: Record<string, () => unknown>; order?: string[][] };
|
|
30
|
+
actions: {
|
|
31
|
+
pickRay(args: { ray: Line3; radius?: number }): PhysicsHit | null;
|
|
32
|
+
pickRayEach(args: { ray: Line3; visit: (hit: PhysicsHit) => boolean | void; radius?: number }): void;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const yieldToEventLoop = () => new Promise<void>(r => setTimeout(r, 0));
|
|
37
|
+
|
|
38
|
+
interface Scene { db: LooseDb; floor: number; ball: number; box: number }
|
|
39
|
+
|
|
40
|
+
// Build a fixed scene of static colliders and drive the sim until the solver's
|
|
41
|
+
// WASM world is live, the bodies are mirrored, and the query pipeline is
|
|
42
|
+
// populated (both engines need at least one step for that). Picks are read-only,
|
|
43
|
+
// so one scene is shared across all cases for a solver.
|
|
44
|
+
async function buildScene(solver: Database.Plugin): Promise<Scene> {
|
|
45
|
+
const db = Database.create(solver) as unknown as LooseDb;
|
|
46
|
+
|
|
47
|
+
// Floor: wide thick box, top face at y = 0.
|
|
48
|
+
const floor = db.store.archetypes.StaticCollider.insert({
|
|
49
|
+
colliderShape: "box", halfExtents: [10, 1, 10], material: 0,
|
|
50
|
+
position: [0, -1, 0], rotation: [0, 0, 0, 1],
|
|
51
|
+
});
|
|
52
|
+
// Sphere radius 1 centred at (0, 3, 0) → top at y = 4.
|
|
53
|
+
const ball = db.store.archetypes.StaticCollider.insert({
|
|
54
|
+
colliderShape: "sphere", halfExtents: [1, 0, 0], material: 0,
|
|
55
|
+
position: [0, 3, 0], rotation: [0, 0, 0, 1],
|
|
56
|
+
});
|
|
57
|
+
// Unit box centred at (5, 3, 0) → top at y = 4, off to the side.
|
|
58
|
+
const box = db.store.archetypes.StaticCollider.insert({
|
|
59
|
+
colliderShape: "box", halfExtents: [1, 1, 1], material: 0,
|
|
60
|
+
position: [5, 3, 0], rotation: [0, 0, 0, 1],
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
const order = db.system.order;
|
|
64
|
+
const names = order ? order.flat() : Object.keys(db.system.functions);
|
|
65
|
+
const runNames = names.filter(n => n !== "_frameTime" && n !== "schedulerSystem");
|
|
66
|
+
const dt = 1 / 60;
|
|
67
|
+
const tick = (f: number): void => {
|
|
68
|
+
db.store.resources.frameTime = { now: f * dt * 1000, dt, elapsed: f * dt };
|
|
69
|
+
for (const n of runNames) { const fn = db.system.functions[n]; if (fn) fn(); }
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// Drive until the solver publishes its query, then a few more steps so the
|
|
73
|
+
// bodies are mirrored and the broad/narrow phase is populated.
|
|
74
|
+
let f = 0;
|
|
75
|
+
for (; f < 300 && db.store.resources.physicsQuery == null; f++) { tick(f); await yieldToEventLoop(); }
|
|
76
|
+
for (let k = 0; k < 30; k++, f++) { tick(f); await yieldToEventLoop(); }
|
|
77
|
+
|
|
78
|
+
return { db, floor, ball, box };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function describePicking(name: string, solver: Database.Plugin): void {
|
|
82
|
+
describe(`pickRay conformance — ${name}`, () => {
|
|
83
|
+
let scene: Scene;
|
|
84
|
+
beforeAll(async () => { scene = await buildScene(solver); }, 60_000);
|
|
85
|
+
|
|
86
|
+
it("publishes a physicsQuery once the world is live", () => {
|
|
87
|
+
expect((scene.db.store.resources as { physicsQuery: unknown }).physicsQuery).not.toBeNull();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("hits the sphere with a ray straight down through its centre", () => {
|
|
91
|
+
// segment y: 10 → −10 (span 20); sphere top at y = 4 ⇒ α = (10 − 4) / 20 = 0.3
|
|
92
|
+
const ray: Line3 = { a: [0, 10, 0], b: [0, -10, 0] };
|
|
93
|
+
const hit = scene.db.actions.pickRay({ ray });
|
|
94
|
+
expect(hit).not.toBeNull();
|
|
95
|
+
expect(hit!.entity).toBe(scene.ball);
|
|
96
|
+
expect(hit!.distance).toBeCloseTo(0.3, 1);
|
|
97
|
+
expect(hit!.point[1]).toBeCloseTo(4, 1);
|
|
98
|
+
// ray points down, so the surface normal points up (opposes the ray)
|
|
99
|
+
expect(hit!.normal[1]).toBeGreaterThan(0.5);
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
it("hits the box off to the side", () => {
|
|
103
|
+
const ray: Line3 = { a: [5, 10, 0], b: [5, -10, 0] };
|
|
104
|
+
const hit = scene.db.actions.pickRay({ ray });
|
|
105
|
+
expect(hit).not.toBeNull();
|
|
106
|
+
expect(hit!.entity).toBe(scene.box);
|
|
107
|
+
expect(hit!.point[1]).toBeCloseTo(4, 1); // box top face
|
|
108
|
+
expect(hit!.normal[1]).toBeGreaterThan(0.5);
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("returns the nearest collider when several lie along the ray", () => {
|
|
112
|
+
// straight down through the centre passes the sphere (top y = 4) before
|
|
113
|
+
// the floor (top y = 0) → the nearer sphere wins
|
|
114
|
+
const hit = scene.db.actions.pickRay({ ray: { a: [0, 10, 0], b: [0, -10, 0] } });
|
|
115
|
+
expect(hit!.entity).toBe(scene.ball);
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
it("returns null when the ray misses every collider", () => {
|
|
119
|
+
const ray: Line3 = { a: [100, 10, 100], b: [100, -10, 100] };
|
|
120
|
+
expect(scene.db.actions.pickRay({ ray })).toBeNull();
|
|
121
|
+
});
|
|
122
|
+
|
|
123
|
+
it("with/without radius: a grazing ray misses as a line but hits as a thick ray", () => {
|
|
124
|
+
// Short segment offset 1.4 in x from the sphere centre, kept above the
|
|
125
|
+
// floor (y: 5 → 2.5). As a thin line it clears the sphere (1.4 > radius 1)
|
|
126
|
+
// and never reaches the floor → miss. A 0.5-radius sweep has effective
|
|
127
|
+
// reach 1.5 > 1.4 → it grazes the sphere.
|
|
128
|
+
const ray: Line3 = { a: [1.4, 5, 0], b: [1.4, 2.5, 0] };
|
|
129
|
+
expect(scene.db.actions.pickRay({ ray })).toBeNull();
|
|
130
|
+
|
|
131
|
+
const hit = scene.db.actions.pickRay({ ray, radius: 0.5 });
|
|
132
|
+
expect(hit).not.toBeNull();
|
|
133
|
+
expect(hit!.entity).toBe(scene.ball);
|
|
134
|
+
expect(hit!.distance).toBeGreaterThanOrEqual(0);
|
|
135
|
+
expect(hit!.distance).toBeLessThanOrEqual(1);
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it("castRayEach visits every collider along the ray, nearest first", () => {
|
|
139
|
+
// straight down at x = 0 crosses the sphere (top y = 4, α = 0.3) then the
|
|
140
|
+
// floor (top y = 0, α = 0.5); the box at x = 5 is not on the ray
|
|
141
|
+
const seen: number[] = [];
|
|
142
|
+
const dists: number[] = [];
|
|
143
|
+
scene.db.actions.pickRayEach({
|
|
144
|
+
ray: { a: [0, 10, 0], b: [0, -10, 0] },
|
|
145
|
+
visit: (h) => { seen.push(h.entity); dists.push(h.distance); },
|
|
146
|
+
});
|
|
147
|
+
expect(seen).toEqual([scene.ball, scene.floor]);
|
|
148
|
+
expect(dists).toEqual([...dists].sort((x, y) => x - y)); // ascending
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it("castRayEach stops early when visit returns false", () => {
|
|
152
|
+
const seen: number[] = [];
|
|
153
|
+
scene.db.actions.pickRayEach({
|
|
154
|
+
ray: { a: [0, 10, 0], b: [0, -10, 0] },
|
|
155
|
+
visit: (h) => { seen.push(h.entity); return false; }, // stop after the nearest
|
|
156
|
+
});
|
|
157
|
+
expect(seen).toEqual([scene.ball]);
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it("castRayEach visits nothing when the ray misses everything", () => {
|
|
161
|
+
let count = 0;
|
|
162
|
+
scene.db.actions.pickRayEach({
|
|
163
|
+
ray: { a: [100, 10, 100], b: [100, -10, 100] },
|
|
164
|
+
visit: () => { count++; },
|
|
165
|
+
});
|
|
166
|
+
expect(count).toBe(0);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("castRayEach with radius visits at least the nearest swept hit on every solver", () => {
|
|
170
|
+
// The Rapier compat binding can't sweep-all, so radius degrades to the
|
|
171
|
+
// nearest there; Jolt returns all. Both must at least visit the nearest.
|
|
172
|
+
const seen: number[] = [];
|
|
173
|
+
scene.db.actions.pickRayEach({
|
|
174
|
+
ray: { a: [1.4, 5, 0], b: [1.4, 2.5, 0] },
|
|
175
|
+
radius: 0.5,
|
|
176
|
+
visit: (h) => { seen.push(h.entity); },
|
|
177
|
+
});
|
|
178
|
+
expect(seen.length).toBeGreaterThanOrEqual(1);
|
|
179
|
+
expect(seen[0]).toBe(scene.ball);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it("castRayEach reuses a single hit instance across visits (zero-alloc contract)", () => {
|
|
183
|
+
const refs: PhysicsHit[] = [];
|
|
184
|
+
scene.db.actions.pickRayEach({ ray: { a: [0, 10, 0], b: [0, -10, 0] }, visit: (h) => { refs.push(h); } });
|
|
185
|
+
expect(refs.length).toBe(2);
|
|
186
|
+
expect(refs[0]).toBe(refs[1]); // same object, refilled per visit — do not retain
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("castRay returns fresh, independent hits (safe to retain)", () => {
|
|
190
|
+
const h1 = scene.db.actions.pickRay({ ray: { a: [0, 10, 0], b: [0, -10, 0] } });
|
|
191
|
+
const h2 = scene.db.actions.pickRay({ ray: { a: [5, 10, 0], b: [5, -10, 0] } });
|
|
192
|
+
expect(h1).not.toBe(h2);
|
|
193
|
+
expect(h1!.entity).toBe(scene.ball); // unaffected by the second pick
|
|
194
|
+
expect(h2!.entity).toBe(scene.box);
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
describePicking("rapierSolver", rapierSolver);
|
|
200
|
+
describePicking("joltSolver", joltSolver);
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import RAPIER from "@dimforge/rapier3d-compat";
|
|
4
4
|
import { Database, type Entity } from "@adobe/data/ecs";
|
|
5
|
+
import type { Line3, Vec3 } from "@adobe/data/math";
|
|
5
6
|
import { isVoxelShapePhysicsPending } from "../is-voxel-shape-physics-pending.js";
|
|
6
7
|
import { True } from "@adobe/data/schema";
|
|
7
8
|
import { physicsClock } from "../physics-clock-plugin.js";
|
|
@@ -9,6 +10,10 @@ import { physicsData } from "../physics-data-plugin.js";
|
|
|
9
10
|
import { jointData } from "../joint/joint-plugin.js";
|
|
10
11
|
import { BodyType } from "../body/body-type/body-type.js";
|
|
11
12
|
import { ColliderShape } from "../body/collider-shape/collider-shape.js";
|
|
13
|
+
import type { PhysicsQuery } from "../physics-query.js";
|
|
14
|
+
import type { PhysicsHit } from "../physics-hit.js";
|
|
15
|
+
import { orientNormalAgainstRay } from "./orient-normal-against-ray.js";
|
|
16
|
+
import { newHit, type MutableHit } from "./mutable-hit.js";
|
|
12
17
|
|
|
13
18
|
/**
|
|
14
19
|
* A second rigid-body solver behind the same `physicsData` seam — the
|
|
@@ -84,6 +89,7 @@ export const rapierSolver = Database.Plugin.create({
|
|
|
84
89
|
desc.setTranslation(px, py, pz).setRotation({ x: q[0], y: q[1], z: q[2], w: q[3] });
|
|
85
90
|
if (motion === "dynamic") { desc.setLinvel(vx, vy, vz); desc.setAngvel({ x: wx, y: wy, z: wz }); }
|
|
86
91
|
const body = world.createRigidBody(desc);
|
|
92
|
+
body.userData = id; // reverse lookup for ray-pick: collider → parent body → entity
|
|
87
93
|
// capsule: Y-aligned, halfHeight = cylinder half (hy), radius = hx.
|
|
88
94
|
// hull: convex hull of the authored point cloud (read once here).
|
|
89
95
|
let col: RAPIER.ColliderDesc | null;
|
|
@@ -107,11 +113,96 @@ export const rapierSolver = Database.Plugin.create({
|
|
|
107
113
|
bodies.set(id, body);
|
|
108
114
|
};
|
|
109
115
|
|
|
116
|
+
// Solver-agnostic ray-pick over the live Rapier world. Passing the
|
|
117
|
+
// segment vector (b − a) as the ray/shape-cast direction and capping
|
|
118
|
+
// the time-of-impact at 1 makes the returned toi the parametric α ∈
|
|
119
|
+
// [0,1] along the segment directly (matching PhysicsHit's convention).
|
|
120
|
+
const makeQuery = (w: RAPIER.World): PhysicsQuery => {
|
|
121
|
+
// Fill `out` with a thin-ray hit. userData was set to the entity id
|
|
122
|
+
// when the body was mirrored. Normal is oriented to oppose the ray.
|
|
123
|
+
const fillRay = (out: MutableHit, entity: Entity, alpha: number, nx: number, ny: number, nz: number, a: Vec3, dx: number, dy: number, dz: number): void => {
|
|
124
|
+
out.entity = entity; out.distance = alpha;
|
|
125
|
+
out.point[0] = a[0] + dx * alpha; out.point[1] = a[1] + dy * alpha; out.point[2] = a[2] + dz * alpha;
|
|
126
|
+
out.normal[0] = nx; out.normal[1] = ny; out.normal[2] = nz;
|
|
127
|
+
orientNormalAgainstRay(out.normal, dx, dy, dz);
|
|
128
|
+
};
|
|
129
|
+
const nearest = (ray: Line3, options?: { radius?: number }): PhysicsHit | null => {
|
|
130
|
+
const a = ray.a, b = ray.b;
|
|
131
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
|
|
132
|
+
const radius = options?.radius ?? 0;
|
|
133
|
+
const out = newHit();
|
|
134
|
+
if (radius > 0) {
|
|
135
|
+
// sphere of `radius` swept along the segment
|
|
136
|
+
const hit = w.castShape(
|
|
137
|
+
{ x: a[0], y: a[1], z: a[2] }, { x: 0, y: 0, z: 0, w: 1 }, { x: dx, y: dy, z: dz },
|
|
138
|
+
new RAPIER.Ball(radius), 0, 1, true,
|
|
139
|
+
);
|
|
140
|
+
if (!hit) return null;
|
|
141
|
+
const bod = hit.collider.parent();
|
|
142
|
+
if (!bod) return null;
|
|
143
|
+
const alpha = hit.time_of_impact;
|
|
144
|
+
out.entity = bod.userData as Entity; out.distance = alpha;
|
|
145
|
+
out.normal[0] = hit.normal1.x; out.normal[1] = hit.normal1.y; out.normal[2] = hit.normal1.z;
|
|
146
|
+
orientNormalAgainstRay(out.normal, dx, dy, dz);
|
|
147
|
+
// pull the swept-sphere centre at impact back onto the collider surface
|
|
148
|
+
out.point[0] = a[0] + dx * alpha - out.normal[0] * radius;
|
|
149
|
+
out.point[1] = a[1] + dy * alpha - out.normal[1] * radius;
|
|
150
|
+
out.point[2] = a[2] + dz * alpha - out.normal[2] * radius;
|
|
151
|
+
return out;
|
|
152
|
+
}
|
|
153
|
+
const hit = w.castRayAndGetNormal(new RAPIER.Ray({ x: a[0], y: a[1], z: a[2] }, { x: dx, y: dy, z: dz }), 1, true);
|
|
154
|
+
if (!hit) return null;
|
|
155
|
+
const bod = hit.collider.parent();
|
|
156
|
+
if (!bod) return null;
|
|
157
|
+
fillRay(out, bod.userData as Entity, hit.timeOfImpact, hit.normal.x, hit.normal.y, hit.normal.z, a, dx, dy, dz);
|
|
158
|
+
return out;
|
|
159
|
+
};
|
|
160
|
+
// Zero-alloc scratch for castRayEach: one reused hit + a grow-only pool
|
|
161
|
+
// of records. intersectionsWithRay reports crossings in broadphase-
|
|
162
|
+
// arbitrary order, so records are collected then index-sorted near→far.
|
|
163
|
+
const scratch = newHit();
|
|
164
|
+
const pool: { entity: Entity; distance: number; nx: number; ny: number; nz: number }[] = [];
|
|
165
|
+
const order: number[] = [];
|
|
166
|
+
return {
|
|
167
|
+
castRay: nearest,
|
|
168
|
+
castRayEach(ray: Line3, visit, options): void {
|
|
169
|
+
// The compat binding has no swept-shape all-hits query, so a
|
|
170
|
+
// radius query degrades to the nearest hit (see PhysicsQuery).
|
|
171
|
+
if ((options?.radius ?? 0) > 0) { const h = nearest(ray, options); if (h) visit(h); return; }
|
|
172
|
+
const a = ray.a, b = ray.b;
|
|
173
|
+
const dx = b[0] - a[0], dy = b[1] - a[1], dz = b[2] - a[2];
|
|
174
|
+
let count = 0;
|
|
175
|
+
w.intersectionsWithRay(new RAPIER.Ray({ x: a[0], y: a[1], z: a[2] }, { x: dx, y: dy, z: dz }), 1, true, (isect: RAPIER.RayColliderIntersection) => {
|
|
176
|
+
const bod = isect.collider.parent();
|
|
177
|
+
if (bod) {
|
|
178
|
+
let rec = pool[count];
|
|
179
|
+
if (!rec) { rec = { entity: 0 as Entity, distance: 0, nx: 0, ny: 0, nz: 0 }; pool[count] = rec; }
|
|
180
|
+
rec.entity = bod.userData as Entity; rec.distance = isect.timeOfImpact;
|
|
181
|
+
rec.nx = isect.normal.x; rec.ny = isect.normal.y; rec.nz = isect.normal.z;
|
|
182
|
+
count++;
|
|
183
|
+
}
|
|
184
|
+
return true; // keep collecting
|
|
185
|
+
});
|
|
186
|
+
for (let i = 0; i < count; i++) order[i] = i;
|
|
187
|
+
order.length = count;
|
|
188
|
+
order.sort((i, j) => pool[i].distance - pool[j].distance);
|
|
189
|
+
for (let k = 0; k < count; k++) {
|
|
190
|
+
const rec = pool[order[k]];
|
|
191
|
+
fillRay(scratch, rec.entity, rec.distance, rec.nx, rec.ny, rec.nz, a, dx, dy, dz);
|
|
192
|
+
if (visit(scratch) === false) break;
|
|
193
|
+
}
|
|
194
|
+
},
|
|
195
|
+
};
|
|
196
|
+
};
|
|
197
|
+
|
|
110
198
|
return () => {
|
|
111
199
|
if (!world) {
|
|
112
200
|
if (!initStarted) {
|
|
113
201
|
initStarted = true;
|
|
114
|
-
RAPIER.init().then(() => {
|
|
202
|
+
RAPIER.init().then(() => {
|
|
203
|
+
world = new RAPIER.World({ x: 0, y: -GRAVITY, z: 0 });
|
|
204
|
+
db.store.resources.physicsQuery = makeQuery(world);
|
|
205
|
+
});
|
|
115
206
|
}
|
|
116
207
|
return; // WASM not ready yet
|
|
117
208
|
}
|