@combos-fun/plugin-cannon 0.0.42 → 0.0.45
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/agent-skill.md +79 -16
- package/combos-plugin.json +8 -2
- package/dist/plugin-cannon.cjs.js +694 -76
- package/dist/plugin-cannon.cjs.js.map +1 -1
- package/dist/plugin-cannon.cjs.prod.js +1 -1
- package/dist/plugin-cannon.d.ts +170 -33
- package/dist/plugin-cannon.esm.js +693 -77
- package/dist/plugin-cannon.esm.js.map +1 -1
- package/package.json +7 -3
|
@@ -1,6 +1,9 @@
|
|
|
1
1
|
import { __decorate } from 'tslib';
|
|
2
2
|
import { Component, System, OBSERVER_TYPE, decorators } from '@combos-fun/engine';
|
|
3
3
|
import * as CANNON from 'cannon-es';
|
|
4
|
+
import { Vector3, Quaternion, Euler, Box3 } from 'three';
|
|
5
|
+
import { Renderer3DSystem } from '@combos-fun/plugin-renderer-3d';
|
|
6
|
+
import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js';
|
|
4
7
|
|
|
5
8
|
var Physics3DType;
|
|
6
9
|
(function (Physics3DType) {
|
|
@@ -8,6 +11,10 @@ var Physics3DType;
|
|
|
8
11
|
Physics3DType["SPHERE"] = "sphere";
|
|
9
12
|
Physics3DType["CYLINDER"] = "cylinder";
|
|
10
13
|
Physics3DType["PLANE"] = "plane";
|
|
14
|
+
Physics3DType["CONE"] = "cone";
|
|
15
|
+
Physics3DType["TORUS"] = "torus";
|
|
16
|
+
Physics3DType["CAPSULE"] = "capsule";
|
|
17
|
+
Physics3DType["COMPOUND"] = "compound";
|
|
11
18
|
})(Physics3DType || (Physics3DType = {}));
|
|
12
19
|
class Physics3D extends Component {
|
|
13
20
|
constructor() {
|
|
@@ -20,10 +27,14 @@ class Physics3D extends Component {
|
|
|
20
27
|
this.rotationY = 0;
|
|
21
28
|
this.rotationZ = 0;
|
|
22
29
|
this._euler = new CANNON.Vec3();
|
|
30
|
+
this._worldPos = new Vector3();
|
|
31
|
+
this._worldQuat = new Quaternion();
|
|
32
|
+
this._bodyQuat = new Quaternion();
|
|
33
|
+
this._localEuler = new Euler();
|
|
23
34
|
}
|
|
24
35
|
static { this.componentName = 'Physics3D'; }
|
|
25
36
|
init(params) {
|
|
26
|
-
this.bodyParams = params;
|
|
37
|
+
this.bodyParams = params || {};
|
|
27
38
|
}
|
|
28
39
|
update() {
|
|
29
40
|
if (this.body && this.gameObject) {
|
|
@@ -38,7 +49,8 @@ class Physics3D extends Component {
|
|
|
38
49
|
this.rotationY = this._euler.y;
|
|
39
50
|
this.rotationZ = this._euler.z;
|
|
40
51
|
}
|
|
41
|
-
|
|
52
|
+
this._worldPoseToLocalIfParented();
|
|
53
|
+
// Auto-sync to sibling Transform3D / visual components on the same GameObject
|
|
42
54
|
this._syncToSiblings();
|
|
43
55
|
}
|
|
44
56
|
}
|
|
@@ -47,6 +59,28 @@ class Physics3D extends Component {
|
|
|
47
59
|
this.world.removeBody(this.body);
|
|
48
60
|
}
|
|
49
61
|
}
|
|
62
|
+
_worldPoseToLocalIfParented() {
|
|
63
|
+
const renderer = this.game?.getSystem(Renderer3DSystem);
|
|
64
|
+
const node = renderer?.threeContext?.object3DFor(this.gameObject.id);
|
|
65
|
+
const scene = renderer?.threeContext?.scene;
|
|
66
|
+
if (!node?.parent || node.parent === scene)
|
|
67
|
+
return;
|
|
68
|
+
node.parent.updateWorldMatrix(true, false);
|
|
69
|
+
this._worldPos.set(this.positionX, this.positionY, this.positionZ);
|
|
70
|
+
node.parent.worldToLocal(this._worldPos);
|
|
71
|
+
this.positionX = this._worldPos.x;
|
|
72
|
+
this.positionY = this._worldPos.y;
|
|
73
|
+
this.positionZ = this._worldPos.z;
|
|
74
|
+
if (!this.bodyParams.stopRotation) {
|
|
75
|
+
node.parent.getWorldQuaternion(this._worldQuat).invert();
|
|
76
|
+
this._bodyQuat.set(this.body.quaternion.x, this.body.quaternion.y, this.body.quaternion.z, this.body.quaternion.w);
|
|
77
|
+
this._bodyQuat.premultiply(this._worldQuat);
|
|
78
|
+
this._localEuler.setFromQuaternion(this._bodyQuat);
|
|
79
|
+
this.rotationX = this._localEuler.x;
|
|
80
|
+
this.rotationY = this._localEuler.y;
|
|
81
|
+
this.rotationZ = this._localEuler.z;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
50
84
|
_syncToSiblings() {
|
|
51
85
|
const components = this.gameObject.components;
|
|
52
86
|
if (!components)
|
|
@@ -69,74 +103,385 @@ class Physics3D extends Component {
|
|
|
69
103
|
}
|
|
70
104
|
}
|
|
71
105
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
106
|
+
const TYPE_BY_SHAPE = {
|
|
107
|
+
box: Physics3DType.BOX,
|
|
108
|
+
sphere: Physics3DType.SPHERE,
|
|
109
|
+
cylinder: Physics3DType.CYLINDER,
|
|
110
|
+
plane: Physics3DType.PLANE,
|
|
111
|
+
cone: Physics3DType.CONE,
|
|
112
|
+
torus: Physics3DType.TORUS,
|
|
113
|
+
capsule: Physics3DType.CAPSULE,
|
|
114
|
+
compound: Physics3DType.COMPOUND,
|
|
115
|
+
};
|
|
116
|
+
function normalizeType(type) {
|
|
117
|
+
if (!type)
|
|
118
|
+
return Physics3DType.BOX;
|
|
119
|
+
return TYPE_BY_SHAPE[type] ?? type;
|
|
120
|
+
}
|
|
121
|
+
function torusTube(radius, tube) {
|
|
122
|
+
return tube && tube > 0 ? tube : radius * 0.4;
|
|
123
|
+
}
|
|
124
|
+
function dimsFromParams(params, fallback) {
|
|
125
|
+
const radius = params.radius ?? fallback?.radius ?? 0.5;
|
|
126
|
+
return {
|
|
127
|
+
width: params.width ?? fallback?.width ?? 1,
|
|
128
|
+
height: params.height ?? fallback?.height ?? 1,
|
|
129
|
+
depth: params.depth ?? fallback?.depth ?? 1,
|
|
130
|
+
radius,
|
|
131
|
+
radiusTop: params.radiusTop ?? radius,
|
|
132
|
+
radiusBottom: params.radiusBottom ?? radius,
|
|
133
|
+
segments: Math.max(6, Math.round(params.segments ?? fallback?.segments ?? 12)),
|
|
134
|
+
tube: torusTube(radius, params.tube ?? fallback?.tube),
|
|
135
|
+
finite: params.finite ?? fallback?.finite,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function offsetFromPart(part) {
|
|
139
|
+
return new CANNON.Vec3(part.positionX ?? part.position?.x ?? 0, part.positionY ?? part.position?.y ?? 0, part.positionZ ?? part.position?.z ?? 0);
|
|
140
|
+
}
|
|
141
|
+
function quaternionFromPart(part) {
|
|
142
|
+
const x = part.rotationX ?? part.rotation?.x ?? 0;
|
|
143
|
+
const y = part.rotationY ?? part.rotation?.y ?? 0;
|
|
144
|
+
const z = part.rotationZ ?? part.rotation?.z ?? 0;
|
|
145
|
+
if (!x && !y && !z)
|
|
146
|
+
return undefined;
|
|
147
|
+
const q = new CANNON.Quaternion();
|
|
148
|
+
q.setFromEuler(x, y, z);
|
|
149
|
+
return q;
|
|
150
|
+
}
|
|
151
|
+
function placedShapesForType(type, dims) {
|
|
152
|
+
const kind = normalizeType(type);
|
|
153
|
+
switch (kind) {
|
|
154
|
+
case Physics3DType.SPHERE:
|
|
155
|
+
return [{ shape: new CANNON.Sphere(dims.radius) }];
|
|
156
|
+
case Physics3DType.CYLINDER:
|
|
157
|
+
return [{
|
|
158
|
+
shape: new CANNON.Cylinder(dims.radiusTop, dims.radiusBottom, dims.height, dims.segments),
|
|
159
|
+
}];
|
|
160
|
+
case Physics3DType.CONE:
|
|
161
|
+
return [{
|
|
162
|
+
shape: new CANNON.Cylinder(0.001, dims.radius, dims.height, dims.segments),
|
|
163
|
+
}];
|
|
164
|
+
case Physics3DType.PLANE:
|
|
165
|
+
if (dims.finite) {
|
|
166
|
+
return [{ shape: new CANNON.Box(new CANNON.Vec3(dims.width / 2, dims.height / 2, 0.01)) }];
|
|
85
167
|
}
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
168
|
+
return [{ shape: new CANNON.Plane() }];
|
|
169
|
+
case Physics3DType.TORUS:
|
|
170
|
+
return torusShapes(dims);
|
|
171
|
+
case Physics3DType.CAPSULE:
|
|
172
|
+
return capsuleShapes(dims);
|
|
173
|
+
case Physics3DType.BOX:
|
|
174
|
+
default:
|
|
175
|
+
return [{
|
|
176
|
+
shape: new CANNON.Box(new CANNON.Vec3(dims.width / 2, dims.height / 2, dims.depth / 2)),
|
|
177
|
+
}];
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
function torusShapes(dims) {
|
|
181
|
+
const n = Math.max(8, Math.min(12, dims.segments));
|
|
182
|
+
const placed = [];
|
|
183
|
+
for (let i = 0; i < n; i++) {
|
|
184
|
+
const angle = (i / n) * Math.PI * 2;
|
|
185
|
+
placed.push({
|
|
186
|
+
shape: new CANNON.Sphere(dims.tube),
|
|
187
|
+
offset: new CANNON.Vec3(Math.cos(angle) * dims.radius, 0, Math.sin(angle) * dims.radius),
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
return placed;
|
|
191
|
+
}
|
|
192
|
+
function capsuleShapes(dims) {
|
|
193
|
+
const cyl = Math.max(0, dims.height - 2 * dims.radius);
|
|
194
|
+
const placed = [];
|
|
195
|
+
if (cyl > 0.001) {
|
|
196
|
+
placed.push({
|
|
197
|
+
shape: new CANNON.Cylinder(dims.radius, dims.radius, cyl, dims.segments),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
placed.push({
|
|
201
|
+
shape: new CANNON.Sphere(dims.radius),
|
|
202
|
+
offset: new CANNON.Vec3(0, cyl / 2, 0),
|
|
203
|
+
});
|
|
204
|
+
placed.push({
|
|
205
|
+
shape: new CANNON.Sphere(dims.radius),
|
|
206
|
+
offset: new CANNON.Vec3(0, -cyl / 2, 0),
|
|
207
|
+
});
|
|
208
|
+
return placed;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const _box = new Box3();
|
|
212
|
+
const _size = new Vector3();
|
|
213
|
+
const _center = new Vector3();
|
|
214
|
+
const _vertex = new Vector3();
|
|
215
|
+
function withIdentityPose(root, fn) {
|
|
216
|
+
const px = root.position.x;
|
|
217
|
+
const py = root.position.y;
|
|
218
|
+
const pz = root.position.z;
|
|
219
|
+
const rx = root.rotation.x;
|
|
220
|
+
const ry = root.rotation.y;
|
|
221
|
+
const rz = root.rotation.z;
|
|
222
|
+
root.position.set(0, 0, 0);
|
|
223
|
+
root.rotation.set(0, 0, 0);
|
|
224
|
+
root.updateMatrixWorld(true);
|
|
225
|
+
try {
|
|
226
|
+
return fn();
|
|
227
|
+
}
|
|
228
|
+
finally {
|
|
229
|
+
root.position.set(px, py, pz);
|
|
230
|
+
root.rotation.set(rx, ry, rz);
|
|
231
|
+
root.updateMatrixWorld(true);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
function addModelCollider(body, root, collider) {
|
|
235
|
+
return withIdentityPose(root, () => {
|
|
236
|
+
if (collider === 'trimesh') {
|
|
237
|
+
return addTrimesh(body, root);
|
|
238
|
+
}
|
|
239
|
+
if (collider === 'hull') {
|
|
240
|
+
return addHull(body, root);
|
|
241
|
+
}
|
|
242
|
+
return addAabb(body, root);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
function addAabb(body, root) {
|
|
246
|
+
_box.setFromObject(root);
|
|
247
|
+
if (_box.isEmpty())
|
|
248
|
+
return false;
|
|
249
|
+
_box.getSize(_size);
|
|
250
|
+
_box.getCenter(_center);
|
|
251
|
+
if (_size.x <= 0 && _size.y <= 0 && _size.z <= 0)
|
|
252
|
+
return false;
|
|
253
|
+
body.addShape(new CANNON.Box(new CANNON.Vec3(Math.max(_size.x / 2, 0.01), Math.max(_size.y / 2, 0.01), Math.max(_size.z / 2, 0.01))), new CANNON.Vec3(_center.x, _center.y, _center.z));
|
|
254
|
+
return true;
|
|
255
|
+
}
|
|
256
|
+
function addTrimesh(body, root) {
|
|
257
|
+
let added = false;
|
|
258
|
+
root.traverse((child) => {
|
|
259
|
+
const mesh = child;
|
|
260
|
+
const geometry = mesh.geometry;
|
|
261
|
+
const position = geometry?.attributes?.position;
|
|
262
|
+
if (!position)
|
|
263
|
+
return;
|
|
264
|
+
const vertices = [];
|
|
265
|
+
for (let i = 0; i < position.count; i++) {
|
|
266
|
+
_vertex.fromBufferAttribute(position, i).applyMatrix4(mesh.matrixWorld);
|
|
267
|
+
vertices.push(_vertex.x, _vertex.y, _vertex.z);
|
|
268
|
+
}
|
|
269
|
+
const indices = [];
|
|
270
|
+
if (geometry.index) {
|
|
271
|
+
const array = geometry.index.array;
|
|
272
|
+
for (let i = 0; i < array.length; i++) {
|
|
273
|
+
indices.push(array[i]);
|
|
93
274
|
}
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
275
|
+
}
|
|
276
|
+
else {
|
|
277
|
+
for (let i = 0; i < position.count; i++) {
|
|
278
|
+
indices.push(i);
|
|
97
279
|
}
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
280
|
+
}
|
|
281
|
+
if (indices.length < 3)
|
|
282
|
+
return;
|
|
283
|
+
body.addShape(new CANNON.Trimesh(vertices, indices));
|
|
284
|
+
added = true;
|
|
285
|
+
});
|
|
286
|
+
return added;
|
|
287
|
+
}
|
|
288
|
+
function addHull(body, root) {
|
|
289
|
+
const points = [];
|
|
290
|
+
root.traverse((child) => {
|
|
291
|
+
const mesh = child;
|
|
292
|
+
const position = mesh.geometry?.attributes?.position;
|
|
293
|
+
if (!position)
|
|
294
|
+
return;
|
|
295
|
+
const stride = position.count > 128 ? Math.ceil(position.count / 128) : 1;
|
|
296
|
+
for (let i = 0; i < position.count; i += stride) {
|
|
297
|
+
points.push(_vertex.fromBufferAttribute(position, i).applyMatrix4(mesh.matrixWorld).clone());
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
if (points.length < 4) {
|
|
301
|
+
return addAabb(body, root);
|
|
302
|
+
}
|
|
303
|
+
try {
|
|
304
|
+
const geometry = new ConvexGeometry(points);
|
|
305
|
+
const pos = geometry.attributes.position;
|
|
306
|
+
const vertices = [];
|
|
307
|
+
const indexOf = new Map();
|
|
308
|
+
const add = (x, y, z) => {
|
|
309
|
+
const key = `${x.toFixed(5)},${y.toFixed(5)},${z.toFixed(5)}`;
|
|
310
|
+
const existing = indexOf.get(key);
|
|
311
|
+
if (existing != null)
|
|
312
|
+
return existing;
|
|
313
|
+
const i = vertices.length;
|
|
314
|
+
vertices.push(new CANNON.Vec3(x, y, z));
|
|
315
|
+
indexOf.set(key, i);
|
|
316
|
+
return i;
|
|
317
|
+
};
|
|
318
|
+
const faces = [];
|
|
319
|
+
if (geometry.index) {
|
|
320
|
+
const array = geometry.index.array;
|
|
321
|
+
for (let i = 0; i + 2 < array.length; i += 3) {
|
|
322
|
+
faces.push([
|
|
323
|
+
add(pos.getX(array[i]), pos.getY(array[i]), pos.getZ(array[i])),
|
|
324
|
+
add(pos.getX(array[i + 1]), pos.getY(array[i + 1]), pos.getZ(array[i + 1])),
|
|
325
|
+
add(pos.getX(array[i + 2]), pos.getY(array[i + 2]), pos.getZ(array[i + 2])),
|
|
326
|
+
]);
|
|
105
327
|
}
|
|
106
328
|
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
329
|
+
else {
|
|
330
|
+
for (let i = 0; i + 2 < pos.count; i += 3) {
|
|
331
|
+
faces.push([
|
|
332
|
+
add(pos.getX(i), pos.getY(i), pos.getZ(i)),
|
|
333
|
+
add(pos.getX(i + 1), pos.getY(i + 1), pos.getZ(i + 1)),
|
|
334
|
+
add(pos.getX(i + 2), pos.getY(i + 2), pos.getZ(i + 2)),
|
|
335
|
+
]);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
geometry.dispose();
|
|
339
|
+
if (vertices.length < 4 || faces.length < 4) {
|
|
340
|
+
return addAabb(body, root);
|
|
341
|
+
}
|
|
342
|
+
body.addShape(new CANNON.ConvexPolyhedron({ vertices, faces }));
|
|
343
|
+
return true;
|
|
344
|
+
}
|
|
345
|
+
catch {
|
|
346
|
+
return addAabb(body, root);
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
function resolveCollider(params, model) {
|
|
351
|
+
if (params.collider)
|
|
352
|
+
return params.collider;
|
|
353
|
+
if (params.type || (params.parts && params.parts.length > 0))
|
|
354
|
+
return 'manual';
|
|
355
|
+
return model ? 'aabb' : 'manual';
|
|
356
|
+
}
|
|
357
|
+
function resolveType(params, graphics) {
|
|
358
|
+
if (params.type)
|
|
359
|
+
return normalizeType(params.type);
|
|
360
|
+
if ((params.parts && params.parts.length > 0) || (graphics?.parts && graphics.parts.length > 0)) {
|
|
361
|
+
return Physics3DType.COMPOUND;
|
|
362
|
+
}
|
|
363
|
+
if (graphics?.shape)
|
|
364
|
+
return normalizeType(graphics.shape);
|
|
365
|
+
return Physics3DType.BOX;
|
|
366
|
+
}
|
|
367
|
+
function meshColliderFingerprint(collider, model) {
|
|
368
|
+
if (!model || collider === 'manual')
|
|
369
|
+
return undefined;
|
|
370
|
+
return `${collider}:${model.resource}:${model.scaleX}:${model.scaleY}:${model.scaleZ}`;
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
function applyBodyOptions(body, options) {
|
|
374
|
+
if (!options)
|
|
375
|
+
return;
|
|
376
|
+
if (options.friction !== undefined) {
|
|
377
|
+
body.material = new CANNON.Material({
|
|
378
|
+
friction: options.friction,
|
|
379
|
+
restitution: options.restitution ?? 0.3,
|
|
380
|
+
});
|
|
381
|
+
}
|
|
382
|
+
if (options.restitution !== undefined && !body.material) {
|
|
383
|
+
body.material = new CANNON.Material({ friction: 0.3, restitution: options.restitution });
|
|
384
|
+
}
|
|
385
|
+
if (options.linearDamping !== undefined)
|
|
386
|
+
body.linearDamping = options.linearDamping;
|
|
387
|
+
if (options.angularDamping !== undefined)
|
|
388
|
+
body.angularDamping = options.angularDamping;
|
|
389
|
+
if (options.fixedRotation !== undefined)
|
|
390
|
+
body.fixedRotation = options.fixedRotation;
|
|
391
|
+
body.updateMassProperties();
|
|
392
|
+
}
|
|
393
|
+
function addPlaced(body, type, dims, part) {
|
|
394
|
+
const placed = placedShapesForType(type, dims);
|
|
395
|
+
const extraOffset = part ? offsetFromPart(part) : new CANNON.Vec3();
|
|
396
|
+
const extraQuat = part ? quaternionFromPart(part) : undefined;
|
|
397
|
+
for (const item of placed) {
|
|
398
|
+
const local = (item.offset ? item.offset.clone() : new CANNON.Vec3());
|
|
399
|
+
if (extraQuat)
|
|
400
|
+
extraQuat.vmult(local, local);
|
|
401
|
+
const offset = extraOffset.vadd(local);
|
|
402
|
+
const quat = extraQuat && item.quaternion
|
|
403
|
+
? extraQuat.mult(item.quaternion)
|
|
404
|
+
: (extraQuat || item.quaternion);
|
|
405
|
+
body.addShape(item.shape, offset, quat);
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
class BodiesFactory {
|
|
409
|
+
create(component, context = {}) {
|
|
410
|
+
const { bodyParams } = component;
|
|
411
|
+
const graphics = context.graphics;
|
|
412
|
+
const model = context.model;
|
|
413
|
+
const collider = resolveCollider(bodyParams, model);
|
|
414
|
+
if (collider !== 'manual') {
|
|
415
|
+
if (!context.modelGroup)
|
|
416
|
+
return null;
|
|
417
|
+
let mass = bodyParams.mass ?? 1;
|
|
418
|
+
if (collider === 'trimesh' && mass !== 0) {
|
|
419
|
+
mass = 0;
|
|
420
|
+
}
|
|
421
|
+
const body = this.createBodyShell(component, mass, context.pose || graphics);
|
|
422
|
+
const ok = addModelCollider(body, context.modelGroup, collider);
|
|
423
|
+
if (!ok)
|
|
424
|
+
return null;
|
|
425
|
+
applyBodyOptions(body, bodyParams.bodyOptions);
|
|
426
|
+
component.meshColliderKey = meshColliderFingerprint(collider, model);
|
|
427
|
+
return body;
|
|
428
|
+
}
|
|
429
|
+
const type = resolveType(bodyParams, graphics);
|
|
430
|
+
const dims = dimsFromParams(bodyParams, {
|
|
431
|
+
width: graphics?.width,
|
|
432
|
+
height: graphics?.height,
|
|
433
|
+
depth: graphics?.depth,
|
|
434
|
+
radius: graphics?.radius,
|
|
435
|
+
segments: graphics?.segments,
|
|
436
|
+
tube: graphics?.tube,
|
|
437
|
+
finite: bodyParams.finite,
|
|
111
438
|
});
|
|
112
|
-
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
body.
|
|
439
|
+
const infinitePlane = type === Physics3DType.PLANE && !dims.finite;
|
|
440
|
+
const mass = infinitePlane ? 0 : (bodyParams.mass ?? 1);
|
|
441
|
+
const body = this.createBodyShell(component, mass, context.pose || graphics);
|
|
442
|
+
if (type === Physics3DType.COMPOUND) {
|
|
443
|
+
const parts = bodyParams.parts?.length ? bodyParams.parts : (graphics?.parts || []);
|
|
444
|
+
if (!parts.length) {
|
|
445
|
+
addPlaced(body, Physics3DType.BOX, dims);
|
|
446
|
+
}
|
|
447
|
+
else {
|
|
448
|
+
for (const part of parts) {
|
|
449
|
+
const partType = part.type || part.shape || Physics3DType.BOX;
|
|
450
|
+
if (normalizeType(partType) === Physics3DType.COMPOUND)
|
|
451
|
+
continue;
|
|
452
|
+
const partDims = dimsFromParams(part, dims);
|
|
453
|
+
addPlaced(body, partType, partDims, part);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
else {
|
|
458
|
+
addPlaced(body, type, dims);
|
|
459
|
+
}
|
|
460
|
+
applyBodyOptions(body, bodyParams.bodyOptions);
|
|
461
|
+
component.meshColliderKey = undefined;
|
|
462
|
+
return body;
|
|
463
|
+
}
|
|
464
|
+
createBodyShell(component, mass, graphics) {
|
|
465
|
+
const body = new CANNON.Body({ mass });
|
|
466
|
+
const pos = component.bodyParams.position;
|
|
467
|
+
body.position.set(pos?.x ?? graphics?.positionX ?? 0, pos?.y ?? graphics?.positionY ?? 0, pos?.z ?? graphics?.positionZ ?? 0);
|
|
468
|
+
const rot = component.bodyParams.rotation;
|
|
469
|
+
const rx = rot?.x ?? graphics?.rotationX ?? 0;
|
|
470
|
+
const ry = rot?.y ?? graphics?.rotationY ?? 0;
|
|
471
|
+
const rz = rot?.z ?? graphics?.rotationZ ?? 0;
|
|
472
|
+
if (rx || ry || rz) {
|
|
473
|
+
body.quaternion.setFromEuler(rx, ry, rz);
|
|
474
|
+
}
|
|
134
475
|
return body;
|
|
135
476
|
}
|
|
136
477
|
}
|
|
137
478
|
|
|
138
479
|
class Physics3DEngine {
|
|
139
480
|
constructor(game, options) {
|
|
481
|
+
this.pending = new Set();
|
|
482
|
+
this.pendingConstraints = new Set();
|
|
483
|
+
this.constraints = new Set();
|
|
484
|
+
this.contactPairs = new Map();
|
|
140
485
|
this.enabled = false;
|
|
141
486
|
this.game = game;
|
|
142
487
|
this.options = options ?? {};
|
|
@@ -144,10 +489,8 @@ class Physics3DEngine {
|
|
|
144
489
|
}
|
|
145
490
|
start() {
|
|
146
491
|
this.world = new CANNON.World();
|
|
147
|
-
// Set gravity
|
|
148
492
|
const gravity = this.options.gravity ?? { x: 0, y: -9.82, z: 0 };
|
|
149
493
|
this.world.gravity.set(gravity.x, gravity.y, gravity.z);
|
|
150
|
-
// Configure solver
|
|
151
494
|
if (this.options.solver) {
|
|
152
495
|
const solver = this.world.solver;
|
|
153
496
|
if (this.options.solver.iterations !== undefined) {
|
|
@@ -165,10 +508,14 @@ class Physics3DEngine {
|
|
|
165
508
|
update(e) {
|
|
166
509
|
if (!this.world || !this.enabled)
|
|
167
510
|
return;
|
|
511
|
+
this.flushPending();
|
|
512
|
+
this.rebuildStaleMeshColliders();
|
|
168
513
|
const fixedTimeStep = this.options.fixedTimeStep ?? 1 / 60;
|
|
169
514
|
const maxSubSteps = this.options.maxSubSteps ?? 3;
|
|
170
515
|
const dt = (e.deltaTime || 16.67) / 1000;
|
|
516
|
+
this.flushConstraints();
|
|
171
517
|
this.world.step(fixedTimeStep, dt, maxSubSteps);
|
|
518
|
+
this.emitCollisionActive();
|
|
172
519
|
}
|
|
173
520
|
stop() {
|
|
174
521
|
this.enabled = false;
|
|
@@ -177,8 +524,16 @@ class Physics3DEngine {
|
|
|
177
524
|
this.enabled = true;
|
|
178
525
|
}
|
|
179
526
|
destroy() {
|
|
527
|
+
this.pending.clear();
|
|
528
|
+
this.pendingConstraints.clear();
|
|
529
|
+
this.contactPairs.clear();
|
|
180
530
|
if (this.world) {
|
|
181
|
-
|
|
531
|
+
for (const constraint of this.constraints) {
|
|
532
|
+
if (constraint.constraint)
|
|
533
|
+
this.world.removeConstraint(constraint.constraint);
|
|
534
|
+
constraint.constraint = undefined;
|
|
535
|
+
}
|
|
536
|
+
this.constraints.clear();
|
|
182
537
|
while (this.world.bodies.length > 0) {
|
|
183
538
|
this.world.removeBody(this.world.bodies[0]);
|
|
184
539
|
}
|
|
@@ -186,32 +541,131 @@ class Physics3DEngine {
|
|
|
186
541
|
}
|
|
187
542
|
}
|
|
188
543
|
add(component) {
|
|
189
|
-
const body = this.
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
body.component = component;
|
|
544
|
+
const body = this.tryCreate(component);
|
|
545
|
+
if (!body) {
|
|
546
|
+
this.pending.add(component);
|
|
547
|
+
return;
|
|
548
|
+
}
|
|
549
|
+
this.attach(component, body);
|
|
196
550
|
}
|
|
197
551
|
change(component) {
|
|
198
|
-
|
|
552
|
+
this.detachConstraintsFor(component);
|
|
553
|
+
this.clearContactsFor(component);
|
|
199
554
|
if (component.body) {
|
|
200
555
|
this.world.removeBody(component.body);
|
|
556
|
+
component.body = undefined;
|
|
201
557
|
}
|
|
202
|
-
|
|
203
|
-
this.
|
|
204
|
-
component.body = newBody;
|
|
205
|
-
newBody.component = component;
|
|
558
|
+
this.pending.delete(component);
|
|
559
|
+
this.add(component);
|
|
206
560
|
}
|
|
207
561
|
remove(component) {
|
|
562
|
+
this.detachConstraintsFor(component);
|
|
563
|
+
this.clearContactsFor(component);
|
|
564
|
+
this.pending.delete(component);
|
|
208
565
|
if (component.body) {
|
|
209
566
|
this.world.removeBody(component.body);
|
|
210
567
|
component.body = undefined;
|
|
211
568
|
}
|
|
569
|
+
component.meshColliderKey = undefined;
|
|
570
|
+
}
|
|
571
|
+
addConstraint(component) {
|
|
572
|
+
this.removeConstraint(component);
|
|
573
|
+
this.pendingConstraints.add(component);
|
|
574
|
+
this.flushConstraints();
|
|
575
|
+
}
|
|
576
|
+
removeConstraint(component) {
|
|
577
|
+
this.pendingConstraints.delete(component);
|
|
578
|
+
this.constraints.delete(component);
|
|
579
|
+
if (component.constraint && this.world) {
|
|
580
|
+
this.world.removeConstraint(component.constraint);
|
|
581
|
+
}
|
|
582
|
+
component.constraint = undefined;
|
|
583
|
+
}
|
|
584
|
+
flushConstraints() {
|
|
585
|
+
if (!this.world)
|
|
586
|
+
return;
|
|
587
|
+
for (const component of [...this.pendingConstraints]) {
|
|
588
|
+
if (!component.gameObject) {
|
|
589
|
+
this.pendingConstraints.delete(component);
|
|
590
|
+
continue;
|
|
591
|
+
}
|
|
592
|
+
if (this.tryAttachConstraint(component)) {
|
|
593
|
+
this.pendingConstraints.delete(component);
|
|
594
|
+
}
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
tryCreate(component) {
|
|
598
|
+
const context = this.contextFor(component);
|
|
599
|
+
return this.bodiesFactory.create(component, context);
|
|
600
|
+
}
|
|
601
|
+
attach(component, body) {
|
|
602
|
+
this.world.addBody(body);
|
|
603
|
+
component.body = body;
|
|
604
|
+
component.world = this.world;
|
|
605
|
+
body.component = component;
|
|
606
|
+
}
|
|
607
|
+
contextFor(component) {
|
|
608
|
+
const gameObject = component.gameObject;
|
|
609
|
+
const graphics = gameObject?.getComponent('Graphics3D');
|
|
610
|
+
const model = gameObject?.getComponent('Model3D');
|
|
611
|
+
const transform3d = gameObject?.getComponent('Transform3D');
|
|
612
|
+
const renderer = this.game.getSystem(Renderer3DSystem);
|
|
613
|
+
const modelGroup = gameObject ? renderer?.threeContext?.models.get(gameObject.id) : undefined;
|
|
614
|
+
const node = gameObject ? renderer?.threeContext?.object3DFor(gameObject.id) : undefined;
|
|
615
|
+
const scene = renderer?.threeContext?.scene;
|
|
616
|
+
let worldPose;
|
|
617
|
+
if (node?.parent && node.parent !== scene) {
|
|
618
|
+
node.updateWorldMatrix(true, false);
|
|
619
|
+
const position = new Vector3();
|
|
620
|
+
const euler = new Euler();
|
|
621
|
+
node.getWorldPosition(position);
|
|
622
|
+
euler.setFromRotationMatrix(node.matrixWorld);
|
|
623
|
+
worldPose = {
|
|
624
|
+
positionX: position.x,
|
|
625
|
+
positionY: position.y,
|
|
626
|
+
positionZ: position.z,
|
|
627
|
+
rotationX: euler.x,
|
|
628
|
+
rotationY: euler.y,
|
|
629
|
+
rotationZ: euler.z,
|
|
630
|
+
};
|
|
631
|
+
}
|
|
632
|
+
return { graphics, model, modelGroup, pose: worldPose || transform3d || graphics };
|
|
633
|
+
}
|
|
634
|
+
flushPending() {
|
|
635
|
+
if (!this.pending.size)
|
|
636
|
+
return;
|
|
637
|
+
for (const component of [...this.pending]) {
|
|
638
|
+
if (!component.gameObject) {
|
|
639
|
+
this.pending.delete(component);
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
const body = this.tryCreate(component);
|
|
643
|
+
if (body) {
|
|
644
|
+
this.attach(component, body);
|
|
645
|
+
this.pending.delete(component);
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
rebuildStaleMeshColliders() {
|
|
650
|
+
if (!this.world)
|
|
651
|
+
return;
|
|
652
|
+
const stale = [];
|
|
653
|
+
for (const body of this.world.bodies) {
|
|
654
|
+
const component = body.component;
|
|
655
|
+
if (!component?.gameObject)
|
|
656
|
+
continue;
|
|
657
|
+
const model = component.gameObject.getComponent('Model3D');
|
|
658
|
+
const collider = resolveCollider(component.bodyParams, model);
|
|
659
|
+
const key = meshColliderFingerprint(collider, model);
|
|
660
|
+
if (!key || key === component.meshColliderKey)
|
|
661
|
+
continue;
|
|
662
|
+
stale.push(component);
|
|
663
|
+
}
|
|
664
|
+
for (const component of stale) {
|
|
665
|
+
this.change(component);
|
|
666
|
+
}
|
|
212
667
|
}
|
|
213
668
|
initCollisionEvents() {
|
|
214
|
-
// beginContact: emitted when two bodies start touching
|
|
215
669
|
this.world.addEventListener('beginContact', (event) => {
|
|
216
670
|
const bodyA = event.bodyA;
|
|
217
671
|
const bodyB = event.bodyB;
|
|
@@ -220,9 +674,10 @@ class Physics3DEngine {
|
|
|
220
674
|
if (componentA && componentB) {
|
|
221
675
|
componentA.emit('collisionStart', componentB.gameObject, componentA.gameObject);
|
|
222
676
|
componentB.emit('collisionStart', componentA.gameObject, componentB.gameObject);
|
|
677
|
+
const pair = this.pairKey(bodyA, bodyB);
|
|
678
|
+
this.contactPairs.set(pair, [bodyA.component, bodyB.component]);
|
|
223
679
|
}
|
|
224
680
|
});
|
|
225
|
-
// endContact: emitted when two bodies stop touching
|
|
226
681
|
this.world.addEventListener('endContact', (event) => {
|
|
227
682
|
const bodyA = event.bodyA;
|
|
228
683
|
const bodyB = event.bodyB;
|
|
@@ -232,8 +687,82 @@ class Physics3DEngine {
|
|
|
232
687
|
componentA.emit('collisionEnd', componentB.gameObject, componentA.gameObject);
|
|
233
688
|
componentB.emit('collisionEnd', componentA.gameObject, componentB.gameObject);
|
|
234
689
|
}
|
|
690
|
+
this.contactPairs.delete(this.pairKey(bodyA, bodyB));
|
|
235
691
|
});
|
|
236
692
|
}
|
|
693
|
+
emitCollisionActive() {
|
|
694
|
+
for (const [componentA, componentB] of this.contactPairs.values()) {
|
|
695
|
+
if (!componentA?.gameObject || !componentB?.gameObject)
|
|
696
|
+
continue;
|
|
697
|
+
componentA.emit('collisionActive', componentB.gameObject, componentA.gameObject);
|
|
698
|
+
componentB.emit('collisionActive', componentA.gameObject, componentB.gameObject);
|
|
699
|
+
}
|
|
700
|
+
}
|
|
701
|
+
pairKey(bodyA, bodyB) {
|
|
702
|
+
return bodyA.id < bodyB.id ? `${bodyA.id}:${bodyB.id}` : `${bodyB.id}:${bodyA.id}`;
|
|
703
|
+
}
|
|
704
|
+
clearContactsFor(component) {
|
|
705
|
+
for (const [key, pair] of this.contactPairs) {
|
|
706
|
+
if (pair[0] === component || pair[1] === component) {
|
|
707
|
+
this.contactPairs.delete(key);
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
}
|
|
711
|
+
tryAttachConstraint(component) {
|
|
712
|
+
const physicsA = component.gameObject?.getComponent('Physics3D');
|
|
713
|
+
const target = this.game.gameObjects.find((go) => go.name === component.target);
|
|
714
|
+
const physicsB = target?.getComponent('Physics3D');
|
|
715
|
+
if (!physicsA?.body || !physicsB?.body || physicsA.body === physicsB.body)
|
|
716
|
+
return false;
|
|
717
|
+
const constraint = this.createConstraint(component, physicsA.body, physicsB.body);
|
|
718
|
+
constraint.collideConnected = component.collideConnected;
|
|
719
|
+
this.world.addConstraint(constraint);
|
|
720
|
+
component.constraint = constraint;
|
|
721
|
+
this.constraints.add(component);
|
|
722
|
+
return true;
|
|
723
|
+
}
|
|
724
|
+
createConstraint(component, bodyA, bodyB) {
|
|
725
|
+
const maxForce = component.maxForce;
|
|
726
|
+
const pivotA = new CANNON.Vec3(component.pivotAX, component.pivotAY, component.pivotAZ);
|
|
727
|
+
const pivotB = new CANNON.Vec3(component.pivotBX, component.pivotBY, component.pivotBZ);
|
|
728
|
+
const axisA = new CANNON.Vec3(component.axisAX, component.axisAY, component.axisAZ);
|
|
729
|
+
const axisB = new CANNON.Vec3(component.axisBX, component.axisBY, component.axisBZ);
|
|
730
|
+
switch (component.type) {
|
|
731
|
+
case 'point':
|
|
732
|
+
return new CANNON.PointToPointConstraint(bodyA, pivotA, bodyB, pivotB, maxForce);
|
|
733
|
+
case 'lock':
|
|
734
|
+
return new CANNON.LockConstraint(bodyA, bodyB, { maxForce });
|
|
735
|
+
case 'distance':
|
|
736
|
+
return new CANNON.DistanceConstraint(bodyA, bodyB, component.distance, maxForce);
|
|
737
|
+
case 'hinge':
|
|
738
|
+
default: {
|
|
739
|
+
const hinge = new CANNON.HingeConstraint(bodyA, bodyB, {
|
|
740
|
+
pivotA,
|
|
741
|
+
pivotB,
|
|
742
|
+
axisA,
|
|
743
|
+
axisB,
|
|
744
|
+
maxForce,
|
|
745
|
+
collideConnected: component.collideConnected,
|
|
746
|
+
});
|
|
747
|
+
if (component.motor) {
|
|
748
|
+
hinge.enableMotor();
|
|
749
|
+
hinge.setMotorSpeed(component.motorSpeed);
|
|
750
|
+
}
|
|
751
|
+
return hinge;
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
detachConstraintsFor(physics) {
|
|
756
|
+
for (const constraint of [...this.constraints]) {
|
|
757
|
+
const physicsA = constraint.gameObject?.getComponent('Physics3D');
|
|
758
|
+
const target = this.game.gameObjects.find((go) => go.name === constraint.target);
|
|
759
|
+
const physicsB = target?.getComponent('Physics3D');
|
|
760
|
+
if (physicsA === physics || physicsB === physics) {
|
|
761
|
+
this.removeConstraint(constraint);
|
|
762
|
+
this.pendingConstraints.add(constraint);
|
|
763
|
+
}
|
|
764
|
+
}
|
|
765
|
+
}
|
|
237
766
|
}
|
|
238
767
|
|
|
239
768
|
let Physics3DSystem = class Physics3DSystem extends System {
|
|
@@ -268,6 +797,7 @@ let Physics3DSystem = class Physics3DSystem extends System {
|
|
|
268
797
|
break;
|
|
269
798
|
}
|
|
270
799
|
case OBSERVER_TYPE.REMOVE: {
|
|
800
|
+
this.engine.remove(changed.component);
|
|
271
801
|
break;
|
|
272
802
|
}
|
|
273
803
|
}
|
|
@@ -311,11 +841,97 @@ Physics3DSystem = __decorate([
|
|
|
311
841
|
], Physics3DSystem);
|
|
312
842
|
var Physics3DSystem$1 = Physics3DSystem;
|
|
313
843
|
|
|
844
|
+
let Physics3DConstraintSystem = class Physics3DConstraintSystem extends System {
|
|
845
|
+
constructor() {
|
|
846
|
+
super(...arguments);
|
|
847
|
+
this.name = 'Physics3DConstraintSystem';
|
|
848
|
+
}
|
|
849
|
+
static { this.systemName = 'Physics3DConstraintSystem'; }
|
|
850
|
+
init() {
|
|
851
|
+
const physics = this.game.getSystem(Physics3DSystem$1);
|
|
852
|
+
if (!physics) {
|
|
853
|
+
console.warn('Physics3DConstraintSystem requires Physics3DSystem');
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
update() {
|
|
857
|
+
const engine = this.engine();
|
|
858
|
+
const changes = this.componentObserver.clear();
|
|
859
|
+
for (const changed of changes) {
|
|
860
|
+
this.componentChanged(changed);
|
|
861
|
+
}
|
|
862
|
+
engine?.flushConstraints();
|
|
863
|
+
}
|
|
864
|
+
componentChanged(changed) {
|
|
865
|
+
if (changed.componentName !== 'Physics3DConstraint')
|
|
866
|
+
return;
|
|
867
|
+
const engine = this.engine();
|
|
868
|
+
if (!engine)
|
|
869
|
+
return;
|
|
870
|
+
const component = changed.component;
|
|
871
|
+
if (changed.type === OBSERVER_TYPE.REMOVE) {
|
|
872
|
+
engine.removeConstraint(component);
|
|
873
|
+
return;
|
|
874
|
+
}
|
|
875
|
+
engine.addConstraint(component);
|
|
876
|
+
}
|
|
877
|
+
engine() {
|
|
878
|
+
return this.game.getSystem(Physics3DSystem$1)?.engine;
|
|
879
|
+
}
|
|
880
|
+
};
|
|
881
|
+
Physics3DConstraintSystem = __decorate([
|
|
882
|
+
decorators.componentObserver({
|
|
883
|
+
Physics3DConstraint: [
|
|
884
|
+
'type', 'target',
|
|
885
|
+
'pivotAX', 'pivotAY', 'pivotAZ',
|
|
886
|
+
'pivotBX', 'pivotBY', 'pivotBZ',
|
|
887
|
+
'axisAX', 'axisAY', 'axisAZ',
|
|
888
|
+
'axisBX', 'axisBY', 'axisBZ',
|
|
889
|
+
'distance', 'maxForce', 'collideConnected',
|
|
890
|
+
'motor', 'motorSpeed',
|
|
891
|
+
],
|
|
892
|
+
})
|
|
893
|
+
], Physics3DConstraintSystem);
|
|
894
|
+
var Physics3DConstraintSystem$1 = Physics3DConstraintSystem;
|
|
895
|
+
|
|
314
896
|
/** Auto-generated by scripts/build-package.mjs — do not edit. */
|
|
897
|
+
Object.assign(Physics3DConstraintSystem$1, {
|
|
898
|
+
packageName: "@combos-fun/plugin-cannon",
|
|
899
|
+
packageVersion: "0.0.45",
|
|
900
|
+
});
|
|
315
901
|
Object.assign(Physics3DSystem$1, {
|
|
316
902
|
packageName: "@combos-fun/plugin-cannon",
|
|
317
|
-
packageVersion: "0.0.
|
|
903
|
+
packageVersion: "0.0.45",
|
|
318
904
|
});
|
|
319
905
|
|
|
320
|
-
|
|
906
|
+
class Physics3DConstraint extends Component {
|
|
907
|
+
constructor() {
|
|
908
|
+
super(...arguments);
|
|
909
|
+
this.type = 'hinge';
|
|
910
|
+
this.target = '';
|
|
911
|
+
this.pivotAX = 0;
|
|
912
|
+
this.pivotAY = 0;
|
|
913
|
+
this.pivotAZ = 0;
|
|
914
|
+
this.pivotBX = 0;
|
|
915
|
+
this.pivotBY = 0;
|
|
916
|
+
this.pivotBZ = 0;
|
|
917
|
+
this.axisAX = 0;
|
|
918
|
+
this.axisAY = 1;
|
|
919
|
+
this.axisAZ = 0;
|
|
920
|
+
this.axisBX = 0;
|
|
921
|
+
this.axisBY = 1;
|
|
922
|
+
this.axisBZ = 0;
|
|
923
|
+
this.distance = 1;
|
|
924
|
+
this.maxForce = 1e6;
|
|
925
|
+
this.collideConnected = true;
|
|
926
|
+
this.motor = false;
|
|
927
|
+
this.motorSpeed = 0;
|
|
928
|
+
}
|
|
929
|
+
static { this.componentName = 'Physics3DConstraint'; }
|
|
930
|
+
init(obj) {
|
|
931
|
+
if (obj)
|
|
932
|
+
Object.assign(this, obj);
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
|
|
936
|
+
export { Physics3D, Physics3DConstraint, Physics3DConstraintSystem$1 as Physics3DConstraintSystem, Physics3DSystem$1 as Physics3DSystem, Physics3DType };
|
|
321
937
|
//# sourceMappingURL=plugin-cannon.esm.js.map
|