@flighthq/physics3d 0.5.1-next.903.f434bc2 → 0.5.1-next.905.5fbf787
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 +2 -2
- package/package.json +8 -8
- package/src/colliderTransform.test.ts +41 -0
- package/src/contacts.test.ts +17 -1
- package/src/debugGeometry.test.ts +10 -1
- package/src/jointFactories.test.ts +48 -0
- package/src/jointReaction.test.ts +37 -31
- package/src/massProperties.test.ts +10 -4
- package/src/solver.test.ts +101 -88
- package/src/world.test.ts +54 -34
- package/src/worldQueries.test.ts +21 -1
package/README.md
CHANGED
|
@@ -13,5 +13,5 @@ Import the supported application-facing API from `@flighthq/physics3d`. The `@fl
|
|
|
13
13
|
This package is part of the locked-version Flight SDK graph. Applications may instead install and import `@flighthq/sdk` when package-level tree shaking is sufficient.
|
|
14
14
|
|
|
15
15
|
- [Flight project](https://github.com/flighthq/flight)
|
|
16
|
-
- [Source for @flighthq/physics3d@0.5.1-next.
|
|
17
|
-
- [License](https://github.com/flighthq/flight/blob/
|
|
16
|
+
- [Source for @flighthq/physics3d@0.5.1-next.905.5fbf787](https://github.com/flighthq/flight/tree/5fbf7874064c384307542d68b4dcb6a895ff0dcf/packages/physics3d)
|
|
17
|
+
- [License](https://github.com/flighthq/flight/blob/5fbf7874064c384307542d68b4dcb6a895ff0dcf/LICENSE.md)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@flighthq/physics3d",
|
|
3
|
-
"version": "0.5.1-next.
|
|
3
|
+
"version": "0.5.1-next.905.5fbf787",
|
|
4
4
|
"author": "Joshua Granick and other contributors",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -38,13 +38,13 @@
|
|
|
38
38
|
"clean:dist": "tsx ../../scripts/clean-package-dist.ts"
|
|
39
39
|
},
|
|
40
40
|
"dependencies": {
|
|
41
|
-
"@flighthq/collision": "0.5.1-next.
|
|
42
|
-
"@flighthq/entity": "0.5.1-next.
|
|
43
|
-
"@flighthq/log": "0.5.1-next.
|
|
44
|
-
"@flighthq/math": "0.5.1-next.
|
|
45
|
-
"@flighthq/node": "0.5.1-next.
|
|
46
|
-
"@flighthq/spatial": "0.5.1-next.
|
|
47
|
-
"@flighthq/types": "0.5.1-next.
|
|
41
|
+
"@flighthq/collision": "0.5.1-next.905.5fbf787",
|
|
42
|
+
"@flighthq/entity": "0.5.1-next.905.5fbf787",
|
|
43
|
+
"@flighthq/log": "0.5.1-next.905.5fbf787",
|
|
44
|
+
"@flighthq/math": "0.5.1-next.905.5fbf787",
|
|
45
|
+
"@flighthq/node": "0.5.1-next.905.5fbf787",
|
|
46
|
+
"@flighthq/spatial": "0.5.1-next.905.5fbf787",
|
|
47
|
+
"@flighthq/types": "0.5.1-next.905.5fbf787"
|
|
48
48
|
},
|
|
49
49
|
"devDependencies": {
|
|
50
50
|
"@flighthq/geometry": "*",
|
|
@@ -4,6 +4,12 @@ import { describe, expect, it } from 'vitest';
|
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
createPhysics3DColliderWorldShape,
|
|
7
|
+
initializeCollisionBox3D,
|
|
8
|
+
initializeCollisionCapsule3D,
|
|
9
|
+
initializeCollisionCone3D,
|
|
10
|
+
initializeCollisionConvex3D,
|
|
11
|
+
initializeCollisionCylinder3D,
|
|
12
|
+
initializeCollisionSphere3D,
|
|
7
13
|
updatePhysics3DColliderWorldShape,
|
|
8
14
|
writePhysics3DColliderBounds,
|
|
9
15
|
} from './colliderTransform';
|
|
@@ -64,6 +70,41 @@ describe('createPhysics3DColliderWorldShape', () => {
|
|
|
64
70
|
});
|
|
65
71
|
});
|
|
66
72
|
|
|
73
|
+
describe('initializeCollisionBox3D', () => {
|
|
74
|
+
it('is the construction initializer of createCollisionBox3D', () => {
|
|
75
|
+
expect(typeof initializeCollisionBox3D).toBe('function');
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
describe('initializeCollisionCapsule3D', () => {
|
|
80
|
+
it('is the construction initializer of createCollisionCapsule3D', () => {
|
|
81
|
+
expect(typeof initializeCollisionCapsule3D).toBe('function');
|
|
82
|
+
});
|
|
83
|
+
});
|
|
84
|
+
describe('initializeCollisionCone3D', () => {
|
|
85
|
+
it('is the construction initializer of createCollisionCone3D', () => {
|
|
86
|
+
expect(typeof initializeCollisionCone3D).toBe('function');
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
describe('initializeCollisionConvex3D', () => {
|
|
91
|
+
it('is the construction initializer of createCollisionConvex3D', () => {
|
|
92
|
+
expect(typeof initializeCollisionConvex3D).toBe('function');
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
describe('initializeCollisionCylinder3D', () => {
|
|
97
|
+
it('is the construction initializer of createCollisionCylinder3D', () => {
|
|
98
|
+
expect(typeof initializeCollisionCylinder3D).toBe('function');
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
describe('initializeCollisionSphere3D', () => {
|
|
103
|
+
it('is the construction initializer of createCollisionSphere3D', () => {
|
|
104
|
+
expect(typeof initializeCollisionSphere3D).toBe('function');
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
67
108
|
describe('updatePhysics3DColliderWorldShape', () => {
|
|
68
109
|
it('translates a sphere by the body position', () => {
|
|
69
110
|
const body = createRigidBody3D('dynamic');
|
package/src/contacts.test.ts
CHANGED
|
@@ -1,6 +1,11 @@
|
|
|
1
1
|
import { describe, expect, it } from 'vitest';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
createPhysics3DContact,
|
|
5
|
+
createPhysics3DContactPoint,
|
|
6
|
+
initializePhysics3DContact,
|
|
7
|
+
initializePhysics3DContactPoint,
|
|
8
|
+
} from './contacts';
|
|
4
9
|
|
|
5
10
|
describe('createPhysics3DContact', () => {
|
|
6
11
|
it('orders the pair by index whichever way round it is given', () => {
|
|
@@ -62,3 +67,14 @@ describe('createPhysics3DContactPoint', () => {
|
|
|
62
67
|
]);
|
|
63
68
|
});
|
|
64
69
|
});
|
|
70
|
+
describe('initializePhysics3DContact', () => {
|
|
71
|
+
it('is the construction initializer of createPhysics3DContact', () => {
|
|
72
|
+
expect(typeof initializePhysics3DContact).toBe('function');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
describe('initializePhysics3DContactPoint', () => {
|
|
77
|
+
it('is the construction initializer of createPhysics3DContactPoint', () => {
|
|
78
|
+
expect(typeof initializePhysics3DContactPoint).toBe('function');
|
|
79
|
+
});
|
|
80
|
+
});
|
|
@@ -8,7 +8,11 @@ import type { CollisionBuiltInShape3D, Physics3DWorld, RigidBody3D } from '@flig
|
|
|
8
8
|
import { beforeEach, describe, expect, it } from 'vitest';
|
|
9
9
|
|
|
10
10
|
import { buildPhysics3DContacts } from './contactIntake';
|
|
11
|
-
import {
|
|
11
|
+
import {
|
|
12
|
+
createPhysics3DDebugGeometry,
|
|
13
|
+
initializePhysics3DDebugGeometry,
|
|
14
|
+
writePhysics3DDebugGeometry,
|
|
15
|
+
} from './debugGeometry';
|
|
12
16
|
import { createPhysics3DBallAndSocketJoint } from './jointFactories';
|
|
13
17
|
import { addPhysics3DJoint } from './jointRegistry';
|
|
14
18
|
import { registerBuiltInPhysics3DJointSolvers } from './registerBuiltInPhysics3DJointSolvers';
|
|
@@ -57,6 +61,11 @@ describe('createPhysics3DDebugGeometry', () => {
|
|
|
57
61
|
});
|
|
58
62
|
});
|
|
59
63
|
|
|
64
|
+
describe('initializePhysics3DDebugGeometry', () => {
|
|
65
|
+
it('is the construction initializer of createPhysics3DDebugGeometry', () => {
|
|
66
|
+
expect(typeof initializePhysics3DDebugGeometry).toBe('function');
|
|
67
|
+
});
|
|
68
|
+
});
|
|
60
69
|
describe('writePhysics3DDebugGeometry', () => {
|
|
61
70
|
it('draws explicit mesh triangles and heightfield grid edges', () => {
|
|
62
71
|
const world = createPhysics3DWorld();
|
|
@@ -8,6 +8,13 @@ import {
|
|
|
8
8
|
createPhysics3DGeneric6DofJoint,
|
|
9
9
|
createPhysics3DHingeJoint,
|
|
10
10
|
createPhysics3DSliderJoint,
|
|
11
|
+
initializePhysics3DBallAndSocketJoint,
|
|
12
|
+
initializePhysics3DConeTwistJoint,
|
|
13
|
+
initializePhysics3DDistanceJoint,
|
|
14
|
+
initializePhysics3DFixedJoint,
|
|
15
|
+
initializePhysics3DGeneric6DofJoint,
|
|
16
|
+
initializePhysics3DHingeJoint,
|
|
17
|
+
initializePhysics3DSliderJoint,
|
|
11
18
|
} from './jointFactories';
|
|
12
19
|
import {
|
|
13
20
|
Physics3DBallAndSocketJointKind,
|
|
@@ -242,3 +249,44 @@ describe('createPhysics3DSliderJoint', () => {
|
|
|
242
249
|
expect(joint.maxMotorForce).toBe(15);
|
|
243
250
|
});
|
|
244
251
|
});
|
|
252
|
+
describe('initializePhysics3DBallAndSocketJoint', () => {
|
|
253
|
+
it('is the construction initializer of createPhysics3DBallAndSocketJoint', () => {
|
|
254
|
+
expect(typeof initializePhysics3DBallAndSocketJoint).toBe('function');
|
|
255
|
+
});
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
describe('initializePhysics3DConeTwistJoint', () => {
|
|
259
|
+
it('is the construction initializer of createPhysics3DConeTwistJoint', () => {
|
|
260
|
+
expect(typeof initializePhysics3DConeTwistJoint).toBe('function');
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe('initializePhysics3DDistanceJoint', () => {
|
|
265
|
+
it('is the construction initializer of createPhysics3DDistanceJoint', () => {
|
|
266
|
+
expect(typeof initializePhysics3DDistanceJoint).toBe('function');
|
|
267
|
+
});
|
|
268
|
+
});
|
|
269
|
+
|
|
270
|
+
describe('initializePhysics3DFixedJoint', () => {
|
|
271
|
+
it('is the construction initializer of createPhysics3DFixedJoint', () => {
|
|
272
|
+
expect(typeof initializePhysics3DFixedJoint).toBe('function');
|
|
273
|
+
});
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
describe('initializePhysics3DGeneric6DofJoint', () => {
|
|
277
|
+
it('is the construction initializer of createPhysics3DGeneric6DofJoint', () => {
|
|
278
|
+
expect(typeof initializePhysics3DGeneric6DofJoint).toBe('function');
|
|
279
|
+
});
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
describe('initializePhysics3DHingeJoint', () => {
|
|
283
|
+
it('is the construction initializer of createPhysics3DHingeJoint', () => {
|
|
284
|
+
expect(typeof initializePhysics3DHingeJoint).toBe('function');
|
|
285
|
+
});
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
describe('initializePhysics3DSliderJoint', () => {
|
|
289
|
+
it('is the construction initializer of createPhysics3DSliderJoint', () => {
|
|
290
|
+
expect(typeof initializePhysics3DSliderJoint).toBe('function');
|
|
291
|
+
});
|
|
292
|
+
});
|
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
createPhysics3DJointReaction,
|
|
9
9
|
getPhysics3DJointReactionForce,
|
|
10
10
|
getPhysics3DJointReactionTorque,
|
|
11
|
+
initializePhysics3DJointReaction,
|
|
11
12
|
writePhysics3DJointReaction,
|
|
12
13
|
} from './jointReaction';
|
|
13
14
|
import { addPhysics3DJoint } from './jointRegistry';
|
|
@@ -117,6 +118,42 @@ describe('getPhysics3DJointReactionTorque', () => {
|
|
|
117
118
|
});
|
|
118
119
|
});
|
|
119
120
|
|
|
121
|
+
describe('initializePhysics3DJointReaction', () => {
|
|
122
|
+
it('is the construction initializer of createPhysics3DJointReaction', () => {
|
|
123
|
+
expect(typeof initializePhysics3DJointReaction).toBe('function');
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
interface HangingScene {
|
|
128
|
+
world: Physics3DWorld;
|
|
129
|
+
joint: ReturnType<typeof createPhysics3DBallAndSocketJoint>;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// One body hanging from a static anchor by a ball-and-socket, which is the simplest arrangement whose
|
|
133
|
+
// reaction has a value known without reference to the solver: it is the hanging weight.
|
|
134
|
+
function createHangingScene(mass: number, gravity: number): HangingScene {
|
|
135
|
+
const world = createPhysics3DWorld();
|
|
136
|
+
world.gravityY = -gravity;
|
|
137
|
+
registerBuiltInPhysics3DJointSolvers(world);
|
|
138
|
+
const anchor = createRigidBody3D('static');
|
|
139
|
+
addPhysics3DBody(world, anchor);
|
|
140
|
+
const hanging = createUnitBody(mass);
|
|
141
|
+
addPhysics3DBody(world, hanging);
|
|
142
|
+
const joint = createPhysics3DBallAndSocketJoint({ bodyA: 0, bodyB: 1 });
|
|
143
|
+
addPhysics3DJoint(world, joint);
|
|
144
|
+
return { world, joint };
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function createUnitBody(mass: number): RigidBody3D {
|
|
148
|
+
const body = createRigidBody3D('dynamic');
|
|
149
|
+
const data = createPhysics3DMassData();
|
|
150
|
+
data.mass = mass;
|
|
151
|
+
data.inertiaXX = mass;
|
|
152
|
+
data.inertiaYY = mass;
|
|
153
|
+
data.inertiaZZ = mass;
|
|
154
|
+
setRigidBody3DMassData(body, data);
|
|
155
|
+
return body;
|
|
156
|
+
}
|
|
120
157
|
describe('writePhysics3DJointReaction', () => {
|
|
121
158
|
it('MEASURES THE WEIGHT A JOINT IS HOLDING UP', () => {
|
|
122
159
|
// The reading that makes the API worth having, checked against a number derived from outside the
|
|
@@ -208,34 +245,3 @@ describe('writePhysics3DJointReaction', () => {
|
|
|
208
245
|
expect(Math.abs(out.forceY)).toBeCloseTo(20, 0);
|
|
209
246
|
});
|
|
210
247
|
});
|
|
211
|
-
|
|
212
|
-
interface HangingScene {
|
|
213
|
-
world: Physics3DWorld;
|
|
214
|
-
joint: ReturnType<typeof createPhysics3DBallAndSocketJoint>;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
// One body hanging from a static anchor by a ball-and-socket, which is the simplest arrangement whose
|
|
218
|
-
// reaction has a value known without reference to the solver: it is the hanging weight.
|
|
219
|
-
function createHangingScene(mass: number, gravity: number): HangingScene {
|
|
220
|
-
const world = createPhysics3DWorld();
|
|
221
|
-
world.gravityY = -gravity;
|
|
222
|
-
registerBuiltInPhysics3DJointSolvers(world);
|
|
223
|
-
const anchor = createRigidBody3D('static');
|
|
224
|
-
addPhysics3DBody(world, anchor);
|
|
225
|
-
const hanging = createUnitBody(mass);
|
|
226
|
-
addPhysics3DBody(world, hanging);
|
|
227
|
-
const joint = createPhysics3DBallAndSocketJoint({ bodyA: 0, bodyB: 1 });
|
|
228
|
-
addPhysics3DJoint(world, joint);
|
|
229
|
-
return { world, joint };
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
function createUnitBody(mass: number): RigidBody3D {
|
|
233
|
-
const body = createRigidBody3D('dynamic');
|
|
234
|
-
const data = createPhysics3DMassData();
|
|
235
|
-
data.mass = mass;
|
|
236
|
-
data.inertiaXX = mass;
|
|
237
|
-
data.inertiaYY = mass;
|
|
238
|
-
data.inertiaZZ = mass;
|
|
239
|
-
setRigidBody3DMassData(body, data);
|
|
240
|
-
return body;
|
|
241
|
-
}
|
|
@@ -4,15 +4,16 @@ import { describe, expect, it } from 'vitest';
|
|
|
4
4
|
import {
|
|
5
5
|
combinePhysics3DMassData,
|
|
6
6
|
computePhysics3DBoxMassData,
|
|
7
|
+
computePhysics3DCapsuleMassData,
|
|
7
8
|
computePhysics3DColliderMassData,
|
|
8
9
|
computePhysics3DConeMassData,
|
|
9
|
-
computePhysics3DCylinderMassData,
|
|
10
10
|
computePhysics3DConvexHullMassData,
|
|
11
|
-
|
|
11
|
+
computePhysics3DCylinderMassData,
|
|
12
12
|
computePhysics3DSphereMassData,
|
|
13
13
|
createPhysics3DMassData,
|
|
14
|
-
|
|
14
|
+
initializePhysics3DMassData,
|
|
15
15
|
setRigidBody3DMassData,
|
|
16
|
+
updateRigidBody3DMassData,
|
|
16
17
|
} from './massProperties';
|
|
17
18
|
import { addPhysics3DBody, createPhysics3DCollider, createPhysics3DWorld, createRigidBody3D } from './world';
|
|
18
19
|
|
|
@@ -427,6 +428,12 @@ describe('createPhysics3DMassData', () => {
|
|
|
427
428
|
});
|
|
428
429
|
});
|
|
429
430
|
|
|
431
|
+
describe('initializePhysics3DMassData', () => {
|
|
432
|
+
it('is the construction initializer of createPhysics3DMassData', () => {
|
|
433
|
+
expect(typeof initializePhysics3DMassData).toBe('function');
|
|
434
|
+
});
|
|
435
|
+
});
|
|
436
|
+
|
|
430
437
|
describe('setRigidBody3DMassData', () => {
|
|
431
438
|
it('derives the inverse mass and a non-zero inverse tensor for a dynamic body', () => {
|
|
432
439
|
const body = testBody('dynamic');
|
|
@@ -496,7 +503,6 @@ describe('setRigidBody3DMassData', () => {
|
|
|
496
503
|
expect(body.centerY).toBe(4);
|
|
497
504
|
});
|
|
498
505
|
});
|
|
499
|
-
|
|
500
506
|
describe('updateRigidBody3DMassData', () => {
|
|
501
507
|
it('derives a body mass from its colliders', () => {
|
|
502
508
|
const world = createPhysics3DWorld();
|
package/src/solver.test.ts
CHANGED
|
@@ -17,6 +17,8 @@ import { setRigidBody3DMassData } from './massProperties';
|
|
|
17
17
|
import {
|
|
18
18
|
createPhysics3DContactConstraint,
|
|
19
19
|
createPhysics3DContactConstraintPoint,
|
|
20
|
+
initializePhysics3DContactConstraint,
|
|
21
|
+
initializePhysics3DContactConstraintPoint,
|
|
20
22
|
preparePhysics3DContactConstraints,
|
|
21
23
|
solvePhysics3DContactPositions,
|
|
22
24
|
solvePhysics3DContactVelocities,
|
|
@@ -55,6 +57,18 @@ describe('createPhysics3DContactConstraintPoint', () => {
|
|
|
55
57
|
});
|
|
56
58
|
});
|
|
57
59
|
|
|
60
|
+
describe('initializePhysics3DContactConstraint', () => {
|
|
61
|
+
it('is the construction initializer of createPhysics3DContactConstraint', () => {
|
|
62
|
+
expect(typeof initializePhysics3DContactConstraint).toBe('function');
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
describe('initializePhysics3DContactConstraintPoint', () => {
|
|
67
|
+
it('is the construction initializer of createPhysics3DContactConstraintPoint', () => {
|
|
68
|
+
expect(typeof initializePhysics3DContactConstraintPoint).toBe('function');
|
|
69
|
+
});
|
|
70
|
+
});
|
|
71
|
+
|
|
58
72
|
describe('preparePhysics3DContactConstraints', () => {
|
|
59
73
|
it('builds a friction basis orthonormal to the contact normal', () => {
|
|
60
74
|
const world = createFallingBoxWorld();
|
|
@@ -293,6 +307,93 @@ describe('solvePhysics3DContactPositions', () => {
|
|
|
293
307
|
});
|
|
294
308
|
});
|
|
295
309
|
|
|
310
|
+
function createContactPoint(featureId: number, depth: number): Physics3DContactPoint {
|
|
311
|
+
const point = createPhysics3DContactPointRecord();
|
|
312
|
+
point.depth = depth;
|
|
313
|
+
point.featureId = featureId;
|
|
314
|
+
point.rAY = -0.5;
|
|
315
|
+
point.rBY = 0.5;
|
|
316
|
+
return point;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function createContact(bodyA: number, bodyB: number): Physics3DContact {
|
|
320
|
+
const contact = createPhysics3DContactRecord(bodyA, bodyB);
|
|
321
|
+
contact.normalY = 1;
|
|
322
|
+
contact.pointCount = 1;
|
|
323
|
+
contact.points.push(createContactPoint(1, 0.01));
|
|
324
|
+
contact.touching = true;
|
|
325
|
+
return contact;
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
function createUnitBox(world: Physics3DWorld): RigidBody3D {
|
|
329
|
+
const body = createRigidBody3D();
|
|
330
|
+
const mass = createPhysics3DMassData();
|
|
331
|
+
computePhysics3DBoxMassData(0.5, 0.5, 0.5, 1, mass);
|
|
332
|
+
setRigidBody3DMassData(body, mass);
|
|
333
|
+
refreshRigidBody3DWorldInertia(body);
|
|
334
|
+
addPhysics3DBody(world, body);
|
|
335
|
+
return body;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// The solver reads the SOLVE ISLAND contact slices rather than `world.contacts`, so a world assembled
|
|
339
|
+
// by hand needs the same workspace `stepPhysics3D` builds before it reaches the solver. Without it every
|
|
340
|
+
// prepare finds nothing to do — which is the correct answer for a world with no awake islands, and the
|
|
341
|
+
// reason this is a factory step rather than something prepare rebuilds for itself.
|
|
342
|
+
//
|
|
343
|
+
// A test that changes what belongs in an island AFTER this — putting a body to sleep, disabling a
|
|
344
|
+
// contact, making a body static — has to call it again, exactly as a real step rebuilds every step.
|
|
345
|
+
function buildSolveWorkspace(world: Physics3DWorld): Physics3DWorld {
|
|
346
|
+
updatePhysics3DSleep(world, 1 / 60);
|
|
347
|
+
buildPhysics3DSolveIslands(world);
|
|
348
|
+
return world;
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// One dynamic box whose single contact point references a body that is not in the world's contact
|
|
352
|
+
// partner slot — used for the basis and mass-denominator tests, where only body A matters.
|
|
353
|
+
function createFallingBoxWorld(): Physics3DWorld {
|
|
354
|
+
const world = createPhysics3DWorld();
|
|
355
|
+
const box = createUnitBox(world);
|
|
356
|
+
const other = createUnitBox(world);
|
|
357
|
+
world.contacts.push(createContact(box.index, other.index));
|
|
358
|
+
return buildSolveWorkspace(world);
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
// Two unit boxes with REAL colliders, overlapping vertically by `depth`, with contacts generated the
|
|
362
|
+
// way a step generates them. The position pass reads geometry, so this is the only fixture shape its
|
|
363
|
+
// tests can be written against.
|
|
364
|
+
function createOverlappingBoxWorld(depth: number): Physics3DWorld {
|
|
365
|
+
const world = createPhysics3DWorld();
|
|
366
|
+
const upper = createUnitBox(world);
|
|
367
|
+
const lower = createUnitBox(world);
|
|
368
|
+
setPhysics3DBodyType(lower, 'static');
|
|
369
|
+
for (const body of [upper, lower]) {
|
|
370
|
+
addPhysics3DCollider(
|
|
371
|
+
world,
|
|
372
|
+
body,
|
|
373
|
+
createPhysics3DCollider({ kind: 'aabb', minX: -0.5, minY: -0.5, minZ: -0.5, maxX: 0.5, maxY: 0.5, maxZ: 0.5 }),
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
upper.y = 1 - depth;
|
|
377
|
+
buildPhysics3DContacts(world);
|
|
378
|
+
return buildSolveWorkspace(world);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
function createBoxOnGroundWorld(): Physics3DWorld {
|
|
382
|
+
const world = createPhysics3DWorld();
|
|
383
|
+
const box = createUnitBox(world);
|
|
384
|
+
const ground = createUnitBox(world);
|
|
385
|
+
setPhysics3DBodyType(ground, 'static');
|
|
386
|
+
world.contacts.push(createContact(box.index, ground.index));
|
|
387
|
+
return buildSolveWorkspace(world);
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
function createTwoDynamicBodyWorld(): Physics3DWorld {
|
|
391
|
+
const world = createPhysics3DWorld();
|
|
392
|
+
const upper = createUnitBox(world);
|
|
393
|
+
const lower = createUnitBox(world);
|
|
394
|
+
world.contacts.push(createContact(upper.index, lower.index));
|
|
395
|
+
return buildSolveWorkspace(world);
|
|
396
|
+
}
|
|
296
397
|
describe('solvePhysics3DContactVelocities', () => {
|
|
297
398
|
it('removes the approach velocity of a body resting on static ground', () => {
|
|
298
399
|
const world = createBoxOnGroundWorld();
|
|
@@ -464,91 +565,3 @@ describe('warmStartPhysics3DContacts', () => {
|
|
|
464
565
|
expect(Math.abs(warm.bodies[0].velocityY)).toBeLessThanOrEqual(Math.abs(cold.bodies[0].velocityY));
|
|
465
566
|
});
|
|
466
567
|
});
|
|
467
|
-
|
|
468
|
-
function createContactPoint(featureId: number, depth: number): Physics3DContactPoint {
|
|
469
|
-
const point = createPhysics3DContactPointRecord();
|
|
470
|
-
point.depth = depth;
|
|
471
|
-
point.featureId = featureId;
|
|
472
|
-
point.rAY = -0.5;
|
|
473
|
-
point.rBY = 0.5;
|
|
474
|
-
return point;
|
|
475
|
-
}
|
|
476
|
-
|
|
477
|
-
function createContact(bodyA: number, bodyB: number): Physics3DContact {
|
|
478
|
-
const contact = createPhysics3DContactRecord(bodyA, bodyB);
|
|
479
|
-
contact.normalY = 1;
|
|
480
|
-
contact.pointCount = 1;
|
|
481
|
-
contact.points.push(createContactPoint(1, 0.01));
|
|
482
|
-
contact.touching = true;
|
|
483
|
-
return contact;
|
|
484
|
-
}
|
|
485
|
-
|
|
486
|
-
function createUnitBox(world: Physics3DWorld): RigidBody3D {
|
|
487
|
-
const body = createRigidBody3D();
|
|
488
|
-
const mass = createPhysics3DMassData();
|
|
489
|
-
computePhysics3DBoxMassData(0.5, 0.5, 0.5, 1, mass);
|
|
490
|
-
setRigidBody3DMassData(body, mass);
|
|
491
|
-
refreshRigidBody3DWorldInertia(body);
|
|
492
|
-
addPhysics3DBody(world, body);
|
|
493
|
-
return body;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
// The solver reads the SOLVE ISLAND contact slices rather than `world.contacts`, so a world assembled
|
|
497
|
-
// by hand needs the same workspace `stepPhysics3D` builds before it reaches the solver. Without it every
|
|
498
|
-
// prepare finds nothing to do — which is the correct answer for a world with no awake islands, and the
|
|
499
|
-
// reason this is a factory step rather than something prepare rebuilds for itself.
|
|
500
|
-
//
|
|
501
|
-
// A test that changes what belongs in an island AFTER this — putting a body to sleep, disabling a
|
|
502
|
-
// contact, making a body static — has to call it again, exactly as a real step rebuilds every step.
|
|
503
|
-
function buildSolveWorkspace(world: Physics3DWorld): Physics3DWorld {
|
|
504
|
-
updatePhysics3DSleep(world, 1 / 60);
|
|
505
|
-
buildPhysics3DSolveIslands(world);
|
|
506
|
-
return world;
|
|
507
|
-
}
|
|
508
|
-
|
|
509
|
-
// One dynamic box whose single contact point references a body that is not in the world's contact
|
|
510
|
-
// partner slot — used for the basis and mass-denominator tests, where only body A matters.
|
|
511
|
-
function createFallingBoxWorld(): Physics3DWorld {
|
|
512
|
-
const world = createPhysics3DWorld();
|
|
513
|
-
const box = createUnitBox(world);
|
|
514
|
-
const other = createUnitBox(world);
|
|
515
|
-
world.contacts.push(createContact(box.index, other.index));
|
|
516
|
-
return buildSolveWorkspace(world);
|
|
517
|
-
}
|
|
518
|
-
|
|
519
|
-
// Two unit boxes with REAL colliders, overlapping vertically by `depth`, with contacts generated the
|
|
520
|
-
// way a step generates them. The position pass reads geometry, so this is the only fixture shape its
|
|
521
|
-
// tests can be written against.
|
|
522
|
-
function createOverlappingBoxWorld(depth: number): Physics3DWorld {
|
|
523
|
-
const world = createPhysics3DWorld();
|
|
524
|
-
const upper = createUnitBox(world);
|
|
525
|
-
const lower = createUnitBox(world);
|
|
526
|
-
setPhysics3DBodyType(lower, 'static');
|
|
527
|
-
for (const body of [upper, lower]) {
|
|
528
|
-
addPhysics3DCollider(
|
|
529
|
-
world,
|
|
530
|
-
body,
|
|
531
|
-
createPhysics3DCollider({ kind: 'aabb', minX: -0.5, minY: -0.5, minZ: -0.5, maxX: 0.5, maxY: 0.5, maxZ: 0.5 }),
|
|
532
|
-
);
|
|
533
|
-
}
|
|
534
|
-
upper.y = 1 - depth;
|
|
535
|
-
buildPhysics3DContacts(world);
|
|
536
|
-
return buildSolveWorkspace(world);
|
|
537
|
-
}
|
|
538
|
-
|
|
539
|
-
function createBoxOnGroundWorld(): Physics3DWorld {
|
|
540
|
-
const world = createPhysics3DWorld();
|
|
541
|
-
const box = createUnitBox(world);
|
|
542
|
-
const ground = createUnitBox(world);
|
|
543
|
-
setPhysics3DBodyType(ground, 'static');
|
|
544
|
-
world.contacts.push(createContact(box.index, ground.index));
|
|
545
|
-
return buildSolveWorkspace(world);
|
|
546
|
-
}
|
|
547
|
-
|
|
548
|
-
function createTwoDynamicBodyWorld(): Physics3DWorld {
|
|
549
|
-
const world = createPhysics3DWorld();
|
|
550
|
-
const upper = createUnitBox(world);
|
|
551
|
-
const lower = createUnitBox(world);
|
|
552
|
-
world.contacts.push(createContact(upper.index, lower.index));
|
|
553
|
-
return buildSolveWorkspace(world);
|
|
554
|
-
}
|
package/src/world.test.ts
CHANGED
|
@@ -15,6 +15,7 @@ import { addPhysics3DJoint } from './jointRegistry';
|
|
|
15
15
|
import { computePhysics3DSphereMassData, createPhysics3DMassData, setRigidBody3DMassData } from './massProperties';
|
|
16
16
|
import { registerBuiltInPhysics3DJointSolvers } from './registerBuiltInPhysics3DJointSolvers';
|
|
17
17
|
import {
|
|
18
|
+
Physics3DWorldVersion,
|
|
18
19
|
addPhysics3DBody,
|
|
19
20
|
addPhysics3DCollider,
|
|
20
21
|
applyPhysics3DForce,
|
|
@@ -29,8 +30,10 @@ import {
|
|
|
29
30
|
createRigidBody3D,
|
|
30
31
|
findPhysics3DBody,
|
|
31
32
|
hydratePhysics3DWorld,
|
|
33
|
+
initializePhysics3DCollider,
|
|
34
|
+
initializePhysics3DWorld,
|
|
35
|
+
initializeRigidBody3D,
|
|
32
36
|
invalidatePhysics3DCollider,
|
|
33
|
-
Physics3DWorldVersion,
|
|
34
37
|
removePhysics3DBody,
|
|
35
38
|
removePhysics3DCollider,
|
|
36
39
|
setPhysics3DBodyBullet,
|
|
@@ -541,6 +544,24 @@ describe('hydratePhysics3DWorld', () => {
|
|
|
541
544
|
});
|
|
542
545
|
});
|
|
543
546
|
|
|
547
|
+
describe('initializePhysics3DCollider', () => {
|
|
548
|
+
it('is the construction initializer of createPhysics3DCollider', () => {
|
|
549
|
+
expect(typeof initializePhysics3DCollider).toBe('function');
|
|
550
|
+
});
|
|
551
|
+
});
|
|
552
|
+
|
|
553
|
+
describe('initializePhysics3DWorld', () => {
|
|
554
|
+
it('is the construction initializer of createPhysics3DWorld', () => {
|
|
555
|
+
expect(typeof initializePhysics3DWorld).toBe('function');
|
|
556
|
+
});
|
|
557
|
+
});
|
|
558
|
+
|
|
559
|
+
describe('initializeRigidBody3D', () => {
|
|
560
|
+
it('is the construction initializer of createRigidBody3D', () => {
|
|
561
|
+
expect(typeof initializeRigidBody3D).toBe('function');
|
|
562
|
+
});
|
|
563
|
+
});
|
|
564
|
+
|
|
544
565
|
describe('invalidatePhysics3DCollider', () => {
|
|
545
566
|
it('rebuilds the mass properties after the local shape is edited in place', () => {
|
|
546
567
|
const world = createPhysics3DWorld();
|
|
@@ -763,6 +784,38 @@ describe('removePhysics3DCollider', () => {
|
|
|
763
784
|
});
|
|
764
785
|
});
|
|
765
786
|
|
|
787
|
+
function contact(bodyA: number, bodyB: number): Physics3DWorld['contacts'][number] {
|
|
788
|
+
const value = createPhysics3DContact(bodyA, bodyB);
|
|
789
|
+
value.normalY = 1;
|
|
790
|
+
value.friction = 0.2;
|
|
791
|
+
value.touching = true;
|
|
792
|
+
return value;
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function sphere(): RigidBody3D {
|
|
796
|
+
const body = createRigidBody3D();
|
|
797
|
+
const data = createPhysics3DMassData();
|
|
798
|
+
computePhysics3DSphereMassData(1, 1, data);
|
|
799
|
+
setRigidBody3DMassData(body, data);
|
|
800
|
+
return body;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
beforeEach(() => {
|
|
804
|
+
registerBuiltInCollisionSupports3D();
|
|
805
|
+
registerBuiltInCollisionFaceQueries3D();
|
|
806
|
+
});
|
|
807
|
+
|
|
808
|
+
function colliderUnitBox(): CollisionBuiltInShape3D {
|
|
809
|
+
return { kind: 'aabb', minX: -0.5, minY: -0.5, minZ: -0.5, maxX: 0.5, maxY: 0.5, maxZ: 0.5 };
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
function addColliderTestBody(world: Physics3DWorld, y = 0): RigidBody3D {
|
|
813
|
+
const body = createRigidBody3D('dynamic');
|
|
814
|
+
body.y = y;
|
|
815
|
+
addPhysics3DBody(world, body);
|
|
816
|
+
return body;
|
|
817
|
+
}
|
|
818
|
+
|
|
766
819
|
describe('setPhysics3DBodyBullet', () => {
|
|
767
820
|
it('changes continuous-collision policy and wakes an owned body', () => {
|
|
768
821
|
const world = createPhysics3DWorld();
|
|
@@ -843,38 +896,6 @@ describe('setPhysics3DBodySleepEnabled', () => {
|
|
|
843
896
|
});
|
|
844
897
|
});
|
|
845
898
|
|
|
846
|
-
function contact(bodyA: number, bodyB: number): Physics3DWorld['contacts'][number] {
|
|
847
|
-
const value = createPhysics3DContact(bodyA, bodyB);
|
|
848
|
-
value.normalY = 1;
|
|
849
|
-
value.friction = 0.2;
|
|
850
|
-
value.touching = true;
|
|
851
|
-
return value;
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
function sphere(): RigidBody3D {
|
|
855
|
-
const body = createRigidBody3D();
|
|
856
|
-
const data = createPhysics3DMassData();
|
|
857
|
-
computePhysics3DSphereMassData(1, 1, data);
|
|
858
|
-
setRigidBody3DMassData(body, data);
|
|
859
|
-
return body;
|
|
860
|
-
}
|
|
861
|
-
|
|
862
|
-
beforeEach(() => {
|
|
863
|
-
registerBuiltInCollisionSupports3D();
|
|
864
|
-
registerBuiltInCollisionFaceQueries3D();
|
|
865
|
-
});
|
|
866
|
-
|
|
867
|
-
function colliderUnitBox(): CollisionBuiltInShape3D {
|
|
868
|
-
return { kind: 'aabb', minX: -0.5, minY: -0.5, minZ: -0.5, maxX: 0.5, maxY: 0.5, maxZ: 0.5 };
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
function addColliderTestBody(world: Physics3DWorld, y = 0): RigidBody3D {
|
|
872
|
-
const body = createRigidBody3D('dynamic');
|
|
873
|
-
body.y = y;
|
|
874
|
-
addPhysics3DBody(world, body);
|
|
875
|
-
return body;
|
|
876
|
-
}
|
|
877
|
-
|
|
878
899
|
describe('setPhysics3DBodyTransform', () => {
|
|
879
900
|
it('moves the body and refreshes the world inertia', () => {
|
|
880
901
|
const body = sphere();
|
|
@@ -948,7 +969,6 @@ describe('setPhysics3DBodyTransform', () => {
|
|
|
948
969
|
expect(neighbour.sleeping).toBe(false);
|
|
949
970
|
});
|
|
950
971
|
});
|
|
951
|
-
|
|
952
972
|
describe('setPhysics3DBodyType', () => {
|
|
953
973
|
it('declines to make a static terrain body movable', () => {
|
|
954
974
|
const body = createRigidBody3D('static');
|
package/src/worldQueries.test.ts
CHANGED
|
@@ -14,6 +14,9 @@ import {
|
|
|
14
14
|
createPhysics3DQueryResult,
|
|
15
15
|
createPhysics3DRayResult,
|
|
16
16
|
createPhysics3DShapeCastResult,
|
|
17
|
+
initializePhysics3DQueryResult,
|
|
18
|
+
initializePhysics3DRayResult,
|
|
19
|
+
initializePhysics3DShapeCastResult,
|
|
17
20
|
queryPhysics3DPoint,
|
|
18
21
|
queryPhysics3DRay,
|
|
19
22
|
queryPhysics3DRayClosest,
|
|
@@ -81,6 +84,24 @@ describe('createPhysics3DShapeCastResult', () => {
|
|
|
81
84
|
});
|
|
82
85
|
});
|
|
83
86
|
|
|
87
|
+
describe('initializePhysics3DQueryResult', () => {
|
|
88
|
+
it('is the construction initializer of createPhysics3DQueryResult', () => {
|
|
89
|
+
expect(typeof initializePhysics3DQueryResult).toBe('function');
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
describe('initializePhysics3DRayResult', () => {
|
|
94
|
+
it('is the construction initializer of createPhysics3DRayResult', () => {
|
|
95
|
+
expect(typeof initializePhysics3DRayResult).toBe('function');
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe('initializePhysics3DShapeCastResult', () => {
|
|
100
|
+
it('is the construction initializer of createPhysics3DShapeCastResult', () => {
|
|
101
|
+
expect(typeof initializePhysics3DShapeCastResult).toBe('function');
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
|
|
84
105
|
describe('queryPhysics3DPoint', () => {
|
|
85
106
|
it('finds the collider containing the point', () => {
|
|
86
107
|
const world = createPhysics3DWorld();
|
|
@@ -311,7 +332,6 @@ describe('queryPhysics3DRay', () => {
|
|
|
311
332
|
expect(out.hits[0].fraction).toBeCloseTo(4.5, 6);
|
|
312
333
|
});
|
|
313
334
|
});
|
|
314
|
-
|
|
315
335
|
describe('queryPhysics3DRayClosest', () => {
|
|
316
336
|
it('writes only the nearest hit', () => {
|
|
317
337
|
const world = createPhysics3DWorld();
|