dacha 0.18.0-alpha.7 → 0.18.0-alpha.9
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/build/contrib/components/rigid-body/index.d.ts +100 -14
- package/build/contrib/components/rigid-body/index.js +183 -22
- package/build/contrib/systems/animator/index.d.ts +3 -2
- package/build/contrib/systems/animator/index.js +6 -3
- package/build/contrib/systems/behavior-system/system.d.ts +4 -2
- package/build/contrib/systems/behavior-system/system.js +11 -3
- package/build/contrib/systems/behavior-system/types.d.ts +7 -10
- package/build/contrib/systems/character-controller/index.d.ts +4 -2
- package/build/contrib/systems/character-controller/index.js +22 -18
- package/build/contrib/systems/character-controller/one-way-validator.d.ts +15 -0
- package/build/contrib/systems/character-controller/one-way-validator.js +73 -0
- package/build/contrib/systems/game-stats-meter/index.d.ts +4 -3
- package/build/contrib/systems/game-stats-meter/index.js +11 -10
- package/build/contrib/systems/physics-system/consts.d.ts +11 -0
- package/build/contrib/systems/physics-system/consts.js +14 -0
- package/build/contrib/systems/physics-system/physics-system.d.ts +2 -2
- package/build/contrib/systems/physics-system/physics-system.js +18 -4
- package/build/contrib/systems/physics-system/subsystems/collision-detection/index.js +3 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/check-boxes-intersection.js +5 -3
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.d.ts +1 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-box/utils.js +2 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-capsule/check-box-and-capsule-intersection.js +4 -1
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/box-segment/check-box-and-segment-intersection.js +4 -2
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.d.ts +2 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/common/points.js +16 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/index.js +2 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/intersection-checkers/tests/helpers.js +3 -14
- package/build/contrib/systems/physics-system/subsystems/collision-detection/shape-cast-checkers/tests/helpers.js +2 -7
- package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.d.ts +1 -0
- package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.js +7 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.d.ts +63 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-state-manager.js +170 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.d.ts +5 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/contact-utils.js +41 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.d.ts +16 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/impulse-utils.js +77 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.d.ts +53 -4
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/index.js +334 -116
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/one-way-validator.d.ts +11 -0
- package/build/contrib/{utils → systems/physics-system/subsystems/constraint-solver}/one-way-validator.js +22 -25
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.d.ts +13 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/sleep-support-tracker.js +42 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.d.ts +4 -0
- package/build/contrib/systems/physics-system/subsystems/constraint-solver/tests/helpers.js +20 -0
- package/build/contrib/systems/physics-system/subsystems/physics/index.d.ts +15 -4
- package/build/contrib/systems/physics-system/subsystems/physics/index.js +131 -53
- package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.d.ts +2 -0
- package/build/contrib/systems/physics-system/subsystems/physics/mass-properties.js +55 -0
- package/build/contrib/systems/physics-system/tests/helpers.d.ts +5 -2
- package/build/contrib/systems/physics-system/tests/helpers.js +11 -2
- package/build/contrib/systems/physics-system/types.d.ts +6 -0
- package/build/contrib/systems/renderer/filters/index.d.ts +1 -1
- package/build/contrib/systems/renderer/filters/index.js +1 -1
- package/build/contrib/systems/renderer/material/index.d.ts +2 -1
- package/build/contrib/systems/renderer/material/index.js +1 -1
- package/build/contrib/systems/renderer/renderer.d.ts +2 -2
- package/build/contrib/systems/renderer/renderer.js +3 -4
- package/build/contrib/systems/renderer/types.d.ts +0 -3
- package/build/engine/engine.js +4 -1
- package/build/engine/game-loop.d.ts +3 -1
- package/build/engine/game-loop.js +8 -5
- package/build/engine/scene/scene-manager.d.ts +4 -1
- package/build/engine/scene/scene-manager.js +5 -1
- package/build/engine/system/index.d.ts +1 -1
- package/build/engine/system/system.d.ts +10 -11
- package/build/engine/system/system.js +5 -2
- package/build/engine/time.d.ts +54 -0
- package/build/engine/time.js +77 -0
- package/build/index.d.ts +2 -1
- package/package.json +1 -1
- package/build/contrib/utils/one-way-validator.d.ts +0 -12
|
@@ -3,8 +3,9 @@ import { SceneSystem } from '../../../engine/system';
|
|
|
3
3
|
import { Vector2, VectorOps } from '../../../engine/math-lib';
|
|
4
4
|
import { Collider, CharacterBody, RigidBody, Transform, } from '../../components';
|
|
5
5
|
import { PhysicsAPI } from '../physics-system';
|
|
6
|
-
import { OneWayValidator } from '../../utils/one-way-validator';
|
|
7
6
|
import { CharacterHit } from '../../events';
|
|
7
|
+
import { RemoveActor } from '../../../engine/events';
|
|
8
|
+
import { OneWayValidator } from './one-way-validator';
|
|
8
9
|
import { clipAgainstNormal } from './utils';
|
|
9
10
|
const DISTANCE_EPSILON = 0.000001;
|
|
10
11
|
const SNAP_EPSILON = 0.000001;
|
|
@@ -21,19 +22,26 @@ const SNAP_EPSILON = 0.000001;
|
|
|
21
22
|
export class CharacterController extends SceneSystem {
|
|
22
23
|
actorQuery;
|
|
23
24
|
world;
|
|
25
|
+
time;
|
|
24
26
|
oneWayValidator;
|
|
25
27
|
constructor(options) {
|
|
26
28
|
super();
|
|
27
29
|
this.world = options.world;
|
|
30
|
+
this.time = options.time;
|
|
28
31
|
this.actorQuery = new ActorQuery({
|
|
29
32
|
scene: options.scene,
|
|
30
33
|
filter: [CharacterBody, Transform, Collider, RigidBody],
|
|
31
34
|
});
|
|
35
|
+
this.actorQuery.addEventListener(RemoveActor, this.handleRemoveActor);
|
|
32
36
|
this.oneWayValidator = new OneWayValidator();
|
|
33
37
|
}
|
|
34
38
|
onSceneDestroy() {
|
|
39
|
+
this.actorQuery.removeEventListener(RemoveActor, this.handleRemoveActor);
|
|
35
40
|
this.actorQuery.destroy();
|
|
36
41
|
}
|
|
42
|
+
handleRemoveActor = (event) => {
|
|
43
|
+
this.oneWayValidator.delete(event.actor);
|
|
44
|
+
};
|
|
37
45
|
isBlockingHit(actor, hit) {
|
|
38
46
|
const rigidBody = hit.actor.getComponent(RigidBody);
|
|
39
47
|
if (!rigidBody || rigidBody.disabled) {
|
|
@@ -42,22 +50,18 @@ export class CharacterController extends SceneSystem {
|
|
|
42
50
|
if (!rigidBody?.oneWay || !rigidBody.oneWayNormal) {
|
|
43
51
|
return true;
|
|
44
52
|
}
|
|
45
|
-
return this.oneWayValidator.
|
|
53
|
+
return this.oneWayValidator.shouldBlock(hit.actor, actor, hit.normal);
|
|
46
54
|
}
|
|
47
55
|
isWalkable(actor, normal) {
|
|
48
56
|
const character = actor.getComponent(CharacterBody);
|
|
49
57
|
return (VectorOps.dotProduct(normal, character.upDirection) >=
|
|
50
58
|
Math.cos(character.maxSlopeAngle));
|
|
51
59
|
}
|
|
52
|
-
isRecoverableOverlap(
|
|
60
|
+
isRecoverableOverlap(hit) {
|
|
53
61
|
const rigidBody = hit.actor.getComponent(RigidBody);
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
if (!rigidBody.oneWay || !rigidBody.oneWayNormal) {
|
|
58
|
-
return true;
|
|
59
|
-
}
|
|
60
|
-
return this.oneWayValidator.validate(hit.actor, actor, hit.normal);
|
|
62
|
+
return (rigidBody !== undefined &&
|
|
63
|
+
!rigidBody.disabled &&
|
|
64
|
+
rigidBody.type !== 'dynamic');
|
|
61
65
|
}
|
|
62
66
|
resetGroundState(character) {
|
|
63
67
|
if (character.onGround) {
|
|
@@ -79,6 +83,7 @@ export class CharacterController extends SceneSystem {
|
|
|
79
83
|
const character = actor.getComponent(CharacterBody);
|
|
80
84
|
const transform = actor.getComponent(Transform);
|
|
81
85
|
const distance = displacement.magnitude;
|
|
86
|
+
this.oneWayValidator.touch(actor);
|
|
82
87
|
return physicsApi.castActor({
|
|
83
88
|
actor,
|
|
84
89
|
offset: {
|
|
@@ -87,7 +92,7 @@ export class CharacterController extends SceneSystem {
|
|
|
87
92
|
},
|
|
88
93
|
direction: displacement,
|
|
89
94
|
maxDistance: distance + character.skinWidth,
|
|
90
|
-
hitFilter,
|
|
95
|
+
hitFilter: (hit) => this.isBlockingHit(actor, hit) && hitFilter(hit),
|
|
91
96
|
});
|
|
92
97
|
}
|
|
93
98
|
castMotion(actor, position, displacement) {
|
|
@@ -96,11 +101,11 @@ export class CharacterController extends SceneSystem {
|
|
|
96
101
|
VectorOps.dotProduct(displacement, hit.normal) > DISTANCE_EPSILON) {
|
|
97
102
|
return false;
|
|
98
103
|
}
|
|
99
|
-
return
|
|
104
|
+
return true;
|
|
100
105
|
});
|
|
101
106
|
}
|
|
102
107
|
castGround(actor, position, displacement) {
|
|
103
|
-
return this.cast(actor, position, displacement, (hit) => this.
|
|
108
|
+
return this.cast(actor, position, displacement, (hit) => this.isWalkable(actor, hit.normal));
|
|
104
109
|
}
|
|
105
110
|
recoverOverlaps(actor, position) {
|
|
106
111
|
const character = actor.getComponent(CharacterBody);
|
|
@@ -119,7 +124,7 @@ export class CharacterController extends SceneSystem {
|
|
|
119
124
|
});
|
|
120
125
|
let recovered = false;
|
|
121
126
|
for (const hit of hits) {
|
|
122
|
-
if (!this.isRecoverableOverlap(
|
|
127
|
+
if (!this.isRecoverableOverlap(hit)) {
|
|
123
128
|
continue;
|
|
124
129
|
}
|
|
125
130
|
const correction = hit.penetration + character.skinWidth;
|
|
@@ -189,7 +194,6 @@ export class CharacterController extends SceneSystem {
|
|
|
189
194
|
position.x += direction.x * safeDistance;
|
|
190
195
|
position.y += direction.y * safeDistance;
|
|
191
196
|
this.handleHit(actor, hit);
|
|
192
|
-
clipAgainstNormal(character.velocity, hit.normal);
|
|
193
197
|
const remainingDistance = Math.max(distance - safeDistance, 0);
|
|
194
198
|
displacement.x = direction.x * remainingDistance;
|
|
195
199
|
displacement.y = direction.y * remainingDistance;
|
|
@@ -221,11 +225,11 @@ export class CharacterController extends SceneSystem {
|
|
|
221
225
|
target.x += snapDirection.x;
|
|
222
226
|
target.y += snapDirection.y;
|
|
223
227
|
}
|
|
224
|
-
fixedUpdate(
|
|
228
|
+
fixedUpdate() {
|
|
225
229
|
if (!this.world.systemApi.has(PhysicsAPI)) {
|
|
226
230
|
return;
|
|
227
231
|
}
|
|
228
|
-
const
|
|
232
|
+
const { fixedDeltaTime } = this.time;
|
|
229
233
|
this.oneWayValidator.update();
|
|
230
234
|
this.actorQuery.getActors().forEach((actor) => {
|
|
231
235
|
const transform = actor.getComponent(Transform);
|
|
@@ -244,7 +248,7 @@ export class CharacterController extends SceneSystem {
|
|
|
244
248
|
this.recoverOverlaps(actor, position);
|
|
245
249
|
const displacement = character.velocity
|
|
246
250
|
.clone()
|
|
247
|
-
.multiplyNumber(
|
|
251
|
+
.multiplyNumber(fixedDeltaTime)
|
|
248
252
|
.add(character._displacement);
|
|
249
253
|
const movingUp = VectorOps.dotProduct(displacement, character.upDirection) >
|
|
250
254
|
SNAP_EPSILON;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Actor } from '../../../engine/actor';
|
|
2
|
+
import { type Point } from '../../../engine/math-lib';
|
|
3
|
+
export declare class OneWayValidator {
|
|
4
|
+
private ignoredOneWayContacts;
|
|
5
|
+
private touchedActors;
|
|
6
|
+
private oneWayContactUpdateIndex;
|
|
7
|
+
constructor();
|
|
8
|
+
private clearOneWayContacts;
|
|
9
|
+
private trackOneWayContact;
|
|
10
|
+
touch(actor: Actor): void;
|
|
11
|
+
shouldBlock(oneWayActor: Actor, otherActor: Actor, normal: Point): boolean;
|
|
12
|
+
delete(actor: Actor): void;
|
|
13
|
+
update(): void;
|
|
14
|
+
lateUpdate(): void;
|
|
15
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { RigidBody, Transform } from '../../components';
|
|
2
|
+
import { VectorOps } from '../../../engine/math-lib';
|
|
3
|
+
export class OneWayValidator {
|
|
4
|
+
ignoredOneWayContacts;
|
|
5
|
+
touchedActors;
|
|
6
|
+
oneWayContactUpdateIndex;
|
|
7
|
+
constructor() {
|
|
8
|
+
this.ignoredOneWayContacts = new Map();
|
|
9
|
+
this.touchedActors = new Set();
|
|
10
|
+
this.oneWayContactUpdateIndex = 0;
|
|
11
|
+
}
|
|
12
|
+
clearOneWayContacts() {
|
|
13
|
+
this.touchedActors.forEach((actor) => {
|
|
14
|
+
const ignoredContacts = this.ignoredOneWayContacts.get(actor);
|
|
15
|
+
if (!ignoredContacts) {
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
ignoredContacts.forEach((lastSeenUpdate, otherActor) => {
|
|
19
|
+
if (lastSeenUpdate !== this.oneWayContactUpdateIndex) {
|
|
20
|
+
ignoredContacts.delete(otherActor);
|
|
21
|
+
}
|
|
22
|
+
});
|
|
23
|
+
if (ignoredContacts.size === 0) {
|
|
24
|
+
this.ignoredOneWayContacts.delete(actor);
|
|
25
|
+
}
|
|
26
|
+
});
|
|
27
|
+
if (this.ignoredOneWayContacts.size === 0) {
|
|
28
|
+
this.oneWayContactUpdateIndex = 0;
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
trackOneWayContact(actor, oneWayActor) {
|
|
32
|
+
let ignoredContacts = this.ignoredOneWayContacts.get(actor);
|
|
33
|
+
if (!ignoredContacts) {
|
|
34
|
+
ignoredContacts = new Map();
|
|
35
|
+
this.ignoredOneWayContacts.set(actor, ignoredContacts);
|
|
36
|
+
}
|
|
37
|
+
ignoredContacts.set(oneWayActor, this.oneWayContactUpdateIndex);
|
|
38
|
+
}
|
|
39
|
+
touch(actor) {
|
|
40
|
+
this.touchedActors.add(actor);
|
|
41
|
+
}
|
|
42
|
+
shouldBlock(oneWayActor, otherActor, normal) {
|
|
43
|
+
if (this.ignoredOneWayContacts.get(otherActor)?.has(oneWayActor)) {
|
|
44
|
+
this.trackOneWayContact(otherActor, oneWayActor);
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
const rigidBody = oneWayActor.getComponent(RigidBody);
|
|
48
|
+
const transform = oneWayActor.getComponent(Transform);
|
|
49
|
+
const oneWayNormal = VectorOps.rotatePoint(rigidBody.oneWayNormal, transform.world.rotation);
|
|
50
|
+
if (VectorOps.dotProduct(oneWayNormal, normal) > 0) {
|
|
51
|
+
return true;
|
|
52
|
+
}
|
|
53
|
+
this.trackOneWayContact(otherActor, oneWayActor);
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
delete(actor) {
|
|
57
|
+
this.ignoredOneWayContacts.delete(actor);
|
|
58
|
+
this.touchedActors.delete(actor);
|
|
59
|
+
this.ignoredOneWayContacts.forEach((ignoredContacts, trackedActor) => {
|
|
60
|
+
ignoredContacts.delete(actor);
|
|
61
|
+
if (ignoredContacts.size === 0) {
|
|
62
|
+
this.ignoredOneWayContacts.delete(trackedActor);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
update() {
|
|
67
|
+
this.oneWayContactUpdateIndex += 1;
|
|
68
|
+
}
|
|
69
|
+
lateUpdate() {
|
|
70
|
+
this.clearOneWayContacts();
|
|
71
|
+
this.touchedActors.clear();
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { WorldSystem } from '../../../engine/system';
|
|
2
|
-
import type { WorldSystemOptions
|
|
2
|
+
import type { WorldSystemOptions } from '../../../engine/system';
|
|
3
3
|
import type { Scene } from '../../../engine/scene';
|
|
4
4
|
/**
|
|
5
5
|
* Game statistics meter that tracks and reports performance metrics
|
|
@@ -13,12 +13,13 @@ import type { Scene } from '../../../engine/scene';
|
|
|
13
13
|
*/
|
|
14
14
|
export declare class GameStatsMeter extends WorldSystem {
|
|
15
15
|
private world;
|
|
16
|
+
private time;
|
|
16
17
|
private actorQuery?;
|
|
17
18
|
private frequency;
|
|
18
19
|
private fps;
|
|
19
|
-
private
|
|
20
|
+
private elapsed;
|
|
20
21
|
constructor(options: WorldSystemOptions);
|
|
21
22
|
onSceneEnter(scene: Scene): void;
|
|
22
23
|
onSceneExit(): void;
|
|
23
|
-
update(
|
|
24
|
+
update(): void;
|
|
24
25
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { WorldSystem } from '../../../engine/system';
|
|
2
2
|
import { GameStatsUpdate } from '../../events';
|
|
3
3
|
import { ActorQuery } from '../../../engine/actor';
|
|
4
|
-
const
|
|
4
|
+
const DEFAULT_FREQUENCY = 1;
|
|
5
5
|
/**
|
|
6
6
|
* Game statistics meter that tracks and reports performance metrics
|
|
7
7
|
*
|
|
@@ -14,17 +14,19 @@ const MS_IN_SEC = 1000;
|
|
|
14
14
|
*/
|
|
15
15
|
export class GameStatsMeter extends WorldSystem {
|
|
16
16
|
world;
|
|
17
|
+
time;
|
|
17
18
|
actorQuery;
|
|
18
19
|
frequency;
|
|
19
20
|
fps;
|
|
20
|
-
|
|
21
|
+
elapsed;
|
|
21
22
|
constructor(options) {
|
|
22
23
|
super();
|
|
23
24
|
const { world, frequency } = options;
|
|
24
25
|
this.world = world;
|
|
25
|
-
this.
|
|
26
|
+
this.time = options.time;
|
|
27
|
+
this.frequency = frequency || DEFAULT_FREQUENCY;
|
|
26
28
|
this.fps = 0;
|
|
27
|
-
this.
|
|
29
|
+
this.elapsed = 0;
|
|
28
30
|
}
|
|
29
31
|
onSceneEnter(scene) {
|
|
30
32
|
this.actorQuery = new ActorQuery({ scene, filter: [] });
|
|
@@ -32,17 +34,16 @@ export class GameStatsMeter extends WorldSystem {
|
|
|
32
34
|
onSceneExit() {
|
|
33
35
|
this.actorQuery = undefined;
|
|
34
36
|
}
|
|
35
|
-
update(
|
|
36
|
-
const { deltaTime } = options;
|
|
37
|
+
update() {
|
|
37
38
|
this.fps += 1;
|
|
38
|
-
this.
|
|
39
|
-
if (this.
|
|
39
|
+
this.elapsed += this.time.deltaTime;
|
|
40
|
+
if (this.elapsed >= this.frequency) {
|
|
40
41
|
this.world.dispatchEvent(GameStatsUpdate, {
|
|
41
|
-
fps:
|
|
42
|
+
fps: this.fps / this.elapsed,
|
|
42
43
|
actorsCount: this.actorQuery?.getActors().size ?? 0,
|
|
43
44
|
});
|
|
44
45
|
this.fps = 0;
|
|
45
|
-
this.
|
|
46
|
+
this.elapsed = 0;
|
|
46
47
|
}
|
|
47
48
|
}
|
|
48
49
|
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare const DEFAULT_SOLVER_ITERATIONS = 8;
|
|
2
|
+
export declare const DEFAULT_LINEAR_SLEEP_THRESHOLD = 2;
|
|
3
|
+
export declare const DEFAULT_ANGULAR_SLEEP_THRESHOLD = 0.05;
|
|
4
|
+
export declare const DEFAULT_SLEEP_TIME_THRESHOLD = 0.5;
|
|
5
|
+
export declare const DEFAULT_MAX_BIAS_VELOCITY = 60;
|
|
6
|
+
export declare const DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION = 0.5;
|
|
7
|
+
export declare const CONTACT_BIAS = 0.2;
|
|
8
|
+
export declare const BIAS_ANGULAR_SLEEP_MULTIPLIER = 2;
|
|
9
|
+
export declare const RESTITUTION_VELOCITY_THRESHOLD = 1;
|
|
10
|
+
export declare const PENETRATION_SLEEP_MARGIN = 0.25;
|
|
11
|
+
export declare const SUPPORT_MIN_GRAVITY_DOT = 0.5;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export const DEFAULT_SOLVER_ITERATIONS = 8;
|
|
2
|
+
export const DEFAULT_LINEAR_SLEEP_THRESHOLD = 2;
|
|
3
|
+
export const DEFAULT_ANGULAR_SLEEP_THRESHOLD = 0.05;
|
|
4
|
+
export const DEFAULT_SLEEP_TIME_THRESHOLD = 0.5;
|
|
5
|
+
export const DEFAULT_MAX_BIAS_VELOCITY = 60;
|
|
6
|
+
export const DEFAULT_CONTACT_MAX_ALLOWED_PENETRATION = 0.5;
|
|
7
|
+
export const CONTACT_BIAS = 0.2;
|
|
8
|
+
export const BIAS_ANGULAR_SLEEP_MULTIPLIER = 2;
|
|
9
|
+
export const RESTITUTION_VELOCITY_THRESHOLD = 1;
|
|
10
|
+
// Kept above maxAllowedPenetration so a settled stack resting at the
|
|
11
|
+
// allowed slop does not wake itself from its own steady-state penetration.
|
|
12
|
+
export const PENETRATION_SLEEP_MARGIN = 0.25;
|
|
13
|
+
// 0.5 treats surfaces up to ~60° from horizontal as holding a body up.
|
|
14
|
+
export const SUPPORT_MIN_GRAVITY_DOT = 0.5;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SceneSystem } from '../../../engine/system';
|
|
2
|
-
import type { SceneSystemOptions
|
|
2
|
+
import type { SceneSystemOptions } from '../../../engine/system';
|
|
3
3
|
/**
|
|
4
4
|
* Physics system that handles 2D physics simulation and collision detection
|
|
5
5
|
*
|
|
@@ -21,5 +21,5 @@ export declare class PhysicsSystem extends SceneSystem {
|
|
|
21
21
|
onSceneEnter(): void;
|
|
22
22
|
onSceneExit(): void;
|
|
23
23
|
onSceneDestroy(): void;
|
|
24
|
-
fixedUpdate(
|
|
24
|
+
fixedUpdate(): void;
|
|
25
25
|
}
|
|
@@ -21,16 +21,27 @@ export class PhysicsSystem extends SceneSystem {
|
|
|
21
21
|
gravity;
|
|
22
22
|
constructor(options) {
|
|
23
23
|
super();
|
|
24
|
-
const { gravityX = 0, gravityY = 0 } = options;
|
|
24
|
+
const { gravityX = 0, gravityY = 0, solverIterations, linearSleepThreshold, angularSleepThreshold, sleepTimeThreshold, maxAllowedPenetration, maxBiasVelocity, } = options;
|
|
25
25
|
this.gravity = new Vector2(gravityX, gravityY);
|
|
26
26
|
this.world = options.world;
|
|
27
27
|
this.physicsSubsystem = new PhysicsSubsystem({
|
|
28
28
|
scene: options.scene,
|
|
29
|
+
time: options.time,
|
|
29
30
|
getGravity: () => this.gravity,
|
|
31
|
+
linearSleepThreshold,
|
|
32
|
+
angularSleepThreshold,
|
|
33
|
+
sleepTimeThreshold,
|
|
30
34
|
});
|
|
31
35
|
this.collisionDetectionSubsystem = new CollisionDetectionSubsystem(options);
|
|
32
36
|
this.collisionBroadcastSubsystem = new CollisionBroadcastSubsystem();
|
|
33
|
-
this.constraintSolver = new ConstraintSolver(
|
|
37
|
+
this.constraintSolver = new ConstraintSolver({
|
|
38
|
+
time: options.time,
|
|
39
|
+
getGravity: () => this.gravity,
|
|
40
|
+
solverIterations,
|
|
41
|
+
maxAllowedPenetration,
|
|
42
|
+
maxBiasVelocity,
|
|
43
|
+
linearSleepThreshold,
|
|
44
|
+
});
|
|
34
45
|
this.physicsApi = new PhysicsAPI({
|
|
35
46
|
raycast: (params) => this.collisionDetectionSubsystem.raycast(params),
|
|
36
47
|
raycastAll: (params) => this.collisionDetectionSubsystem.raycastAll(params),
|
|
@@ -56,10 +67,13 @@ export class PhysicsSystem extends SceneSystem {
|
|
|
56
67
|
this.physicsSubsystem.destroy();
|
|
57
68
|
this.collisionDetectionSubsystem.destroy();
|
|
58
69
|
}
|
|
59
|
-
fixedUpdate(
|
|
60
|
-
this.physicsSubsystem.
|
|
70
|
+
fixedUpdate() {
|
|
71
|
+
this.physicsSubsystem.integrateVelocities();
|
|
72
|
+
this.physicsSubsystem.integrateKinematicPositions();
|
|
61
73
|
const contacts = this.collisionDetectionSubsystem.update();
|
|
62
74
|
this.constraintSolver.update(contacts);
|
|
75
|
+
this.physicsSubsystem.integrateDynamicPositions();
|
|
76
|
+
this.physicsSubsystem.updateSleepTimers();
|
|
63
77
|
this.collisionBroadcastSubsystem.update(contacts);
|
|
64
78
|
this.physicsSubsystem.lateUpdate();
|
|
65
79
|
}
|
|
@@ -198,7 +198,9 @@ export class CollisionDetectionSubsystem {
|
|
|
198
198
|
return (aabb1.max[axis] >= aabb2.min[axis] && aabb1.min[axis] <= aabb2.max[axis]);
|
|
199
199
|
}
|
|
200
200
|
testCollisionLayers(proxy1, proxy2) {
|
|
201
|
-
return this.collisionMatrix[proxy1.layer]?.[proxy2.layer] ??
|
|
201
|
+
return (this.collisionMatrix[proxy1.layer]?.[proxy2.layer] ??
|
|
202
|
+
this.collisionMatrix[proxy2.layer]?.[proxy1.layer] ??
|
|
203
|
+
true);
|
|
202
204
|
}
|
|
203
205
|
testState(proxy1, proxy2) {
|
|
204
206
|
const actor1 = 'actor' in proxy1 ? proxy1.actor : undefined;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { orientNormal } from '../common/normals';
|
|
2
|
-
import {
|
|
2
|
+
import { sortPoints } from '../common/points';
|
|
3
|
+
import { findMinBoxesOverlap, buildContactPoints, OVERLAP_TIE_EPSILON, } from './utils';
|
|
3
4
|
/**
|
|
4
5
|
* Checks box colliders for intersection.
|
|
5
6
|
*
|
|
@@ -17,7 +18,7 @@ export const checkBoxesIntersection = (geometry1, geometry2) => {
|
|
|
17
18
|
if (overlap2 === false) {
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
20
|
-
const isArg1Reference = overlap1.overlap <= overlap2.overlap;
|
|
21
|
+
const isArg1Reference = overlap1.overlap <= overlap2.overlap + OVERLAP_TIE_EPSILON;
|
|
21
22
|
const referenceGeometry = isArg1Reference ? geometry1 : geometry2;
|
|
22
23
|
const incidentGeometry = isArg1Reference ? geometry2 : geometry1;
|
|
23
24
|
const referenceOverlap = isArg1Reference ? overlap1 : overlap2;
|
|
@@ -26,9 +27,10 @@ export const checkBoxesIntersection = (geometry1, geometry2) => {
|
|
|
26
27
|
if (!isArg1Reference) {
|
|
27
28
|
referenceNormal.multiplyNumber(-1);
|
|
28
29
|
}
|
|
30
|
+
const contactPoints = buildContactPoints(referenceGeometry, referenceNormal, incidentGeometry);
|
|
29
31
|
return {
|
|
30
32
|
normal,
|
|
31
33
|
penetration: referenceOverlap.overlap,
|
|
32
|
-
contactPoints:
|
|
34
|
+
contactPoints: sortPoints(contactPoints, normal),
|
|
33
35
|
};
|
|
34
36
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Vector2 } from '../../../../../../../engine/math-lib';
|
|
2
2
|
import type { BoxGeometry, Point } from '../../types';
|
|
3
3
|
export declare const CONTACT_EPSILON = 0.0001;
|
|
4
|
+
export declare const OVERLAP_TIE_EPSILON = 0.0001;
|
|
4
5
|
export declare const MAX_CONTACT_POINTS = 2;
|
|
5
6
|
export interface AxisOverlap {
|
|
6
7
|
axis: Vector2;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { Vector2, VectorOps } from '../../../../../../../engine/math-lib';
|
|
2
2
|
import { getProjectionOverlap, projectPolygon } from '../common/projections';
|
|
3
3
|
export const CONTACT_EPSILON = 1e-4;
|
|
4
|
+
export const OVERLAP_TIE_EPSILON = 1e-4;
|
|
4
5
|
export const MAX_CONTACT_POINTS = 2;
|
|
5
6
|
/**
|
|
6
7
|
* Runs the SAT pass for one box's face normals against another box.
|
|
@@ -22,7 +23,7 @@ export const findMinBoxesOverlap = (geometry1, geometry2) => {
|
|
|
22
23
|
if (overlap === false) {
|
|
23
24
|
return false;
|
|
24
25
|
}
|
|
25
|
-
if (overlap < minOverlap) {
|
|
26
|
+
if (overlap < minOverlap - OVERLAP_TIE_EPSILON) {
|
|
26
27
|
minOverlap = overlap;
|
|
27
28
|
bestAxis = axis;
|
|
28
29
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { orientNormal } from '../common/normals';
|
|
2
|
+
import { sortPoints } from '../common/points';
|
|
2
3
|
import { buildBoxCapsuleIntersection } from './utils';
|
|
3
4
|
/**
|
|
4
5
|
* Checks a box against a capsule.
|
|
@@ -23,8 +24,10 @@ export const checkBoxAndCapsuleIntersection = (box, capsule) => {
|
|
|
23
24
|
if (!intersection) {
|
|
24
25
|
return false;
|
|
25
26
|
}
|
|
27
|
+
const normal = orientNormal(intersection.normal, box.center, capsule.center);
|
|
26
28
|
return {
|
|
27
29
|
...intersection,
|
|
28
|
-
normal
|
|
30
|
+
normal,
|
|
31
|
+
contactPoints: sortPoints(intersection.contactPoints, normal),
|
|
29
32
|
};
|
|
30
33
|
};
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { buildBoxSegmentContactPoints, findMinBoxSegmentOverlap, } from './utils';
|
|
2
2
|
import { orientNormal } from '../common/normals';
|
|
3
|
+
import { sortPoints } from '../common/points';
|
|
3
4
|
/**
|
|
4
5
|
* Checks a box against a segment.
|
|
5
6
|
*
|
|
@@ -17,9 +18,10 @@ export const checkBoxAndSegmentIntersection = (box, segment) => {
|
|
|
17
18
|
if (contactPoints.length === 0) {
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
21
|
+
const normal = orientNormal(overlap.axis.clone(), box.center, segment.center);
|
|
20
22
|
return {
|
|
21
|
-
normal
|
|
23
|
+
normal,
|
|
22
24
|
penetration: overlap.overlap,
|
|
23
|
-
contactPoints,
|
|
25
|
+
contactPoints: sortPoints(contactPoints, normal),
|
|
24
26
|
};
|
|
25
27
|
};
|
|
@@ -1,4 +1,6 @@
|
|
|
1
|
+
import type { Vector2 } from '../../../../../../../engine/math-lib';
|
|
1
2
|
import type { Point } from '../../types';
|
|
2
3
|
export declare const arePointsEqual: (point1: Point, point2: Point) => boolean;
|
|
3
4
|
export declare const lerpPoint: (point1: Point, point2: Point, value: number) => Point;
|
|
4
5
|
export declare const getDistanceSquared: (point1: Point, point2: Point) => number;
|
|
6
|
+
export declare const sortPoints: (points: Point[], normal: Vector2) => Point[];
|
|
@@ -5,3 +5,19 @@ export const lerpPoint = (point1, point2, value) => ({
|
|
|
5
5
|
y: point1.y + (point2.y - point1.y) * value,
|
|
6
6
|
});
|
|
7
7
|
export const getDistanceSquared = (point1, point2) => (point1.x - point2.x) ** 2 + (point1.y - point2.y) ** 2;
|
|
8
|
+
export const sortPoints = (points, normal) => {
|
|
9
|
+
if (points.length !== 2) {
|
|
10
|
+
return points;
|
|
11
|
+
}
|
|
12
|
+
const tangentX = -normal.y;
|
|
13
|
+
const tangentY = normal.x;
|
|
14
|
+
const projection1 = points[0].x * tangentX + points[0].y * tangentY;
|
|
15
|
+
const projection2 = points[1].x * tangentX + points[1].y * tangentY;
|
|
16
|
+
if (projection1 <= projection2) {
|
|
17
|
+
return points;
|
|
18
|
+
}
|
|
19
|
+
const point = points[0];
|
|
20
|
+
points[0] = points[1];
|
|
21
|
+
points[1] = point;
|
|
22
|
+
return points;
|
|
23
|
+
};
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sortPoints } from './common/points';
|
|
1
2
|
import { checkBoxAndCapsuleIntersection } from './box-capsule/check-box-and-capsule-intersection';
|
|
2
3
|
import { checkBoxAndCircleIntersection } from './box-circle/check-box-and-circle-intersection';
|
|
3
4
|
import { checkBoxAndSegmentIntersection } from './box-segment/check-box-and-segment-intersection';
|
|
@@ -18,6 +19,7 @@ const swapArgs = (checker) => {
|
|
|
18
19
|
return false;
|
|
19
20
|
}
|
|
20
21
|
intersection.normal.multiplyNumber(-1);
|
|
22
|
+
sortPoints(intersection.contactPoints, intersection.normal);
|
|
21
23
|
return intersection;
|
|
22
24
|
};
|
|
23
25
|
};
|
|
@@ -5,6 +5,7 @@ import { buildCircleGeometry } from '../../geometry-builders/build-circle-geomet
|
|
|
5
5
|
import { buildPointGeometry } from '../../geometry-builders/build-point-geometry';
|
|
6
6
|
import { buildRayGeometry } from '../../geometry-builders/build-ray-geometry';
|
|
7
7
|
import { buildSegmentGeometry } from '../../geometry-builders/build-segment-geometry';
|
|
8
|
+
import { expectHit } from '../../tests/assertions';
|
|
8
9
|
export const createBoxGeometry = (centerX, centerY, sizeX, sizeY) => {
|
|
9
10
|
return createRotatedBoxGeometry(centerX, centerY, sizeX, sizeY, 0);
|
|
10
11
|
};
|
|
@@ -71,20 +72,8 @@ export const expectToBeClose = (point, x, y, digits = 6) => {
|
|
|
71
72
|
expect(point.x).toBeCloseTo(x, digits);
|
|
72
73
|
expect(point.y).toBeCloseTo(y, digits);
|
|
73
74
|
};
|
|
74
|
-
export const expectIntersection = (intersection) =>
|
|
75
|
-
|
|
76
|
-
if (intersection === false) {
|
|
77
|
-
throw new Error('Expected intersection, received false');
|
|
78
|
-
}
|
|
79
|
-
return intersection;
|
|
80
|
-
};
|
|
81
|
-
export const expectCastHit = (hit) => {
|
|
82
|
-
expect(hit).not.toBe(false);
|
|
83
|
-
if (hit === false) {
|
|
84
|
-
throw new Error('Expected cast hit, received false');
|
|
85
|
-
}
|
|
86
|
-
return hit;
|
|
87
|
-
};
|
|
75
|
+
export const expectIntersection = (intersection) => expectHit(intersection, 'intersection');
|
|
76
|
+
export const expectCastHit = (hit) => expectHit(hit, 'cast hit');
|
|
88
77
|
export const sortPoints = (points) => [...points].sort((point1, point2) => {
|
|
89
78
|
if (point1.x !== point2.x) {
|
|
90
79
|
return point1.x - point2.x;
|
|
@@ -2,6 +2,7 @@ import { Vector2 } from '../../../../../../../engine/math-lib';
|
|
|
2
2
|
import { buildBoxCastGeometry } from '../../geometry-builders/build-box-cast-geometry';
|
|
3
3
|
import { buildCapsuleCastGeometry } from '../../geometry-builders/build-capsule-cast-geometry';
|
|
4
4
|
import { buildCircleCastGeometry } from '../../geometry-builders/build-circle-cast-geometry';
|
|
5
|
+
import { expectHit } from '../../tests/assertions';
|
|
5
6
|
export const createCircleCastGeometry = (centerX, centerY, radius, directionX, directionY, maxDistance) => buildCircleCastGeometry({
|
|
6
7
|
shape: {
|
|
7
8
|
type: 'circle',
|
|
@@ -30,10 +31,4 @@ export const createBoxCastGeometry = (centerX, centerY, sizeX, sizeY, directionX
|
|
|
30
31
|
direction: new Vector2(directionX, directionY),
|
|
31
32
|
maxDistance,
|
|
32
33
|
});
|
|
33
|
-
export const expectShapeCastHit = (hit) =>
|
|
34
|
-
expect(hit).not.toBe(false);
|
|
35
|
-
if (hit === false) {
|
|
36
|
-
throw new Error('Expected shape-cast hit, received false');
|
|
37
|
-
}
|
|
38
|
-
return hit;
|
|
39
|
-
};
|
|
34
|
+
export const expectShapeCastHit = (hit) => expectHit(hit, 'shape-cast hit');
|
package/build/contrib/systems/physics-system/subsystems/collision-detection/tests/assertions.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const expectHit: <T>(hit: false | T, label: string) => T;
|