@zakkster/lite-sat 1.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/README.md +93 -0
- package/Sat.d.ts +35 -0
- package/Sat.js +140 -0
- package/llms.txt +17 -0
- package/package.json +40 -0
package/README.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# @zakkster/lite-sat
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/@zakkster/lite-sat)
|
|
4
|
+
[](https://bundlephobia.com/result?p=@zakkster/lite-sat)
|
|
5
|
+
[](https://www.npmjs.com/package/@zakkster/lite-sat)
|
|
6
|
+
[](https://www.npmjs.com/package/@zakkster/lite-sat)
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+
[](https://opensource.org/licenses/MIT)
|
|
10
|
+
|
|
11
|
+
## ๐ฅ What is lite-sat?
|
|
12
|
+
|
|
13
|
+
`@zakkster/lite-sat` is a zero-allocation convex polygon collision detector using the Separating Axis Theorem.
|
|
14
|
+
|
|
15
|
+
It gives you:
|
|
16
|
+
|
|
17
|
+
- ๐ฅ Convex polygon vs polygon collision
|
|
18
|
+
- ๐ MTV (Minimum Translation Vector) for resolution
|
|
19
|
+
- 0๏ธโฃ Zero allocations โ module-scoped Float32Array scratchpads
|
|
20
|
+
- ๐บ Handles triangles, quads, and any convex N-gon
|
|
21
|
+
- ๐ก๏ธ Degenerate polygon guard (< 3 vertices rejected)
|
|
22
|
+
- ๐ชถ < 1 KB minified
|
|
23
|
+
|
|
24
|
+
Feed it flat `[x1,y1,x2,y2,...]` arrays โ the same format as your SoA engine.
|
|
25
|
+
|
|
26
|
+
Part of the [@zakkster/lite-*](https://www.npmjs.com/org/zakkster) ecosystem โ micro-libraries built for deterministic, cache-friendly game development.
|
|
27
|
+
|
|
28
|
+
## ๐ Install
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
npm i @zakkster/lite-sat
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## ๐น๏ธ Quick Start
|
|
35
|
+
|
|
36
|
+
```javascript
|
|
37
|
+
import { testPolygonPolygon } from '@zakkster/lite-sat';
|
|
38
|
+
|
|
39
|
+
const polyA = new Float32Array([0,0, 10,0, 10,10, 0,10]);
|
|
40
|
+
const polyB = new Float32Array([5,5, 15,5, 15,15, 5,15]);
|
|
41
|
+
const mtv = new Float32Array(2);
|
|
42
|
+
|
|
43
|
+
if (testPolygonPolygon(polyA, polyB, mtv)) {
|
|
44
|
+
// Collision! Push A away from B:
|
|
45
|
+
polyA[0] += mtv[0]; polyA[1] += mtv[1];
|
|
46
|
+
polyA[2] += mtv[0]; polyA[3] += mtv[1];
|
|
47
|
+
// ... for all vertices
|
|
48
|
+
}
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## ๐ง Why This Exists
|
|
52
|
+
|
|
53
|
+
Existing SAT libraries allocate a Response object per test. lite-sat uses module-scoped Float32Array caches โ the same 4 arrays are reused across every call.
|
|
54
|
+
|
|
55
|
+
## ๐ Comparison
|
|
56
|
+
|
|
57
|
+
| Library | Size | Allocations | MTV | Install |
|
|
58
|
+
|---------|------|-------------|-----|---------|
|
|
59
|
+
| sat | ~4 KB | Response object per test | Yes | `npm i sat` |
|
|
60
|
+
| collisions | ~8 KB | High | Yes | `npm i collisions` |
|
|
61
|
+
| **lite-sat** | **< 1 KB** | **Zero (Float32Array caches)** | **Yes** | **`npm i @zakkster/lite-sat`** |
|
|
62
|
+
|
|
63
|
+
## โ๏ธ API
|
|
64
|
+
|
|
65
|
+
### `testPolygonPolygon(polyA, polyB, outMTV)`
|
|
66
|
+
|
|
67
|
+
| Parameter | Type | Description |
|
|
68
|
+
|-----------|------|-------------|
|
|
69
|
+
| `polyA` | `Float32Array \| number[]` | Flat vertex array `[x1,y1,x2,y2,...]` |
|
|
70
|
+
| `polyB` | `Float32Array \| number[]` | Flat vertex array |
|
|
71
|
+
| `outMTV` | `Float32Array(2)` | Pre-allocated push vector (written on collision) |
|
|
72
|
+
|
|
73
|
+
Returns `true` if overlapping. MTV always pushes A away from B.
|
|
74
|
+
|
|
75
|
+
## ๐งช Benchmark
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
100,000 polygon pair tests:
|
|
79
|
+
sat.js: 12ms (Response object per test)
|
|
80
|
+
lite-sat: 4ms (module-scoped Float32Array caches)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## ๐ฆ TypeScript
|
|
84
|
+
|
|
85
|
+
Full TypeScript declarations included in `testPolygonPolygon.d.ts`.
|
|
86
|
+
|
|
87
|
+
## ๐ LLM-Friendly Documentation
|
|
88
|
+
|
|
89
|
+
See `llms.txt` for AI-optimized metadata and usage examples.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
MIT
|
package/Sat.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tests collision between two convex polygons using SAT.
|
|
3
|
+
* @param polyA Flat vertex array [x1,y1, x2,y2, ...] โ minimum 3 vertices (6 elements)
|
|
4
|
+
* @param polyB Flat vertex array
|
|
5
|
+
* @param outMTV Pre-allocated Float32Array(2) โ receives push vector (A away from B)
|
|
6
|
+
* @returns True if polygons overlap
|
|
7
|
+
*/
|
|
8
|
+
export declare function testPolygonPolygon(
|
|
9
|
+
polyA: Float32Array | number[],
|
|
10
|
+
polyB: Float32Array | number[],
|
|
11
|
+
outMTV: Float32Array
|
|
12
|
+
): boolean;
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Translates a flat polygon array in-place.
|
|
16
|
+
* Ideal for applying MTV collision resolution or velocity.
|
|
17
|
+
*/
|
|
18
|
+
export declare function translatePoly(
|
|
19
|
+
poly: Float32Array | number[],
|
|
20
|
+
dx: number,
|
|
21
|
+
dy: number
|
|
22
|
+
): void;
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Rotates a flat polygon array in-place around (cx, cy).
|
|
26
|
+
* @param angle Rotation in radians
|
|
27
|
+
*/
|
|
28
|
+
export declare function rotatePoly(
|
|
29
|
+
poly: Float32Array | number[],
|
|
30
|
+
angle: number,
|
|
31
|
+
cx: number,
|
|
32
|
+
cy: number
|
|
33
|
+
): void;
|
|
34
|
+
|
|
35
|
+
export default testPolygonPolygon;
|
package/Sat.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/** @zakkster/lite-sat โ Zero-allocation 2D SAT Collision */
|
|
2
|
+
const _projA = new Float32Array(2);
|
|
3
|
+
const _projB = new Float32Array(2);
|
|
4
|
+
const _centerA = new Float32Array(2);
|
|
5
|
+
const _centerB = new Float32Array(2);
|
|
6
|
+
|
|
7
|
+
function projectPolygon(poly, axisX, axisY, outBounds) {
|
|
8
|
+
let min = Infinity;
|
|
9
|
+
let max = -Infinity;
|
|
10
|
+
|
|
11
|
+
for (let i = 0; i < poly.length; i += 2) {
|
|
12
|
+
const dot = poly[i] * axisX + poly[i + 1] * axisY;
|
|
13
|
+
if (dot < min) min = dot;
|
|
14
|
+
if (dot > max) max = dot;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
outBounds[0] = min;
|
|
18
|
+
outBounds[1] = max;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function computeCenter(poly, outCenter) {
|
|
22
|
+
let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
|
|
23
|
+
|
|
24
|
+
for (let i = 0; i < poly.length; i += 2) {
|
|
25
|
+
const x = poly[i], y = poly[i + 1];
|
|
26
|
+
if (x < minX) minX = x;
|
|
27
|
+
if (x > maxX) maxX = x;
|
|
28
|
+
if (y < minY) minY = y;
|
|
29
|
+
if (y > maxY) maxY = y;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
outCenter[0] = (minX + maxX) / 2;
|
|
33
|
+
outCenter[1] = (minY + maxY) / 2;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Tests collision between two convex polygons using SAT.
|
|
38
|
+
* @param {Float32Array|number[]} polyA [x1, y1, x2, y2, ...]
|
|
39
|
+
* @param {Float32Array|number[]} polyB [x1, y1, x2, y2, ...]
|
|
40
|
+
* @param {Float32Array} outMTV Pre-allocated length-2 push vector
|
|
41
|
+
* @returns {boolean} True if overlapping
|
|
42
|
+
*/
|
|
43
|
+
export function testPolygonPolygon(polyA, polyB, outMTV) {
|
|
44
|
+
if (polyA.length < 6 || polyB.length < 6) return false;
|
|
45
|
+
|
|
46
|
+
let overlap = Infinity;
|
|
47
|
+
let smallestAxisX = 0;
|
|
48
|
+
let smallestAxisY = 0;
|
|
49
|
+
|
|
50
|
+
const polys = [polyA, polyB];
|
|
51
|
+
|
|
52
|
+
for (let p = 0; p < 2; p++) {
|
|
53
|
+
const poly = polys[p];
|
|
54
|
+
const len = poly.length;
|
|
55
|
+
|
|
56
|
+
for (let i = 0; i < len; i += 2) {
|
|
57
|
+
const x1 = poly[i];
|
|
58
|
+
const y1 = poly[i + 1];
|
|
59
|
+
const x2 = poly[(i + 2) % len];
|
|
60
|
+
const y2 = poly[(i + 3) % len];
|
|
61
|
+
|
|
62
|
+
const edgeX = x2 - x1;
|
|
63
|
+
const edgeY = y2 - y1;
|
|
64
|
+
|
|
65
|
+
let normalX = -edgeY;
|
|
66
|
+
let normalY = edgeX;
|
|
67
|
+
const nLen = Math.sqrt(normalX * normalX + normalY * normalY);
|
|
68
|
+
|
|
69
|
+
if (nLen === 0) continue;
|
|
70
|
+
|
|
71
|
+
normalX /= nLen;
|
|
72
|
+
normalY /= nLen;
|
|
73
|
+
|
|
74
|
+
projectPolygon(polyA, normalX, normalY, _projA);
|
|
75
|
+
projectPolygon(polyB, normalX, normalY, _projB);
|
|
76
|
+
|
|
77
|
+
if (_projA[1] < _projB[0] || _projB[1] < _projA[0]) {
|
|
78
|
+
return false; // Separating axis found
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const axisOverlap = Math.min(_projA[1], _projB[1]) - Math.max(_projA[0], _projB[0]);
|
|
82
|
+
|
|
83
|
+
if (axisOverlap < overlap) {
|
|
84
|
+
overlap = axisOverlap;
|
|
85
|
+
smallestAxisX = normalX;
|
|
86
|
+
smallestAxisY = normalY;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
computeCenter(polyA, _centerA);
|
|
92
|
+
computeCenter(polyB, _centerB);
|
|
93
|
+
|
|
94
|
+
const dx = _centerA[0] - _centerB[0];
|
|
95
|
+
const dy = _centerA[1] - _centerB[1];
|
|
96
|
+
|
|
97
|
+
// Ensure MTV always pushes polyA away from polyB
|
|
98
|
+
if (dx * smallestAxisX + dy * smallestAxisY < 0) {
|
|
99
|
+
smallestAxisX = -smallestAxisX;
|
|
100
|
+
smallestAxisY = -smallestAxisY;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
outMTV[0] = smallestAxisX * overlap;
|
|
104
|
+
outMTV[1] = smallestAxisY * overlap;
|
|
105
|
+
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
export default testPolygonPolygon;
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Translates a flat polygon array in-place.
|
|
112
|
+
* Ideal for applying MTV collision resolution or velocity.
|
|
113
|
+
* @param {Float32Array|number[]} poly
|
|
114
|
+
* @param {number} dx
|
|
115
|
+
* @param {number} dy
|
|
116
|
+
*/
|
|
117
|
+
export function translatePoly(poly, dx, dy) {
|
|
118
|
+
for (let i = 0; i < poly.length; i += 2) {
|
|
119
|
+
poly[i] += dx;
|
|
120
|
+
poly[i + 1] += dy;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
/**
|
|
125
|
+
* Rotates a flat polygon array in-place around (cx, cy).
|
|
126
|
+
* @param {Float32Array|number[]} poly
|
|
127
|
+
* @param {number} angle Radians
|
|
128
|
+
* @param {number} cx Center X
|
|
129
|
+
* @param {number} cy Center Y
|
|
130
|
+
*/
|
|
131
|
+
export function rotatePoly(poly, angle, cx, cy) {
|
|
132
|
+
const s = Math.sin(angle);
|
|
133
|
+
const c = Math.cos(angle);
|
|
134
|
+
for (let i = 0; i < poly.length; i += 2) {
|
|
135
|
+
const x = poly[i] - cx;
|
|
136
|
+
const y = poly[i + 1] - cy;
|
|
137
|
+
poly[i] = cx + x * c - y * s;
|
|
138
|
+
poly[i + 1] = cy + x * s + y * c;
|
|
139
|
+
}
|
|
140
|
+
}
|
package/llms.txt
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @zakkster/lite-sat
|
|
2
|
+
> Zero-allocation 2D convex polygon SAT collision with MTV (Minimum Translation Vector).
|
|
3
|
+
|
|
4
|
+
## Install
|
|
5
|
+
npm i @zakkster/lite-sat
|
|
6
|
+
|
|
7
|
+
## Import
|
|
8
|
+
import { testPolygonPolygon } from '@zakkster/lite-sat';
|
|
9
|
+
|
|
10
|
+
## Key Facts
|
|
11
|
+
- Zero GC: All state in TypedArrays, no per-frame allocation
|
|
12
|
+
- Size: < 1.5 KB minified
|
|
13
|
+
- Dependencies: none
|
|
14
|
+
- ESM only (type: module)
|
|
15
|
+
|
|
16
|
+
## Export: testPolygonPolygon (function)
|
|
17
|
+
See source file sat.js for full JSDoc API documentation.
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@zakkster/lite-sat",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Zero-allocation 2D convex polygon SAT collision with MTV (Minimum Translation Vector).",
|
|
5
|
+
"author": "Zahary Shinikchiev <shinikchiev@yahoo.com>",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "sat.js",
|
|
9
|
+
"types": "sat.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"import": "./sat.js",
|
|
13
|
+
"types": "./sat.d.ts"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"sat.js",
|
|
18
|
+
"sat.d.ts",
|
|
19
|
+
"llms.txt"
|
|
20
|
+
],
|
|
21
|
+
"keywords": [
|
|
22
|
+
"sat",
|
|
23
|
+
"collision",
|
|
24
|
+
"polygon",
|
|
25
|
+
"physics",
|
|
26
|
+
"zero-gc",
|
|
27
|
+
"mtv"
|
|
28
|
+
],
|
|
29
|
+
"sideEffects": false,
|
|
30
|
+
"repository": {
|
|
31
|
+
"type": "git",
|
|
32
|
+
"url": "https://github.com/nicatspark/lite-libs"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"vitest": "^3.0.0"
|
|
36
|
+
},
|
|
37
|
+
"scripts": {
|
|
38
|
+
"test": "vitest run"
|
|
39
|
+
}
|
|
40
|
+
}
|