@typeonce/ritmo-math 0.13.0 → 0.14.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +45 -0
- package/README.md +2 -2
- package/dist/Collide2.d.ts +48 -14
- package/dist/Collide2.d.ts.map +1 -1
- package/dist/Collide2.js +140 -17
- package/dist/Collide2.js.map +1 -1
- package/dist/Segment2.d.ts +56 -0
- package/dist/Segment2.d.ts.map +1 -0
- package/dist/Segment2.js +32 -0
- package/dist/Segment2.js.map +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -1
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/src/Collide2.ts +153 -22
- package/src/Segment2.ts +71 -0
- package/src/index.ts +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,50 @@
|
|
|
1
1
|
# @typeonce/ritmo-math
|
|
2
2
|
|
|
3
|
+
## 0.14.0
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- e169263: **`Collide2` collides circles with line segments, so walls can run at any angle.** The new `Segment2` module is the plain segment `Collide2` reads every frame (`Segment2.between(a, b)`, `closestPoint`, `overlapsCircle`), like `Box2` for boxes. A segment is a two-sided wall with round ends; both ends at one point make a round post. `Collide2.sweepCircleSegment` finds the first contact with one, next to `sweepCircleBox`. A map traced from survey data, a slanted fence, or a polygon's edges no longer need a hand-written port of `moveCircle`.
|
|
8
|
+
|
|
9
|
+
```ts
|
|
10
|
+
const fence = Segment2.between({ x: 0, y: 0 }, { x: 40, y: 30 });
|
|
11
|
+
const moved = Collide2.moveCircle(position, delta, radius, {
|
|
12
|
+
boxes: rooms,
|
|
13
|
+
segments: [fence],
|
|
14
|
+
});
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
- **Breaking: `moveCircle` and `pushOut` take the walls by kind, as `Collide2.Walls`** (`{ boxes?, segments? }`), instead of an array of boxes. An array no longer compiles (`pass walls by kind: { boxes: walls } or { segments: walls }`):
|
|
18
|
+
|
|
19
|
+
```diff
|
|
20
|
+
-Collide2.moveCircle(position, delta, radius, walls)
|
|
21
|
+
+Collide2.moveCircle(position, delta, radius, { boxes: walls })
|
|
22
|
+
-Collide2.pushOut(position, radius, walls)
|
|
23
|
+
+Collide2.pushOut(position, radius, { boxes: walls })
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
- **Breaking: `sweepCircleBoxes` is `sweepCircle`**, the first contact with any of the walls (line of sight with a radius of 0, as before):
|
|
27
|
+
|
|
28
|
+
```diff
|
|
29
|
+
-Collide2.sweepCircleBoxes(eye, Vec2.subtract(target, eye), 0, walls) === undefined
|
|
30
|
+
+Collide2.sweepCircle(eye, Vec2.subtract(target, eye), 0, { boxes: walls }) === undefined
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
Results against boxes are unchanged.
|
|
34
|
+
|
|
35
|
+
### Patch Changes
|
|
36
|
+
|
|
37
|
+
- Updated dependencies [c3719a5]
|
|
38
|
+
- Updated dependencies [70d054e]
|
|
39
|
+
- Updated dependencies [adb4066]
|
|
40
|
+
- Updated dependencies [97e95fb]
|
|
41
|
+
- Updated dependencies [dbfbd9d]
|
|
42
|
+
- Updated dependencies [343660e]
|
|
43
|
+
- Updated dependencies [d24258e]
|
|
44
|
+
- Updated dependencies [cfe332a]
|
|
45
|
+
- Updated dependencies [8aa1e85]
|
|
46
|
+
- @typeonce/ritmo@0.14.0
|
|
47
|
+
|
|
3
48
|
## 0.13.0
|
|
4
49
|
|
|
5
50
|
### Minor Changes
|
package/README.md
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
Math for ritmo games, in two kinds:
|
|
4
4
|
|
|
5
5
|
- **Validated values**: `Scalar` (`Finite`, `NonNegative`, `Positive`), `Vector2`, `Size2`, and `Aabb`. Each has constructors that validate input (`result`, `option`), a guard `is`, and one `codec`, a Standard Schema: `Descriptor.ConstructedComponent(Vector2.codec)` saves the value and checks it on restore. Inside a game's own schema, build the field from the guard (`Schema.declare(Vector2.is)` in Effect, `z.custom(Vector2.is)` in Zod). `Random.codec` does the same for a generator kept in a resource.
|
|
6
|
-
- **Hot-path helpers** on plain numbers, for code that runs every step: `Vec2` vector arithmetic (validated `Vector2` values pass straight in), `Box2` edge boxes, `Collide2` swept circle collision that slides along walls and never tunnels through them, a `SpatialGrid` broad phase that allocates nothing per step, a `FlowField` that leads bodies around walls, a deterministic `Random` whose whole state is one integer (keep it in a resource, so runs replay), and `Ops`, math functions that return the same bits on every engine.
|
|
6
|
+
- **Hot-path helpers** on plain numbers, for code that runs every step: `Vec2` vector arithmetic (validated `Vector2` values pass straight in), `Box2` edge boxes, `Segment2` walls at any angle, `Collide2` swept circle collision against both that slides along walls and never tunnels through them, a `SpatialGrid` broad phase that allocates nothing per step, a `FlowField` that leads bodies around walls, a deterministic `Random` whose whole state is one integer (keep it in a resource, so runs replay), and `Ops`, math functions that return the same bits on every engine.
|
|
7
7
|
|
|
8
8
|
```ts
|
|
9
9
|
import { Collide2, FlowField, Random, SpatialGrid, Vec2 } from "@typeonce/ritmo-math"
|
|
@@ -12,7 +12,7 @@ import { Collide2, FlowField, Random, SpatialGrid, Vec2 } from "@typeonce/ritmo-
|
|
|
12
12
|
paths.setWalls(walls, radius)
|
|
13
13
|
paths.setGoal(player)
|
|
14
14
|
const way = paths.way(enemy) ?? Vec2.normalizeOr(Vec2.subtract(player, enemy), Vec2.zero)
|
|
15
|
-
const moved = Collide2.moveCircle(enemy, Vec2.scale(way, speed * dt), radius, walls)
|
|
15
|
+
const moved = Collide2.moveCircle(enemy, Vec2.scale(way, speed * dt), radius, { boxes: walls })
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
```sh
|
package/dist/Collide2.d.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Continuous collision for moving circles against static
|
|
2
|
+
* Continuous collision for moving circles against static walls (axis-aligned
|
|
3
|
+
* boxes and line segments) and circles.
|
|
3
4
|
*
|
|
4
|
-
* Movement is swept, not sampled: `
|
|
5
|
+
* Movement is swept, not sampled: `sweepCircle` finds the first time of
|
|
5
6
|
* contact along the whole displacement, so fast movers (dashes, projectiles)
|
|
6
7
|
* cannot skip over a wall between two steps. `moveCircle` stops at walls and
|
|
7
|
-
* slides along them.
|
|
8
|
+
* slides along them. Walls are passed as {@link Walls}: `Box2` boxes for a
|
|
9
|
+
* grid of rooms, `Segment2` segments for walls at any angle, or both.
|
|
8
10
|
*
|
|
9
11
|
* Every query returns `undefined` for "no contact", never throws, and
|
|
10
12
|
* allocates only its result. Inputs are expected to be finite.
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
* @example
|
|
13
15
|
* ```ts
|
|
14
16
|
* // Move a body by its velocity this step, sliding along the walls.
|
|
15
|
-
* const moved = Collide2.moveCircle(position, Vec2.scale(velocity, dt), radius, walls)
|
|
17
|
+
* const moved = Collide2.moveCircle(position, Vec2.scale(velocity, dt), radius, { boxes: walls })
|
|
16
18
|
* if (moved.blocked) bumpedWall()
|
|
17
19
|
* ```
|
|
18
20
|
*
|
|
@@ -29,6 +31,7 @@
|
|
|
29
31
|
* Moving a circle through walls with sliding.
|
|
30
32
|
*/
|
|
31
33
|
import type { Box2 } from "./Box2.ts";
|
|
34
|
+
import type { Segment2 } from "./Segment2.ts";
|
|
32
35
|
import type { Vec2 } from "./Vec2.ts";
|
|
33
36
|
/**
|
|
34
37
|
* A contact along a sweep.
|
|
@@ -38,10 +41,22 @@ import type { Vec2 } from "./Vec2.ts";
|
|
|
38
41
|
export interface Hit {
|
|
39
42
|
/** Fraction of the displacement travelled before contact, in [0, 1]. */
|
|
40
43
|
readonly time: number;
|
|
41
|
-
/** Unit contact normal, pointing away from the
|
|
44
|
+
/** Unit contact normal, pointing away from the wall. */
|
|
42
45
|
readonly nx: number;
|
|
43
46
|
readonly ny: number;
|
|
44
47
|
}
|
|
48
|
+
/**
|
|
49
|
+
* The static walls a circle collides with: axis-aligned `boxes`, line
|
|
50
|
+
* `segments`, or both. A missing list is no walls of that kind.
|
|
51
|
+
*
|
|
52
|
+
* @category Collision Types
|
|
53
|
+
*/
|
|
54
|
+
export interface Walls {
|
|
55
|
+
readonly boxes?: ReadonlyArray<Box2>;
|
|
56
|
+
readonly segments?: ReadonlyArray<Segment2>;
|
|
57
|
+
/** Not a setting: an array of walls is passed by kind. */
|
|
58
|
+
readonly length?: "pass walls by kind: { boxes: walls } or { segments: walls }";
|
|
59
|
+
}
|
|
45
60
|
/**
|
|
46
61
|
* Where a moved circle ended up.
|
|
47
62
|
*
|
|
@@ -79,7 +94,18 @@ export declare const rayCircle: (ox: number, oy: number, dx: number, dy: number,
|
|
|
79
94
|
*/
|
|
80
95
|
export declare const sweepCircleBox: (origin: Vec2, delta: Vec2, radius: number, box: Box2) => Hit | undefined;
|
|
81
96
|
/**
|
|
82
|
-
* First contact of a
|
|
97
|
+
* First contact of a circle of `radius` at `origin` moving by `delta` with
|
|
98
|
+
* `segment`, or `undefined`. Contact is computed against the segment
|
|
99
|
+
* thickened by the radius with round ends (the exact Minkowski sum), so the
|
|
100
|
+
* circle slides along it and around its ends. A circle that starts exactly
|
|
101
|
+
* touching it and moves into it hits at time 0. A circle that starts
|
|
102
|
+
* overlapping the segment is not reported; resolve it with `pushOut` first.
|
|
103
|
+
*
|
|
104
|
+
* @category Queries
|
|
105
|
+
*/
|
|
106
|
+
export declare const sweepCircleSegment: (origin: Vec2, delta: Vec2, radius: number, segment: Segment2) => Hit | undefined;
|
|
107
|
+
/**
|
|
108
|
+
* First contact of a moving circle with any of `walls`, or `undefined`.
|
|
83
109
|
*
|
|
84
110
|
* With a `radius` of 0 the circle is a point and the sweep is a segment, so
|
|
85
111
|
* this is also line of sight: `undefined` means nothing blocks the line from
|
|
@@ -88,12 +114,12 @@ export declare const sweepCircleBox: (origin: Vec2, delta: Vec2, radius: number,
|
|
|
88
114
|
*
|
|
89
115
|
* @example
|
|
90
116
|
* ```ts
|
|
91
|
-
* const sees = Collide2.
|
|
117
|
+
* const sees = Collide2.sweepCircle(eye, Vec2.subtract(target, eye), 0, { boxes: walls }) === undefined
|
|
92
118
|
* ```
|
|
93
119
|
*
|
|
94
120
|
* @category Queries
|
|
95
121
|
*/
|
|
96
|
-
export declare const
|
|
122
|
+
export declare const sweepCircle: (origin: Vec2, delta: Vec2, radius: number, walls: Walls) => Hit | undefined;
|
|
97
123
|
/**
|
|
98
124
|
* First time in [0, 1] at which a circle of `radius` moving from `origin` by
|
|
99
125
|
* `delta` touches a circle of `targetRadius` at `target`, or `undefined`. A
|
|
@@ -103,20 +129,28 @@ export declare const sweepCircleBoxes: (origin: Vec2, delta: Vec2, radius: numbe
|
|
|
103
129
|
*/
|
|
104
130
|
export declare const sweepCircleCircle: (origin: Vec2, delta: Vec2, radius: number, target: Vec2, targetRadius: number) => number | undefined;
|
|
105
131
|
/**
|
|
106
|
-
* Pushes a circle out of every
|
|
107
|
-
* between it and each wall. Returns
|
|
132
|
+
* Pushes a circle out of every wall it overlaps, boxes first and then
|
|
133
|
+
* segments, each in order, leaving `skin` between it and each wall. Returns
|
|
134
|
+
* the corrected center. A center inside a box leaves through the nearest
|
|
135
|
+
* side; a center exactly on a segment leaves along the segment's left normal.
|
|
108
136
|
*
|
|
109
137
|
* @category Movement
|
|
110
138
|
*/
|
|
111
|
-
export declare const pushOut: (center: Vec2, radius: number,
|
|
139
|
+
export declare const pushOut: (center: Vec2, radius: number, walls: Walls, skin?: number) => Vec2;
|
|
112
140
|
/**
|
|
113
141
|
* Moves a circle by `delta`, stopping at walls and sliding along them (up to
|
|
114
142
|
* four contacts per call). The whole path is swept, so no displacement can
|
|
115
143
|
* tunnel through a wall. A circle that starts inside a wall is pushed out
|
|
116
|
-
* first. A circle meeting a box's corner dead-on goes
|
|
117
|
-
* stopping against it.
|
|
144
|
+
* first. A circle meeting a box's corner or a segment's end dead-on goes
|
|
145
|
+
* around it rather than stopping against it.
|
|
146
|
+
*
|
|
147
|
+
* @example
|
|
148
|
+
* ```ts
|
|
149
|
+
* // A map traced from survey data: every wall is a segment.
|
|
150
|
+
* const moved = Collide2.moveCircle(position, delta, radius, { segments: nearbyWalls })
|
|
151
|
+
* ```
|
|
118
152
|
*
|
|
119
153
|
* @category Movement
|
|
120
154
|
*/
|
|
121
|
-
export declare const moveCircle: (start: Vec2, delta: Vec2, radius: number,
|
|
155
|
+
export declare const moveCircle: (start: Vec2, delta: Vec2, radius: number, walls: Walls, skin?: number) => MoveResult;
|
|
122
156
|
//# sourceMappingURL=Collide2.d.ts.map
|
package/dist/Collide2.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collide2.d.ts","sourceRoot":"","sources":["../src/Collide2.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"Collide2.d.ts","sourceRoot":"","sources":["../src/Collide2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA+BG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AAC7C,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;;;GAIG;AACH,MAAM,WAAW,GAAG;IAClB,wEAAwE;IACxE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,wDAAwD;IACxD,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED;;;;;GAKG;AACH,MAAM,WAAW,KAAK;IACpB,QAAQ,CAAC,KAAK,CAAC,EAAE,aAAa,CAAC,IAAI,CAAC,CAAA;IACpC,QAAQ,CAAC,QAAQ,CAAC,EAAE,aAAa,CAAC,QAAQ,CAAC,CAAA;IAC3C,0DAA0D;IAC1D,QAAQ,CAAC,MAAM,CAAC,EAAE,6DAA6D,CAAA;CAChF;AAKD;;;;GAIG;AACH,MAAM,WAAW,UAAU;IACzB,QAAQ,CAAC,QAAQ,EAAE,IAAI,CAAA;IACvB,oCAAoC;IACpC,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAA;CAC1B;AAED;;;;;GAKG;AACH,eAAO,MAAM,WAAW,OAAO,CAAA;AAE/B;;;;;;GAMG;AACH,eAAO,MAAM,SAAS,OAAQ,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,MAAM,KAAK,MAAM,KAAG,MAAM,GAAG,SAWtH,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,cAAc,WAAY,IAAI,SAAS,IAAI,UAAU,MAAM,OAAO,IAAI,KAAG,GAAG,GAAG,SAyE3F,CAAA;AAED;;;;;;;;;GASG;AACH,eAAO,MAAM,kBAAkB,WAAY,IAAI,SAAS,IAAI,UAAU,MAAM,WAAW,QAAQ,KAAG,GAAG,GAAG,SA0CvG,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,WAAW,WAAY,IAAI,SAAS,IAAI,UAAU,MAAM,SAAS,KAAK,KAAG,GAAG,GAAG,SAW3F,CAAA;AAED;;;;;;GAMG;AACH,eAAO,MAAM,iBAAiB,WACpB,IAAI,SACL,IAAI,UACH,MAAM,UACN,IAAI,gBACE,MAAM,KACnB,MAAM,GAAG,SAMX,CAAA;AAED;;;;;;;GAOG;AACH,eAAO,MAAM,OAAO,WAAY,IAAI,UAAU,MAAM,SAAS,KAAK,SAAQ,MAAM,KAAiB,IAqDhG,CAAA;AAED;;;;;;;;;;;;;;GAcG;AACH,eAAO,MAAM,UAAU,UAAW,IAAI,SAAS,IAAI,UAAU,MAAM,SAAS,KAAK,SAAQ,MAAM,KAAiB,UAyD/G,CAAA"}
|
package/dist/Collide2.js
CHANGED
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import * as Ops from "./Ops.js";
|
|
2
|
+
const noBoxes = [];
|
|
3
|
+
const noSegments = [];
|
|
2
4
|
/**
|
|
3
5
|
* The distance `pushOut` and `moveCircle` keep from walls after a contact
|
|
4
6
|
* (in world units), so the next sweep starts outside the wall.
|
|
@@ -120,7 +122,64 @@ export const sweepCircleBox = (origin, delta, radius, box) => {
|
|
|
120
122
|
return { time: enter, nx, ny };
|
|
121
123
|
};
|
|
122
124
|
/**
|
|
123
|
-
* First contact of a
|
|
125
|
+
* First contact of a circle of `radius` at `origin` moving by `delta` with
|
|
126
|
+
* `segment`, or `undefined`. Contact is computed against the segment
|
|
127
|
+
* thickened by the radius with round ends (the exact Minkowski sum), so the
|
|
128
|
+
* circle slides along it and around its ends. A circle that starts exactly
|
|
129
|
+
* touching it and moves into it hits at time 0. A circle that starts
|
|
130
|
+
* overlapping the segment is not reported; resolve it with `pushOut` first.
|
|
131
|
+
*
|
|
132
|
+
* @category Queries
|
|
133
|
+
*/
|
|
134
|
+
export const sweepCircleSegment = (origin, delta, radius, segment) => {
|
|
135
|
+
const ex = segment.bx - segment.ax;
|
|
136
|
+
const ey = segment.by - segment.ay;
|
|
137
|
+
const lengthSquared = ex * ex + ey * ey;
|
|
138
|
+
if (lengthSquared > 1e-12) {
|
|
139
|
+
// The flat side facing the circle, `radius` away from the segment's line.
|
|
140
|
+
const length = Math.sqrt(lengthSquared);
|
|
141
|
+
let nx = -ey / length;
|
|
142
|
+
let ny = ex / length;
|
|
143
|
+
let side = (origin.x - segment.ax) * nx + (origin.y - segment.ay) * ny;
|
|
144
|
+
if (side < 0) {
|
|
145
|
+
nx = -nx;
|
|
146
|
+
ny = -ny;
|
|
147
|
+
side = -side;
|
|
148
|
+
}
|
|
149
|
+
if (side < radius) {
|
|
150
|
+
// Beside the line: overlapping when level with the segment, otherwise
|
|
151
|
+
// only its ends are in the way.
|
|
152
|
+
const along = ((origin.x - segment.ax) * ex + (origin.y - segment.ay) * ey) / lengthSquared;
|
|
153
|
+
if (along >= 0 && along <= 1)
|
|
154
|
+
return undefined;
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const into = delta.x * nx + delta.y * ny;
|
|
158
|
+
if (into < 0) {
|
|
159
|
+
// `0 +` turns a -0 (touching at the start) into 0.
|
|
160
|
+
const time = 0 + (radius - side) / into;
|
|
161
|
+
if (time <= 1) {
|
|
162
|
+
const along = ((origin.x + delta.x * time - segment.ax) * ex + (origin.y + delta.y * time - segment.ay) * ey) / lengthSquared;
|
|
163
|
+
// The thickened segment is convex, so entering through its flat side is the first contact.
|
|
164
|
+
if (along >= 0 && along <= 1)
|
|
165
|
+
return { time, nx, ny };
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
const atA = rayCircle(origin.x, origin.y, delta.x, delta.y, segment.ax, segment.ay, radius);
|
|
171
|
+
const atB = lengthSquared > 1e-12 ? rayCircle(origin.x, origin.y, delta.x, delta.y, segment.bx, segment.by, radius) : undefined;
|
|
172
|
+
if (atA === undefined && atB === undefined)
|
|
173
|
+
return undefined;
|
|
174
|
+
const fromA = atB === undefined || (atA !== undefined && atA <= atB);
|
|
175
|
+
const time = fromA ? atA : atB;
|
|
176
|
+
const cx = origin.x + delta.x * time - (fromA ? segment.ax : segment.bx);
|
|
177
|
+
const cy = origin.y + delta.y * time - (fromA ? segment.ay : segment.by);
|
|
178
|
+
const length = Ops.hypot(cx, cy) || 1;
|
|
179
|
+
return { time, nx: cx / length, ny: cy / length };
|
|
180
|
+
};
|
|
181
|
+
/**
|
|
182
|
+
* First contact of a moving circle with any of `walls`, or `undefined`.
|
|
124
183
|
*
|
|
125
184
|
* With a `radius` of 0 the circle is a point and the sweep is a segment, so
|
|
126
185
|
* this is also line of sight: `undefined` means nothing blocks the line from
|
|
@@ -129,18 +188,23 @@ export const sweepCircleBox = (origin, delta, radius, box) => {
|
|
|
129
188
|
*
|
|
130
189
|
* @example
|
|
131
190
|
* ```ts
|
|
132
|
-
* const sees = Collide2.
|
|
191
|
+
* const sees = Collide2.sweepCircle(eye, Vec2.subtract(target, eye), 0, { boxes: walls }) === undefined
|
|
133
192
|
* ```
|
|
134
193
|
*
|
|
135
194
|
* @category Queries
|
|
136
195
|
*/
|
|
137
|
-
export const
|
|
196
|
+
export const sweepCircle = (origin, delta, radius, walls) => {
|
|
138
197
|
let first;
|
|
139
|
-
for (const box of boxes) {
|
|
198
|
+
for (const box of walls.boxes ?? noBoxes) {
|
|
140
199
|
const hit = sweepCircleBox(origin, delta, radius, box);
|
|
141
200
|
if (hit !== undefined && (first === undefined || hit.time < first.time))
|
|
142
201
|
first = hit;
|
|
143
202
|
}
|
|
203
|
+
for (const segment of walls.segments ?? noSegments) {
|
|
204
|
+
const hit = sweepCircleSegment(origin, delta, radius, segment);
|
|
205
|
+
if (hit !== undefined && (first === undefined || hit.time < first.time))
|
|
206
|
+
first = hit;
|
|
207
|
+
}
|
|
144
208
|
return first;
|
|
145
209
|
};
|
|
146
210
|
/**
|
|
@@ -159,15 +223,17 @@ export const sweepCircleCircle = (origin, delta, radius, target, targetRadius) =
|
|
|
159
223
|
return rayCircle(origin.x, origin.y, delta.x, delta.y, target.x, target.y, r);
|
|
160
224
|
};
|
|
161
225
|
/**
|
|
162
|
-
* Pushes a circle out of every
|
|
163
|
-
* between it and each wall. Returns
|
|
226
|
+
* Pushes a circle out of every wall it overlaps, boxes first and then
|
|
227
|
+
* segments, each in order, leaving `skin` between it and each wall. Returns
|
|
228
|
+
* the corrected center. A center inside a box leaves through the nearest
|
|
229
|
+
* side; a center exactly on a segment leaves along the segment's left normal.
|
|
164
230
|
*
|
|
165
231
|
* @category Movement
|
|
166
232
|
*/
|
|
167
|
-
export const pushOut = (center, radius,
|
|
233
|
+
export const pushOut = (center, radius, walls, skin = defaultSkin) => {
|
|
168
234
|
let x = center.x;
|
|
169
235
|
let y = center.y;
|
|
170
|
-
for (const box of boxes) {
|
|
236
|
+
for (const box of walls.boxes ?? noBoxes) {
|
|
171
237
|
const closestX = Math.max(box.minX, Math.min(x, box.maxX));
|
|
172
238
|
const closestY = Math.max(box.minY, Math.min(y, box.maxY));
|
|
173
239
|
const dx = x - closestX;
|
|
@@ -197,26 +263,83 @@ export const pushOut = (center, radius, boxes, skin = defaultSkin) => {
|
|
|
197
263
|
y = box.maxY + radius + skin;
|
|
198
264
|
}
|
|
199
265
|
}
|
|
266
|
+
for (const segment of walls.segments ?? noSegments) {
|
|
267
|
+
const ex = segment.bx - segment.ax;
|
|
268
|
+
const ey = segment.by - segment.ay;
|
|
269
|
+
const lengthSquared = ex * ex + ey * ey;
|
|
270
|
+
const along = lengthSquared > 1e-12 ? Math.max(0, Math.min(1, ((x - segment.ax) * ex + (y - segment.ay) * ey) / lengthSquared)) : 0;
|
|
271
|
+
const closestX = segment.ax + ex * along;
|
|
272
|
+
const closestY = segment.ay + ey * along;
|
|
273
|
+
const dx = x - closestX;
|
|
274
|
+
const dy = y - closestY;
|
|
275
|
+
const distanceSquared = dx * dx + dy * dy;
|
|
276
|
+
if (distanceSquared >= radius * radius)
|
|
277
|
+
continue;
|
|
278
|
+
if (distanceSquared > 1e-12) {
|
|
279
|
+
const distance = Math.sqrt(distanceSquared);
|
|
280
|
+
x = closestX + (dx / distance) * (radius + skin);
|
|
281
|
+
y = closestY + (dy / distance) * (radius + skin);
|
|
282
|
+
}
|
|
283
|
+
else if (lengthSquared > 1e-12) {
|
|
284
|
+
// Center on the segment: leave along its left normal.
|
|
285
|
+
const length = Math.sqrt(lengthSquared);
|
|
286
|
+
x = closestX - (ey / length) * (radius + skin);
|
|
287
|
+
y = closestY + (ex / length) * (radius + skin);
|
|
288
|
+
}
|
|
289
|
+
else {
|
|
290
|
+
// Center on a round post: leave along +x.
|
|
291
|
+
x = closestX + radius + skin;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
200
294
|
return { x, y };
|
|
201
295
|
};
|
|
202
296
|
/**
|
|
203
297
|
* Moves a circle by `delta`, stopping at walls and sliding along them (up to
|
|
204
298
|
* four contacts per call). The whole path is swept, so no displacement can
|
|
205
299
|
* tunnel through a wall. A circle that starts inside a wall is pushed out
|
|
206
|
-
* first. A circle meeting a box's corner dead-on goes
|
|
207
|
-
* stopping against it.
|
|
300
|
+
* first. A circle meeting a box's corner or a segment's end dead-on goes
|
|
301
|
+
* around it rather than stopping against it.
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* ```ts
|
|
305
|
+
* // A map traced from survey data: every wall is a segment.
|
|
306
|
+
* const moved = Collide2.moveCircle(position, delta, radius, { segments: nearbyWalls })
|
|
307
|
+
* ```
|
|
208
308
|
*
|
|
209
309
|
* @category Movement
|
|
210
310
|
*/
|
|
211
|
-
export const moveCircle = (start, delta, radius,
|
|
212
|
-
let position = pushOut(start, radius,
|
|
311
|
+
export const moveCircle = (start, delta, radius, walls, skin = defaultSkin) => {
|
|
312
|
+
let position = pushOut(start, radius, walls, skin);
|
|
213
313
|
let rx = delta.x;
|
|
214
314
|
let ry = delta.y;
|
|
215
315
|
let blocked = false;
|
|
216
316
|
for (let iteration = 0; iteration < 4; iteration++) {
|
|
217
317
|
if (rx * rx + ry * ry < 1e-12)
|
|
218
318
|
break;
|
|
219
|
-
const
|
|
319
|
+
const step = { x: rx, y: ry };
|
|
320
|
+
// The first contact, and whether it is round (a box's corner or a
|
|
321
|
+
// segment's end) rather than a flat side.
|
|
322
|
+
let hit;
|
|
323
|
+
let round = false;
|
|
324
|
+
for (const box of walls.boxes ?? noBoxes) {
|
|
325
|
+
const found = sweepCircleBox(position, step, radius, box);
|
|
326
|
+
if (found === undefined || (hit !== undefined && found.time >= hit.time))
|
|
327
|
+
continue;
|
|
328
|
+
hit = found;
|
|
329
|
+
round = found.nx !== 0 && found.ny !== 0;
|
|
330
|
+
}
|
|
331
|
+
for (const segment of walls.segments ?? noSegments) {
|
|
332
|
+
const found = sweepCircleSegment(position, step, radius, segment);
|
|
333
|
+
if (found === undefined || (hit !== undefined && found.time >= hit.time))
|
|
334
|
+
continue;
|
|
335
|
+
hit = found;
|
|
336
|
+
const ex = segment.bx - segment.ax;
|
|
337
|
+
const ey = segment.by - segment.ay;
|
|
338
|
+
const lengthSquared = ex * ex + ey * ey;
|
|
339
|
+
const along = found.nx * ex + found.ny * ey;
|
|
340
|
+
// A flat side's normal is perpendicular to the segment.
|
|
341
|
+
round = lengthSquared < 1e-12 || along * along > 1e-18 * lengthSquared;
|
|
342
|
+
}
|
|
220
343
|
if (hit === undefined) {
|
|
221
344
|
position = { x: position.x + rx, y: position.y + ry };
|
|
222
345
|
break;
|
|
@@ -234,10 +357,10 @@ export const moveCircle = (start, delta, radius, boxes, skin = defaultSkin) => {
|
|
|
234
357
|
if (into < 0) {
|
|
235
358
|
rx -= into * hit.nx;
|
|
236
359
|
ry -= into * hit.ny;
|
|
237
|
-
// Dead-on into a rounded corner (as an exact diagonal meets an
|
|
238
|
-
// axis-aligned box) nothing is left to slide along: go around
|
|
239
|
-
//
|
|
240
|
-
if (
|
|
360
|
+
// Dead-on into a rounded corner or end (as an exact diagonal meets an
|
|
361
|
+
// axis-aligned box) nothing is left to slide along: go around it
|
|
362
|
+
// instead of stopping against it.
|
|
363
|
+
if (round && rx * rx + ry * ry < 1e-12 * into * into) {
|
|
241
364
|
const side = rx * -hit.ny + ry * hit.nx < 0 ? -1 : 1;
|
|
242
365
|
rx = -hit.ny * -into * side;
|
|
243
366
|
ry = hit.nx * -into * side;
|
package/dist/Collide2.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Collide2.js","sourceRoot":"","sources":["../src/Collide2.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"Collide2.js","sourceRoot":"","sources":["../src/Collide2.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain line segments stored by their ends, for hot-path collision against
|
|
3
|
+
* walls that are not axis-aligned (a map traced from survey data, a slanted
|
|
4
|
+
* fence, a polygon's edges).
|
|
5
|
+
*
|
|
6
|
+
* Like `Box2`, it is the plain form the collision helpers (`Collide2`) read
|
|
7
|
+
* every frame, built once from whatever the game stores. A wall is
|
|
8
|
+
* two-sided: a circle is kept off it on both sides and around both ends.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const fence = Segment2.between({ x: 0, y: 0 }, { x: 40, y: 30 })
|
|
13
|
+
* const moved = Collide2.moveCircle(position, delta, radius, { segments: [fence] })
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @module Segment2
|
|
17
|
+
* @docGroup helpers
|
|
18
|
+
*
|
|
19
|
+
* @categoryDescription Segment Types
|
|
20
|
+
* The structural segment shape.
|
|
21
|
+
*
|
|
22
|
+
* @categoryDescription Operations
|
|
23
|
+
* Building segments and measuring them against points and circles.
|
|
24
|
+
*/
|
|
25
|
+
import type { Vec2 } from "./Vec2.ts";
|
|
26
|
+
/**
|
|
27
|
+
* The segment from (`ax`, `ay`) to (`bx`, `by`). Both ends may be the same
|
|
28
|
+
* point, which is then a round post.
|
|
29
|
+
*
|
|
30
|
+
* @category Segment Types
|
|
31
|
+
*/
|
|
32
|
+
export interface Segment2 {
|
|
33
|
+
readonly ax: number;
|
|
34
|
+
readonly ay: number;
|
|
35
|
+
readonly bx: number;
|
|
36
|
+
readonly by: number;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* The segment from `a` to `b`.
|
|
40
|
+
*
|
|
41
|
+
* @category Operations
|
|
42
|
+
*/
|
|
43
|
+
export declare const between: (a: Vec2, b: Vec2) => Segment2;
|
|
44
|
+
/**
|
|
45
|
+
* The point of the segment nearest to `point`.
|
|
46
|
+
*
|
|
47
|
+
* @category Operations
|
|
48
|
+
*/
|
|
49
|
+
export declare const closestPoint: (segment: Segment2, point: Vec2) => Vec2;
|
|
50
|
+
/**
|
|
51
|
+
* Whether a circle overlaps (or touches) the segment.
|
|
52
|
+
*
|
|
53
|
+
* @category Operations
|
|
54
|
+
*/
|
|
55
|
+
export declare const overlapsCircle: (segment: Segment2, center: Vec2, radius: number) => boolean;
|
|
56
|
+
//# sourceMappingURL=Segment2.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Segment2.d.ts","sourceRoot":"","sources":["../src/Segment2.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,WAAW,CAAA;AAErC;;;;;GAKG;AACH,MAAM,WAAW,QAAQ;IACvB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;IACnB,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAA;CACpB;AAED;;;;GAIG;AACH,eAAO,MAAM,OAAO,MAAO,IAAI,KAAK,IAAI,KAAG,QAAoD,CAAA;AAE/F;;;;GAIG;AACH,eAAO,MAAM,YAAY,YAAa,QAAQ,SAAS,IAAI,KAAG,IAO7D,CAAA;AAED;;;;GAIG;AACH,eAAO,MAAM,cAAc,YAAa,QAAQ,UAAU,IAAI,UAAU,MAAM,KAAG,OAKhF,CAAA"}
|
package/dist/Segment2.js
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The segment from `a` to `b`.
|
|
3
|
+
*
|
|
4
|
+
* @category Operations
|
|
5
|
+
*/
|
|
6
|
+
export const between = (a, b) => ({ ax: a.x, ay: a.y, bx: b.x, by: b.y });
|
|
7
|
+
/**
|
|
8
|
+
* The point of the segment nearest to `point`.
|
|
9
|
+
*
|
|
10
|
+
* @category Operations
|
|
11
|
+
*/
|
|
12
|
+
export const closestPoint = (segment, point) => {
|
|
13
|
+
const ex = segment.bx - segment.ax;
|
|
14
|
+
const ey = segment.by - segment.ay;
|
|
15
|
+
const lengthSquared = ex * ex + ey * ey;
|
|
16
|
+
if (lengthSquared < 1e-12)
|
|
17
|
+
return { x: segment.ax, y: segment.ay };
|
|
18
|
+
const along = Math.max(0, Math.min(1, ((point.x - segment.ax) * ex + (point.y - segment.ay) * ey) / lengthSquared));
|
|
19
|
+
return { x: segment.ax + ex * along, y: segment.ay + ey * along };
|
|
20
|
+
};
|
|
21
|
+
/**
|
|
22
|
+
* Whether a circle overlaps (or touches) the segment.
|
|
23
|
+
*
|
|
24
|
+
* @category Operations
|
|
25
|
+
*/
|
|
26
|
+
export const overlapsCircle = (segment, center, radius) => {
|
|
27
|
+
const nearest = closestPoint(segment, center);
|
|
28
|
+
const dx = center.x - nearest.x;
|
|
29
|
+
const dy = center.y - nearest.y;
|
|
30
|
+
return dx * dx + dy * dy <= radius * radius;
|
|
31
|
+
};
|
|
32
|
+
//# sourceMappingURL=Segment2.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"Segment2.js","sourceRoot":"","sources":["../src/Segment2.ts"],"names":[],"mappings":"AAuCA;;;;GAIG;AACH,MAAM,CAAC,MAAM,OAAO,GAAG,CAAC,CAAO,EAAE,CAAO,EAAY,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAA;AAE/F;;;;GAIG;AACH,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,OAAiB,EAAE,KAAW,EAAQ,EAAE;IACnE,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAA;IAClC,MAAM,EAAE,GAAG,OAAO,CAAC,EAAE,GAAG,OAAO,CAAC,EAAE,CAAA;IAClC,MAAM,aAAa,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,CAAA;IACvC,IAAI,aAAa,GAAG,KAAK;QAAE,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,EAAE,CAAA;IAClE,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,CAAC,GAAG,OAAO,CAAC,EAAE,CAAC,GAAG,EAAE,CAAC,GAAG,aAAa,CAAC,CAAC,CAAA;IACnH,OAAO,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,CAAC,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,GAAG,KAAK,EAAE,CAAA;AACnE,CAAC,CAAA;AAED;;;;GAIG;AACH,MAAM,CAAC,MAAM,cAAc,GAAG,CAAC,OAAiB,EAAE,MAAY,EAAE,MAAc,EAAW,EAAE;IACzF,MAAM,OAAO,GAAG,YAAY,CAAC,OAAO,EAAE,MAAM,CAAC,CAAA;IAC7C,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;IAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAA;IAC/B,OAAO,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,IAAI,MAAM,GAAG,MAAM,CAAA;AAC7C,CAAC,CAAA"}
|
package/dist/index.d.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* `ConstructedResource(Random.codec)` and are validated once at the boundary. `InputAxis` normalizes directional input.
|
|
8
8
|
*
|
|
9
9
|
* Hot-path helpers work on plain numbers every frame: `Vec2` arithmetic,
|
|
10
|
-
* `Box2` edges, `
|
|
10
|
+
* `Box2` edges, `Segment2` walls at any angle, `Collide2` swept circle
|
|
11
|
+
* collision against both, a `SpatialGrid` broad
|
|
11
12
|
* phase, a `FlowField` for finding the way around walls, and a deterministic
|
|
12
13
|
* `Random` whose state is one integer.
|
|
13
14
|
*
|
|
@@ -23,6 +24,7 @@ export * as InputAxis from "./InputAxis.ts";
|
|
|
23
24
|
export * as Ops from "./Ops.ts";
|
|
24
25
|
export * as Random from "./Random.ts";
|
|
25
26
|
export * as Scalar from "./Scalar.ts";
|
|
27
|
+
export * as Segment2 from "./Segment2.ts";
|
|
26
28
|
export * as Size2 from "./Size2.ts";
|
|
27
29
|
export * as SpatialGrid from "./SpatialGrid.ts";
|
|
28
30
|
export * as Vec2 from "./Vec2.ts";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
|
package/dist/index.js
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* `ConstructedResource(Random.codec)` and are validated once at the boundary. `InputAxis` normalizes directional input.
|
|
8
8
|
*
|
|
9
9
|
* Hot-path helpers work on plain numbers every frame: `Vec2` arithmetic,
|
|
10
|
-
* `Box2` edges, `
|
|
10
|
+
* `Box2` edges, `Segment2` walls at any angle, `Collide2` swept circle
|
|
11
|
+
* collision against both, a `SpatialGrid` broad
|
|
11
12
|
* phase, a `FlowField` for finding the way around walls, and a deterministic
|
|
12
13
|
* `Random` whose state is one integer.
|
|
13
14
|
*
|
|
@@ -23,6 +24,7 @@ export * as InputAxis from "./InputAxis.js";
|
|
|
23
24
|
export * as Ops from "./Ops.js";
|
|
24
25
|
export * as Random from "./Random.js";
|
|
25
26
|
export * as Scalar from "./Scalar.js";
|
|
27
|
+
export * as Segment2 from "./Segment2.js";
|
|
26
28
|
export * as Size2 from "./Size2.js";
|
|
27
29
|
export * as SpatialGrid from "./SpatialGrid.js";
|
|
28
30
|
export * as Vec2 from "./Vec2.js";
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,SAAS,MAAM,gBAAgB,CAAA;AAC3C,OAAO,KAAK,GAAG,MAAM,UAAU,CAAA;AAC/B,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,MAAM,MAAM,aAAa,CAAA;AACrC,OAAO,KAAK,QAAQ,MAAM,eAAe,CAAA;AACzC,OAAO,KAAK,KAAK,MAAM,YAAY,CAAA;AACnC,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAA;AAC/C,OAAO,KAAK,IAAI,MAAM,WAAW,CAAA;AACjC,OAAO,KAAK,OAAO,MAAM,cAAc,CAAA"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@typeonce/ritmo-math",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.14.0",
|
|
4
4
|
"description": "Validated math values and hot-path game helpers (collision, spatial grid, flow field, random) for Ritmo games",
|
|
5
5
|
"author": "Sandro Maglione",
|
|
6
6
|
"license": "MIT",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"provenance": true
|
|
48
48
|
},
|
|
49
49
|
"dependencies": {
|
|
50
|
-
"@typeonce/ritmo": "^0.
|
|
50
|
+
"@typeonce/ritmo": "^0.14.0"
|
|
51
51
|
},
|
|
52
52
|
"engines": {
|
|
53
53
|
"node": ">=20"
|
package/src/Collide2.ts
CHANGED
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Continuous collision for moving circles against static
|
|
2
|
+
* Continuous collision for moving circles against static walls (axis-aligned
|
|
3
|
+
* boxes and line segments) and circles.
|
|
3
4
|
*
|
|
4
|
-
* Movement is swept, not sampled: `
|
|
5
|
+
* Movement is swept, not sampled: `sweepCircle` finds the first time of
|
|
5
6
|
* contact along the whole displacement, so fast movers (dashes, projectiles)
|
|
6
7
|
* cannot skip over a wall between two steps. `moveCircle` stops at walls and
|
|
7
|
-
* slides along them.
|
|
8
|
+
* slides along them. Walls are passed as {@link Walls}: `Box2` boxes for a
|
|
9
|
+
* grid of rooms, `Segment2` segments for walls at any angle, or both.
|
|
8
10
|
*
|
|
9
11
|
* Every query returns `undefined` for "no contact", never throws, and
|
|
10
12
|
* allocates only its result. Inputs are expected to be finite.
|
|
@@ -12,7 +14,7 @@
|
|
|
12
14
|
* @example
|
|
13
15
|
* ```ts
|
|
14
16
|
* // Move a body by its velocity this step, sliding along the walls.
|
|
15
|
-
* const moved = Collide2.moveCircle(position, Vec2.scale(velocity, dt), radius, walls)
|
|
17
|
+
* const moved = Collide2.moveCircle(position, Vec2.scale(velocity, dt), radius, { boxes: walls })
|
|
16
18
|
* if (moved.blocked) bumpedWall()
|
|
17
19
|
* ```
|
|
18
20
|
*
|
|
@@ -30,6 +32,7 @@
|
|
|
30
32
|
*/
|
|
31
33
|
import type { Box2 } from "./Box2.ts"
|
|
32
34
|
import * as Ops from "./Ops.ts"
|
|
35
|
+
import type { Segment2 } from "./Segment2.ts"
|
|
33
36
|
import type { Vec2 } from "./Vec2.ts"
|
|
34
37
|
|
|
35
38
|
/**
|
|
@@ -40,11 +43,27 @@ import type { Vec2 } from "./Vec2.ts"
|
|
|
40
43
|
export interface Hit {
|
|
41
44
|
/** Fraction of the displacement travelled before contact, in [0, 1]. */
|
|
42
45
|
readonly time: number
|
|
43
|
-
/** Unit contact normal, pointing away from the
|
|
46
|
+
/** Unit contact normal, pointing away from the wall. */
|
|
44
47
|
readonly nx: number
|
|
45
48
|
readonly ny: number
|
|
46
49
|
}
|
|
47
50
|
|
|
51
|
+
/**
|
|
52
|
+
* The static walls a circle collides with: axis-aligned `boxes`, line
|
|
53
|
+
* `segments`, or both. A missing list is no walls of that kind.
|
|
54
|
+
*
|
|
55
|
+
* @category Collision Types
|
|
56
|
+
*/
|
|
57
|
+
export interface Walls {
|
|
58
|
+
readonly boxes?: ReadonlyArray<Box2>
|
|
59
|
+
readonly segments?: ReadonlyArray<Segment2>
|
|
60
|
+
/** Not a setting: an array of walls is passed by kind. */
|
|
61
|
+
readonly length?: "pass walls by kind: { boxes: walls } or { segments: walls }"
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const noBoxes: ReadonlyArray<Box2> = []
|
|
65
|
+
const noSegments: ReadonlyArray<Segment2> = []
|
|
66
|
+
|
|
48
67
|
/**
|
|
49
68
|
* Where a moved circle ended up.
|
|
50
69
|
*
|
|
@@ -170,7 +189,61 @@ export const sweepCircleBox = (origin: Vec2, delta: Vec2, radius: number, box: B
|
|
|
170
189
|
}
|
|
171
190
|
|
|
172
191
|
/**
|
|
173
|
-
* First contact of a
|
|
192
|
+
* First contact of a circle of `radius` at `origin` moving by `delta` with
|
|
193
|
+
* `segment`, or `undefined`. Contact is computed against the segment
|
|
194
|
+
* thickened by the radius with round ends (the exact Minkowski sum), so the
|
|
195
|
+
* circle slides along it and around its ends. A circle that starts exactly
|
|
196
|
+
* touching it and moves into it hits at time 0. A circle that starts
|
|
197
|
+
* overlapping the segment is not reported; resolve it with `pushOut` first.
|
|
198
|
+
*
|
|
199
|
+
* @category Queries
|
|
200
|
+
*/
|
|
201
|
+
export const sweepCircleSegment = (origin: Vec2, delta: Vec2, radius: number, segment: Segment2): Hit | undefined => {
|
|
202
|
+
const ex = segment.bx - segment.ax
|
|
203
|
+
const ey = segment.by - segment.ay
|
|
204
|
+
const lengthSquared = ex * ex + ey * ey
|
|
205
|
+
if (lengthSquared > 1e-12) {
|
|
206
|
+
// The flat side facing the circle, `radius` away from the segment's line.
|
|
207
|
+
const length = Math.sqrt(lengthSquared)
|
|
208
|
+
let nx = -ey / length
|
|
209
|
+
let ny = ex / length
|
|
210
|
+
let side = (origin.x - segment.ax) * nx + (origin.y - segment.ay) * ny
|
|
211
|
+
if (side < 0) {
|
|
212
|
+
nx = -nx
|
|
213
|
+
ny = -ny
|
|
214
|
+
side = -side
|
|
215
|
+
}
|
|
216
|
+
if (side < radius) {
|
|
217
|
+
// Beside the line: overlapping when level with the segment, otherwise
|
|
218
|
+
// only its ends are in the way.
|
|
219
|
+
const along = ((origin.x - segment.ax) * ex + (origin.y - segment.ay) * ey) / lengthSquared
|
|
220
|
+
if (along >= 0 && along <= 1) return undefined
|
|
221
|
+
} else {
|
|
222
|
+
const into = delta.x * nx + delta.y * ny
|
|
223
|
+
if (into < 0) {
|
|
224
|
+
// `0 +` turns a -0 (touching at the start) into 0.
|
|
225
|
+
const time = 0 + (radius - side) / into
|
|
226
|
+
if (time <= 1) {
|
|
227
|
+
const along = ((origin.x + delta.x * time - segment.ax) * ex + (origin.y + delta.y * time - segment.ay) * ey) / lengthSquared
|
|
228
|
+
// The thickened segment is convex, so entering through its flat side is the first contact.
|
|
229
|
+
if (along >= 0 && along <= 1) return { time, nx, ny }
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
const atA = rayCircle(origin.x, origin.y, delta.x, delta.y, segment.ax, segment.ay, radius)
|
|
235
|
+
const atB = lengthSquared > 1e-12 ? rayCircle(origin.x, origin.y, delta.x, delta.y, segment.bx, segment.by, radius) : undefined
|
|
236
|
+
if (atA === undefined && atB === undefined) return undefined
|
|
237
|
+
const fromA = atB === undefined || (atA !== undefined && atA <= atB)
|
|
238
|
+
const time = fromA ? atA! : atB
|
|
239
|
+
const cx = origin.x + delta.x * time - (fromA ? segment.ax : segment.bx)
|
|
240
|
+
const cy = origin.y + delta.y * time - (fromA ? segment.ay : segment.by)
|
|
241
|
+
const length = Ops.hypot(cx, cy) || 1
|
|
242
|
+
return { time, nx: cx / length, ny: cy / length }
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* First contact of a moving circle with any of `walls`, or `undefined`.
|
|
174
247
|
*
|
|
175
248
|
* With a `radius` of 0 the circle is a point and the sweep is a segment, so
|
|
176
249
|
* this is also line of sight: `undefined` means nothing blocks the line from
|
|
@@ -179,17 +252,21 @@ export const sweepCircleBox = (origin: Vec2, delta: Vec2, radius: number, box: B
|
|
|
179
252
|
*
|
|
180
253
|
* @example
|
|
181
254
|
* ```ts
|
|
182
|
-
* const sees = Collide2.
|
|
255
|
+
* const sees = Collide2.sweepCircle(eye, Vec2.subtract(target, eye), 0, { boxes: walls }) === undefined
|
|
183
256
|
* ```
|
|
184
257
|
*
|
|
185
258
|
* @category Queries
|
|
186
259
|
*/
|
|
187
|
-
export const
|
|
260
|
+
export const sweepCircle = (origin: Vec2, delta: Vec2, radius: number, walls: Walls): Hit | undefined => {
|
|
188
261
|
let first: Hit | undefined
|
|
189
|
-
for (const box of boxes) {
|
|
262
|
+
for (const box of walls.boxes ?? noBoxes) {
|
|
190
263
|
const hit = sweepCircleBox(origin, delta, radius, box)
|
|
191
264
|
if (hit !== undefined && (first === undefined || hit.time < first.time)) first = hit
|
|
192
265
|
}
|
|
266
|
+
for (const segment of walls.segments ?? noSegments) {
|
|
267
|
+
const hit = sweepCircleSegment(origin, delta, radius, segment)
|
|
268
|
+
if (hit !== undefined && (first === undefined || hit.time < first.time)) first = hit
|
|
269
|
+
}
|
|
193
270
|
return first
|
|
194
271
|
}
|
|
195
272
|
|
|
@@ -215,15 +292,17 @@ export const sweepCircleCircle = (
|
|
|
215
292
|
}
|
|
216
293
|
|
|
217
294
|
/**
|
|
218
|
-
* Pushes a circle out of every
|
|
219
|
-
* between it and each wall. Returns
|
|
295
|
+
* Pushes a circle out of every wall it overlaps, boxes first and then
|
|
296
|
+
* segments, each in order, leaving `skin` between it and each wall. Returns
|
|
297
|
+
* the corrected center. A center inside a box leaves through the nearest
|
|
298
|
+
* side; a center exactly on a segment leaves along the segment's left normal.
|
|
220
299
|
*
|
|
221
300
|
* @category Movement
|
|
222
301
|
*/
|
|
223
|
-
export const pushOut = (center: Vec2, radius: number,
|
|
302
|
+
export const pushOut = (center: Vec2, radius: number, walls: Walls, skin: number = defaultSkin): Vec2 => {
|
|
224
303
|
let x = center.x
|
|
225
304
|
let y = center.y
|
|
226
|
-
for (const box of boxes) {
|
|
305
|
+
for (const box of walls.boxes ?? noBoxes) {
|
|
227
306
|
const closestX = Math.max(box.minX, Math.min(x, box.maxX))
|
|
228
307
|
const closestY = Math.max(box.minY, Math.min(y, box.maxY))
|
|
229
308
|
const dx = x - closestX
|
|
@@ -247,6 +326,31 @@ export const pushOut = (center: Vec2, radius: number, boxes: ReadonlyArray<Box2>
|
|
|
247
326
|
else y = box.maxY + radius + skin
|
|
248
327
|
}
|
|
249
328
|
}
|
|
329
|
+
for (const segment of walls.segments ?? noSegments) {
|
|
330
|
+
const ex = segment.bx - segment.ax
|
|
331
|
+
const ey = segment.by - segment.ay
|
|
332
|
+
const lengthSquared = ex * ex + ey * ey
|
|
333
|
+
const along = lengthSquared > 1e-12 ? Math.max(0, Math.min(1, ((x - segment.ax) * ex + (y - segment.ay) * ey) / lengthSquared)) : 0
|
|
334
|
+
const closestX = segment.ax + ex * along
|
|
335
|
+
const closestY = segment.ay + ey * along
|
|
336
|
+
const dx = x - closestX
|
|
337
|
+
const dy = y - closestY
|
|
338
|
+
const distanceSquared = dx * dx + dy * dy
|
|
339
|
+
if (distanceSquared >= radius * radius) continue
|
|
340
|
+
if (distanceSquared > 1e-12) {
|
|
341
|
+
const distance = Math.sqrt(distanceSquared)
|
|
342
|
+
x = closestX + (dx / distance) * (radius + skin)
|
|
343
|
+
y = closestY + (dy / distance) * (radius + skin)
|
|
344
|
+
} else if (lengthSquared > 1e-12) {
|
|
345
|
+
// Center on the segment: leave along its left normal.
|
|
346
|
+
const length = Math.sqrt(lengthSquared)
|
|
347
|
+
x = closestX - (ey / length) * (radius + skin)
|
|
348
|
+
y = closestY + (ex / length) * (radius + skin)
|
|
349
|
+
} else {
|
|
350
|
+
// Center on a round post: leave along +x.
|
|
351
|
+
x = closestX + radius + skin
|
|
352
|
+
}
|
|
353
|
+
}
|
|
250
354
|
return { x, y }
|
|
251
355
|
}
|
|
252
356
|
|
|
@@ -254,19 +358,46 @@ export const pushOut = (center: Vec2, radius: number, boxes: ReadonlyArray<Box2>
|
|
|
254
358
|
* Moves a circle by `delta`, stopping at walls and sliding along them (up to
|
|
255
359
|
* four contacts per call). The whole path is swept, so no displacement can
|
|
256
360
|
* tunnel through a wall. A circle that starts inside a wall is pushed out
|
|
257
|
-
* first. A circle meeting a box's corner dead-on goes
|
|
258
|
-
* stopping against it.
|
|
361
|
+
* first. A circle meeting a box's corner or a segment's end dead-on goes
|
|
362
|
+
* around it rather than stopping against it.
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* ```ts
|
|
366
|
+
* // A map traced from survey data: every wall is a segment.
|
|
367
|
+
* const moved = Collide2.moveCircle(position, delta, radius, { segments: nearbyWalls })
|
|
368
|
+
* ```
|
|
259
369
|
*
|
|
260
370
|
* @category Movement
|
|
261
371
|
*/
|
|
262
|
-
export const moveCircle = (start: Vec2, delta: Vec2, radius: number,
|
|
263
|
-
let position = pushOut(start, radius,
|
|
372
|
+
export const moveCircle = (start: Vec2, delta: Vec2, radius: number, walls: Walls, skin: number = defaultSkin): MoveResult => {
|
|
373
|
+
let position = pushOut(start, radius, walls, skin)
|
|
264
374
|
let rx = delta.x
|
|
265
375
|
let ry = delta.y
|
|
266
376
|
let blocked = false
|
|
267
377
|
for (let iteration = 0; iteration < 4; iteration++) {
|
|
268
378
|
if (rx * rx + ry * ry < 1e-12) break
|
|
269
|
-
const
|
|
379
|
+
const step = { x: rx, y: ry }
|
|
380
|
+
// The first contact, and whether it is round (a box's corner or a
|
|
381
|
+
// segment's end) rather than a flat side.
|
|
382
|
+
let hit: Hit | undefined
|
|
383
|
+
let round = false
|
|
384
|
+
for (const box of walls.boxes ?? noBoxes) {
|
|
385
|
+
const found = sweepCircleBox(position, step, radius, box)
|
|
386
|
+
if (found === undefined || (hit !== undefined && found.time >= hit.time)) continue
|
|
387
|
+
hit = found
|
|
388
|
+
round = found.nx !== 0 && found.ny !== 0
|
|
389
|
+
}
|
|
390
|
+
for (const segment of walls.segments ?? noSegments) {
|
|
391
|
+
const found = sweepCircleSegment(position, step, radius, segment)
|
|
392
|
+
if (found === undefined || (hit !== undefined && found.time >= hit.time)) continue
|
|
393
|
+
hit = found
|
|
394
|
+
const ex = segment.bx - segment.ax
|
|
395
|
+
const ey = segment.by - segment.ay
|
|
396
|
+
const lengthSquared = ex * ex + ey * ey
|
|
397
|
+
const along = found.nx * ex + found.ny * ey
|
|
398
|
+
// A flat side's normal is perpendicular to the segment.
|
|
399
|
+
round = lengthSquared < 1e-12 || along * along > 1e-18 * lengthSquared
|
|
400
|
+
}
|
|
270
401
|
if (hit === undefined) {
|
|
271
402
|
position = { x: position.x + rx, y: position.y + ry }
|
|
272
403
|
break
|
|
@@ -284,10 +415,10 @@ export const moveCircle = (start: Vec2, delta: Vec2, radius: number, boxes: Read
|
|
|
284
415
|
if (into < 0) {
|
|
285
416
|
rx -= into * hit.nx
|
|
286
417
|
ry -= into * hit.ny
|
|
287
|
-
// Dead-on into a rounded corner (as an exact diagonal meets an
|
|
288
|
-
// axis-aligned box) nothing is left to slide along: go around
|
|
289
|
-
//
|
|
290
|
-
if (
|
|
418
|
+
// Dead-on into a rounded corner or end (as an exact diagonal meets an
|
|
419
|
+
// axis-aligned box) nothing is left to slide along: go around it
|
|
420
|
+
// instead of stopping against it.
|
|
421
|
+
if (round && rx * rx + ry * ry < 1e-12 * into * into) {
|
|
291
422
|
const side = rx * -hit.ny + ry * hit.nx < 0 ? -1 : 1
|
|
292
423
|
rx = -hit.ny * -into * side
|
|
293
424
|
ry = hit.nx * -into * side
|
package/src/Segment2.ts
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Plain line segments stored by their ends, for hot-path collision against
|
|
3
|
+
* walls that are not axis-aligned (a map traced from survey data, a slanted
|
|
4
|
+
* fence, a polygon's edges).
|
|
5
|
+
*
|
|
6
|
+
* Like `Box2`, it is the plain form the collision helpers (`Collide2`) read
|
|
7
|
+
* every frame, built once from whatever the game stores. A wall is
|
|
8
|
+
* two-sided: a circle is kept off it on both sides and around both ends.
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```ts
|
|
12
|
+
* const fence = Segment2.between({ x: 0, y: 0 }, { x: 40, y: 30 })
|
|
13
|
+
* const moved = Collide2.moveCircle(position, delta, radius, { segments: [fence] })
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @module Segment2
|
|
17
|
+
* @docGroup helpers
|
|
18
|
+
*
|
|
19
|
+
* @categoryDescription Segment Types
|
|
20
|
+
* The structural segment shape.
|
|
21
|
+
*
|
|
22
|
+
* @categoryDescription Operations
|
|
23
|
+
* Building segments and measuring them against points and circles.
|
|
24
|
+
*/
|
|
25
|
+
import type { Vec2 } from "./Vec2.ts"
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The segment from (`ax`, `ay`) to (`bx`, `by`). Both ends may be the same
|
|
29
|
+
* point, which is then a round post.
|
|
30
|
+
*
|
|
31
|
+
* @category Segment Types
|
|
32
|
+
*/
|
|
33
|
+
export interface Segment2 {
|
|
34
|
+
readonly ax: number
|
|
35
|
+
readonly ay: number
|
|
36
|
+
readonly bx: number
|
|
37
|
+
readonly by: number
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* The segment from `a` to `b`.
|
|
42
|
+
*
|
|
43
|
+
* @category Operations
|
|
44
|
+
*/
|
|
45
|
+
export const between = (a: Vec2, b: Vec2): Segment2 => ({ ax: a.x, ay: a.y, bx: b.x, by: b.y })
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* The point of the segment nearest to `point`.
|
|
49
|
+
*
|
|
50
|
+
* @category Operations
|
|
51
|
+
*/
|
|
52
|
+
export const closestPoint = (segment: Segment2, point: Vec2): Vec2 => {
|
|
53
|
+
const ex = segment.bx - segment.ax
|
|
54
|
+
const ey = segment.by - segment.ay
|
|
55
|
+
const lengthSquared = ex * ex + ey * ey
|
|
56
|
+
if (lengthSquared < 1e-12) return { x: segment.ax, y: segment.ay }
|
|
57
|
+
const along = Math.max(0, Math.min(1, ((point.x - segment.ax) * ex + (point.y - segment.ay) * ey) / lengthSquared))
|
|
58
|
+
return { x: segment.ax + ex * along, y: segment.ay + ey * along }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Whether a circle overlaps (or touches) the segment.
|
|
63
|
+
*
|
|
64
|
+
* @category Operations
|
|
65
|
+
*/
|
|
66
|
+
export const overlapsCircle = (segment: Segment2, center: Vec2, radius: number): boolean => {
|
|
67
|
+
const nearest = closestPoint(segment, center)
|
|
68
|
+
const dx = center.x - nearest.x
|
|
69
|
+
const dy = center.y - nearest.y
|
|
70
|
+
return dx * dx + dy * dy <= radius * radius
|
|
71
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* `ConstructedResource(Random.codec)` and are validated once at the boundary. `InputAxis` normalizes directional input.
|
|
8
8
|
*
|
|
9
9
|
* Hot-path helpers work on plain numbers every frame: `Vec2` arithmetic,
|
|
10
|
-
* `Box2` edges, `
|
|
10
|
+
* `Box2` edges, `Segment2` walls at any angle, `Collide2` swept circle
|
|
11
|
+
* collision against both, a `SpatialGrid` broad
|
|
11
12
|
* phase, a `FlowField` for finding the way around walls, and a deterministic
|
|
12
13
|
* `Random` whose state is one integer.
|
|
13
14
|
*
|
|
@@ -23,6 +24,7 @@ export * as InputAxis from "./InputAxis.ts"
|
|
|
23
24
|
export * as Ops from "./Ops.ts"
|
|
24
25
|
export * as Random from "./Random.ts"
|
|
25
26
|
export * as Scalar from "./Scalar.ts"
|
|
27
|
+
export * as Segment2 from "./Segment2.ts"
|
|
26
28
|
export * as Size2 from "./Size2.ts"
|
|
27
29
|
export * as SpatialGrid from "./SpatialGrid.ts"
|
|
28
30
|
export * as Vec2 from "./Vec2.ts"
|