@benev/math 0.1.0 → 0.2.0-1
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 +110 -23
- package/package.json +3 -1
- package/s/{concepts → core}/quat.ts +56 -16
- package/s/{concepts → core}/vec2.ts +52 -38
- package/s/{concepts → core}/vec3.ts +57 -47
- package/s/{concepts → core}/vec4.ts +13 -16
- package/s/index.ts +25 -10
- package/s/optimizers/hash-map.ts +71 -0
- package/s/optimizers/hash-set.ts +50 -0
- package/s/optimizers/zen.ts +151 -0
- package/s/physics/2d/collide2d.ts +51 -0
- package/s/physics/2d/intersect2d.ts +80 -0
- package/s/shapes/2d/circle.ts +43 -0
- package/s/shapes/2d/edge.ts +3 -0
- package/s/shapes/2d/index.ts +6 -0
- package/s/shapes/2d/pill.ts +3 -0
- package/s/shapes/2d/rect.ts +74 -0
- package/s/shapes/3d/box.ts +84 -0
- package/s/shapes/3d/capsule.ts +3 -0
- package/s/shapes/3d/index.ts +6 -0
- package/s/shapes/3d/segment.ts +48 -0
- package/s/shapes/3d/sphere.ts +3 -0
- package/s/tools/angles.ts +49 -0
- package/s/{concepts → tools}/circular.ts +5 -3
- package/s/{concepts → tools}/randy.ts +0 -14
- package/s/{concepts → tools}/scalar.ts +11 -11
- package/s/{concepts → tools}/spline.ts +11 -10
- package/x/{concepts → core}/quat.d.ts +10 -5
- package/x/{concepts → core}/quat.js +49 -12
- package/x/core/quat.js.map +1 -0
- package/x/{concepts → core}/vec2.d.ts +16 -17
- package/x/{concepts → core}/vec2.js +37 -21
- package/x/core/vec2.js.map +1 -0
- package/x/{concepts → core}/vec3.d.ts +19 -20
- package/x/{concepts → core}/vec3.js +46 -27
- package/x/core/vec3.js.map +1 -0
- package/x/{concepts → core}/vec4.d.ts +5 -7
- package/x/{concepts → core}/vec4.js +12 -12
- package/x/core/vec4.js.map +1 -0
- package/x/index.d.ts +21 -10
- package/x/index.js +21 -10
- package/x/index.js.map +1 -1
- package/x/optimizers/hash-map.d.ts +17 -0
- package/x/optimizers/hash-map.js +55 -0
- package/x/optimizers/hash-map.js.map +1 -0
- package/x/optimizers/hash-set.d.ts +13 -0
- package/x/optimizers/hash-set.js +39 -0
- package/x/optimizers/hash-set.js.map +1 -0
- package/x/optimizers/zen.d.ts +33 -0
- package/x/optimizers/zen.js +121 -0
- package/x/optimizers/zen.js.map +1 -0
- package/x/physics/2d/collide2d.d.ts +8 -0
- package/x/physics/2d/collide2d.js +36 -0
- package/x/physics/2d/collide2d.js.map +1 -0
- package/x/physics/2d/intersect2d.d.ts +13 -0
- package/x/physics/2d/intersect2d.js +57 -0
- package/x/physics/2d/intersect2d.js.map +1 -0
- package/x/shapes/2d/circle.d.ts +18 -0
- package/x/shapes/2d/circle.js +34 -0
- package/x/shapes/2d/circle.js.map +1 -0
- package/x/shapes/2d/edge.d.ts +1 -0
- package/x/shapes/2d/edge.js +3 -0
- package/x/shapes/2d/edge.js.map +1 -0
- package/x/shapes/2d/index.d.ts +4 -0
- package/x/shapes/2d/index.js +5 -0
- package/x/shapes/2d/index.js.map +1 -0
- package/x/shapes/2d/pill.d.ts +1 -0
- package/x/shapes/2d/pill.js +3 -0
- package/x/shapes/2d/pill.js.map +1 -0
- package/x/shapes/2d/rect.d.ts +23 -0
- package/x/shapes/2d/rect.js +59 -0
- package/x/shapes/2d/rect.js.map +1 -0
- package/x/shapes/3d/box.d.ts +24 -0
- package/x/shapes/3d/box.js +68 -0
- package/x/shapes/3d/box.js.map +1 -0
- package/x/shapes/3d/capsule.d.ts +1 -0
- package/x/shapes/3d/capsule.js +3 -0
- package/x/shapes/3d/capsule.js.map +1 -0
- package/x/shapes/3d/index.d.ts +4 -0
- package/x/shapes/3d/index.js +5 -0
- package/x/shapes/3d/index.js.map +1 -0
- package/x/shapes/3d/segment.d.ts +13 -0
- package/x/shapes/3d/segment.js +37 -0
- package/x/shapes/3d/segment.js.map +1 -0
- package/x/shapes/3d/sphere.d.ts +1 -0
- package/x/shapes/3d/sphere.js +3 -0
- package/x/shapes/3d/sphere.js.map +1 -0
- package/x/{concepts → tools}/angles.d.ts +3 -13
- package/x/tools/angles.js +41 -0
- package/x/tools/angles.js.map +1 -0
- package/x/{concepts → tools}/circular.js +4 -3
- package/x/tools/circular.js.map +1 -0
- package/x/tools/noise.js.map +1 -0
- package/x/{concepts → tools}/randy.d.ts +0 -8
- package/x/{concepts → tools}/randy.js +0 -10
- package/x/tools/randy.js.map +1 -0
- package/x/{concepts → tools}/scalar.d.ts +4 -4
- package/x/{concepts → tools}/scalar.js +11 -11
- package/x/tools/scalar.js.map +1 -0
- package/x/{concepts → tools}/spline.d.ts +3 -3
- package/x/{concepts → tools}/spline.js +3 -1
- package/x/tools/spline.js.map +1 -0
- package/s/concepts/angles.ts +0 -74
- package/x/concepts/angles.js +0 -62
- package/x/concepts/angles.js.map +0 -1
- package/x/concepts/circular.js.map +0 -1
- package/x/concepts/noise.js.map +0 -1
- package/x/concepts/quat.js.map +0 -1
- package/x/concepts/randy.js.map +0 -1
- package/x/concepts/scalar.js.map +0 -1
- package/x/concepts/spline.js.map +0 -1
- package/x/concepts/vec2.js.map +0 -1
- package/x/concepts/vec3.js.map +0 -1
- package/x/concepts/vec4.js.map +0 -1
- /package/s/{concepts → tools}/noise.ts +0 -0
- /package/x/{concepts → tools}/circular.d.ts +0 -0
- /package/x/{concepts → tools}/noise.d.ts +0 -0
- /package/x/{concepts → tools}/noise.js +0 -0
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
import {Xyzw} from "./quat.js"
|
|
3
|
-
|
|
4
|
-
export type Vec4Array = [number, number, number, number]
|
|
2
|
+
import {Xyzw, XyzwArray} from "./quat.js"
|
|
5
3
|
|
|
6
4
|
export class Vec4 {
|
|
7
5
|
constructor(
|
|
@@ -19,21 +17,24 @@ export class Vec4 {
|
|
|
19
17
|
return new this(0, 0, 0, 0)
|
|
20
18
|
}
|
|
21
19
|
|
|
22
|
-
static
|
|
23
|
-
return
|
|
20
|
+
static from(v: XyzwArray | Xyzw) {
|
|
21
|
+
return Array.isArray(v)
|
|
22
|
+
? new this(...v)
|
|
23
|
+
: new this(v.x, v.y, v.z, v.w)
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
return new this
|
|
26
|
+
clone() {
|
|
27
|
+
return new Vec4(this.x, this.y, this.z, this.w)
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
30
|
+
*[Symbol.iterator]() {
|
|
31
|
+
yield this.x
|
|
32
|
+
yield this.y
|
|
33
|
+
yield this.z
|
|
34
|
+
yield this.w
|
|
34
35
|
}
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
toJSON(): XyzwArray {
|
|
37
38
|
const {x, y, z, w} = this
|
|
38
39
|
return [x, y, z, w]
|
|
39
40
|
}
|
|
@@ -42,10 +43,6 @@ export class Vec4 {
|
|
|
42
43
|
return `(Vec4 x${this.x.toFixed(2)}, y${this.y.toFixed(2)}, z${this.z.toFixed(2)}, w${this.w.toFixed(2)})`
|
|
43
44
|
}
|
|
44
45
|
|
|
45
|
-
clone() {
|
|
46
|
-
return new Vec4(...this.array())
|
|
47
|
-
}
|
|
48
|
-
|
|
49
46
|
set_(x: number, y: number, z: number, w: number) {
|
|
50
47
|
this.x = x
|
|
51
48
|
this.y = y
|
package/s/index.ts
CHANGED
|
@@ -1,12 +1,27 @@
|
|
|
1
1
|
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
|
|
7
|
-
export * from "./
|
|
8
|
-
export *
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
11
|
-
export * from "./
|
|
2
|
+
export * from "./core/quat.js"
|
|
3
|
+
export * from "./core/vec2.js"
|
|
4
|
+
export * from "./core/vec3.js"
|
|
5
|
+
export * from "./core/vec4.js"
|
|
6
|
+
|
|
7
|
+
export * from "./tools/angles.js"
|
|
8
|
+
export * from "./tools/circular.js"
|
|
9
|
+
export * from "./tools/noise.js"
|
|
10
|
+
export * from "./tools/randy.js"
|
|
11
|
+
export * from "./tools/scalar.js"
|
|
12
|
+
export * as spline from "./tools/spline.js"
|
|
13
|
+
|
|
14
|
+
export * from "./optimizers/hash-map.js"
|
|
15
|
+
export * from "./optimizers/hash-set.js"
|
|
16
|
+
export * from "./optimizers/zen.js"
|
|
17
|
+
|
|
18
|
+
export * from "./physics/2d/collide2d.js"
|
|
19
|
+
export * as collide2d from "./physics/2d/collide2d.js"
|
|
20
|
+
export * from "./physics/2d/intersect2d.js"
|
|
21
|
+
export * as intersect2d from "./physics/2d/intersect2d.js"
|
|
22
|
+
|
|
23
|
+
export * from "./shapes/2d/index.js"
|
|
24
|
+
export * as shapes2d from "./shapes/2d/index.js"
|
|
25
|
+
export * from "./shapes/3d/index.js"
|
|
26
|
+
export * as shapes3d from "./shapes/3d/index.js"
|
|
12
27
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
|
|
2
|
+
import {MapG} from "@e280/stz"
|
|
3
|
+
|
|
4
|
+
export class HashMap<Key, Value> {
|
|
5
|
+
#map = new MapG<string, [Key, Value]>
|
|
6
|
+
|
|
7
|
+
constructor(
|
|
8
|
+
private hash: (x: Key) => string,
|
|
9
|
+
entries: [Key, Value][] = [],
|
|
10
|
+
) {
|
|
11
|
+
for (const [key, value] of entries)
|
|
12
|
+
this.set(key, value)
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
get size() {
|
|
16
|
+
return this.#map.size
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
get(key: Key) {
|
|
20
|
+
const result = this.#map.get(this.hash(key))
|
|
21
|
+
if (result)
|
|
22
|
+
return result[1]
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
require(key: Key) {
|
|
26
|
+
return this.#map.require(this.hash(key))[1]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
has(key: Key) {
|
|
30
|
+
return this.#map.has(this.hash(key))
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
set(key: Key, value: Value) {
|
|
34
|
+
this.#map.set(this.hash(key), [key, value])
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
delete(...keys: Key[]) {
|
|
39
|
+
for (const key of keys)
|
|
40
|
+
this.#map.delete(this.hash(key))
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
entries() {
|
|
45
|
+
return this.#map.values()
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
[Symbol.iterator]() {
|
|
49
|
+
return this.entries()
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
*keys() {
|
|
53
|
+
for (const [key] of this.#map.values())
|
|
54
|
+
yield key
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
*values() {
|
|
58
|
+
for (const [,value] of this.#map.values())
|
|
59
|
+
yield value
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
array() {
|
|
63
|
+
return [...this.entries()]
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
guarantee(key: Key, fn: () => Value) {
|
|
67
|
+
const [,value] = this.#map.guarantee(this.hash(key), () => [key, fn()])
|
|
68
|
+
return value
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
|
|
2
|
+
export class HashSet<Value> {
|
|
3
|
+
#map = new Map<string, Value>
|
|
4
|
+
|
|
5
|
+
constructor(
|
|
6
|
+
private hash: (value: Value) => string,
|
|
7
|
+
values: Value[] = [],
|
|
8
|
+
) {
|
|
9
|
+
this.add(...values)
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
get size() {
|
|
13
|
+
return this.#map.size
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
get(value: Value) {
|
|
17
|
+
return this.#map.get(this.hash(value))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
has(value: Value) {
|
|
21
|
+
return this.#map.has(this.hash(value))
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
add(...values: Value[]) {
|
|
25
|
+
for (const value of values) {
|
|
26
|
+
const key = this.hash(value)
|
|
27
|
+
this.#map.set(key, value)
|
|
28
|
+
}
|
|
29
|
+
return this
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
delete(...values: Value[]) {
|
|
33
|
+
for (const value of values)
|
|
34
|
+
this.#map.delete(this.hash(value))
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
array() {
|
|
39
|
+
return [...this.#map.values()]
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
values() {
|
|
43
|
+
return this.#map.values()
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
[Symbol.iterator]() {
|
|
47
|
+
return this.values()
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
|
|
2
|
+
import {MapG} from "@e280/stz"
|
|
3
|
+
import {Vec2} from "../core/vec2.js"
|
|
4
|
+
import {Rect} from "../shapes/2d/rect.js"
|
|
5
|
+
import {rectVsRect} from "../physics/2d/collide2d.js"
|
|
6
|
+
|
|
7
|
+
export class Zen<X> {
|
|
8
|
+
zones = new Set<ZenZone<X>>()
|
|
9
|
+
|
|
10
|
+
constructor(
|
|
11
|
+
public grid: ZenGrid<X>,
|
|
12
|
+
public rect: Rect,
|
|
13
|
+
public item: X,
|
|
14
|
+
) {}
|
|
15
|
+
|
|
16
|
+
update() {
|
|
17
|
+
this.grid.update(this)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
delete() {
|
|
21
|
+
this.grid.delete(this)
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export class ZenZone<X> {
|
|
26
|
+
rect: Rect
|
|
27
|
+
zens = new Set<Zen<X>>()
|
|
28
|
+
|
|
29
|
+
constructor(public hash: string, center: Vec2, size: Vec2) {
|
|
30
|
+
this.rect = Rect.fromCenter(center, size)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class ZenGrid<X> {
|
|
35
|
+
#zones = new MapG<string, ZenZone<X>>()
|
|
36
|
+
|
|
37
|
+
constructor(private zoneExtent: Vec2) {}
|
|
38
|
+
|
|
39
|
+
count() {
|
|
40
|
+
let n = 0
|
|
41
|
+
for (const zone of this.#zones.values())
|
|
42
|
+
n += zone.zens.size
|
|
43
|
+
return n
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
create(rect: Rect, item: X) {
|
|
47
|
+
const zen = new Zen<X>(this, rect, item)
|
|
48
|
+
this.update(zen)
|
|
49
|
+
return zen
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
update(zen: Zen<X>) {
|
|
53
|
+
const wantedZones = this.#selectZones(zen.rect)
|
|
54
|
+
|
|
55
|
+
// delete stale zones
|
|
56
|
+
for (const zone of zen.zones) {
|
|
57
|
+
if (!wantedZones.has(zone)) {
|
|
58
|
+
zen.zones.delete(zone)
|
|
59
|
+
zone.zens.delete(zen)
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// add fresh zones
|
|
64
|
+
for (const zone of wantedZones) {
|
|
65
|
+
if (!zen.zones.has(zone)) {
|
|
66
|
+
zone.zens.add(zen)
|
|
67
|
+
zen.zones.add(zone)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
delete(zen: Zen<X>) {
|
|
73
|
+
const emptyZones: ZenZone<X>[] = []
|
|
74
|
+
|
|
75
|
+
for (const zone of zen.zones) {
|
|
76
|
+
zone.zens.delete(zen)
|
|
77
|
+
if (zone.zens.size === 0)
|
|
78
|
+
emptyZones.push(zone)
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for (const emptyZone of emptyZones)
|
|
82
|
+
this.#zones.delete(emptyZone.hash)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
check(rect: Rect) {
|
|
86
|
+
const zones = this.#selectZones(rect)
|
|
87
|
+
|
|
88
|
+
for (const zone of zones)
|
|
89
|
+
for (const zen of zone.zens)
|
|
90
|
+
if (rectVsRect(rect, zen.rect))
|
|
91
|
+
return true
|
|
92
|
+
|
|
93
|
+
return false
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/** return set of zens that touch the given rect */
|
|
97
|
+
query(rect: Rect) {
|
|
98
|
+
const zones = this.#selectZones(rect)
|
|
99
|
+
const selected = new Set<Zen<X>>()
|
|
100
|
+
|
|
101
|
+
for (const zone of zones)
|
|
102
|
+
for (const zen of zone.zens)
|
|
103
|
+
if (!selected.has(zen) && rectVsRect(rect, zen.rect))
|
|
104
|
+
selected.add(zen)
|
|
105
|
+
|
|
106
|
+
return selected
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/** return all zen items that touch the given rect */
|
|
110
|
+
queryItems(rect: Rect) {
|
|
111
|
+
return [...this.query(rect)].map(zen => zen.item)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/** return all zen rects that touch the given rect */
|
|
115
|
+
queryRects(rect: Rect) {
|
|
116
|
+
return [...this.query(rect)].map(zen => zen.rect)
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#hash(v: Vec2) {
|
|
120
|
+
return `${v.x},${v.y}`
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#calculateZoneCorner(point: Vec2) {
|
|
124
|
+
return new Vec2(
|
|
125
|
+
Math.floor(point.x / this.zoneExtent.x),
|
|
126
|
+
Math.floor(point.y / this.zoneExtent.y),
|
|
127
|
+
).multiply(this.zoneExtent)
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
#obtainZone(zoneCorner: Vec2) {
|
|
131
|
+
const hash = this.#hash(zoneCorner)
|
|
132
|
+
return this.#zones.guarantee(hash, () => new ZenZone(
|
|
133
|
+
hash,
|
|
134
|
+
zoneCorner.clone().add(this.zoneExtent.clone().half()),
|
|
135
|
+
this.zoneExtent,
|
|
136
|
+
))
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
#selectZones(rect: Rect) {
|
|
140
|
+
const zones = new Set<ZenZone<X>>()
|
|
141
|
+
const minZoneCorner = this.#calculateZoneCorner(rect.min)
|
|
142
|
+
const maxZoneCorner = this.#calculateZoneCorner(rect.max)
|
|
143
|
+
|
|
144
|
+
for (let x = minZoneCorner.x; x <= maxZoneCorner.x; x += this.zoneExtent.x)
|
|
145
|
+
for (let y = minZoneCorner.y; y <= maxZoneCorner.y; y += this.zoneExtent.y)
|
|
146
|
+
zones.add(this.#obtainZone(new Vec2(x, y)))
|
|
147
|
+
|
|
148
|
+
return zones
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec2} from "../../core/vec2.js"
|
|
3
|
+
import {Rect} from "../../shapes/2d/rect.js"
|
|
4
|
+
import {Scalar} from "../../tools/scalar.js"
|
|
5
|
+
import {Circle} from "../../shapes/2d/circle.js"
|
|
6
|
+
|
|
7
|
+
export function pointVsRect(point: Vec2, box: Rect) {
|
|
8
|
+
const {min, max} = box
|
|
9
|
+
return (
|
|
10
|
+
point.x >= min.x &&
|
|
11
|
+
point.x <= max.x &&
|
|
12
|
+
point.y >= min.y &&
|
|
13
|
+
point.y <= max.y
|
|
14
|
+
)
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function pointVsCircle(point: Vec2, circle: Circle) {
|
|
18
|
+
const dx = point.x - circle.center.x
|
|
19
|
+
const dy = point.y - circle.center.y
|
|
20
|
+
const distanceSquared = (dx ** 2) + (dy ** 2)
|
|
21
|
+
return distanceSquared <= (circle.radius ** 2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function rectVsRect(a: Rect, b: Rect) {
|
|
25
|
+
return !(
|
|
26
|
+
a.max.x <= b.min.x ||
|
|
27
|
+
a.min.x >= b.max.x ||
|
|
28
|
+
a.max.y <= b.min.y ||
|
|
29
|
+
a.min.y >= b.max.y
|
|
30
|
+
)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function rectVsCircle(rect: Rect, circle: Circle) {
|
|
34
|
+
const clamped = new Vec2(
|
|
35
|
+
Scalar.clamp(circle.center.x, rect.min.x, rect.max.x),
|
|
36
|
+
Scalar.clamp(circle.center.y, rect.min.y, rect.max.y),
|
|
37
|
+
)
|
|
38
|
+
const difference = circle.center.clone().subtract(clamped)
|
|
39
|
+
const distanceSquared = (difference.x ** 2) + (difference.y ** 2)
|
|
40
|
+
const radiusSquared = circle.radius ** 2
|
|
41
|
+
return distanceSquared <= radiusSquared
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function circleVsCircle(circleA: Circle, circleB: Circle) {
|
|
45
|
+
const dx = circleB.center.x - circleA.center.x
|
|
46
|
+
const dy = circleB.center.y - circleA.center.y
|
|
47
|
+
const distanceSquared = (dx ** 2) + (dy ** 2)
|
|
48
|
+
const radiusSum = circleA.radius + circleB.radius
|
|
49
|
+
return distanceSquared <= (radiusSum ** 2)
|
|
50
|
+
}
|
|
51
|
+
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec2} from "../../core/vec2.js"
|
|
3
|
+
import {Rect} from "../../shapes/2d/rect.js"
|
|
4
|
+
import {Scalar} from "../../tools/scalar.js"
|
|
5
|
+
import {Circle} from "../../shapes/2d/circle.js"
|
|
6
|
+
import {rectVsCircle, rectVsRect} from "./collide2d.js"
|
|
7
|
+
|
|
8
|
+
export class Intersection {
|
|
9
|
+
constructor(
|
|
10
|
+
public contactPoint: Vec2,
|
|
11
|
+
public depth: number,
|
|
12
|
+
public normalA: Vec2,
|
|
13
|
+
public normalB: Vec2,
|
|
14
|
+
) {}
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function intersectRectVsRect(a: Rect, b: Rect) {
|
|
18
|
+
if (!rectVsRect(a, b)) return null
|
|
19
|
+
|
|
20
|
+
const overlapX = Math.min(a.max.x - b.min.x, b.max.x - a.min.x)
|
|
21
|
+
const overlapY = Math.min(a.max.y - b.min.y, b.max.y - a.min.y)
|
|
22
|
+
const depth = Math.min(overlapX, overlapY)
|
|
23
|
+
|
|
24
|
+
const aCenter = a.center()
|
|
25
|
+
const bCenter = b.center()
|
|
26
|
+
|
|
27
|
+
const contactPoint = new Vec2(
|
|
28
|
+
Scalar.clamp((aCenter.x + bCenter.x) / 2, b.min.x, b.max.x),
|
|
29
|
+
Scalar.clamp((aCenter.y + bCenter.y) / 2, b.min.y, b.max.y)
|
|
30
|
+
)
|
|
31
|
+
|
|
32
|
+
const normalA = depth === overlapX
|
|
33
|
+
? new Vec2(bCenter.x > aCenter.x ? -1 : 1, 0)
|
|
34
|
+
: new Vec2(0, bCenter.y > aCenter.y ? -1 : 1)
|
|
35
|
+
|
|
36
|
+
const normalB = normalA.clone().multiplyBy(-1)
|
|
37
|
+
|
|
38
|
+
return new Intersection(contactPoint, depth, normalA, normalB)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function intersectRectVsCircle(rect: Rect, circle: Circle) {
|
|
42
|
+
if (!rectVsCircle(rect, circle)) return null
|
|
43
|
+
|
|
44
|
+
const clamped = new Vec2(
|
|
45
|
+
Scalar.clamp(circle.center.x, rect.min.x, rect.max.x),
|
|
46
|
+
Scalar.clamp(circle.center.y, rect.min.y, rect.max.y),
|
|
47
|
+
)
|
|
48
|
+
const difference = circle.center.clone().subtract(clamped)
|
|
49
|
+
const distance = difference.magnitude()
|
|
50
|
+
const depth = circle.radius - distance
|
|
51
|
+
|
|
52
|
+
const contactPoint = clamped
|
|
53
|
+
const normalA = difference.normalize()
|
|
54
|
+
const normalB = normalA.clone().multiplyBy(-1)
|
|
55
|
+
|
|
56
|
+
return new Intersection(contactPoint, depth, normalA, normalB)
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function intersectCircleVsCircle(a: Circle, b: Circle) {
|
|
60
|
+
const dx = b.center.x - a.center.x
|
|
61
|
+
const dy = b.center.y - a.center.y
|
|
62
|
+
const distance = Math.sqrt(dx ** 2 + dy ** 2)
|
|
63
|
+
if (distance >= a.radius + b.radius) return null
|
|
64
|
+
|
|
65
|
+
const depth = Math.max(0, a.radius + b.radius - distance)
|
|
66
|
+
|
|
67
|
+
const normalA = distance === 0
|
|
68
|
+
? new Vec2(1, 0) // fallback for perfectly overlapping circles
|
|
69
|
+
: new Vec2(dx / distance, dy / distance)
|
|
70
|
+
|
|
71
|
+
const normalB = normalA.clone().multiplyBy(-1)
|
|
72
|
+
|
|
73
|
+
const contactPoint = new Vec2(
|
|
74
|
+
(a.center.x + b.center.x) / 2,
|
|
75
|
+
(a.center.y + b.center.y) / 2
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
return new Intersection(contactPoint, depth, normalA, normalB)
|
|
79
|
+
}
|
|
80
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
import {Rect} from "./rect.js"
|
|
3
|
+
import {Vec2, XyArray, Xy} from "../../core/vec2.js"
|
|
4
|
+
|
|
5
|
+
export type CircleJson = [center: XyArray, radius: number]
|
|
6
|
+
export type CircleLike = {center: Xy, radius: number}
|
|
7
|
+
|
|
8
|
+
export class Circle {
|
|
9
|
+
constructor(
|
|
10
|
+
public center: Vec2,
|
|
11
|
+
public radius: number,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
static from(data: CircleJson | CircleLike) {
|
|
15
|
+
return Array.isArray(data)
|
|
16
|
+
? new this(Vec2.from(data[0]), data[1])
|
|
17
|
+
: new this(Vec2.from(data.center), data.radius)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
toJSON(): CircleJson {
|
|
21
|
+
return [this.center.clone().toJSON(), this.radius]
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
clone() {
|
|
25
|
+
return new Circle(this.center.clone(), this.radius)
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
set(circle: CircleLike) {
|
|
29
|
+
this.center.set(circle.center)
|
|
30
|
+
this.radius = circle.radius
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
translate(delta: Vec2) {
|
|
34
|
+
this.center.add(delta)
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
boundingBox() {
|
|
39
|
+
const size = Vec2.all(this.radius * 2)
|
|
40
|
+
return Rect.fromCenter(this.center.clone(), size)
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec2, XyArray, Xy} from "../../core/vec2.js"
|
|
3
|
+
import {pointVsRect} from "../../physics/2d/collide2d.js"
|
|
4
|
+
|
|
5
|
+
export type RectJson = [min: XyArray, max: XyArray]
|
|
6
|
+
export type RectLike = {min: Xy, max: Xy}
|
|
7
|
+
|
|
8
|
+
export class Rect {
|
|
9
|
+
constructor(
|
|
10
|
+
public min: Vec2,
|
|
11
|
+
public max: Vec2,
|
|
12
|
+
) {}
|
|
13
|
+
|
|
14
|
+
static from(data: RectJson | RectLike) {
|
|
15
|
+
return Array.isArray(data)
|
|
16
|
+
? new this(Vec2.from(data[0]), Vec2.from(data[1]))
|
|
17
|
+
: new this(Vec2.from(data.min), Vec2.from(data.max))
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
static fromCorner(min: Vec2, size: Vec2) {
|
|
21
|
+
const max = min.clone().add(size)
|
|
22
|
+
return new this(min, max)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
static fromCenter(center: Vec2, size: Vec2) {
|
|
26
|
+
const halfSize = size.clone().half()
|
|
27
|
+
const min = center.clone().subtract(halfSize)
|
|
28
|
+
const max = center.clone().add(halfSize)
|
|
29
|
+
return new this(min, max)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
clone() {
|
|
33
|
+
return new Rect(this.min.clone(), this.max.clone())
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
toJSON(): RectJson {
|
|
37
|
+
return [this.min.toJSON(), this.max.toJSON()]
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
set(rect: RectLike) {
|
|
41
|
+
this.min.set(rect.min)
|
|
42
|
+
this.max.set(rect.max)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
normalize() {
|
|
46
|
+
const {min, max} = this
|
|
47
|
+
this.min.set(Vec2.min(min, max))
|
|
48
|
+
this.max.set(Vec2.max(min, max))
|
|
49
|
+
return this
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
size() {
|
|
53
|
+
return this.max.clone().subtract(this.min)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
center() {
|
|
57
|
+
return this.min.clone().add(this.size().half())
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
translate(delta: Vec2) {
|
|
61
|
+
this.min.add(delta)
|
|
62
|
+
this.max.add(delta)
|
|
63
|
+
return this
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
contains(point: Vec2) {
|
|
67
|
+
return pointVsRect(point, this)
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
boundingBox() {
|
|
71
|
+
return this.clone()
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|