@frsource/babylon-box3d 1.0.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/LICENSE +21 -0
- package/README.md +95 -0
- package/dist/index.d.mts +782 -0
- package/dist/index.d.ts +782 -0
- package/dist/index.js +1243 -0
- package/dist/index.mjs +1229 -0
- package/package.json +72 -0
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,1229 @@
|
|
|
1
|
+
// box3dPlugin.ts
|
|
2
|
+
import {
|
|
3
|
+
BoundingBox,
|
|
4
|
+
Matrix,
|
|
5
|
+
Observable,
|
|
6
|
+
PhysicsConstraintAxis,
|
|
7
|
+
PhysicsConstraintAxisLimitMode,
|
|
8
|
+
PhysicsConstraintType,
|
|
9
|
+
PhysicsEventType,
|
|
10
|
+
PhysicsMotionType,
|
|
11
|
+
PhysicsShapeType,
|
|
12
|
+
Quaternion,
|
|
13
|
+
Vector3,
|
|
14
|
+
VertexBuffer
|
|
15
|
+
} from "@babylonjs/core";
|
|
16
|
+
var DEFAULT_SUB_STEP_COUNT = 4;
|
|
17
|
+
function toB3Vec(v) {
|
|
18
|
+
return { x: v.x, y: v.y, z: v.z };
|
|
19
|
+
}
|
|
20
|
+
function fromB3Vec(v, out) {
|
|
21
|
+
if (out) {
|
|
22
|
+
out.set(v.x, v.y, v.z);
|
|
23
|
+
return out;
|
|
24
|
+
}
|
|
25
|
+
return new Vector3(v.x, v.y, v.z);
|
|
26
|
+
}
|
|
27
|
+
function toB3Quat(q) {
|
|
28
|
+
return { x: q.x, y: q.y, z: q.z, w: q.w };
|
|
29
|
+
}
|
|
30
|
+
function fromB3Quat(q, out) {
|
|
31
|
+
if (out) {
|
|
32
|
+
out.set(q.x, q.y, q.z, q.w);
|
|
33
|
+
return out;
|
|
34
|
+
}
|
|
35
|
+
return new Quaternion(q.x, q.y, q.z, q.w);
|
|
36
|
+
}
|
|
37
|
+
function quaternionFromXAxis(axis) {
|
|
38
|
+
const from = Vector3.Right();
|
|
39
|
+
const to = axis.normalizeToNew();
|
|
40
|
+
const dot = Vector3.Dot(from, to);
|
|
41
|
+
if (dot > 1 - 1e-6) {
|
|
42
|
+
return Quaternion.Identity();
|
|
43
|
+
}
|
|
44
|
+
if (dot < -1 + 1e-6) {
|
|
45
|
+
const perp = Math.abs(from.y) < 0.99 ? Vector3.Up() : Vector3.Forward();
|
|
46
|
+
const axisOfRotation2 = Vector3.Cross(from, perp).normalize();
|
|
47
|
+
return Quaternion.RotationAxis(axisOfRotation2, Math.PI);
|
|
48
|
+
}
|
|
49
|
+
const axisOfRotation = Vector3.Cross(from, to);
|
|
50
|
+
const s = Math.sqrt((1 + dot) * 2);
|
|
51
|
+
const invS = 1 / s;
|
|
52
|
+
return new Quaternion(
|
|
53
|
+
axisOfRotation.x * invS,
|
|
54
|
+
axisOfRotation.y * invS,
|
|
55
|
+
axisOfRotation.z * invS,
|
|
56
|
+
s * 0.5
|
|
57
|
+
).normalize();
|
|
58
|
+
}
|
|
59
|
+
var Box3DBodyPluginData = class {
|
|
60
|
+
constructor(native) {
|
|
61
|
+
this.native = native;
|
|
62
|
+
this.shape = null;
|
|
63
|
+
this.attachments = [];
|
|
64
|
+
this.motionType = PhysicsMotionType.STATIC;
|
|
65
|
+
this.eventMask = 0;
|
|
66
|
+
this.collisionCBEnabled = false;
|
|
67
|
+
this.collisionEndedCBEnabled = false;
|
|
68
|
+
this.onCollisionObservable = new Observable();
|
|
69
|
+
this.onCollisionEndedObservable = new Observable();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
var Box3DShapeDescriptor = class {
|
|
73
|
+
constructor(type, parameters) {
|
|
74
|
+
this.type = type;
|
|
75
|
+
this.parameters = parameters;
|
|
76
|
+
this.filterMembershipMask = 1;
|
|
77
|
+
this.filterCollideMask = 4294967295;
|
|
78
|
+
this.material = { friction: 0.5, restitution: 0 };
|
|
79
|
+
this.density = 1;
|
|
80
|
+
this.isTrigger = false;
|
|
81
|
+
this.children = [];
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
var Box3DConstraintPluginData = class {
|
|
85
|
+
constructor(type, bodyPair, buildJoint) {
|
|
86
|
+
this.type = type;
|
|
87
|
+
this.bodyPair = bodyPair;
|
|
88
|
+
this.buildJoint = buildJoint;
|
|
89
|
+
this.native = null;
|
|
90
|
+
this.enabled = true;
|
|
91
|
+
this.collideConnected = false;
|
|
92
|
+
this.axisMotorType = /* @__PURE__ */ new Map();
|
|
93
|
+
this.axisMotorTarget = /* @__PURE__ */ new Map();
|
|
94
|
+
this.axisMotorMaxForce = /* @__PURE__ */ new Map();
|
|
95
|
+
this.axisLimitMode = /* @__PURE__ */ new Map();
|
|
96
|
+
this.axisMinLimit = /* @__PURE__ */ new Map();
|
|
97
|
+
this.axisMaxLimit = /* @__PURE__ */ new Map();
|
|
98
|
+
this.axisFriction = /* @__PURE__ */ new Map();
|
|
99
|
+
}
|
|
100
|
+
rebuild() {
|
|
101
|
+
if (this.native && this.native.isValid()) {
|
|
102
|
+
this.native.destroy(true);
|
|
103
|
+
}
|
|
104
|
+
this.native = this.enabled ? this.buildJoint(this) : null;
|
|
105
|
+
}
|
|
106
|
+
};
|
|
107
|
+
var Box3DPlugin = class {
|
|
108
|
+
/**
|
|
109
|
+
* @param box3d - The resolved box3d-wasm module, e.g. `await Box3D()`.
|
|
110
|
+
* @param worldOptions - Forwarded to box3d-wasm's `World` constructor (gravity, sleep, continuous collision, thread count, ...).
|
|
111
|
+
*/
|
|
112
|
+
constructor(box3d, worldOptions) {
|
|
113
|
+
this.name = "Box3D";
|
|
114
|
+
this.onCollisionObservable = new Observable();
|
|
115
|
+
this.onCollisionEndedObservable = new Observable();
|
|
116
|
+
this.onTriggerCollisionObservable = new Observable();
|
|
117
|
+
this._timeStep = 1 / 60;
|
|
118
|
+
this._subStepCount = DEFAULT_SUB_STEP_COUNT;
|
|
119
|
+
this._maxLinearVelocity = Infinity;
|
|
120
|
+
this._maxAngularVelocity = Infinity;
|
|
121
|
+
this._bodies = /* @__PURE__ */ new Map();
|
|
122
|
+
this._shapeOwners = /* @__PURE__ */ new Map();
|
|
123
|
+
this._warnedAboutInstancing = false;
|
|
124
|
+
this.world = new box3d.World(worldOptions);
|
|
125
|
+
}
|
|
126
|
+
setGravity(gravity) {
|
|
127
|
+
this.world.setGravity(toB3Vec(gravity));
|
|
128
|
+
}
|
|
129
|
+
setTimeStep(timeStep) {
|
|
130
|
+
this._timeStep = timeStep;
|
|
131
|
+
}
|
|
132
|
+
getTimeStep() {
|
|
133
|
+
return this._timeStep;
|
|
134
|
+
}
|
|
135
|
+
/** Number of box3d solver sub-steps per `executeStep` call. Defaults to 4, matching box3d's own samples. */
|
|
136
|
+
setSubStepCount(subStepCount) {
|
|
137
|
+
this._subStepCount = subStepCount;
|
|
138
|
+
}
|
|
139
|
+
getPluginVersion() {
|
|
140
|
+
return 2;
|
|
141
|
+
}
|
|
142
|
+
setVelocityLimits(maxLinearVelocity, maxAngularVelocity) {
|
|
143
|
+
this._maxLinearVelocity = maxLinearVelocity;
|
|
144
|
+
this._maxAngularVelocity = maxAngularVelocity;
|
|
145
|
+
}
|
|
146
|
+
getMaxLinearVelocity() {
|
|
147
|
+
return this._maxLinearVelocity;
|
|
148
|
+
}
|
|
149
|
+
getMaxAngularVelocity() {
|
|
150
|
+
return this._maxAngularVelocity;
|
|
151
|
+
}
|
|
152
|
+
executeStep(delta, bodies) {
|
|
153
|
+
for (const body of bodies) {
|
|
154
|
+
if (!body.disablePreStep) {
|
|
155
|
+
this._prestep(body);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
this.world.step(delta, this._subStepCount);
|
|
159
|
+
if (Number.isFinite(this._maxLinearVelocity)) {
|
|
160
|
+
this._clampLinearVelocities(bodies);
|
|
161
|
+
}
|
|
162
|
+
for (const body of bodies) {
|
|
163
|
+
if (!body.disableSync) {
|
|
164
|
+
this.sync(body);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
this._dispatchEvents();
|
|
168
|
+
}
|
|
169
|
+
_clampLinearVelocities(bodies) {
|
|
170
|
+
const max = this._maxLinearVelocity;
|
|
171
|
+
for (const body of bodies) {
|
|
172
|
+
const data = this._getPluginData(body);
|
|
173
|
+
const v = data.native.getLinearVelocity();
|
|
174
|
+
const speed = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
|
|
175
|
+
if (speed > max && speed > 0) {
|
|
176
|
+
const scale = max / speed;
|
|
177
|
+
data.native.setLinearVelocity({
|
|
178
|
+
x: v.x * scale,
|
|
179
|
+
y: v.y * scale,
|
|
180
|
+
z: v.z * scale
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/** Pushes kinematic/animated transform-node motion into box3d before stepping. Dynamic bodies are driven by the solver and skip this. */
|
|
186
|
+
_prestep(body) {
|
|
187
|
+
const data = body._pluginData;
|
|
188
|
+
if (!data || data.motionType === PhysicsMotionType.DYNAMIC) {
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
const node = body.transformNode;
|
|
192
|
+
const position = node.position;
|
|
193
|
+
const rotation = node.rotationQuaternion ?? Quaternion.FromEulerVector(node.rotation);
|
|
194
|
+
data.native.setTargetTransform(
|
|
195
|
+
{ position: toB3Vec(position), rotation: toB3Quat(rotation) },
|
|
196
|
+
this._timeStep,
|
|
197
|
+
true
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
// -------------------------------------------------------------------
|
|
201
|
+
// Body
|
|
202
|
+
// -------------------------------------------------------------------
|
|
203
|
+
initBody(body, motionType, position, orientation) {
|
|
204
|
+
const native = this.world.createBody({
|
|
205
|
+
type: toBox3DBodyType(motionType),
|
|
206
|
+
position: toB3Vec(position),
|
|
207
|
+
rotation: toB3Quat(orientation),
|
|
208
|
+
isAwake: !body.startAsleep
|
|
209
|
+
});
|
|
210
|
+
const data = new Box3DBodyPluginData(native);
|
|
211
|
+
data.motionType = motionType;
|
|
212
|
+
body._pluginData = data;
|
|
213
|
+
this._bodies.set(body, data);
|
|
214
|
+
}
|
|
215
|
+
initBodyInstances(body, motionType, mesh) {
|
|
216
|
+
if (!this._warnedAboutInstancing) {
|
|
217
|
+
this._warnedAboutInstancing = true;
|
|
218
|
+
console.warn(
|
|
219
|
+
"Box3DPlugin: box3d-wasm has no batched rigid body instancing; only the master mesh transform is simulated."
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
const position = mesh.position.clone();
|
|
223
|
+
const rotation = mesh.rotationQuaternion?.clone() ?? Quaternion.FromEulerVector(mesh.rotation);
|
|
224
|
+
this.initBody(body, motionType, position, rotation);
|
|
225
|
+
}
|
|
226
|
+
updateBodyInstances(_body, _mesh) {
|
|
227
|
+
}
|
|
228
|
+
removeBody(body) {
|
|
229
|
+
const data = this._bodies.get(body);
|
|
230
|
+
if (!data) {
|
|
231
|
+
return;
|
|
232
|
+
}
|
|
233
|
+
this._destroyAttachments(data);
|
|
234
|
+
if (data.native.isValid()) {
|
|
235
|
+
data.native.destroy();
|
|
236
|
+
}
|
|
237
|
+
this._bodies.delete(body);
|
|
238
|
+
}
|
|
239
|
+
sync(body) {
|
|
240
|
+
this.syncTransform(body, body.transformNode);
|
|
241
|
+
}
|
|
242
|
+
syncTransform(body, transformNode) {
|
|
243
|
+
const data = body._pluginData;
|
|
244
|
+
if (!data || data.motionType !== PhysicsMotionType.DYNAMIC) {
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
247
|
+
fromB3Vec(data.native.getPosition(), transformNode.position);
|
|
248
|
+
transformNode.rotationQuaternion = fromB3Quat(
|
|
249
|
+
data.native.getRotation(),
|
|
250
|
+
transformNode.rotationQuaternion ?? new Quaternion()
|
|
251
|
+
);
|
|
252
|
+
}
|
|
253
|
+
setShape(body, shape) {
|
|
254
|
+
const data = this._getPluginData(body);
|
|
255
|
+
this._destroyAttachments(data);
|
|
256
|
+
data.shape = shape;
|
|
257
|
+
if (shape) {
|
|
258
|
+
this._attachShape(
|
|
259
|
+
body,
|
|
260
|
+
data,
|
|
261
|
+
shape,
|
|
262
|
+
Vector3.Zero(),
|
|
263
|
+
Quaternion.Identity(),
|
|
264
|
+
Vector3.One()
|
|
265
|
+
);
|
|
266
|
+
data.native.applyMassFromShapes();
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
getShape(body) {
|
|
270
|
+
return this._getPluginData(body).shape;
|
|
271
|
+
}
|
|
272
|
+
getShapeType(shape) {
|
|
273
|
+
return this._getShapeData(shape).type;
|
|
274
|
+
}
|
|
275
|
+
setEventMask(body, eventMask) {
|
|
276
|
+
this._getPluginData(body).eventMask = eventMask;
|
|
277
|
+
}
|
|
278
|
+
getEventMask(body) {
|
|
279
|
+
return this._getPluginData(body).eventMask;
|
|
280
|
+
}
|
|
281
|
+
setMotionType(body, motionType) {
|
|
282
|
+
const data = this._getPluginData(body);
|
|
283
|
+
data.motionType = motionType;
|
|
284
|
+
data.native.setType(toBox3DBodyType(motionType));
|
|
285
|
+
}
|
|
286
|
+
getMotionType(body) {
|
|
287
|
+
return this._getPluginData(body).motionType;
|
|
288
|
+
}
|
|
289
|
+
computeMassProperties(body) {
|
|
290
|
+
const data = this._getPluginData(body);
|
|
291
|
+
data.native.applyMassFromShapes();
|
|
292
|
+
return {
|
|
293
|
+
mass: data.native.getMass(),
|
|
294
|
+
centerOfMass: fromB3Vec(data.native.getLocalCenterOfMass())
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
setMassProperties(body, massProps) {
|
|
298
|
+
const data = this._getPluginData(body);
|
|
299
|
+
if (massProps.inertia || massProps.inertiaOrientation || massProps.centerOfMass) {
|
|
300
|
+
console.warn(
|
|
301
|
+
"Box3DPlugin: box3d-wasm derives inertia and center of mass from shape density; only `mass` can be overridden."
|
|
302
|
+
);
|
|
303
|
+
}
|
|
304
|
+
if (massProps.mass === void 0) {
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
data.native.applyMassFromShapes();
|
|
308
|
+
const currentMass = data.native.getMass();
|
|
309
|
+
if (currentMass <= 0 || data.attachments.length === 0) {
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
const scale = massProps.mass / currentMass;
|
|
313
|
+
for (const attachment of data.attachments) {
|
|
314
|
+
attachment.native.setDensity(
|
|
315
|
+
attachment.native.getDensity() * scale,
|
|
316
|
+
false
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
data.native.applyMassFromShapes();
|
|
320
|
+
}
|
|
321
|
+
getMassProperties(body) {
|
|
322
|
+
const data = this._getPluginData(body);
|
|
323
|
+
return {
|
|
324
|
+
mass: data.native.getMass(),
|
|
325
|
+
centerOfMass: fromB3Vec(data.native.getLocalCenterOfMass())
|
|
326
|
+
};
|
|
327
|
+
}
|
|
328
|
+
setLinearDamping(body, damping) {
|
|
329
|
+
this._getPluginData(body).native.setLinearDamping(damping);
|
|
330
|
+
}
|
|
331
|
+
getLinearDamping(body) {
|
|
332
|
+
return this._getPluginData(body).native.getLinearDamping();
|
|
333
|
+
}
|
|
334
|
+
setAngularDamping(body, damping) {
|
|
335
|
+
this._getPluginData(body).native.setAngularDamping(damping);
|
|
336
|
+
}
|
|
337
|
+
getAngularDamping(body) {
|
|
338
|
+
return this._getPluginData(body).native.getAngularDamping();
|
|
339
|
+
}
|
|
340
|
+
setLinearVelocity(body, linVel) {
|
|
341
|
+
this._getPluginData(body).native.setLinearVelocity(toB3Vec(linVel));
|
|
342
|
+
}
|
|
343
|
+
getLinearVelocityToRef(body, linVel) {
|
|
344
|
+
fromB3Vec(this._getPluginData(body).native.getLinearVelocity(), linVel);
|
|
345
|
+
}
|
|
346
|
+
applyImpulse(body, impulse, location) {
|
|
347
|
+
this._getPluginData(body).native.applyLinearImpulse(
|
|
348
|
+
toB3Vec(impulse),
|
|
349
|
+
toB3Vec(location),
|
|
350
|
+
true
|
|
351
|
+
);
|
|
352
|
+
}
|
|
353
|
+
applyAngularImpulse(body, angularImpulse) {
|
|
354
|
+
this._getPluginData(body).native.applyAngularImpulse(
|
|
355
|
+
toB3Vec(angularImpulse),
|
|
356
|
+
true
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
applyForce(body, force, location) {
|
|
360
|
+
this._getPluginData(body).native.applyForce(
|
|
361
|
+
toB3Vec(force),
|
|
362
|
+
toB3Vec(location),
|
|
363
|
+
true
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
applyTorque(body, torque) {
|
|
367
|
+
this._getPluginData(body).native.applyTorque(toB3Vec(torque), true);
|
|
368
|
+
}
|
|
369
|
+
setAngularVelocity(body, angVel) {
|
|
370
|
+
this._getPluginData(body).native.setAngularVelocity(toB3Vec(angVel));
|
|
371
|
+
}
|
|
372
|
+
getAngularVelocityToRef(body, angVel) {
|
|
373
|
+
fromB3Vec(this._getPluginData(body).native.getAngularVelocity(), angVel);
|
|
374
|
+
}
|
|
375
|
+
getBodyGeometry(_body) {
|
|
376
|
+
return {};
|
|
377
|
+
}
|
|
378
|
+
disposeBody(body) {
|
|
379
|
+
const data = this._bodies.get(body);
|
|
380
|
+
if (!data) {
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
383
|
+
data.onCollisionObservable.clear();
|
|
384
|
+
data.onCollisionEndedObservable.clear();
|
|
385
|
+
this.removeBody(body);
|
|
386
|
+
body._pluginData = null;
|
|
387
|
+
}
|
|
388
|
+
setCollisionCallbackEnabled(body, enabled) {
|
|
389
|
+
const data = this._getPluginData(body);
|
|
390
|
+
data.collisionCBEnabled = enabled;
|
|
391
|
+
}
|
|
392
|
+
setCollisionEndedCallbackEnabled(body, enabled) {
|
|
393
|
+
const data = this._getPluginData(body);
|
|
394
|
+
data.collisionEndedCBEnabled = enabled;
|
|
395
|
+
}
|
|
396
|
+
addConstraint(body, childBody, constraint) {
|
|
397
|
+
this.initConstraint(constraint, body, childBody);
|
|
398
|
+
}
|
|
399
|
+
getCollisionObservable(body) {
|
|
400
|
+
return this._getPluginData(body).onCollisionObservable;
|
|
401
|
+
}
|
|
402
|
+
getCollisionEndedObservable(body) {
|
|
403
|
+
return this._getPluginData(body).onCollisionEndedObservable;
|
|
404
|
+
}
|
|
405
|
+
setGravityFactor(body, factor) {
|
|
406
|
+
this._getPluginData(body).native.setGravityScale(factor);
|
|
407
|
+
}
|
|
408
|
+
getGravityFactor(body) {
|
|
409
|
+
return this._getPluginData(body).native.getGravityScale();
|
|
410
|
+
}
|
|
411
|
+
setTargetTransform(body, position, rotation) {
|
|
412
|
+
this._getPluginData(body).native.setTargetTransform(
|
|
413
|
+
{ position: toB3Vec(position), rotation: toB3Quat(rotation) },
|
|
414
|
+
this._timeStep,
|
|
415
|
+
true
|
|
416
|
+
);
|
|
417
|
+
}
|
|
418
|
+
_getPluginData(body) {
|
|
419
|
+
const data = body._pluginData;
|
|
420
|
+
if (!data) {
|
|
421
|
+
throw new Error(
|
|
422
|
+
"Box3DPlugin: PhysicsBody has not been initialized by this plugin."
|
|
423
|
+
);
|
|
424
|
+
}
|
|
425
|
+
return data;
|
|
426
|
+
}
|
|
427
|
+
_destroyAttachments(data) {
|
|
428
|
+
for (const attachment of data.attachments) {
|
|
429
|
+
this._shapeOwners.delete(attachment.native.getUserData());
|
|
430
|
+
if (attachment.native.isValid()) {
|
|
431
|
+
attachment.native.destroy(false);
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
data.attachments = [];
|
|
435
|
+
}
|
|
436
|
+
// -------------------------------------------------------------------
|
|
437
|
+
// Shape
|
|
438
|
+
// -------------------------------------------------------------------
|
|
439
|
+
initShape(shape, type, options) {
|
|
440
|
+
if (type === PhysicsShapeType.MESH || type === PhysicsShapeType.HEIGHTFIELD || type === PhysicsShapeType.CYLINDER) {
|
|
441
|
+
throw new Error(
|
|
442
|
+
`Box3DPlugin: shape type ${PhysicsShapeType[type]} is not supported by box3d-wasm.`
|
|
443
|
+
);
|
|
444
|
+
}
|
|
445
|
+
shape._pluginData = new Box3DShapeDescriptor(type, options);
|
|
446
|
+
}
|
|
447
|
+
setShapeFilterMembershipMask(shape, membershipMask) {
|
|
448
|
+
this._getShapeData(shape).filterMembershipMask = membershipMask;
|
|
449
|
+
this._refreshShapeFilters(shape);
|
|
450
|
+
}
|
|
451
|
+
getShapeFilterMembershipMask(shape) {
|
|
452
|
+
return this._getShapeData(shape).filterMembershipMask;
|
|
453
|
+
}
|
|
454
|
+
setShapeFilterCollideMask(shape, collideMask) {
|
|
455
|
+
this._getShapeData(shape).filterCollideMask = collideMask;
|
|
456
|
+
this._refreshShapeFilters(shape);
|
|
457
|
+
}
|
|
458
|
+
getShapeFilterCollideMask(shape) {
|
|
459
|
+
return this._getShapeData(shape).filterCollideMask;
|
|
460
|
+
}
|
|
461
|
+
setMaterial(shape, material) {
|
|
462
|
+
const data = this._getShapeData(shape);
|
|
463
|
+
data.material = material;
|
|
464
|
+
for (const native of this._nativeShapesOf(shape)) {
|
|
465
|
+
native.setFriction(material.friction ?? 0.5);
|
|
466
|
+
native.setRestitution(material.restitution ?? 0);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
getMaterial(shape) {
|
|
470
|
+
return this._getShapeData(shape).material;
|
|
471
|
+
}
|
|
472
|
+
setDensity(shape, density) {
|
|
473
|
+
const data = this._getShapeData(shape);
|
|
474
|
+
data.density = density;
|
|
475
|
+
for (const native of this._nativeShapesOf(shape)) {
|
|
476
|
+
native.setDensity(density, true);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
getDensity(shape) {
|
|
480
|
+
return this._getShapeData(shape).density;
|
|
481
|
+
}
|
|
482
|
+
addChild(shape, newChild, translation, rotation, scale) {
|
|
483
|
+
const data = this._getShapeData(shape);
|
|
484
|
+
data.children.push({
|
|
485
|
+
shape: newChild,
|
|
486
|
+
translation: translation?.clone() ?? Vector3.Zero(),
|
|
487
|
+
rotation: rotation?.clone() ?? Quaternion.Identity(),
|
|
488
|
+
scale: scale?.clone() ?? Vector3.One()
|
|
489
|
+
});
|
|
490
|
+
this._reattachOwners(shape);
|
|
491
|
+
}
|
|
492
|
+
removeChild(shape, childIndex) {
|
|
493
|
+
this._getShapeData(shape).children.splice(childIndex, 1);
|
|
494
|
+
this._reattachOwners(shape);
|
|
495
|
+
}
|
|
496
|
+
getNumChildren(shape) {
|
|
497
|
+
return this._getShapeData(shape).children.length;
|
|
498
|
+
}
|
|
499
|
+
getBoundingBox(shape) {
|
|
500
|
+
const { min, max } = computeLocalBounds(this._getShapeData(shape));
|
|
501
|
+
return new BoundingBox(min, max);
|
|
502
|
+
}
|
|
503
|
+
getBodyBoundingBox(body) {
|
|
504
|
+
const aabb = this._getPluginData(body).native.computeAABB();
|
|
505
|
+
return new BoundingBox(
|
|
506
|
+
fromB3Vec(aabb.lowerBound),
|
|
507
|
+
fromB3Vec(aabb.upperBound)
|
|
508
|
+
);
|
|
509
|
+
}
|
|
510
|
+
disposeShape(shape) {
|
|
511
|
+
for (const [owner] of this._ownersOfShape(shape)) {
|
|
512
|
+
const attachmentIndex = owner.attachments.findIndex(
|
|
513
|
+
(a) => a.shape === shape
|
|
514
|
+
);
|
|
515
|
+
if (attachmentIndex < 0) {
|
|
516
|
+
continue;
|
|
517
|
+
}
|
|
518
|
+
const attachment = owner.attachments.splice(attachmentIndex, 1)[0];
|
|
519
|
+
if (!attachment) {
|
|
520
|
+
continue;
|
|
521
|
+
}
|
|
522
|
+
this._shapeOwners.delete(attachment.native.getUserData());
|
|
523
|
+
if (attachment.native.isValid()) {
|
|
524
|
+
attachment.native.destroy(true);
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
shape._pluginData = null;
|
|
528
|
+
}
|
|
529
|
+
setTrigger(shape, isTrigger) {
|
|
530
|
+
const data = this._getShapeData(shape);
|
|
531
|
+
data.isTrigger = isTrigger;
|
|
532
|
+
this._reattachOwners(shape);
|
|
533
|
+
}
|
|
534
|
+
_getShapeData(shape) {
|
|
535
|
+
const data = shape._pluginData;
|
|
536
|
+
if (!data) {
|
|
537
|
+
throw new Error(
|
|
538
|
+
"Box3DPlugin: PhysicsShape has not been initialized by this plugin."
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
return data;
|
|
542
|
+
}
|
|
543
|
+
_nativeShapesOf(shape) {
|
|
544
|
+
const result = [];
|
|
545
|
+
for (const data of this._bodies.values()) {
|
|
546
|
+
for (const attachment of data.attachments) {
|
|
547
|
+
if (attachment.shape === shape) {
|
|
548
|
+
result.push(attachment.native);
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
}
|
|
552
|
+
return result;
|
|
553
|
+
}
|
|
554
|
+
*_ownersOfShape(shape) {
|
|
555
|
+
for (const [body, data] of this._bodies.entries()) {
|
|
556
|
+
if (data.attachments.some((a) => a.shape === shape)) {
|
|
557
|
+
yield [data, body];
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
/** Re-creates every body's attachment tree that (transitively) references `shape`, e.g. after `addChild`/`removeChild`/`setTrigger`. */
|
|
562
|
+
_reattachOwners(shape) {
|
|
563
|
+
for (const [body, data] of this._bodies.entries()) {
|
|
564
|
+
if (data.shape && shapeTreeContains(data.shape, shape)) {
|
|
565
|
+
this._destroyAttachments(data);
|
|
566
|
+
this._attachShape(
|
|
567
|
+
body,
|
|
568
|
+
data,
|
|
569
|
+
data.shape,
|
|
570
|
+
Vector3.Zero(),
|
|
571
|
+
Quaternion.Identity(),
|
|
572
|
+
Vector3.One()
|
|
573
|
+
);
|
|
574
|
+
data.native.applyMassFromShapes();
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
_refreshShapeFilters(shape) {
|
|
579
|
+
const data = this._getShapeData(shape);
|
|
580
|
+
for (const native of this._nativeShapesOf(shape)) {
|
|
581
|
+
native.setFilter({
|
|
582
|
+
categoryBits: data.filterMembershipMask,
|
|
583
|
+
maskBits: data.filterCollideMask
|
|
584
|
+
});
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
/** Recursively creates native box3d shapes for `shape` (and, for CONTAINER shapes, its children) on `bodyData.native`, composing local transforms along the way. */
|
|
588
|
+
_attachShape(owner, bodyData, shape, parentT, parentR, parentS) {
|
|
589
|
+
const data = this._getShapeData(shape);
|
|
590
|
+
if (data.type !== PhysicsShapeType.CONTAINER) {
|
|
591
|
+
const native = createNativeShape(
|
|
592
|
+
bodyData.native,
|
|
593
|
+
data,
|
|
594
|
+
parentT,
|
|
595
|
+
parentR,
|
|
596
|
+
parentS
|
|
597
|
+
);
|
|
598
|
+
native.setUserData(nextShapeTag());
|
|
599
|
+
native.enableContactEvents(true);
|
|
600
|
+
native.enableHitEvents(true);
|
|
601
|
+
native.enableSensorEvents(true);
|
|
602
|
+
native.setFilter({
|
|
603
|
+
categoryBits: data.filterMembershipMask,
|
|
604
|
+
maskBits: data.filterCollideMask
|
|
605
|
+
});
|
|
606
|
+
bodyData.attachments.push({ shape, native });
|
|
607
|
+
this._shapeOwners.set(native.getUserData(), { body: owner, shape });
|
|
608
|
+
}
|
|
609
|
+
for (const child of data.children) {
|
|
610
|
+
const t = parentT.add(
|
|
611
|
+
Vector3.TransformCoordinates(
|
|
612
|
+
child.translation.multiply(parentS),
|
|
613
|
+
Matrix_fromQuaternion(parentR)
|
|
614
|
+
)
|
|
615
|
+
);
|
|
616
|
+
const r = parentR.multiply(child.rotation);
|
|
617
|
+
const s = parentS.multiply(child.scale);
|
|
618
|
+
this._attachShape(owner, bodyData, child.shape, t, r, s);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
// -------------------------------------------------------------------
|
|
622
|
+
// Constraint
|
|
623
|
+
// -------------------------------------------------------------------
|
|
624
|
+
initConstraint(constraint, body, childBody) {
|
|
625
|
+
const bodyDataA = this._getPluginData(body);
|
|
626
|
+
const bodyDataB = this._getPluginData(childBody);
|
|
627
|
+
const options = constraint.options;
|
|
628
|
+
const bodyPair = {
|
|
629
|
+
parentBody: body,
|
|
630
|
+
parentBodyIndex: 0,
|
|
631
|
+
childBody,
|
|
632
|
+
childBodyIndex: 0
|
|
633
|
+
};
|
|
634
|
+
const buildJoint = createJointFactory(
|
|
635
|
+
this.world,
|
|
636
|
+
bodyDataA.native,
|
|
637
|
+
bodyDataB.native,
|
|
638
|
+
constraint.type,
|
|
639
|
+
options
|
|
640
|
+
);
|
|
641
|
+
const pluginData = new Box3DConstraintPluginData(
|
|
642
|
+
constraint.type,
|
|
643
|
+
bodyPair,
|
|
644
|
+
buildJoint
|
|
645
|
+
);
|
|
646
|
+
pluginData.collideConnected = options.collision ?? false;
|
|
647
|
+
constraint._pluginData = pluginData;
|
|
648
|
+
pluginData.rebuild();
|
|
649
|
+
}
|
|
650
|
+
setEnabled(constraint, isEnabled) {
|
|
651
|
+
const data = this._getConstraintData(constraint);
|
|
652
|
+
data.enabled = isEnabled;
|
|
653
|
+
data.rebuild();
|
|
654
|
+
}
|
|
655
|
+
getEnabled(constraint) {
|
|
656
|
+
return this._getConstraintData(constraint).enabled;
|
|
657
|
+
}
|
|
658
|
+
setCollisionsEnabled(constraint, isEnabled) {
|
|
659
|
+
const data = this._getConstraintData(constraint);
|
|
660
|
+
data.collideConnected = isEnabled;
|
|
661
|
+
data.native?.setCollideConnected(isEnabled);
|
|
662
|
+
}
|
|
663
|
+
getCollisionsEnabled(constraint) {
|
|
664
|
+
return this._getConstraintData(constraint).collideConnected;
|
|
665
|
+
}
|
|
666
|
+
setAxisFriction(constraint, axis, friction) {
|
|
667
|
+
const data = this._getConstraintData(constraint);
|
|
668
|
+
data.axisFriction.set(axis, friction);
|
|
669
|
+
data.rebuild();
|
|
670
|
+
}
|
|
671
|
+
getAxisFriction(constraint, axis) {
|
|
672
|
+
return this._getConstraintData(constraint).axisFriction.get(axis) ?? null;
|
|
673
|
+
}
|
|
674
|
+
setAxisMode(constraint, axis, limitMode) {
|
|
675
|
+
const data = this._getConstraintData(constraint);
|
|
676
|
+
data.axisLimitMode.set(axis, limitMode);
|
|
677
|
+
data.rebuild();
|
|
678
|
+
}
|
|
679
|
+
getAxisMode(constraint, axis) {
|
|
680
|
+
return this._getConstraintData(constraint).axisLimitMode.get(axis) ?? null;
|
|
681
|
+
}
|
|
682
|
+
setAxisMinLimit(constraint, axis, minLimit) {
|
|
683
|
+
const data = this._getConstraintData(constraint);
|
|
684
|
+
data.axisMinLimit.set(axis, minLimit);
|
|
685
|
+
data.rebuild();
|
|
686
|
+
}
|
|
687
|
+
getAxisMinLimit(constraint, axis) {
|
|
688
|
+
return this._getConstraintData(constraint).axisMinLimit.get(axis) ?? null;
|
|
689
|
+
}
|
|
690
|
+
setAxisMaxLimit(constraint, axis, limit) {
|
|
691
|
+
const data = this._getConstraintData(constraint);
|
|
692
|
+
data.axisMaxLimit.set(axis, limit);
|
|
693
|
+
data.rebuild();
|
|
694
|
+
}
|
|
695
|
+
getAxisMaxLimit(constraint, axis) {
|
|
696
|
+
return this._getConstraintData(constraint).axisMaxLimit.get(axis) ?? null;
|
|
697
|
+
}
|
|
698
|
+
setAxisMotorType(constraint, axis, motorType) {
|
|
699
|
+
const data = this._getConstraintData(constraint);
|
|
700
|
+
data.axisMotorType.set(axis, motorType);
|
|
701
|
+
data.rebuild();
|
|
702
|
+
}
|
|
703
|
+
getAxisMotorType(constraint, axis) {
|
|
704
|
+
return this._getConstraintData(constraint).axisMotorType.get(axis) ?? null;
|
|
705
|
+
}
|
|
706
|
+
setAxisMotorTarget(constraint, axis, target) {
|
|
707
|
+
const data = this._getConstraintData(constraint);
|
|
708
|
+
data.axisMotorTarget.set(axis, target);
|
|
709
|
+
data.rebuild();
|
|
710
|
+
}
|
|
711
|
+
getAxisMotorTarget(constraint, axis) {
|
|
712
|
+
return this._getConstraintData(constraint).axisMotorTarget.get(axis) ?? null;
|
|
713
|
+
}
|
|
714
|
+
setAxisMotorMaxForce(constraint, axis, maxForce) {
|
|
715
|
+
const data = this._getConstraintData(constraint);
|
|
716
|
+
data.axisMotorMaxForce.set(axis, maxForce);
|
|
717
|
+
data.rebuild();
|
|
718
|
+
}
|
|
719
|
+
getAxisMotorMaxForce(constraint, axis) {
|
|
720
|
+
return this._getConstraintData(constraint).axisMotorMaxForce.get(axis) ?? null;
|
|
721
|
+
}
|
|
722
|
+
disposeConstraint(constraint) {
|
|
723
|
+
const data = this._getConstraintData(constraint);
|
|
724
|
+
if (data.native?.isValid()) {
|
|
725
|
+
data.native.destroy(true);
|
|
726
|
+
}
|
|
727
|
+
constraint._pluginData = null;
|
|
728
|
+
}
|
|
729
|
+
getBodiesUsingConstraint(constraint) {
|
|
730
|
+
return [this._getConstraintData(constraint).bodyPair];
|
|
731
|
+
}
|
|
732
|
+
_getConstraintData(constraint) {
|
|
733
|
+
const data = constraint._pluginData;
|
|
734
|
+
if (!data) {
|
|
735
|
+
throw new Error(
|
|
736
|
+
"Box3DPlugin: PhysicsConstraint has not been initialized by this plugin."
|
|
737
|
+
);
|
|
738
|
+
}
|
|
739
|
+
return data;
|
|
740
|
+
}
|
|
741
|
+
// -------------------------------------------------------------------
|
|
742
|
+
// Raycast
|
|
743
|
+
// -------------------------------------------------------------------
|
|
744
|
+
raycast(from, to, result, query) {
|
|
745
|
+
const target = Array.isArray(result) ? result[0] : result;
|
|
746
|
+
if (!target) {
|
|
747
|
+
return;
|
|
748
|
+
}
|
|
749
|
+
target.reset(from, to);
|
|
750
|
+
const translation = to.subtract(from);
|
|
751
|
+
const hit = this.world.castRayClosest(toB3Vec(from), toB3Vec(translation), {
|
|
752
|
+
categoryBits: query?.membership,
|
|
753
|
+
maskBits: query?.collideWith
|
|
754
|
+
});
|
|
755
|
+
if (!hit.hit || hit.shapeUserData === void 0) {
|
|
756
|
+
return;
|
|
757
|
+
}
|
|
758
|
+
const owner = this._shapeOwners.get(hit.shapeUserData);
|
|
759
|
+
if (query?.ignoreBody && owner?.body === query.ignoreBody) {
|
|
760
|
+
return;
|
|
761
|
+
}
|
|
762
|
+
target.body = owner?.body;
|
|
763
|
+
target.shape = owner?.shape;
|
|
764
|
+
if (hit.normal && hit.point) {
|
|
765
|
+
target.setHitData(hit.normal, hit.point);
|
|
766
|
+
}
|
|
767
|
+
if (hit.fraction !== void 0) {
|
|
768
|
+
target.setHitDistance(hit.fraction * translation.length());
|
|
769
|
+
}
|
|
770
|
+
}
|
|
771
|
+
dispose() {
|
|
772
|
+
for (const body of [...this._bodies.keys()]) {
|
|
773
|
+
this.disposeBody(body);
|
|
774
|
+
}
|
|
775
|
+
this._shapeOwners.clear();
|
|
776
|
+
this.onCollisionObservable.clear();
|
|
777
|
+
this.onCollisionEndedObservable.clear();
|
|
778
|
+
this.onTriggerCollisionObservable.clear();
|
|
779
|
+
if (this.world.isValid()) {
|
|
780
|
+
this.world.destroy();
|
|
781
|
+
}
|
|
782
|
+
}
|
|
783
|
+
// -------------------------------------------------------------------
|
|
784
|
+
// Event dispatch
|
|
785
|
+
// -------------------------------------------------------------------
|
|
786
|
+
_dispatchEvents() {
|
|
787
|
+
const contacts = this.world.getContactEvents();
|
|
788
|
+
for (const begin of contacts.begin) {
|
|
789
|
+
this._notifyCollision(
|
|
790
|
+
begin.shapeUserDataA,
|
|
791
|
+
begin.shapeUserDataB,
|
|
792
|
+
PhysicsEventType.COLLISION_STARTED,
|
|
793
|
+
null,
|
|
794
|
+
null,
|
|
795
|
+
0
|
|
796
|
+
);
|
|
797
|
+
}
|
|
798
|
+
for (const end of contacts.end) {
|
|
799
|
+
if (end.shapeUserDataA === null || end.shapeUserDataB === null) {
|
|
800
|
+
continue;
|
|
801
|
+
}
|
|
802
|
+
this._notifyCollisionEnded(end.shapeUserDataA, end.shapeUserDataB);
|
|
803
|
+
}
|
|
804
|
+
for (const hit of contacts.hit) {
|
|
805
|
+
this._notifyCollision(
|
|
806
|
+
hit.shapeUserDataA,
|
|
807
|
+
hit.shapeUserDataB,
|
|
808
|
+
PhysicsEventType.COLLISION_CONTINUED,
|
|
809
|
+
fromB3Vec(hit.point),
|
|
810
|
+
fromB3Vec(hit.normal),
|
|
811
|
+
hit.approachSpeed
|
|
812
|
+
);
|
|
813
|
+
}
|
|
814
|
+
const sensors = this.world.getSensorEvents();
|
|
815
|
+
for (const begin of sensors.begin) {
|
|
816
|
+
this._notifyTrigger(
|
|
817
|
+
begin.sensorUserData,
|
|
818
|
+
begin.visitorUserData,
|
|
819
|
+
PhysicsEventType.TRIGGER_ENTERED
|
|
820
|
+
);
|
|
821
|
+
}
|
|
822
|
+
for (const end of sensors.end) {
|
|
823
|
+
if (end.sensorUserData === null || end.visitorUserData === null) {
|
|
824
|
+
continue;
|
|
825
|
+
}
|
|
826
|
+
this._notifyTrigger(
|
|
827
|
+
end.sensorUserData,
|
|
828
|
+
end.visitorUserData,
|
|
829
|
+
PhysicsEventType.TRIGGER_EXITED
|
|
830
|
+
);
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
_notifyCollision(tagA, tagB, type, point, normal, impulse) {
|
|
834
|
+
const ownerA = this._shapeOwners.get(tagA);
|
|
835
|
+
const ownerB = this._shapeOwners.get(tagB);
|
|
836
|
+
if (!ownerA || !ownerB) {
|
|
837
|
+
return;
|
|
838
|
+
}
|
|
839
|
+
const dataA = this._bodies.get(ownerA.body);
|
|
840
|
+
const dataB = this._bodies.get(ownerB.body);
|
|
841
|
+
const event = {
|
|
842
|
+
collider: ownerA.body,
|
|
843
|
+
collidedAgainst: ownerB.body,
|
|
844
|
+
colliderIndex: 0,
|
|
845
|
+
collidedAgainstIndex: 0,
|
|
846
|
+
type,
|
|
847
|
+
point,
|
|
848
|
+
normal,
|
|
849
|
+
distance: 0,
|
|
850
|
+
impulse
|
|
851
|
+
};
|
|
852
|
+
if (dataA?.collisionCBEnabled) {
|
|
853
|
+
dataA.onCollisionObservable.notifyObservers(event);
|
|
854
|
+
}
|
|
855
|
+
if (dataB?.collisionCBEnabled) {
|
|
856
|
+
dataB.onCollisionObservable.notifyObservers({
|
|
857
|
+
...event,
|
|
858
|
+
collider: ownerB.body,
|
|
859
|
+
collidedAgainst: ownerA.body
|
|
860
|
+
});
|
|
861
|
+
}
|
|
862
|
+
this.onCollisionObservable.notifyObservers(event);
|
|
863
|
+
}
|
|
864
|
+
_notifyCollisionEnded(tagA, tagB) {
|
|
865
|
+
const ownerA = this._shapeOwners.get(tagA);
|
|
866
|
+
const ownerB = this._shapeOwners.get(tagB);
|
|
867
|
+
if (!ownerA || !ownerB) {
|
|
868
|
+
return;
|
|
869
|
+
}
|
|
870
|
+
const dataA = this._bodies.get(ownerA.body);
|
|
871
|
+
const dataB = this._bodies.get(ownerB.body);
|
|
872
|
+
const event = {
|
|
873
|
+
collider: ownerA.body,
|
|
874
|
+
collidedAgainst: ownerB.body,
|
|
875
|
+
colliderIndex: 0,
|
|
876
|
+
collidedAgainstIndex: 0,
|
|
877
|
+
type: PhysicsEventType.COLLISION_FINISHED
|
|
878
|
+
};
|
|
879
|
+
if (dataA?.collisionEndedCBEnabled) {
|
|
880
|
+
dataA.onCollisionEndedObservable.notifyObservers(event);
|
|
881
|
+
}
|
|
882
|
+
if (dataB?.collisionEndedCBEnabled) {
|
|
883
|
+
dataB.onCollisionEndedObservable.notifyObservers({
|
|
884
|
+
...event,
|
|
885
|
+
collider: ownerB.body,
|
|
886
|
+
collidedAgainst: ownerA.body
|
|
887
|
+
});
|
|
888
|
+
}
|
|
889
|
+
this.onCollisionEndedObservable.notifyObservers(event);
|
|
890
|
+
}
|
|
891
|
+
_notifyTrigger(sensorTag, visitorTag, type) {
|
|
892
|
+
const sensorOwner = this._shapeOwners.get(sensorTag);
|
|
893
|
+
const visitorOwner = this._shapeOwners.get(visitorTag);
|
|
894
|
+
if (!sensorOwner || !visitorOwner) {
|
|
895
|
+
return;
|
|
896
|
+
}
|
|
897
|
+
this.onTriggerCollisionObservable.notifyObservers({
|
|
898
|
+
collider: sensorOwner.body,
|
|
899
|
+
collidedAgainst: visitorOwner.body,
|
|
900
|
+
colliderIndex: 0,
|
|
901
|
+
collidedAgainstIndex: 0,
|
|
902
|
+
type
|
|
903
|
+
});
|
|
904
|
+
}
|
|
905
|
+
};
|
|
906
|
+
function toBox3DBodyType(motionType) {
|
|
907
|
+
switch (motionType) {
|
|
908
|
+
case PhysicsMotionType.DYNAMIC:
|
|
909
|
+
return "dynamic";
|
|
910
|
+
case PhysicsMotionType.ANIMATED:
|
|
911
|
+
return "kinematic";
|
|
912
|
+
case PhysicsMotionType.STATIC:
|
|
913
|
+
default:
|
|
914
|
+
return "static";
|
|
915
|
+
}
|
|
916
|
+
}
|
|
917
|
+
var _nextShapeTag = 1;
|
|
918
|
+
function nextShapeTag() {
|
|
919
|
+
return _nextShapeTag++;
|
|
920
|
+
}
|
|
921
|
+
function Matrix_fromQuaternion(q) {
|
|
922
|
+
return q.toRotationMatrix(new Matrix());
|
|
923
|
+
}
|
|
924
|
+
function shapeTreeContains(root, target) {
|
|
925
|
+
if (root === target) {
|
|
926
|
+
return true;
|
|
927
|
+
}
|
|
928
|
+
const data = root._pluginData;
|
|
929
|
+
if (!data) {
|
|
930
|
+
return false;
|
|
931
|
+
}
|
|
932
|
+
return data.children.some(
|
|
933
|
+
(child) => shapeTreeContains(child.shape, target)
|
|
934
|
+
);
|
|
935
|
+
}
|
|
936
|
+
function computeLocalBounds(descriptor) {
|
|
937
|
+
const p = descriptor.parameters;
|
|
938
|
+
switch (descriptor.type) {
|
|
939
|
+
case PhysicsShapeType.BOX: {
|
|
940
|
+
const extents = p.extents ?? new Vector3(1, 1, 1);
|
|
941
|
+
const center = p.center ?? Vector3.Zero();
|
|
942
|
+
const half = extents.scale(0.5);
|
|
943
|
+
return { min: center.subtract(half), max: center.add(half) };
|
|
944
|
+
}
|
|
945
|
+
case PhysicsShapeType.SPHERE: {
|
|
946
|
+
const radius = p.radius ?? 0.5;
|
|
947
|
+
const center = p.center ?? Vector3.Zero();
|
|
948
|
+
const r = new Vector3(radius, radius, radius);
|
|
949
|
+
return { min: center.subtract(r), max: center.add(r) };
|
|
950
|
+
}
|
|
951
|
+
case PhysicsShapeType.CAPSULE: {
|
|
952
|
+
const radius = p.radius ?? 0.5;
|
|
953
|
+
const a = p.pointA ?? new Vector3(0, -0.5, 0);
|
|
954
|
+
const b = p.pointB ?? new Vector3(0, 0.5, 0);
|
|
955
|
+
const min = Vector3.Minimize(a, b).subtractFromFloats(
|
|
956
|
+
radius,
|
|
957
|
+
radius,
|
|
958
|
+
radius
|
|
959
|
+
);
|
|
960
|
+
const max = Vector3.Maximize(a, b).addInPlaceFromFloats(
|
|
961
|
+
radius,
|
|
962
|
+
radius,
|
|
963
|
+
radius
|
|
964
|
+
);
|
|
965
|
+
return { min, max };
|
|
966
|
+
}
|
|
967
|
+
case PhysicsShapeType.CONVEX_HULL: {
|
|
968
|
+
const points = extractHullPoints(p);
|
|
969
|
+
let min = points[0]?.clone() ?? Vector3.Zero();
|
|
970
|
+
let max = points[0]?.clone() ?? Vector3.Zero();
|
|
971
|
+
for (const point of points) {
|
|
972
|
+
min = Vector3.Minimize(min, point);
|
|
973
|
+
max = Vector3.Maximize(max, point);
|
|
974
|
+
}
|
|
975
|
+
return { min, max };
|
|
976
|
+
}
|
|
977
|
+
case PhysicsShapeType.CONTAINER: {
|
|
978
|
+
let min = new Vector3(Infinity, Infinity, Infinity);
|
|
979
|
+
let max = new Vector3(-Infinity, -Infinity, -Infinity);
|
|
980
|
+
for (const child of descriptor.children) {
|
|
981
|
+
const childData = child.shape._pluginData;
|
|
982
|
+
if (!childData) {
|
|
983
|
+
continue;
|
|
984
|
+
}
|
|
985
|
+
const childBounds = computeLocalBounds(childData);
|
|
986
|
+
const worldMin = Vector3.TransformCoordinates(
|
|
987
|
+
childBounds.min.multiply(child.scale),
|
|
988
|
+
Matrix_fromQuaternion(child.rotation)
|
|
989
|
+
).add(child.translation);
|
|
990
|
+
const worldMax = Vector3.TransformCoordinates(
|
|
991
|
+
childBounds.max.multiply(child.scale),
|
|
992
|
+
Matrix_fromQuaternion(child.rotation)
|
|
993
|
+
).add(child.translation);
|
|
994
|
+
min = Vector3.Minimize(min, Vector3.Minimize(worldMin, worldMax));
|
|
995
|
+
max = Vector3.Maximize(max, Vector3.Maximize(worldMin, worldMax));
|
|
996
|
+
}
|
|
997
|
+
return { min, max };
|
|
998
|
+
}
|
|
999
|
+
default:
|
|
1000
|
+
return { min: Vector3.Zero(), max: Vector3.Zero() };
|
|
1001
|
+
}
|
|
1002
|
+
}
|
|
1003
|
+
function extractHullPoints(parameters) {
|
|
1004
|
+
if (!parameters.mesh) {
|
|
1005
|
+
return [];
|
|
1006
|
+
}
|
|
1007
|
+
const positions = parameters.mesh.getVerticesData(VertexBuffer.PositionKind);
|
|
1008
|
+
if (!positions) {
|
|
1009
|
+
return [];
|
|
1010
|
+
}
|
|
1011
|
+
const points = [];
|
|
1012
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
1013
|
+
points.push(
|
|
1014
|
+
new Vector3(
|
|
1015
|
+
positions[i] ?? 0,
|
|
1016
|
+
positions[i + 1] ?? 0,
|
|
1017
|
+
positions[i + 2] ?? 0
|
|
1018
|
+
)
|
|
1019
|
+
);
|
|
1020
|
+
}
|
|
1021
|
+
return points;
|
|
1022
|
+
}
|
|
1023
|
+
function createNativeShape(nativeBody, descriptor, t, r, s) {
|
|
1024
|
+
const p = descriptor.parameters;
|
|
1025
|
+
const shared = {
|
|
1026
|
+
density: descriptor.density,
|
|
1027
|
+
friction: descriptor.material.friction ?? 0.5,
|
|
1028
|
+
restitution: descriptor.material.restitution ?? 0,
|
|
1029
|
+
isSensor: descriptor.isTrigger,
|
|
1030
|
+
filter: {
|
|
1031
|
+
categoryBits: descriptor.filterMembershipMask,
|
|
1032
|
+
maskBits: descriptor.filterCollideMask
|
|
1033
|
+
}
|
|
1034
|
+
};
|
|
1035
|
+
if (Math.abs(s.x - s.y) > 1e-4 || Math.abs(s.y - s.z) > 1e-4) {
|
|
1036
|
+
console.warn(
|
|
1037
|
+
"Box3DPlugin: non-uniform shape scaling is not supported; using the average scale factor."
|
|
1038
|
+
);
|
|
1039
|
+
}
|
|
1040
|
+
const uniformScale = (s.x + s.y + s.z) / 3;
|
|
1041
|
+
switch (descriptor.type) {
|
|
1042
|
+
case PhysicsShapeType.BOX: {
|
|
1043
|
+
const extents = (p.extents ?? new Vector3(1, 1, 1)).scale(uniformScale);
|
|
1044
|
+
const center = (p.center ?? Vector3.Zero()).multiply(s);
|
|
1045
|
+
const localT = t.add(
|
|
1046
|
+
Vector3.TransformCoordinates(center, Matrix_fromQuaternion(r))
|
|
1047
|
+
);
|
|
1048
|
+
const localR = r.multiply(p.rotation ?? Quaternion.Identity());
|
|
1049
|
+
return nativeBody.createBox({
|
|
1050
|
+
...shared,
|
|
1051
|
+
halfExtents: toB3Vec(extents.scale(0.5)),
|
|
1052
|
+
offset: toB3Vec(localT),
|
|
1053
|
+
rotation: toB3Quat(localR)
|
|
1054
|
+
});
|
|
1055
|
+
}
|
|
1056
|
+
case PhysicsShapeType.SPHERE: {
|
|
1057
|
+
const center = (p.center ?? Vector3.Zero()).multiply(s);
|
|
1058
|
+
const localT = t.add(
|
|
1059
|
+
Vector3.TransformCoordinates(center, Matrix_fromQuaternion(r))
|
|
1060
|
+
);
|
|
1061
|
+
return nativeBody.createSphere({
|
|
1062
|
+
...shared,
|
|
1063
|
+
radius: (p.radius ?? 0.5) * uniformScale,
|
|
1064
|
+
center: toB3Vec(localT)
|
|
1065
|
+
});
|
|
1066
|
+
}
|
|
1067
|
+
case PhysicsShapeType.CAPSULE: {
|
|
1068
|
+
const a = (p.pointA ?? new Vector3(0, -0.5, 0)).multiply(s);
|
|
1069
|
+
const b = (p.pointB ?? new Vector3(0, 0.5, 0)).multiply(s);
|
|
1070
|
+
const worldA = t.add(
|
|
1071
|
+
Vector3.TransformCoordinates(a, Matrix_fromQuaternion(r))
|
|
1072
|
+
);
|
|
1073
|
+
const worldB = t.add(
|
|
1074
|
+
Vector3.TransformCoordinates(b, Matrix_fromQuaternion(r))
|
|
1075
|
+
);
|
|
1076
|
+
return nativeBody.createCapsule({
|
|
1077
|
+
...shared,
|
|
1078
|
+
radius: (p.radius ?? 0.5) * uniformScale,
|
|
1079
|
+
center1: toB3Vec(worldA),
|
|
1080
|
+
center2: toB3Vec(worldB)
|
|
1081
|
+
});
|
|
1082
|
+
}
|
|
1083
|
+
case PhysicsShapeType.CONVEX_HULL: {
|
|
1084
|
+
const localPoints = extractHullPoints(p);
|
|
1085
|
+
const worldPoints = localPoints.map(
|
|
1086
|
+
(point) => t.add(
|
|
1087
|
+
Vector3.TransformCoordinates(
|
|
1088
|
+
point.multiply(s),
|
|
1089
|
+
Matrix_fromQuaternion(r)
|
|
1090
|
+
)
|
|
1091
|
+
)
|
|
1092
|
+
);
|
|
1093
|
+
return nativeBody.createHull({
|
|
1094
|
+
...shared,
|
|
1095
|
+
points: worldPoints.map(toB3Vec)
|
|
1096
|
+
});
|
|
1097
|
+
}
|
|
1098
|
+
default:
|
|
1099
|
+
throw new Error(
|
|
1100
|
+
`Box3DPlugin: shape type ${PhysicsShapeType[descriptor.type]} is not supported by box3d-wasm.`
|
|
1101
|
+
);
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
function createJointFactory(world, bodyA, bodyB, type, options) {
|
|
1105
|
+
const anchorA = options.pivotA ? toB3Vec(options.pivotA) : void 0;
|
|
1106
|
+
const anchorB = options.pivotB ? toB3Vec(options.pivotB) : void 0;
|
|
1107
|
+
return (data) => {
|
|
1108
|
+
const base = { anchorA, anchorB, collideConnected: data.collideConnected };
|
|
1109
|
+
switch (type) {
|
|
1110
|
+
case PhysicsConstraintType.BALL_AND_SOCKET: {
|
|
1111
|
+
const twist = readLimit(data, PhysicsConstraintAxis.ANGULAR_X);
|
|
1112
|
+
const motorSpeed = data.axisMotorTarget.get(
|
|
1113
|
+
PhysicsConstraintAxis.ANGULAR_X
|
|
1114
|
+
);
|
|
1115
|
+
return world.createSphericalJoint(bodyA, bodyB, {
|
|
1116
|
+
...base,
|
|
1117
|
+
enableTwistLimit: twist !== null,
|
|
1118
|
+
lowerTwistAngle: twist?.min,
|
|
1119
|
+
upperTwistAngle: twist?.max,
|
|
1120
|
+
enableMotor: motorSpeed !== void 0,
|
|
1121
|
+
motorVelocity: motorSpeed !== void 0 ? { x: motorSpeed, y: 0, z: 0 } : void 0,
|
|
1122
|
+
maxMotorTorque: data.axisMotorMaxForce.get(
|
|
1123
|
+
PhysicsConstraintAxis.ANGULAR_X
|
|
1124
|
+
)
|
|
1125
|
+
});
|
|
1126
|
+
}
|
|
1127
|
+
case PhysicsConstraintType.DISTANCE: {
|
|
1128
|
+
const limit = readLimit(data, PhysicsConstraintAxis.LINEAR_DISTANCE);
|
|
1129
|
+
const maxDistance = options.maxDistance ?? limit?.max ?? 0;
|
|
1130
|
+
return world.createDistanceJoint(bodyA, bodyB, {
|
|
1131
|
+
...base,
|
|
1132
|
+
length: maxDistance,
|
|
1133
|
+
enableLimit: true,
|
|
1134
|
+
minLength: limit?.min ?? 0,
|
|
1135
|
+
maxLength: maxDistance,
|
|
1136
|
+
enableMotor: data.axisMotorType.get(PhysicsConstraintAxis.LINEAR_DISTANCE) !== void 0,
|
|
1137
|
+
motorSpeed: data.axisMotorTarget.get(
|
|
1138
|
+
PhysicsConstraintAxis.LINEAR_DISTANCE
|
|
1139
|
+
),
|
|
1140
|
+
maxMotorForce: data.axisMotorMaxForce.get(
|
|
1141
|
+
PhysicsConstraintAxis.LINEAR_DISTANCE
|
|
1142
|
+
)
|
|
1143
|
+
});
|
|
1144
|
+
}
|
|
1145
|
+
case PhysicsConstraintType.HINGE: {
|
|
1146
|
+
const localFrameA = {
|
|
1147
|
+
rotation: toB3Quat(
|
|
1148
|
+
quaternionFromXAxis(options.axisA ?? Vector3.Right())
|
|
1149
|
+
)
|
|
1150
|
+
};
|
|
1151
|
+
const localFrameB = {
|
|
1152
|
+
rotation: toB3Quat(
|
|
1153
|
+
quaternionFromXAxis(options.axisB ?? Vector3.Right())
|
|
1154
|
+
)
|
|
1155
|
+
};
|
|
1156
|
+
const limit = readLimit(data, PhysicsConstraintAxis.ANGULAR_X);
|
|
1157
|
+
const motorType = data.axisMotorType.get(
|
|
1158
|
+
PhysicsConstraintAxis.ANGULAR_X
|
|
1159
|
+
);
|
|
1160
|
+
const friction = data.axisFriction.get(PhysicsConstraintAxis.ANGULAR_X);
|
|
1161
|
+
const useFrictionAsMotor = !motorType && friction !== void 0;
|
|
1162
|
+
return world.createRevoluteJoint(bodyA, bodyB, {
|
|
1163
|
+
...base,
|
|
1164
|
+
localFrameA,
|
|
1165
|
+
localFrameB,
|
|
1166
|
+
enableLimit: limit !== null,
|
|
1167
|
+
lowerAngle: limit?.min,
|
|
1168
|
+
upperAngle: limit?.max,
|
|
1169
|
+
enableMotor: motorType !== void 0 || useFrictionAsMotor,
|
|
1170
|
+
motorSpeed: useFrictionAsMotor ? 0 : data.axisMotorTarget.get(PhysicsConstraintAxis.ANGULAR_X),
|
|
1171
|
+
maxMotorTorque: useFrictionAsMotor ? friction : data.axisMotorMaxForce.get(PhysicsConstraintAxis.ANGULAR_X)
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
case PhysicsConstraintType.SLIDER:
|
|
1175
|
+
case PhysicsConstraintType.PRISMATIC: {
|
|
1176
|
+
const localFrameA = {
|
|
1177
|
+
rotation: toB3Quat(
|
|
1178
|
+
quaternionFromXAxis(options.axisA ?? Vector3.Right())
|
|
1179
|
+
)
|
|
1180
|
+
};
|
|
1181
|
+
const localFrameB = {
|
|
1182
|
+
rotation: toB3Quat(
|
|
1183
|
+
quaternionFromXAxis(options.axisB ?? Vector3.Right())
|
|
1184
|
+
)
|
|
1185
|
+
};
|
|
1186
|
+
const limit = readLimit(data, PhysicsConstraintAxis.LINEAR_X);
|
|
1187
|
+
return world.createPrismaticJoint(bodyA, bodyB, {
|
|
1188
|
+
...base,
|
|
1189
|
+
localFrameA,
|
|
1190
|
+
localFrameB,
|
|
1191
|
+
enableLimit: limit !== null,
|
|
1192
|
+
lowerTranslation: limit?.min,
|
|
1193
|
+
upperTranslation: limit?.max,
|
|
1194
|
+
enableMotor: data.axisMotorType.get(PhysicsConstraintAxis.LINEAR_X) !== void 0,
|
|
1195
|
+
motorSpeed: data.axisMotorTarget.get(PhysicsConstraintAxis.LINEAR_X),
|
|
1196
|
+
maxMotorForce: data.axisMotorMaxForce.get(
|
|
1197
|
+
PhysicsConstraintAxis.LINEAR_X
|
|
1198
|
+
)
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
case PhysicsConstraintType.LOCK:
|
|
1202
|
+
case PhysicsConstraintType.SIX_DOF: {
|
|
1203
|
+
if (type === PhysicsConstraintType.SIX_DOF) {
|
|
1204
|
+
console.warn(
|
|
1205
|
+
"Box3DPlugin: SIX_DOF constraints are approximated with a weld joint; per-axis limits/motors are tracked but not enforced."
|
|
1206
|
+
);
|
|
1207
|
+
}
|
|
1208
|
+
return world.createWeldJoint(bodyA, bodyB, base);
|
|
1209
|
+
}
|
|
1210
|
+
default:
|
|
1211
|
+
throw new Error(
|
|
1212
|
+
`Box3DPlugin: constraint type ${PhysicsConstraintType[type]} is not supported.`
|
|
1213
|
+
);
|
|
1214
|
+
}
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
function readLimit(data, axis) {
|
|
1218
|
+
const mode = data.axisLimitMode.get(axis);
|
|
1219
|
+
if (mode === void 0 || mode === PhysicsConstraintAxisLimitMode.FREE) {
|
|
1220
|
+
return null;
|
|
1221
|
+
}
|
|
1222
|
+
if (mode === PhysicsConstraintAxisLimitMode.LOCKED) {
|
|
1223
|
+
return { min: 0, max: 0 };
|
|
1224
|
+
}
|
|
1225
|
+
return { min: data.axisMinLimit.get(axis), max: data.axisMaxLimit.get(axis) };
|
|
1226
|
+
}
|
|
1227
|
+
export {
|
|
1228
|
+
Box3DPlugin
|
|
1229
|
+
};
|