@benev/math 0.1.0 → 0.2.0-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/package.json +2 -1
- package/s/index.ts +26 -10
- package/s/optimizers/hash-map.ts +67 -0
- package/s/optimizers/hash-set.ts +46 -0
- package/s/optimizers/zen.ts +150 -0
- package/s/physics/2d/collide2d.ts +51 -0
- package/s/physics/2d/intersect2d.ts +77 -0
- package/s/shapes/2d/circle.ts +25 -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 +45 -0
- package/s/shapes/3d/box.ts +39 -0
- package/s/shapes/3d/capsule.ts +3 -0
- package/s/shapes/3d/index.ts +6 -0
- package/s/shapes/3d/segment.ts +46 -0
- package/s/shapes/3d/sphere.ts +3 -0
- package/s/utils/angles.ts +43 -0
- package/s/{concepts → utils}/randy.ts +0 -14
- package/s/{concepts → utils}/spline.ts +2 -2
- 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 +16 -0
- package/x/optimizers/hash-map.js +52 -0
- package/x/optimizers/hash-map.js.map +1 -0
- package/x/optimizers/hash-set.d.ts +12 -0
- package/x/optimizers/hash-set.js +36 -0
- package/x/optimizers/hash-set.js.map +1 -0
- package/x/optimizers/zen.d.ts +32 -0
- package/x/optimizers/zen.js +120 -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 +55 -0
- package/x/physics/2d/intersect2d.js.map +1 -0
- package/x/primitives/circular.js.map +1 -0
- package/x/primitives/quat.js.map +1 -0
- package/x/primitives/scalar.js.map +1 -0
- package/x/primitives/vec2.js.map +1 -0
- package/x/primitives/vec3.js.map +1 -0
- package/x/primitives/vec4.js.map +1 -0
- package/x/shapes/2d/circle.d.ts +10 -0
- package/x/shapes/2d/circle.js +22 -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 +13 -0
- package/x/shapes/2d/rect.js +36 -0
- package/x/shapes/2d/rect.js.map +1 -0
- package/x/shapes/3d/box.d.ts +11 -0
- package/x/shapes/3d/box.js +29 -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 +35 -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 → utils}/angles.d.ts +1 -13
- package/x/utils/angles.js +35 -0
- package/x/utils/angles.js.map +1 -0
- package/x/utils/noise.js.map +1 -0
- package/x/{concepts → utils}/randy.d.ts +0 -8
- package/x/{concepts → utils}/randy.js +0 -10
- package/x/utils/randy.js.map +1 -0
- package/x/{concepts → utils}/spline.d.ts +1 -1
- package/x/{concepts → utils}/spline.js +1 -1
- package/x/utils/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 → primitives}/circular.ts +0 -0
- /package/s/{concepts → primitives}/quat.ts +0 -0
- /package/s/{concepts → primitives}/scalar.ts +0 -0
- /package/s/{concepts → primitives}/vec2.ts +0 -0
- /package/s/{concepts → primitives}/vec3.ts +0 -0
- /package/s/{concepts → primitives}/vec4.ts +0 -0
- /package/s/{concepts → utils}/noise.ts +0 -0
- /package/x/{concepts → primitives}/circular.d.ts +0 -0
- /package/x/{concepts → primitives}/circular.js +0 -0
- /package/x/{concepts → primitives}/quat.d.ts +0 -0
- /package/x/{concepts → primitives}/quat.js +0 -0
- /package/x/{concepts → primitives}/scalar.d.ts +0 -0
- /package/x/{concepts → primitives}/scalar.js +0 -0
- /package/x/{concepts → primitives}/vec2.d.ts +0 -0
- /package/x/{concepts → primitives}/vec2.js +0 -0
- /package/x/{concepts → primitives}/vec3.d.ts +0 -0
- /package/x/{concepts → primitives}/vec3.js +0 -0
- /package/x/{concepts → primitives}/vec4.d.ts +0 -0
- /package/x/{concepts → primitives}/vec4.js +0 -0
- /package/x/{concepts → utils}/noise.d.ts +0 -0
- /package/x/{concepts → utils}/noise.js +0 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@benev/math",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-0",
|
|
4
4
|
"description": "game math toolkit",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Chase Moskal <chasemoskal@gmail.com>",
|
|
@@ -21,6 +21,7 @@
|
|
|
21
21
|
"count": "find s -path '*/_archive' -prune -o -name '*.ts' -exec wc -l {} +"
|
|
22
22
|
},
|
|
23
23
|
"dependencies": {
|
|
24
|
+
"@e280/stz": "^0.1.0",
|
|
24
25
|
"simplex-noise": "^4.0.3"
|
|
25
26
|
},
|
|
26
27
|
"devDependencies": {
|
package/s/index.ts
CHANGED
|
@@ -1,12 +1,28 @@
|
|
|
1
1
|
|
|
2
|
-
export * from "./
|
|
3
|
-
export * from "./
|
|
4
|
-
export * from "./
|
|
5
|
-
export * from "./
|
|
6
|
-
export * from "./
|
|
7
|
-
export * from "./
|
|
8
|
-
|
|
9
|
-
export * from "./
|
|
10
|
-
export * from "./
|
|
11
|
-
export * from "./
|
|
2
|
+
export * from "./primitives/circular.js"
|
|
3
|
+
export * from "./primitives/quat.js"
|
|
4
|
+
export * from "./primitives/scalar.js"
|
|
5
|
+
export * from "./primitives/vec2.js"
|
|
6
|
+
export * from "./primitives/vec3.js"
|
|
7
|
+
export * from "./primitives/vec4.js"
|
|
8
|
+
|
|
9
|
+
export * from "./optimizers/hash-map.js"
|
|
10
|
+
export * from "./optimizers/hash-set.js"
|
|
11
|
+
export * from "./optimizers/zen.js"
|
|
12
|
+
|
|
13
|
+
export * from "./physics/2d/collide2d.js"
|
|
14
|
+
export * as collide2d from "./physics/2d/collide2d.js"
|
|
15
|
+
export * from "./physics/2d/intersect2d.js"
|
|
16
|
+
export * as intersect2d from "./physics/2d/intersect2d.js"
|
|
17
|
+
|
|
18
|
+
export * from "./shapes/2d/index.js"
|
|
19
|
+
export * as shapes2d from "./shapes/2d/index.js"
|
|
20
|
+
|
|
21
|
+
export * from "./shapes/3d/index.js"
|
|
22
|
+
export * as shapes3d from "./shapes/3d/index.js"
|
|
23
|
+
|
|
24
|
+
export * from "./utils/angles.js"
|
|
25
|
+
export * from "./utils/noise.js"
|
|
26
|
+
export * from "./utils/randy.js"
|
|
27
|
+
export * as spline from "./utils/spline.js"
|
|
12
28
|
|
|
@@ -0,0 +1,67 @@
|
|
|
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
|
+
*keys() {
|
|
49
|
+
for (const [key] of this.#map.values())
|
|
50
|
+
yield key
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
*values() {
|
|
54
|
+
for (const [,value] of this.#map.values())
|
|
55
|
+
yield value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
array() {
|
|
59
|
+
return [...this.entries()]
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
guarantee(key: Key, fn: () => Value) {
|
|
63
|
+
const [,value] = this.#map.guarantee(this.hash(key), () => [key, fn()])
|
|
64
|
+
return value
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
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
|
+
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
|
|
2
|
+
import {MapG} from "@e280/stz"
|
|
3
|
+
import {Rect} from "../shapes/2d/rect.js"
|
|
4
|
+
import {Vec2} from "../primitives/vec2.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 box: 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> extends Rect {
|
|
26
|
+
zens = new Set<Zen<X>>()
|
|
27
|
+
|
|
28
|
+
constructor(public hash: string, center: Vec2, extent: Vec2) {
|
|
29
|
+
super(center, extent)
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export class ZenGrid<X> {
|
|
34
|
+
#zones = new MapG<string, ZenZone<X>>()
|
|
35
|
+
|
|
36
|
+
constructor(private zoneExtent: Vec2) {}
|
|
37
|
+
|
|
38
|
+
count() {
|
|
39
|
+
let n = 0
|
|
40
|
+
for (const zone of this.#zones.values())
|
|
41
|
+
n += zone.zens.size
|
|
42
|
+
return n
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
create(box: Rect, item: X) {
|
|
46
|
+
const zen = new Zen<X>(this, box, item)
|
|
47
|
+
this.update(zen)
|
|
48
|
+
return zen
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
update(zen: Zen<X>) {
|
|
52
|
+
const wantedZones = this.#selectZones(zen.box)
|
|
53
|
+
|
|
54
|
+
// delete stale zones
|
|
55
|
+
for (const zone of zen.zones) {
|
|
56
|
+
if (!wantedZones.has(zone)) {
|
|
57
|
+
zen.zones.delete(zone)
|
|
58
|
+
zone.zens.delete(zen)
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// add fresh zones
|
|
63
|
+
for (const zone of wantedZones) {
|
|
64
|
+
if (!zen.zones.has(zone)) {
|
|
65
|
+
zone.zens.add(zen)
|
|
66
|
+
zen.zones.add(zone)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
delete(zen: Zen<X>) {
|
|
72
|
+
const emptyZones: ZenZone<X>[] = []
|
|
73
|
+
|
|
74
|
+
for (const zone of zen.zones) {
|
|
75
|
+
zone.zens.delete(zen)
|
|
76
|
+
if (zone.zens.size === 0)
|
|
77
|
+
emptyZones.push(zone)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
for (const emptyZone of emptyZones)
|
|
81
|
+
this.#zones.delete(emptyZone.hash)
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
check(box: Rect) {
|
|
85
|
+
const zones = this.#selectZones(box)
|
|
86
|
+
|
|
87
|
+
for (const zone of zones)
|
|
88
|
+
for (const zen of zone.zens)
|
|
89
|
+
if (rectVsRect(box, zen.box))
|
|
90
|
+
return true
|
|
91
|
+
|
|
92
|
+
return false
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** return all zens that touch the given box */
|
|
96
|
+
query(box: Rect) {
|
|
97
|
+
const zones = this.#selectZones(box)
|
|
98
|
+
const selected: Zen<X>[] = []
|
|
99
|
+
|
|
100
|
+
for (const zone of zones)
|
|
101
|
+
for (const zen of zone.zens)
|
|
102
|
+
if (rectVsRect(box, zen.box) && !selected.includes(zen))
|
|
103
|
+
selected.push(zen)
|
|
104
|
+
|
|
105
|
+
return selected
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/** return all zen items that touch the given box */
|
|
109
|
+
queryItems(box: Rect) {
|
|
110
|
+
return this.query(box).map(zen => zen.item)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** return all zen boxes that touch the given box */
|
|
114
|
+
queryBoxes(box: Rect) {
|
|
115
|
+
return this.query(box).map(zen => zen.box)
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
#hash(v: Vec2) {
|
|
119
|
+
return `${v.x},${v.y}`
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
#calculateZoneCorner(point: Vec2) {
|
|
123
|
+
return new Vec2(
|
|
124
|
+
Math.floor(point.x / this.zoneExtent.x),
|
|
125
|
+
Math.floor(point.y / this.zoneExtent.y),
|
|
126
|
+
).multiply(this.zoneExtent)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
#obtainZone(zoneCorner: Vec2) {
|
|
130
|
+
const hash = this.#hash(zoneCorner)
|
|
131
|
+
return this.#zones.guarantee(hash, () => new ZenZone(
|
|
132
|
+
hash,
|
|
133
|
+
zoneCorner.clone().add(this.zoneExtent.clone().half()),
|
|
134
|
+
this.zoneExtent,
|
|
135
|
+
))
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
#selectZones(box: Rect) {
|
|
139
|
+
const zones = new Set<ZenZone<X>>()
|
|
140
|
+
const minZoneCorner = this.#calculateZoneCorner(box.min)
|
|
141
|
+
const maxZoneCorner = this.#calculateZoneCorner(box.max)
|
|
142
|
+
|
|
143
|
+
for (let x = minZoneCorner.x; x <= maxZoneCorner.x; x += this.zoneExtent.x)
|
|
144
|
+
for (let y = minZoneCorner.y; y <= maxZoneCorner.y; y += this.zoneExtent.y)
|
|
145
|
+
zones.add(this.#obtainZone(new Vec2(x, y)))
|
|
146
|
+
|
|
147
|
+
return zones
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec2} from "../../primitives/vec2.js"
|
|
3
|
+
import {Rect} from "../../shapes/2d/rect.js"
|
|
4
|
+
import {Scalar} from "../../primitives/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,77 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec2} from "../../primitives/vec2.js"
|
|
3
|
+
import {Rect} from "../../shapes/2d/rect.js"
|
|
4
|
+
import {Scalar} from "../../primitives/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 contactPoint = new Vec2(
|
|
25
|
+
Scalar.clamp((a.center.x + b.center.x) / 2, b.min.x, b.max.x),
|
|
26
|
+
Scalar.clamp((a.center.y + b.center.y) / 2, b.min.y, b.max.y)
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
const normalA = depth === overlapX
|
|
30
|
+
? new Vec2(b.center.x > a.center.x ? -1 : 1, 0)
|
|
31
|
+
: new Vec2(0, b.center.y > a.center.y ? -1 : 1)
|
|
32
|
+
|
|
33
|
+
const normalB = normalA.clone().multiplyBy(-1)
|
|
34
|
+
|
|
35
|
+
return new Intersection(contactPoint, depth, normalA, normalB)
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function intersectRectVsCircle(rect: Rect, circle: Circle) {
|
|
39
|
+
if (!rectVsCircle(rect, circle)) return null
|
|
40
|
+
|
|
41
|
+
const clamped = new Vec2(
|
|
42
|
+
Scalar.clamp(circle.center.x, rect.min.x, rect.max.x),
|
|
43
|
+
Scalar.clamp(circle.center.y, rect.min.y, rect.max.y),
|
|
44
|
+
)
|
|
45
|
+
const difference = circle.center.clone().subtract(clamped)
|
|
46
|
+
const distance = difference.magnitude()
|
|
47
|
+
const depth = circle.radius - distance
|
|
48
|
+
|
|
49
|
+
const contactPoint = clamped
|
|
50
|
+
const normalA = difference.normalize()
|
|
51
|
+
const normalB = normalA.clone().multiplyBy(-1)
|
|
52
|
+
|
|
53
|
+
return new Intersection(contactPoint, depth, normalA, normalB)
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function intersectCircleVsCircle(a: Circle, b: Circle) {
|
|
57
|
+
const dx = b.center.x - a.center.x
|
|
58
|
+
const dy = b.center.y - a.center.y
|
|
59
|
+
const distance = Math.sqrt(dx ** 2 + dy ** 2)
|
|
60
|
+
if (distance >= a.radius + b.radius) return null
|
|
61
|
+
|
|
62
|
+
const depth = Math.max(0, a.radius + b.radius - distance)
|
|
63
|
+
|
|
64
|
+
const normalA = distance === 0
|
|
65
|
+
? new Vec2(1, 0) // fallback for perfectly overlapping circles
|
|
66
|
+
: new Vec2(dx / distance, dy / distance)
|
|
67
|
+
|
|
68
|
+
const normalB = normalA.clone().multiplyBy(-1)
|
|
69
|
+
|
|
70
|
+
const contactPoint = new Vec2(
|
|
71
|
+
(a.center.x + b.center.x) / 2,
|
|
72
|
+
(a.center.y + b.center.y) / 2
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
return new Intersection(contactPoint, depth, normalA, normalB)
|
|
76
|
+
}
|
|
77
|
+
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
|
|
2
|
+
import {Rect} from "./rect.js"
|
|
3
|
+
import {Vec2} from "../../primitives/vec2.js"
|
|
4
|
+
|
|
5
|
+
export class Circle {
|
|
6
|
+
constructor(
|
|
7
|
+
public center: Vec2,
|
|
8
|
+
public radius: number,
|
|
9
|
+
) {}
|
|
10
|
+
|
|
11
|
+
offset(delta: Vec2) {
|
|
12
|
+
this.center.add(delta)
|
|
13
|
+
return this
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
boundingBox() {
|
|
17
|
+
const extent = Vec2.all(this.radius * 2)
|
|
18
|
+
return new Rect(this.center, extent)
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
clone() {
|
|
22
|
+
return new Circle(this.center.clone(), this.radius)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec2} from "../../primitives/vec2.js"
|
|
3
|
+
import {pointVsRect} from "../../physics/2d/collide2d.js"
|
|
4
|
+
|
|
5
|
+
export class Rect {
|
|
6
|
+
constructor(
|
|
7
|
+
public center: Vec2,
|
|
8
|
+
public extent: Vec2,
|
|
9
|
+
) {
|
|
10
|
+
if (extent.x < 0 || extent.y < 0)
|
|
11
|
+
throw new Error(`invalid negative extent, ${extent.toString()}`)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
static fromCorner(min: Vec2, extent: Vec2) {
|
|
15
|
+
return new this(min.add(extent.clone().half()), extent)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get min() {
|
|
19
|
+
return this.center.clone()
|
|
20
|
+
.subtract(this.extent.clone().half())
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get max() {
|
|
24
|
+
return this.center.clone()
|
|
25
|
+
.add(this.extent.clone().half())
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
offset(delta: Vec2) {
|
|
29
|
+
this.center.add(delta)
|
|
30
|
+
return this
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
boundingBox() {
|
|
34
|
+
return this
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
contains(point: Vec2) {
|
|
38
|
+
return pointVsRect(point, this)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
clone() {
|
|
42
|
+
return new Rect(this.center.clone(), this.extent.clone())
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec3} from "../../primitives/vec3.js"
|
|
3
|
+
|
|
4
|
+
export class Box {
|
|
5
|
+
constructor(
|
|
6
|
+
public center: Vec3,
|
|
7
|
+
public extent: Vec3,
|
|
8
|
+
) {
|
|
9
|
+
if (extent.x < 0 || extent.y < 0 || extent.z < 0)
|
|
10
|
+
throw new Error(`invalid negative extent, ${extent.toString()}`)
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
static fromCorner(min: Vec3, extent: Vec3) {
|
|
14
|
+
return new this(
|
|
15
|
+
min.clone().add(extent.clone().half()),
|
|
16
|
+
extent,
|
|
17
|
+
)
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
get min() {
|
|
21
|
+
return this.center.clone()
|
|
22
|
+
.subtract(this.extent.clone().half())
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
get max() {
|
|
26
|
+
return this.center.clone()
|
|
27
|
+
.add(this.extent.clone().half())
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
clone() {
|
|
31
|
+
return new Box(this.center.clone(), this.extent.clone())
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
grow(increase: number) {
|
|
35
|
+
this.extent.addBy(increase)
|
|
36
|
+
return this
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
|
|
2
|
+
import {Vec3} from "../../primitives/vec3.js"
|
|
3
|
+
|
|
4
|
+
export class Segment {
|
|
5
|
+
constructor(
|
|
6
|
+
public start: Vec3,
|
|
7
|
+
public end: Vec3,
|
|
8
|
+
) {}
|
|
9
|
+
|
|
10
|
+
get vector() {
|
|
11
|
+
return this.end.clone().subtract(this.start)
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
get length() {
|
|
15
|
+
return this.start.distance(this.end)
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
get center() {
|
|
19
|
+
return this.start.clone()
|
|
20
|
+
.add(this.end)
|
|
21
|
+
.divideBy(2)
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
clone() {
|
|
25
|
+
return new Segment(
|
|
26
|
+
this.start.clone(),
|
|
27
|
+
this.end.clone(),
|
|
28
|
+
)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
fromStart(length: number) {
|
|
32
|
+
const direction = this.vector.normalize()
|
|
33
|
+
return this.start.clone().add(direction.multiplyBy(length))
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
point(fraction: number) {
|
|
37
|
+
return this.start.clone().add(this.vector.multiplyBy(fraction))
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
scale(fraction: number) {
|
|
41
|
+
const newHalfVector = this.vector.multiplyBy(fraction / 2)
|
|
42
|
+
this.start.set(this.center.subtract(newHalfVector))
|
|
43
|
+
this.end.set(this.center.add(newHalfVector))
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
|
|
2
|
+
import {Scalar} from "../primitives/scalar.js"
|
|
3
|
+
|
|
4
|
+
const pi = Math.PI
|
|
5
|
+
|
|
6
|
+
export const Radians = {
|
|
7
|
+
circle: 2 * pi,
|
|
8
|
+
halfcircle: pi,
|
|
9
|
+
|
|
10
|
+
toDegrees(r: number) {
|
|
11
|
+
return r * (180 / pi)
|
|
12
|
+
},
|
|
13
|
+
toArcseconds(r: number) {
|
|
14
|
+
return Radians.toDegrees(r) * 3600
|
|
15
|
+
},
|
|
16
|
+
toTurns(r: number) {
|
|
17
|
+
return r / Radians.circle
|
|
18
|
+
},
|
|
19
|
+
|
|
20
|
+
circleDistance(radiansA: number, radiansB: number): number {
|
|
21
|
+
const diff = Math.abs(Scalar.wrap(radiansA - radiansB, 0, Radians.circle))
|
|
22
|
+
return Math.min(diff, Radians.circle - diff)
|
|
23
|
+
},
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export const Turns = {
|
|
27
|
+
toRadians(t: number) {
|
|
28
|
+
return t * Radians.circle
|
|
29
|
+
},
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const Arcseconds = {
|
|
33
|
+
toRadians(a: number) {
|
|
34
|
+
return Degrees.toRadians(a / 3600)
|
|
35
|
+
},
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export const Degrees = {
|
|
39
|
+
toRadians(d: number) {
|
|
40
|
+
return d * (pi / 180)
|
|
41
|
+
},
|
|
42
|
+
}
|
|
43
|
+
|