@aethervtt/shard-physics 0.0.1
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/LICENSE +21 -0
- package/README.md +3 -0
- package/dist/character.d.ts +62 -0
- package/dist/character.js +509 -0
- package/dist/components.d.ts +155 -0
- package/dist/components.js +340 -0
- package/dist/gravity.d.ts +17 -0
- package/dist/gravity.js +73 -0
- package/dist/index.d.ts +5 -0
- package/dist/index.js +64 -0
- package/dist/methods.d.ts +2 -0
- package/dist/methods.js +131 -0
- package/dist/plugin.d.ts +13 -0
- package/dist/plugin.js +853 -0
- package/dist/pose.d.ts +41 -0
- package/dist/pose.js +341 -0
- package/dist/world.d.ts +246 -0
- package/dist/world.js +1104 -0
- package/package.json +40 -0
package/dist/plugin.js
ADDED
|
@@ -0,0 +1,853 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ChildOf,
|
|
3
|
+
defineSystem,
|
|
4
|
+
defineSystemSet,
|
|
5
|
+
FixedUpdate,
|
|
6
|
+
onAdd,
|
|
7
|
+
onRemove,
|
|
8
|
+
onSet,
|
|
9
|
+
PostUpdate,
|
|
10
|
+
ShardError
|
|
11
|
+
} from "@aethervtt/shard-core";
|
|
12
|
+
import { defineOverlay } from "@aethervtt/shard-render";
|
|
13
|
+
import { FixedTime } from "@aethervtt/shard-runtime";
|
|
14
|
+
import {
|
|
15
|
+
GlobalTransform,
|
|
16
|
+
GridCell,
|
|
17
|
+
GridFramesResource,
|
|
18
|
+
OriginShift,
|
|
19
|
+
Transform,
|
|
20
|
+
TransformSystems
|
|
21
|
+
} from "@aethervtt/shard-transform";
|
|
22
|
+
import { characterSystem } from "./character.js";
|
|
23
|
+
import * as componentsModule from "./components.js";
|
|
24
|
+
import {
|
|
25
|
+
CharacterController,
|
|
26
|
+
CharacterState,
|
|
27
|
+
Collider,
|
|
28
|
+
CollisionEvent,
|
|
29
|
+
ContactForceEvent,
|
|
30
|
+
ExternalForce,
|
|
31
|
+
ExternalImpulse,
|
|
32
|
+
GravitySource,
|
|
33
|
+
Joint,
|
|
34
|
+
PhysicsConfig,
|
|
35
|
+
PhysicsRange,
|
|
36
|
+
RigidBody,
|
|
37
|
+
Velocity
|
|
38
|
+
} from "./components.js";
|
|
39
|
+
import { createGravitySources, gatherGravitySources, sampleGravity } from "./gravity.js";
|
|
40
|
+
import { physicsMethods } from "./methods.js";
|
|
41
|
+
import * as pluginModule from "./plugin.js";
|
|
42
|
+
import { isGrid, parentOf, rotate, worldToLocal } from "./pose.js";
|
|
43
|
+
import { loadRapier, Physics, PhysicsWorld } from "./world.js";
|
|
44
|
+
const PhysicsSystems = defineSystemSet("physics/PhysicsSystems");
|
|
45
|
+
function physics(world) {
|
|
46
|
+
const p = world.tryResource(Physics);
|
|
47
|
+
if (!p) {
|
|
48
|
+
throw new ShardError("physics/not-ready", "Physics is not ready", {
|
|
49
|
+
hint: "Add the physics3d or physics2d plugin and await app.init() before querying."
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
return p;
|
|
53
|
+
}
|
|
54
|
+
const KIND_DYNAMIC = 0;
|
|
55
|
+
const KIND_FIXED = 1;
|
|
56
|
+
const KIND_KINEMATIC_POSITION = 2;
|
|
57
|
+
const reparented = /* @__PURE__ */ new WeakMap();
|
|
58
|
+
const syncIn = defineSystem({
|
|
59
|
+
name: "physics/sync-in",
|
|
60
|
+
description: "Creates and rebuilds bodies, colliders, and joints from component changes; pushes teleports, velocities, forces, and gravity sources into Rapier.",
|
|
61
|
+
setup: (world) => ({
|
|
62
|
+
bodies: world.query({ with: [RigidBody] }),
|
|
63
|
+
colliders: world.query({ with: [Collider] }),
|
|
64
|
+
joints: world.query({ with: [Joint] }),
|
|
65
|
+
velocities: world.query({ with: [Velocity, RigidBody] }),
|
|
66
|
+
impulses: world.query({ with: [ExternalImpulse] }),
|
|
67
|
+
forces: world.query({ with: [ExternalForce] }),
|
|
68
|
+
sources: world.query({ with: [GravitySource, GlobalTransform] }),
|
|
69
|
+
forcedLast: [],
|
|
70
|
+
forcedNow: [],
|
|
71
|
+
stamp: 0,
|
|
72
|
+
stamps: new Uint32Array(64),
|
|
73
|
+
gravity: createGravitySources(),
|
|
74
|
+
pull: new Float64Array(4),
|
|
75
|
+
gravityVisit: void 0
|
|
76
|
+
}),
|
|
77
|
+
run: (s, world, ctx) => {
|
|
78
|
+
const p = world.tryResource(Physics);
|
|
79
|
+
if (!p) return;
|
|
80
|
+
const since = ctx.lastRunTick;
|
|
81
|
+
const moved = reparented.get(world);
|
|
82
|
+
if (moved && moved.size > 0) {
|
|
83
|
+
for (const e of moved) p.reparented(world, e);
|
|
84
|
+
moved.clear();
|
|
85
|
+
}
|
|
86
|
+
const userTick = Math.max(p.writeTick, p.interpTick);
|
|
87
|
+
if (p.config.interpolate && p.interpTick > p.writeTick) restorePoses(world, p);
|
|
88
|
+
const frames = world.tryResource(GridFramesResource);
|
|
89
|
+
const gridsActive = frames?.active === true;
|
|
90
|
+
if (gridsActive !== p.gridsActive) {
|
|
91
|
+
p.gridsActive = gridsActive;
|
|
92
|
+
p.resync(world);
|
|
93
|
+
}
|
|
94
|
+
for (let t = 0; t < s.bodies.tables.length; t++) {
|
|
95
|
+
const table = s.bodies.tables[t];
|
|
96
|
+
if (table.lastChanged(RigidBody) <= since) continue;
|
|
97
|
+
const ticks = table.changedTicks(RigidBody);
|
|
98
|
+
for (let row = 0; row < table.count; row++) {
|
|
99
|
+
if (ticks[row] > since) p.createBody(world, table.entities[row]);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
if (p.dirtyBodies.size > 0) {
|
|
103
|
+
for (const e of p.dirtyBodies) {
|
|
104
|
+
const record = p.bodies.get(e);
|
|
105
|
+
if (record && world.isAlive(e)) p.moveBody(world, record);
|
|
106
|
+
}
|
|
107
|
+
p.dirtyBodies.clear();
|
|
108
|
+
}
|
|
109
|
+
for (let t = 0; t < s.colliders.tables.length; t++) {
|
|
110
|
+
const table = s.colliders.tables[t];
|
|
111
|
+
if (table.lastChanged(Collider) <= since) continue;
|
|
112
|
+
const ticks = table.changedTicks(Collider);
|
|
113
|
+
for (let row = 0; row < table.count; row++) {
|
|
114
|
+
if (ticks[row] > since) p.dirtyColliders.add(table.entities[row]);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (p.meshColliders.size > 0) {
|
|
118
|
+
for (const [e, m] of p.meshColliders)
|
|
119
|
+
if (m.mesh.version !== m.version) p.dirtyColliders.add(e);
|
|
120
|
+
}
|
|
121
|
+
if (p.pendingMesh.size > 0) for (const e of p.pendingMesh) p.dirtyColliders.add(e);
|
|
122
|
+
if (p.dirtyColliders.size > 0) {
|
|
123
|
+
for (const e of p.dirtyColliders) {
|
|
124
|
+
if (world.isAlive(e) && world.has(e, Collider)) p.createCollider(world, e);
|
|
125
|
+
}
|
|
126
|
+
p.dirtyColliders.clear();
|
|
127
|
+
}
|
|
128
|
+
for (let t = 0; t < s.joints.tables.length; t++) {
|
|
129
|
+
const table = s.joints.tables[t];
|
|
130
|
+
if (table.lastChanged(Joint) <= since) continue;
|
|
131
|
+
const ticks = table.changedTicks(Joint);
|
|
132
|
+
for (let row = 0; row < table.count; row++) {
|
|
133
|
+
if (ticks[row] > since) p.dirtyJoints.add(table.entities[row]);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
if (p.dirtyJoints.size > 0) {
|
|
137
|
+
for (const e of p.dirtyJoints) {
|
|
138
|
+
if (!world.isAlive(e) || !world.has(e, Joint)) {
|
|
139
|
+
p.dirtyJoints.delete(e);
|
|
140
|
+
continue;
|
|
141
|
+
}
|
|
142
|
+
p.createJoint(world, e);
|
|
143
|
+
if (p.joints.has(e)) p.dirtyJoints.delete(e);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
for (let t = 0; t < s.bodies.tables.length; t++) {
|
|
147
|
+
const table = s.bodies.tables[t];
|
|
148
|
+
const kinematicMoved = table.lastChanged(GlobalTransform) > since;
|
|
149
|
+
const cells = table.has(GridCell) && table.lastChanged(GridCell) > userTick ? table.changedTicks(GridCell) : void 0;
|
|
150
|
+
if (table.lastChanged(Transform) <= userTick && !kinematicMoved && cells === void 0) {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
const ticks = table.changedTicks(Transform);
|
|
154
|
+
const globals = table.changedTicks(GlobalTransform);
|
|
155
|
+
for (let row = 0; row < table.count; row++) {
|
|
156
|
+
const record = p.bodies.get(table.entities[row]);
|
|
157
|
+
if (!record) continue;
|
|
158
|
+
if (ticks[row] > userTick || cells !== void 0 && cells[row] > userTick) {
|
|
159
|
+
p.moveBody(world, record);
|
|
160
|
+
} else if (record.kind === KIND_KINEMATIC_POSITION && globals[row] > since) {
|
|
161
|
+
p.moveBody(world, record);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
for (let t = 0; t < s.velocities.tables.length; t++) {
|
|
166
|
+
const table = s.velocities.tables[t];
|
|
167
|
+
if (table.lastChanged(Velocity) <= userTick) continue;
|
|
168
|
+
const ticks = table.changedTicks(Velocity);
|
|
169
|
+
for (let row = 0; row < table.count; row++) {
|
|
170
|
+
if (ticks[row] <= userTick) continue;
|
|
171
|
+
const record = p.bodies.get(table.entities[row]);
|
|
172
|
+
if (record && record.kind !== KIND_FIXED) p.setVelocity(world, record);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
for (let t = 0; t < s.impulses.tables.length; t++) {
|
|
176
|
+
const table = s.impulses.tables[t];
|
|
177
|
+
if (table.lastChanged(ExternalImpulse) <= since) continue;
|
|
178
|
+
const imp = table.column(ExternalImpulse, "impulse");
|
|
179
|
+
const tor = table.column(ExternalImpulse, "torque");
|
|
180
|
+
const ticks = table.changedTicks(ExternalImpulse);
|
|
181
|
+
let wrote = false;
|
|
182
|
+
for (let row = 0; row < table.count; row++) {
|
|
183
|
+
if (ticks[row] <= since) continue;
|
|
184
|
+
const o = row * 3;
|
|
185
|
+
const record = p.bodies.get(table.entities[row]);
|
|
186
|
+
if (!record || record.kind !== KIND_DYNAMIC) continue;
|
|
187
|
+
if (imp[o] || imp[o + 1] || imp[o + 2]) {
|
|
188
|
+
record.body.applyImpulse(p.vec(imp[o], imp[o + 1], imp[o + 2]), true);
|
|
189
|
+
}
|
|
190
|
+
if (tor[o] || tor[o + 1] || tor[o + 2]) {
|
|
191
|
+
if (p.dim === 3)
|
|
192
|
+
record.body.applyTorqueImpulse(p.vec(tor[o], tor[o + 1], tor[o + 2]), true);
|
|
193
|
+
else
|
|
194
|
+
record.body.applyTorqueImpulse(tor[o + 2], true);
|
|
195
|
+
}
|
|
196
|
+
imp[o] = imp[o + 1] = imp[o + 2] = 0;
|
|
197
|
+
tor[o] = tor[o + 1] = tor[o + 2] = 0;
|
|
198
|
+
wrote = true;
|
|
199
|
+
}
|
|
200
|
+
if (wrote) table.markChanged(ExternalImpulse);
|
|
201
|
+
}
|
|
202
|
+
applyForces(s, p, since);
|
|
203
|
+
if (gridsActive) p.updateParking(world, world.resource(PhysicsRange).radius);
|
|
204
|
+
else if (p.parkedCount > 0) p.unparkAll(world);
|
|
205
|
+
}
|
|
206
|
+
});
|
|
207
|
+
const scratchPos = new Float64Array(3);
|
|
208
|
+
const scratchRot = new Float64Array(4);
|
|
209
|
+
function rootIsOrigin(world) {
|
|
210
|
+
return world.tryResource(GridFramesResource)?.rootIdentity !== false;
|
|
211
|
+
}
|
|
212
|
+
function writeMode(world, entity, rootIdentity) {
|
|
213
|
+
const parent = parentOf(world, entity);
|
|
214
|
+
if (parent === void 0) return rootIdentity ? 0 : 1;
|
|
215
|
+
return isGrid(world, parent) ? 1 : -1;
|
|
216
|
+
}
|
|
217
|
+
function writeGridPose(world, entity, table, row, pos, rot, dim) {
|
|
218
|
+
const tr = table.column(Transform, "translation");
|
|
219
|
+
const z = tr[row * 3 + 2];
|
|
220
|
+
worldToLocal(
|
|
221
|
+
world,
|
|
222
|
+
parentOf(world, entity),
|
|
223
|
+
entity,
|
|
224
|
+
pos,
|
|
225
|
+
rot,
|
|
226
|
+
tr,
|
|
227
|
+
row * 3,
|
|
228
|
+
table.column(Transform, "rotation"),
|
|
229
|
+
row * 4
|
|
230
|
+
);
|
|
231
|
+
if (dim === 2) tr[row * 3 + 2] = z;
|
|
232
|
+
table.markChanged(Transform, row);
|
|
233
|
+
}
|
|
234
|
+
function restorePoses(world, p) {
|
|
235
|
+
const rootIdentity = rootIsOrigin(world);
|
|
236
|
+
for (let i = 0; i < p.list.length; i++) {
|
|
237
|
+
const r = p.list[i];
|
|
238
|
+
if (r.kind === KIND_FIXED || r.kind === KIND_KINEMATIC_POSITION || r.parked) continue;
|
|
239
|
+
const table = world.entityTableUnchecked(r.entity);
|
|
240
|
+
const row = world.entityRowUnchecked(r.entity);
|
|
241
|
+
if (table.changedTicks(Transform)[row] !== p.interpTick) continue;
|
|
242
|
+
const mode = writeMode(world, r.entity, rootIdentity);
|
|
243
|
+
if (mode < 0) continue;
|
|
244
|
+
if (mode === 0) {
|
|
245
|
+
writePose(table, row, p.curr, r.slot * 7, p.dim);
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
const o = r.slot * 7;
|
|
249
|
+
for (let k = 0; k < 3; k++) scratchPos[k] = p.curr[o + k];
|
|
250
|
+
for (let k = 0; k < 4; k++) scratchRot[k] = p.curr[o + 3 + k];
|
|
251
|
+
writeGridPose(world, r.entity, table, row, scratchPos, scratchRot, p.dim);
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function writePose(table, row, pose, o, dim) {
|
|
255
|
+
const tr = table.column(Transform, "translation");
|
|
256
|
+
const rot = table.column(Transform, "rotation");
|
|
257
|
+
tr[row * 3] = pose[o];
|
|
258
|
+
tr[row * 3 + 1] = pose[o + 1];
|
|
259
|
+
if (dim === 3) tr[row * 3 + 2] = pose[o + 2];
|
|
260
|
+
rot[row * 4] = pose[o + 3];
|
|
261
|
+
rot[row * 4 + 1] = pose[o + 4];
|
|
262
|
+
rot[row * 4 + 2] = pose[o + 5];
|
|
263
|
+
rot[row * 4 + 3] = pose[o + 6];
|
|
264
|
+
table.markChanged(Transform, row);
|
|
265
|
+
}
|
|
266
|
+
function applyForces(s, p, since) {
|
|
267
|
+
const raw = p.raw.bodies.raw;
|
|
268
|
+
const rv = p.rawVec;
|
|
269
|
+
for (let i = 0; i < s.forcedLast.length; i++) {
|
|
270
|
+
const r = s.forcedLast[i];
|
|
271
|
+
if (!p.bodyByHandle.has(r.handle)) continue;
|
|
272
|
+
raw.rbResetForces(r.handle, false);
|
|
273
|
+
raw.rbResetTorques(r.handle, false);
|
|
274
|
+
}
|
|
275
|
+
const swap = s.forcedLast;
|
|
276
|
+
s.forcedLast = s.forcedNow;
|
|
277
|
+
s.forcedNow = swap;
|
|
278
|
+
s.forcedNow.length = 0;
|
|
279
|
+
s.stamp++;
|
|
280
|
+
if (s.stamps.length < p.slotCapacity) {
|
|
281
|
+
const grown = new Uint32Array(p.slotCapacity);
|
|
282
|
+
grown.set(s.stamps);
|
|
283
|
+
s.stamps = grown;
|
|
284
|
+
}
|
|
285
|
+
for (let t = 0; t < s.forces.tables.length; t++) {
|
|
286
|
+
const table = s.forces.tables[t];
|
|
287
|
+
const f = table.column(ExternalForce, "force");
|
|
288
|
+
const tq = table.column(ExternalForce, "torque");
|
|
289
|
+
const ticks = table.changedTicks(ExternalForce);
|
|
290
|
+
for (let row = 0; row < table.count; row++) {
|
|
291
|
+
const record = p.bodies.get(table.entities[row]);
|
|
292
|
+
if (!record || record.kind !== KIND_DYNAMIC) continue;
|
|
293
|
+
const o = row * 3;
|
|
294
|
+
const wake = ticks[row] > since;
|
|
295
|
+
if (s.stamps[record.slot] !== s.stamp) {
|
|
296
|
+
s.stamps[record.slot] = s.stamp;
|
|
297
|
+
s.forcedLast.push(record);
|
|
298
|
+
}
|
|
299
|
+
if (f[o] || f[o + 1] || f[o + 2]) {
|
|
300
|
+
rv.x = f[o];
|
|
301
|
+
rv.y = f[o + 1];
|
|
302
|
+
if (p.dim === 3) rv.z = f[o + 2];
|
|
303
|
+
raw.rbAddForce(record.handle, rv, wake);
|
|
304
|
+
}
|
|
305
|
+
if (tq[o] || tq[o + 1] || tq[o + 2]) {
|
|
306
|
+
if (p.dim === 3) {
|
|
307
|
+
rv.x = tq[o];
|
|
308
|
+
rv.y = tq[o + 1];
|
|
309
|
+
rv.z = tq[o + 2];
|
|
310
|
+
raw.rbAddTorque(record.handle, rv, wake);
|
|
311
|
+
} else {
|
|
312
|
+
;
|
|
313
|
+
raw.rbAddTorque(
|
|
314
|
+
record.handle,
|
|
315
|
+
tq[o + 2],
|
|
316
|
+
wake
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
gatherGravitySources(s.sources, s.gravity);
|
|
323
|
+
if (s.gravity.count === 0) return;
|
|
324
|
+
s.gravityVisit ??= (handle) => {
|
|
325
|
+
const record = p.bodyByHandle.get(handle);
|
|
326
|
+
if (!record || record.kind !== KIND_DYNAMIC) return;
|
|
327
|
+
const c = p.curr;
|
|
328
|
+
const o = record.slot * 7;
|
|
329
|
+
const g = s.pull;
|
|
330
|
+
sampleGravity(s.gravity, p.dim, c[o], c[o + 1], c[o + 2], false, g);
|
|
331
|
+
const k = record.body.mass() * record.body.gravityScale();
|
|
332
|
+
if (s.stamps[record.slot] !== s.stamp) {
|
|
333
|
+
s.stamps[record.slot] = s.stamp;
|
|
334
|
+
s.forcedLast.push(record);
|
|
335
|
+
}
|
|
336
|
+
rv.x = g[0] * k;
|
|
337
|
+
rv.y = g[1] * k;
|
|
338
|
+
if (p.dim === 3) rv.z = g[2] * k;
|
|
339
|
+
raw.rbAddForce(handle, rv, false);
|
|
340
|
+
};
|
|
341
|
+
p.raw.islands.forEachActiveRigidBodyHandle(s.gravityVisit);
|
|
342
|
+
}
|
|
343
|
+
const step = defineSystem({
|
|
344
|
+
name: "physics/step",
|
|
345
|
+
description: "Steps Rapier by the fixed timestep and sends collision and contact force events.",
|
|
346
|
+
setup: (world) => ({
|
|
347
|
+
onCollision: (e) => world.send(CollisionEvent, e),
|
|
348
|
+
onForce: (a, b, force) => world.send(ContactForceEvent, { a, b, force })
|
|
349
|
+
}),
|
|
350
|
+
run: (s, world) => {
|
|
351
|
+
const p = world.tryResource(Physics);
|
|
352
|
+
if (!p || p.config.paused) return;
|
|
353
|
+
p.step(world.resource(FixedTime).step, s.onCollision, s.onForce);
|
|
354
|
+
}
|
|
355
|
+
});
|
|
356
|
+
function cacheTable(c, table) {
|
|
357
|
+
c.table = table;
|
|
358
|
+
c.tr = table.column(Transform, "translation");
|
|
359
|
+
c.rt = table.column(Transform, "rotation");
|
|
360
|
+
c.trTicks = table.changedTicks(Transform);
|
|
361
|
+
table.touch(Transform);
|
|
362
|
+
if (table.has(Velocity)) {
|
|
363
|
+
c.lin = table.column(Velocity, "linear");
|
|
364
|
+
c.ang = table.column(Velocity, "angular");
|
|
365
|
+
c.velTicks = table.changedTicks(Velocity);
|
|
366
|
+
table.touch(Velocity);
|
|
367
|
+
} else {
|
|
368
|
+
c.lin = c.ang = c.velTicks = void 0;
|
|
369
|
+
}
|
|
370
|
+
c.parented = table.has(ChildOf);
|
|
371
|
+
}
|
|
372
|
+
const syncOut = defineSystem({
|
|
373
|
+
name: "physics/sync-out",
|
|
374
|
+
description: "Writes the poses and velocities of moving bodies to Transform and Velocity.",
|
|
375
|
+
setup: (world) => ({
|
|
376
|
+
visit: void 0,
|
|
377
|
+
world,
|
|
378
|
+
tick: 0,
|
|
379
|
+
pos: new Float64Array(3),
|
|
380
|
+
rot: new Float64Array(4),
|
|
381
|
+
rootIdentity: true,
|
|
382
|
+
cache: {
|
|
383
|
+
table: void 0,
|
|
384
|
+
tr: new Float32Array(0),
|
|
385
|
+
rt: new Float32Array(0),
|
|
386
|
+
trTicks: new Uint32Array(0),
|
|
387
|
+
lin: void 0,
|
|
388
|
+
ang: void 0,
|
|
389
|
+
velTicks: void 0,
|
|
390
|
+
parented: false
|
|
391
|
+
}
|
|
392
|
+
}),
|
|
393
|
+
run: (s, world, ctx) => {
|
|
394
|
+
const p = world.tryResource(Physics);
|
|
395
|
+
if (!p) return;
|
|
396
|
+
p.writeTick = ctx.thisRunTick;
|
|
397
|
+
s.tick = ctx.thisRunTick;
|
|
398
|
+
s.cache.table = void 0;
|
|
399
|
+
s.rootIdentity = rootIsOrigin(world);
|
|
400
|
+
s.visit ??= (handle) => {
|
|
401
|
+
const record = p.bodyByHandle.get(handle);
|
|
402
|
+
if (!record || record.kind === KIND_FIXED) return;
|
|
403
|
+
const o = record.slot * 7;
|
|
404
|
+
const curr = p.curr;
|
|
405
|
+
for (let k = 0; k < 7; k++) p.prev[o + k] = curr[o + k];
|
|
406
|
+
p.storePose(record);
|
|
407
|
+
const e = record.entity;
|
|
408
|
+
const table = world.entityTableUnchecked(e);
|
|
409
|
+
const row = world.entityRowUnchecked(e);
|
|
410
|
+
const c = s.cache;
|
|
411
|
+
if (c.table !== table) cacheTable(c, table);
|
|
412
|
+
if (record.kind !== KIND_KINEMATIC_POSITION) {
|
|
413
|
+
const parent = c.parented ? parentOf(world, e) : void 0;
|
|
414
|
+
const tr = c.tr;
|
|
415
|
+
const rt = c.rt;
|
|
416
|
+
if (parent === void 0 && s.rootIdentity) {
|
|
417
|
+
tr[row * 3] = curr[o];
|
|
418
|
+
tr[row * 3 + 1] = curr[o + 1];
|
|
419
|
+
if (p.dim === 3) tr[row * 3 + 2] = curr[o + 2];
|
|
420
|
+
rt[row * 4] = curr[o + 3];
|
|
421
|
+
rt[row * 4 + 1] = curr[o + 4];
|
|
422
|
+
rt[row * 4 + 2] = curr[o + 5];
|
|
423
|
+
rt[row * 4 + 3] = curr[o + 6];
|
|
424
|
+
} else {
|
|
425
|
+
for (let k = 0; k < 3; k++) s.pos[k] = curr[o + k];
|
|
426
|
+
for (let k = 0; k < 4; k++) s.rot[k] = curr[o + 3 + k];
|
|
427
|
+
const z = tr[row * 3 + 2];
|
|
428
|
+
worldToLocal(world, parent, e, s.pos, s.rot, tr, row * 3, rt, row * 4);
|
|
429
|
+
if (p.dim === 2) tr[row * 3 + 2] = z;
|
|
430
|
+
}
|
|
431
|
+
c.trTicks[row] = s.tick;
|
|
432
|
+
}
|
|
433
|
+
if (c.lin) {
|
|
434
|
+
const lin = c.lin;
|
|
435
|
+
const ang = c.ang;
|
|
436
|
+
const v = record.body.linvel(p.v3);
|
|
437
|
+
lin[row * 3] = v.x;
|
|
438
|
+
lin[row * 3 + 1] = v.y;
|
|
439
|
+
lin[row * 3 + 2] = p.dim === 3 ? v.z : 0;
|
|
440
|
+
if (p.dim === 3) {
|
|
441
|
+
const a = record.body.angvel(p.v3);
|
|
442
|
+
ang[row * 3] = a.x;
|
|
443
|
+
ang[row * 3 + 1] = a.y;
|
|
444
|
+
ang[row * 3 + 2] = a.z;
|
|
445
|
+
} else {
|
|
446
|
+
ang[row * 3] = 0;
|
|
447
|
+
ang[row * 3 + 1] = 0;
|
|
448
|
+
ang[row * 3 + 2] = record.body.angvel();
|
|
449
|
+
}
|
|
450
|
+
c.velTicks[row] = s.tick;
|
|
451
|
+
}
|
|
452
|
+
};
|
|
453
|
+
p.raw.islands.forEachActiveRigidBodyHandle(s.visit);
|
|
454
|
+
}
|
|
455
|
+
});
|
|
456
|
+
const interpolate = defineSystem({
|
|
457
|
+
name: "physics/interpolate",
|
|
458
|
+
description: "With PhysicsConfig.interpolate, blends body Transforms between the last two steps by FixedTime.alpha.",
|
|
459
|
+
run: (_, world, ctx) => {
|
|
460
|
+
const p = world.tryResource(Physics);
|
|
461
|
+
if (!p?.config.interpolate) return;
|
|
462
|
+
const alpha = world.resource(FixedTime).alpha;
|
|
463
|
+
const ours = Math.max(p.writeTick, p.interpTick);
|
|
464
|
+
const prev = p.prev;
|
|
465
|
+
const curr = p.curr;
|
|
466
|
+
const rootIdentity = rootIsOrigin(world);
|
|
467
|
+
const pos = scratchPos;
|
|
468
|
+
const rot = scratchRot;
|
|
469
|
+
for (let i = 0; i < p.list.length; i++) {
|
|
470
|
+
const r = p.list[i];
|
|
471
|
+
if (r.kind === KIND_FIXED || r.kind === KIND_KINEMATIC_POSITION || r.parked) continue;
|
|
472
|
+
const table = world.entityTableUnchecked(r.entity);
|
|
473
|
+
const row = world.entityRowUnchecked(r.entity);
|
|
474
|
+
if (table.changedTicks(Transform)[row] > ours) continue;
|
|
475
|
+
const mode = writeMode(world, r.entity, rootIdentity);
|
|
476
|
+
if (mode < 0) continue;
|
|
477
|
+
const o = r.slot * 7;
|
|
478
|
+
pos[0] = prev[o] + (curr[o] - prev[o]) * alpha;
|
|
479
|
+
pos[1] = prev[o + 1] + (curr[o + 1] - prev[o + 1]) * alpha;
|
|
480
|
+
pos[2] = prev[o + 2] + (curr[o + 2] - prev[o + 2]) * alpha;
|
|
481
|
+
let ax = prev[o + 3];
|
|
482
|
+
let ay = prev[o + 4];
|
|
483
|
+
let az = prev[o + 5];
|
|
484
|
+
let aw = prev[o + 6];
|
|
485
|
+
const bx = curr[o + 3];
|
|
486
|
+
const by = curr[o + 4];
|
|
487
|
+
const bz = curr[o + 5];
|
|
488
|
+
const bw = curr[o + 6];
|
|
489
|
+
if (ax * bx + ay * by + az * bz + aw * bw < 0) {
|
|
490
|
+
ax = -ax;
|
|
491
|
+
ay = -ay;
|
|
492
|
+
az = -az;
|
|
493
|
+
aw = -aw;
|
|
494
|
+
}
|
|
495
|
+
const x = ax + (bx - ax) * alpha;
|
|
496
|
+
const y = ay + (by - ay) * alpha;
|
|
497
|
+
const z = az + (bz - az) * alpha;
|
|
498
|
+
const w = aw + (bw - aw) * alpha;
|
|
499
|
+
const len = Math.sqrt(x * x + y * y + z * z + w * w) || 1;
|
|
500
|
+
rot[0] = x / len;
|
|
501
|
+
rot[1] = y / len;
|
|
502
|
+
rot[2] = z / len;
|
|
503
|
+
rot[3] = w / len;
|
|
504
|
+
if (mode === 1) {
|
|
505
|
+
writeGridPose(world, r.entity, table, row, pos, rot, p.dim);
|
|
506
|
+
continue;
|
|
507
|
+
}
|
|
508
|
+
const tr = table.column(Transform, "translation");
|
|
509
|
+
const rt = table.column(Transform, "rotation");
|
|
510
|
+
tr[row * 3] = pos[0];
|
|
511
|
+
tr[row * 3 + 1] = pos[1];
|
|
512
|
+
if (p.dim === 3) tr[row * 3 + 2] = pos[2];
|
|
513
|
+
rt[row * 4] = rot[0];
|
|
514
|
+
rt[row * 4 + 1] = rot[1];
|
|
515
|
+
rt[row * 4 + 2] = rot[2];
|
|
516
|
+
rt[row * 4 + 3] = rot[3];
|
|
517
|
+
table.markChanged(Transform, row);
|
|
518
|
+
}
|
|
519
|
+
p.interpTick = ctx.thisRunTick;
|
|
520
|
+
}
|
|
521
|
+
});
|
|
522
|
+
const dimensionOf = /* @__PURE__ */ new WeakMap();
|
|
523
|
+
function observe(world) {
|
|
524
|
+
const get = () => world.tryResource(Physics);
|
|
525
|
+
world.observe(onRemove(RigidBody), (e) => get()?.removeBody(world, e.entity));
|
|
526
|
+
world.observe(onRemove(CharacterController), (e) => {
|
|
527
|
+
const p = get();
|
|
528
|
+
if (p?.characters.has(e.entity)) p.removeBody(world, e.entity);
|
|
529
|
+
});
|
|
530
|
+
world.observe(onRemove(Collider), (e) => {
|
|
531
|
+
const p = get();
|
|
532
|
+
p?.removeCollider(e.entity);
|
|
533
|
+
p?.pendingMesh.delete(e.entity);
|
|
534
|
+
});
|
|
535
|
+
world.observe(onRemove(Joint), (e) => {
|
|
536
|
+
const p = get();
|
|
537
|
+
p?.removeJoint(e.entity);
|
|
538
|
+
p?.dirtyJoints.delete(e.entity);
|
|
539
|
+
});
|
|
540
|
+
let set = reparented.get(world);
|
|
541
|
+
if (!set) {
|
|
542
|
+
set = /* @__PURE__ */ new Set();
|
|
543
|
+
reparented.set(world, set);
|
|
544
|
+
}
|
|
545
|
+
const moved = set;
|
|
546
|
+
const mark = (e) => void moved.add(e.entity);
|
|
547
|
+
world.observe(onAdd(ChildOf), mark);
|
|
548
|
+
world.observe(onSet(ChildOf), mark);
|
|
549
|
+
world.observe(onRemove(ChildOf), mark);
|
|
550
|
+
const onCollision = (e) => world.send(CollisionEvent, e);
|
|
551
|
+
world.observe(OriginShift, ({ data }) => {
|
|
552
|
+
get()?.shiftOrigin(data.offset[0], data.offset[1], data.offset[2], onCollision);
|
|
553
|
+
});
|
|
554
|
+
}
|
|
555
|
+
function physicsPlugin(dim) {
|
|
556
|
+
return {
|
|
557
|
+
name: dim === 3 ? "physics3d" : "physics2d",
|
|
558
|
+
provides: [componentsModule, pluginModule],
|
|
559
|
+
dependencies: ["core/transform"],
|
|
560
|
+
build(app) {
|
|
561
|
+
const other = dimensionOf.get(app.world);
|
|
562
|
+
if (other !== void 0 && other !== dim) {
|
|
563
|
+
throw new ShardError(
|
|
564
|
+
"physics/both-dimensions",
|
|
565
|
+
"physics2d and physics3d are both enabled",
|
|
566
|
+
{ hint: "Enable one physics plugin per app: physics3d for 3D games, physics2d for 2D." }
|
|
567
|
+
);
|
|
568
|
+
}
|
|
569
|
+
dimensionOf.set(app.world, dim);
|
|
570
|
+
app.world.initResource(PhysicsConfig);
|
|
571
|
+
app.world.initResource(PhysicsRange);
|
|
572
|
+
if (dim === 2) {
|
|
573
|
+
const config = app.world.resource(PhysicsConfig);
|
|
574
|
+
config.gravity = [0, -9.81, 0];
|
|
575
|
+
}
|
|
576
|
+
observe(app.world);
|
|
577
|
+
app.addSystems(
|
|
578
|
+
FixedUpdate,
|
|
579
|
+
syncIn.inSet(PhysicsSystems),
|
|
580
|
+
characterSystem.after(syncIn).inSet(PhysicsSystems),
|
|
581
|
+
step.after(characterSystem).inSet(PhysicsSystems),
|
|
582
|
+
syncOut.after(step).inSet(PhysicsSystems)
|
|
583
|
+
);
|
|
584
|
+
app.addSystems(PostUpdate, interpolate.before(TransformSystems));
|
|
585
|
+
app.addMethod(...physicsMethods);
|
|
586
|
+
},
|
|
587
|
+
async ready(app) {
|
|
588
|
+
const R = await loadRapier(dim);
|
|
589
|
+
app.world.insertResource(Physics, new PhysicsWorld(R, dim, app.world.resource(PhysicsConfig)));
|
|
590
|
+
}
|
|
591
|
+
};
|
|
592
|
+
}
|
|
593
|
+
const physics3dPlugin = physicsPlugin(3);
|
|
594
|
+
const physics2dPlugin = physicsPlugin(2);
|
|
595
|
+
const COLORS = [
|
|
596
|
+
[1, 0.55, 0.2, 1],
|
|
597
|
+
// dynamic
|
|
598
|
+
[0.55, 0.55, 0.6, 1],
|
|
599
|
+
// fixed
|
|
600
|
+
[0.3, 0.8, 1, 1],
|
|
601
|
+
// kinematic
|
|
602
|
+
[0.3, 0.8, 1, 1]
|
|
603
|
+
];
|
|
604
|
+
const SLEEPING = [0.5, 0.35, 0.2, 1];
|
|
605
|
+
const SENSOR = [0.3, 1, 0.4, 1];
|
|
606
|
+
const CONTACT = [1, 0.2, 0.3, 1];
|
|
607
|
+
const UP = [1, 0.9, 0.2, 1];
|
|
608
|
+
const pa = new Float64Array(3);
|
|
609
|
+
const pb = new Float64Array(3);
|
|
610
|
+
const q = new Float64Array(4);
|
|
611
|
+
const center = new Float64Array(3);
|
|
612
|
+
const collidersOverlay = defineOverlay({
|
|
613
|
+
name: "colliders",
|
|
614
|
+
description: "Physics collider outlines, colored by body kind (sleeping dimmed, sensors green, characters cyan with their up in yellow), and contact normals.",
|
|
615
|
+
draw(world, g, passes) {
|
|
616
|
+
const p = world.tryResource(Physics);
|
|
617
|
+
if (!p) return;
|
|
618
|
+
for (const [entity, collider] of p.colliders) {
|
|
619
|
+
if (!passes(entity)) continue;
|
|
620
|
+
const owner = p.colliderOwner.get(entity);
|
|
621
|
+
const record = owner === void 0 ? void 0 : p.bodies.get(owner);
|
|
622
|
+
const color = collider.isSensor() ? SENSOR : record?.body.isSleeping() ? SLEEPING : COLORS[record?.kind ?? KIND_FIXED];
|
|
623
|
+
const t = collider.translation(p.v3);
|
|
624
|
+
center[0] = t.x;
|
|
625
|
+
center[1] = t.y;
|
|
626
|
+
center[2] = p.dim === 3 ? t.z : 0;
|
|
627
|
+
if (p.dim === 3) {
|
|
628
|
+
const r = collider.rotation(p.q4);
|
|
629
|
+
q[0] = r.x;
|
|
630
|
+
q[1] = r.y;
|
|
631
|
+
q[2] = r.z;
|
|
632
|
+
q[3] = r.w;
|
|
633
|
+
} else {
|
|
634
|
+
const angle = collider.rotation();
|
|
635
|
+
q[0] = 0;
|
|
636
|
+
q[1] = 0;
|
|
637
|
+
q[2] = Math.sin(angle / 2);
|
|
638
|
+
q[3] = Math.cos(angle / 2);
|
|
639
|
+
}
|
|
640
|
+
drawCollider(p, g, collider, p.colliderShape.get(entity), color);
|
|
641
|
+
}
|
|
642
|
+
for (const [entity, c] of p.characters) {
|
|
643
|
+
if (!passes(entity) || !world.has(entity, CharacterState)) continue;
|
|
644
|
+
const up = world.get(entity, CharacterState).up;
|
|
645
|
+
const t = c.collider.translation(p.v3);
|
|
646
|
+
const tall = p.colliderShape.get(entity) === "capsule" ? c.collider.halfHeight() : 0;
|
|
647
|
+
const reach = c.collider.radius() + tall + 0.5;
|
|
648
|
+
pa[0] = t.x;
|
|
649
|
+
pa[1] = t.y;
|
|
650
|
+
pa[2] = p.dim === 3 ? t.z : 0;
|
|
651
|
+
pb[0] = pa[0] + up[0] * reach;
|
|
652
|
+
pb[1] = pa[1] + up[1] * reach;
|
|
653
|
+
pb[2] = pa[2] + up[2] * reach;
|
|
654
|
+
g.line(pa, pb, UP);
|
|
655
|
+
}
|
|
656
|
+
contactBudget = MAX_CONTACT_PAIRS;
|
|
657
|
+
for (let i = 0; i < p.list.length && contactBudget > 0; i++) {
|
|
658
|
+
const record = p.list[i];
|
|
659
|
+
if (record.kind !== KIND_DYNAMIC || record.body.isSleeping()) continue;
|
|
660
|
+
const n = record.body.numColliders();
|
|
661
|
+
for (let c = 0; c < n; c++) drawContacts(p, g, record.body.collider(c));
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
});
|
|
665
|
+
const MAX_CONTACT_PAIRS = 1e3;
|
|
666
|
+
let contactBudget = 0;
|
|
667
|
+
function visitedFromOtherSide(other) {
|
|
668
|
+
const body = other.parent();
|
|
669
|
+
return body?.isDynamic() === true && !body.isSleeping();
|
|
670
|
+
}
|
|
671
|
+
function drawContacts(p, g, collider) {
|
|
672
|
+
p.raw.contactPairsWith(collider, (other) => {
|
|
673
|
+
if (contactBudget <= 0) return;
|
|
674
|
+
if (other.handle < collider.handle && visitedFromOtherSide(other)) return;
|
|
675
|
+
contactBudget--;
|
|
676
|
+
p.raw.contactPair(collider, other, (manifold) => {
|
|
677
|
+
const count = manifold.numSolverContacts();
|
|
678
|
+
if (count === 0) return;
|
|
679
|
+
const n = manifold.normal();
|
|
680
|
+
for (let i = 0; i < count; i++) {
|
|
681
|
+
const point = manifold.solverContactPoint(i);
|
|
682
|
+
if (!point) continue;
|
|
683
|
+
pa[0] = point.x;
|
|
684
|
+
pa[1] = point.y;
|
|
685
|
+
pa[2] = p.dim === 3 ? point.z : 0;
|
|
686
|
+
pb[0] = pa[0] + n.x * 0.25;
|
|
687
|
+
pb[1] = pa[1] + n.y * 0.25;
|
|
688
|
+
pb[2] = pa[2] + (p.dim === 3 ? n.z : 0) * 0.25;
|
|
689
|
+
g.line(pa, pb, CONTACT);
|
|
690
|
+
}
|
|
691
|
+
});
|
|
692
|
+
});
|
|
693
|
+
}
|
|
694
|
+
function local(out, x, y, z) {
|
|
695
|
+
rotate(out, 0, q, 0, x, y, z);
|
|
696
|
+
out[0] = out[0] + center[0];
|
|
697
|
+
out[1] = out[1] + center[1];
|
|
698
|
+
out[2] = out[2] + center[2];
|
|
699
|
+
return out;
|
|
700
|
+
}
|
|
701
|
+
const SEGMENTS = 24;
|
|
702
|
+
const UNIT = (() => {
|
|
703
|
+
const out = new Float64Array((SEGMENTS + 1) * 2);
|
|
704
|
+
for (let i = 0; i <= SEGMENTS; i++) {
|
|
705
|
+
const t = i / SEGMENTS * Math.PI * 2;
|
|
706
|
+
out[i * 2] = Math.cos(t);
|
|
707
|
+
out[i * 2 + 1] = Math.sin(t);
|
|
708
|
+
}
|
|
709
|
+
return out;
|
|
710
|
+
})();
|
|
711
|
+
function localCircle(g, axis, r, dy, color) {
|
|
712
|
+
for (let i = 0; i <= SEGMENTS; i++) {
|
|
713
|
+
const u = UNIT[i * 2] * r;
|
|
714
|
+
const v = UNIT[i * 2 + 1] * r;
|
|
715
|
+
if (axis === 0) local(pb, 0, u + dy, v);
|
|
716
|
+
else if (axis === 1) local(pb, u, dy, v);
|
|
717
|
+
else local(pb, u, v + dy, 0);
|
|
718
|
+
if (i > 0) g.line(pa, pb, color);
|
|
719
|
+
pa[0] = pb[0];
|
|
720
|
+
pa[1] = pb[1];
|
|
721
|
+
pa[2] = pb[2];
|
|
722
|
+
}
|
|
723
|
+
}
|
|
724
|
+
function drawCollider(p, g, collider, shape, color) {
|
|
725
|
+
const is2d = p.dim === 2;
|
|
726
|
+
switch (shape) {
|
|
727
|
+
case "ball": {
|
|
728
|
+
const r = collider.radius();
|
|
729
|
+
localCircle(g, 2, r, 0, color);
|
|
730
|
+
if (!is2d) {
|
|
731
|
+
localCircle(g, 0, r, 0, color);
|
|
732
|
+
localCircle(g, 1, r, 0, color);
|
|
733
|
+
}
|
|
734
|
+
return;
|
|
735
|
+
}
|
|
736
|
+
case "cuboid": {
|
|
737
|
+
const he = collider.halfExtents();
|
|
738
|
+
const hz = is2d ? 0 : he.z;
|
|
739
|
+
const size = [he.x * 2, he.y * 2, hz * 2];
|
|
740
|
+
g.box(center, size, q, color);
|
|
741
|
+
return;
|
|
742
|
+
}
|
|
743
|
+
case "capsule":
|
|
744
|
+
case "cylinder":
|
|
745
|
+
case "cone": {
|
|
746
|
+
const r = collider.radius();
|
|
747
|
+
const h = collider.halfHeight();
|
|
748
|
+
if (is2d) {
|
|
749
|
+
local(pa, -r, -h, 0);
|
|
750
|
+
local(pb, -r, h, 0);
|
|
751
|
+
g.line(pa, pb, color);
|
|
752
|
+
local(pa, r, -h, 0);
|
|
753
|
+
local(pb, r, h, 0);
|
|
754
|
+
g.line(pa, pb, color);
|
|
755
|
+
localCircle(g, 2, r, h, color);
|
|
756
|
+
localCircle(g, 2, r, -h, color);
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
const top = shape === "cone" ? 0 : r;
|
|
760
|
+
localCircle(g, 1, r, -h, color);
|
|
761
|
+
if (top > 0) localCircle(g, 1, top, h, color);
|
|
762
|
+
for (let k = 0; k < 4; k++) {
|
|
763
|
+
const a = k / 4 * Math.PI * 2;
|
|
764
|
+
local(pa, Math.cos(a) * r, -h, Math.sin(a) * r);
|
|
765
|
+
local(pb, Math.cos(a) * top, h, Math.sin(a) * top);
|
|
766
|
+
g.line(pa, pb, color);
|
|
767
|
+
}
|
|
768
|
+
if (shape === "capsule") {
|
|
769
|
+
localCircle(g, 0, r, h, color);
|
|
770
|
+
localCircle(g, 0, r, -h, color);
|
|
771
|
+
localCircle(g, 2, r, h, color);
|
|
772
|
+
localCircle(g, 2, r, -h, color);
|
|
773
|
+
}
|
|
774
|
+
return;
|
|
775
|
+
}
|
|
776
|
+
case "heightfield":
|
|
777
|
+
drawHeightfield(p, g, collider, color);
|
|
778
|
+
return;
|
|
779
|
+
default: {
|
|
780
|
+
const vertices = collider.vertices?.();
|
|
781
|
+
const indices = collider.indices?.();
|
|
782
|
+
if (!vertices || vertices.length === 0) return;
|
|
783
|
+
const d = p.dim;
|
|
784
|
+
const edge = (i, j) => {
|
|
785
|
+
local(pa, vertices[i * d], vertices[i * d + 1], d === 3 ? vertices[i * d + 2] : 0);
|
|
786
|
+
local(pb, vertices[j * d], vertices[j * d + 1], d === 3 ? vertices[j * d + 2] : 0);
|
|
787
|
+
g.line(pa, pb, color);
|
|
788
|
+
};
|
|
789
|
+
if (indices && indices.length > 0) {
|
|
790
|
+
const stride = shape === "polyline" || is2d ? 2 : 3;
|
|
791
|
+
const limit = Math.min(indices.length, 3e4);
|
|
792
|
+
for (let i = 0; i + stride - 1 < limit; i += stride) {
|
|
793
|
+
for (let k = 0; k < stride; k++) {
|
|
794
|
+
if (stride === 2 && k === 1) break;
|
|
795
|
+
edge(indices[i + k], indices[i + (k + 1) % stride]);
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
} else {
|
|
799
|
+
const n = vertices.length / d;
|
|
800
|
+
for (let i = 0; i + 1 < n; i++) edge(i, i + 1);
|
|
801
|
+
if (shape === "convex" && n > 2) edge(n - 1, 0);
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
function drawHeightfield(p, g, collider, color) {
|
|
807
|
+
const hf = collider;
|
|
808
|
+
const heights = hf.heightfieldHeights();
|
|
809
|
+
const scale = hf.heightfieldScale();
|
|
810
|
+
if (p.dim === 2) {
|
|
811
|
+
const n = heights.length;
|
|
812
|
+
for (let i = 0; i < n; i++) {
|
|
813
|
+
local(pb, (i / (n - 1) - 0.5) * scale.x, heights[i] * scale.y, 0);
|
|
814
|
+
if (i > 0) g.line(pa, pb, color);
|
|
815
|
+
pa[0] = pb[0];
|
|
816
|
+
pa[1] = pb[1];
|
|
817
|
+
pa[2] = pb[2];
|
|
818
|
+
}
|
|
819
|
+
return;
|
|
820
|
+
}
|
|
821
|
+
const rows = hf.heightfieldNRows() + 1;
|
|
822
|
+
const cols = hf.heightfieldNCols() + 1;
|
|
823
|
+
const sz = scale.z ?? 1;
|
|
824
|
+
const at = (out, r, c) => local(
|
|
825
|
+
out,
|
|
826
|
+
(c / (cols - 1) - 0.5) * scale.x,
|
|
827
|
+
heights[c * rows + r] * scale.y,
|
|
828
|
+
(r / (rows - 1) - 0.5) * sz
|
|
829
|
+
);
|
|
830
|
+
const step2 = Math.max(1, Math.ceil(Math.max(rows, cols) / 64));
|
|
831
|
+
for (let r = 0; r < rows; r += step2) {
|
|
832
|
+
for (let c = 0; c + step2 < cols; c += step2) {
|
|
833
|
+
at(pa, r, c);
|
|
834
|
+
at(pb, r, c + step2);
|
|
835
|
+
g.line(pa, pb, color);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
838
|
+
for (let c = 0; c < cols; c += step2) {
|
|
839
|
+
for (let r = 0; r + step2 < rows; r += step2) {
|
|
840
|
+
at(pa, r, c);
|
|
841
|
+
at(pb, r + step2, c);
|
|
842
|
+
g.line(pa, pb, color);
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
export {
|
|
847
|
+
Physics,
|
|
848
|
+
PhysicsSystems,
|
|
849
|
+
collidersOverlay,
|
|
850
|
+
physics,
|
|
851
|
+
physics2dPlugin,
|
|
852
|
+
physics3dPlugin
|
|
853
|
+
};
|