emeraldengine 2.2.1 → 3.1.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 +2198 -968
- package/dist/types/index.d.ts +75 -32
- package/dist/types/src/Animator.d.ts +50 -0
- package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
- package/dist/types/src/Camera.d.ts +122 -0
- package/dist/types/src/CameraController.d.ts +107 -0
- package/dist/types/src/CanvasText.d.ts +91 -0
- package/dist/types/src/CollisionLayers.d.ts +58 -0
- package/dist/types/{Color.d.ts → src/Color.d.ts} +6 -6
- package/dist/types/src/Coroutine.d.ts +111 -0
- package/dist/types/src/DebugOverlay.d.ts +86 -0
- package/dist/types/src/Drawable.d.ts +271 -0
- package/dist/types/src/Easing.d.ts +22 -0
- package/dist/types/src/Emerald.d.ts +420 -0
- package/dist/types/src/EmeraldDB.d.ts +159 -0
- package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
- package/dist/types/src/GLUtils.d.ts +4 -0
- package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
- package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +75 -23
- package/dist/types/src/Interpolator.d.ts +66 -0
- package/dist/types/src/Material.d.ts +87 -0
- package/dist/types/src/MathUtils.d.ts +81 -0
- package/dist/types/src/ParticleEmitter.d.ts +131 -0
- package/dist/types/src/Physics.d.ts +337 -0
- package/dist/types/src/Pool.d.ts +53 -0
- package/dist/types/src/PostEffects.d.ts +68 -0
- package/dist/types/src/PostProcessor.d.ts +124 -0
- package/dist/types/src/RenderTarget.d.ts +56 -0
- package/dist/types/src/Scene.d.ts +62 -0
- package/dist/types/src/ScreenEffects.d.ts +111 -0
- package/dist/types/src/Serializer.d.ts +86 -0
- package/dist/types/src/Shaders.d.ts +2 -0
- package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
- package/dist/types/src/SpatialGrid.d.ts +50 -0
- package/dist/types/src/SpriteBatch.d.ts +89 -0
- package/dist/types/src/StateMachine.d.ts +59 -0
- package/dist/types/src/Storage.d.ts +89 -0
- package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
- package/dist/types/src/TextureAtlas.d.ts +55 -0
- package/dist/types/src/Tilemap.d.ts +91 -0
- package/dist/types/src/Time.d.ts +53 -0
- package/dist/types/src/Timer.d.ts +54 -0
- package/dist/types/src/Transform.d.ts +94 -0
- package/dist/types/src/Tween.d.ts +81 -0
- package/dist/types/src/UI.d.ts +121 -0
- package/dist/types/src/components/Behaviour.d.ts +71 -0
- package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
- package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
- package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
- package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
- package/dist/types/src/components/Collider.d.ts +102 -0
- package/dist/types/src/components/GameObject.d.ts +135 -0
- package/dist/types/src/components/PolygonCollider.d.ts +33 -0
- package/dist/types/src/components/RigidBody.d.ts +456 -0
- package/dist/types/src/importers/Aseprite.d.ts +79 -0
- package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
- package/dist/types/src/importers/TiledMap.d.ts +62 -0
- package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
- package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
- package/dist/types/src/managers/AssetManager.d.ts +116 -0
- package/dist/types/src/managers/AudioManager.d.ts +259 -0
- package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
- package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
- package/dist/types/src/managers/GLManager.d.ts +84 -0
- package/dist/types/src/managers/GLState.d.ts +37 -0
- package/dist/types/src/managers/Gamepad.d.ts +102 -0
- package/dist/types/src/managers/IDManager.d.ts +31 -0
- package/dist/types/src/managers/InputManager.d.ts +381 -0
- package/dist/types/src/managers/NetworkManager.d.ts +93 -0
- package/dist/types/src/managers/RenderStats.d.ts +34 -0
- package/dist/types/src/managers/SceneManager.d.ts +44 -0
- package/dist/types/src/managers/ShaderManager.d.ts +55 -0
- package/dist/types/src/managers/TextureManager.d.ts +102 -0
- package/dist/types/src/particlesystem/Particle.d.ts +64 -0
- package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
- package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
- package/dist/types/src/physics/AABB.d.ts +92 -0
- package/dist/types/src/physics/Body.d.ts +435 -0
- package/dist/types/src/physics/BodyType.d.ts +6 -0
- package/dist/types/src/physics/BroadPhase.d.ts +210 -0
- package/dist/types/src/physics/Collision.d.ts +102 -0
- package/dist/types/src/physics/Contact.d.ts +206 -0
- package/dist/types/src/physics/ContactSolver.d.ts +108 -0
- package/dist/types/src/physics/Distance.d.ts +54 -0
- package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
- package/dist/types/src/physics/Fixture.d.ts +221 -0
- package/dist/types/src/physics/Island.d.ts +52 -0
- package/dist/types/src/physics/Joint.d.ts +59 -0
- package/dist/types/src/physics/Math2D.d.ts +371 -0
- package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
- package/dist/types/src/physics/Settings.d.ts +22 -0
- package/dist/types/src/physics/Shapes.d.ts +207 -0
- package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
- package/dist/types/src/physics/World.d.ts +274 -0
- package/dist/types/src/physics/index.d.ts +34 -0
- package/index.js +78 -0
- package/package.json +73 -60
- package/src/Animator.js +95 -0
- package/src/BitmapText.js +6 -5
- package/src/Camera.js +183 -0
- package/src/CameraController.js +192 -0
- package/src/CanvasText.js +281 -0
- package/src/CollisionLayers.js +86 -0
- package/src/Color.js +26 -18
- package/src/Coroutine.js +259 -0
- package/src/DebugOverlay.js +246 -0
- package/src/Drawable.js +842 -582
- package/src/Easing.js +57 -0
- package/src/Emerald.js +1150 -459
- package/src/EmeraldDB.js +328 -0
- package/src/FPSCounter.js +43 -43
- package/src/GLUtils.js +60 -67
- package/src/Instance.js +41 -5
- package/src/InstancedTexture.js +308 -127
- package/src/Interpolator.js +124 -0
- package/src/Material.js +202 -0
- package/src/MathUtils.js +134 -0
- package/src/ParticleEmitter.js +284 -0
- package/src/Physics.js +431 -40
- package/src/Pool.js +85 -0
- package/src/PostEffects.js +296 -0
- package/src/PostProcessor.js +304 -0
- package/src/RenderTarget.js +134 -0
- package/src/Scene.js +115 -83
- package/src/ScreenEffects.js +266 -0
- package/src/Serializer.js +131 -0
- package/src/Shaders.js +150 -165
- package/src/Shapes.js +118 -129
- package/src/SpatialGrid.js +111 -0
- package/src/SpriteBatch.js +299 -0
- package/src/StateMachine.js +82 -0
- package/src/Storage.js +175 -47
- package/src/Texture.js +58 -67
- package/src/TextureAtlas.js +96 -0
- package/src/Tilemap.js +274 -0
- package/src/Time.js +51 -6
- package/src/Timer.js +99 -0
- package/src/Transform.js +100 -7
- package/src/Tween.js +160 -0
- package/src/UI.js +394 -0
- package/src/components/Behaviour.js +90 -0
- package/src/components/BoxCollider.js +29 -12
- package/src/components/BoxColliderDebug.js +3 -4
- package/src/components/CircleCollider.js +26 -12
- package/src/components/CircleColliderDebug.js +25 -24
- package/src/components/Collider.js +150 -34
- package/src/components/GameObject.js +130 -21
- package/src/components/PolygonCollider.js +55 -0
- package/src/components/RigidBody.js +556 -8
- package/src/importers/Aseprite.js +142 -0
- package/src/importers/ForgeLevel.js +581 -0
- package/src/importers/TiledMap.js +158 -0
- package/src/lights/DirectionalLight.js +6 -15
- package/src/lights/PointLight.js +4 -4
- package/src/managers/AssetManager.js +239 -0
- package/src/managers/AudioManager.js +565 -146
- package/src/managers/EventManager.js +488 -477
- package/src/managers/GLManager.js +57 -0
- package/src/managers/GLState.js +70 -0
- package/src/managers/Gamepad.js +126 -0
- package/src/managers/IDManager.js +24 -2
- package/src/managers/InputManager.js +905 -0
- package/src/managers/NetworkManager.js +178 -0
- package/src/managers/RenderStats.js +34 -0
- package/src/managers/SceneManager.js +30 -0
- package/src/managers/ShaderManager.js +0 -2
- package/src/managers/TextureManager.js +218 -0
- package/src/particlesystem/Particle.js +82 -7
- package/src/particlesystem/ParticleSettings.js +21 -3
- package/src/particlesystem/Particles.js +80 -31
- package/src/physics/AABB.js +207 -0
- package/src/physics/Body.js +862 -0
- package/src/physics/BodyType.js +16 -0
- package/src/physics/BroadPhase.js +641 -0
- package/src/physics/Collision.js +534 -0
- package/src/physics/Contact.js +500 -0
- package/src/physics/ContactSolver.js +526 -0
- package/src/physics/Distance.js +403 -0
- package/src/physics/DistanceJoint.js +227 -0
- package/src/physics/Fixture.js +346 -0
- package/src/physics/Island.js +203 -0
- package/src/physics/Joint.js +78 -0
- package/src/physics/Math2D.js +573 -0
- package/src/physics/RevoluteJoint.js +278 -0
- package/src/physics/Settings.js +78 -0
- package/src/physics/Shapes.js +549 -0
- package/src/physics/TimeOfImpact.js +87 -0
- package/src/physics/World.js +731 -0
- package/src/physics/index.js +79 -0
- package/dist/types/Drawable.d.ts +0 -157
- package/dist/types/Emerald.d.ts +0 -73
- package/dist/types/GLUtils.d.ts +0 -6
- package/dist/types/Physics.d.ts +0 -139
- package/dist/types/Scene.d.ts +0 -39
- package/dist/types/Shaders.d.ts +0 -4
- package/dist/types/Storage.d.ts +0 -46
- package/dist/types/Time.d.ts +0 -22
- package/dist/types/Transform.d.ts +0 -41
- package/dist/types/components/BoxColliderDebug.d.ts +0 -19
- package/dist/types/components/CircleColliderDebug.d.ts +0 -19
- package/dist/types/components/Collider.d.ts +0 -53
- package/dist/types/components/GameObject.d.ts +0 -72
- package/dist/types/components/RigidBody.d.ts +0 -104
- package/dist/types/managers/AudioManager.d.ts +0 -60
- package/dist/types/managers/GLManager.d.ts +0 -47
- package/dist/types/managers/IDManager.d.ts +0 -21
- package/dist/types/managers/SceneManager.d.ts +0 -22
- package/dist/types/particlesystem/Particle.d.ts +0 -42
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
import { Vec2, Rot, Transform2, EPSILON } from "./Math2D.js";
|
|
2
|
+
import Settings from "./Settings.js";
|
|
3
|
+
import AABB from "./AABB.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @enum ShapeType
|
|
7
|
+
* @description The shape kinds the narrowphase knows how to collide.
|
|
8
|
+
*/
|
|
9
|
+
const ShapeType = {
|
|
10
|
+
CIRCLE: "circle",
|
|
11
|
+
POLYGON: "polygon",
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @class Shape
|
|
16
|
+
* @description Base class for collision shapes. A shape is pure geometry in a
|
|
17
|
+
* body's local frame: it carries no position of its own and can be shared
|
|
18
|
+
* between fixtures.
|
|
19
|
+
*/
|
|
20
|
+
class Shape {
|
|
21
|
+
constructor(type, radius) {
|
|
22
|
+
this.type = type;
|
|
23
|
+
this.radius = radius;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* @method getType
|
|
28
|
+
* @description Returns the shape type ("circle" or "polygon").
|
|
29
|
+
* @returns {string}
|
|
30
|
+
*/
|
|
31
|
+
getType() {
|
|
32
|
+
return this.type;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* @method getRadius
|
|
37
|
+
* @description Returns the shape's skin radius.
|
|
38
|
+
* @returns {number}
|
|
39
|
+
*/
|
|
40
|
+
getRadius() {
|
|
41
|
+
return this.radius;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* @class CircleShape
|
|
47
|
+
* @extends Shape
|
|
48
|
+
* @description A circle, optionally offset from the body origin.
|
|
49
|
+
* @param {number} [radius=0] - Radius in physics units
|
|
50
|
+
* @param {Object} [center] - Local center `{ x, y }`, defaults to the origin
|
|
51
|
+
*/
|
|
52
|
+
class CircleShape extends Shape {
|
|
53
|
+
constructor(radius = 0, center = null) {
|
|
54
|
+
super(ShapeType.CIRCLE, radius);
|
|
55
|
+
this.p = center ? new Vec2(center) : new Vec2(0, 0);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @method getVertexCount
|
|
60
|
+
* @description A circle is a single-point proxy plus a radius.
|
|
61
|
+
* @returns {number}
|
|
62
|
+
*/
|
|
63
|
+
getVertexCount() {
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* @method getVertex
|
|
69
|
+
* @description Returns the circle's local center (its only support point).
|
|
70
|
+
* @returns {Vec2}
|
|
71
|
+
*/
|
|
72
|
+
getVertex() {
|
|
73
|
+
return this.p;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* @method getSupport
|
|
78
|
+
* @description Index of the vertex furthest along a direction (always 0).
|
|
79
|
+
* @returns {number}
|
|
80
|
+
*/
|
|
81
|
+
getSupport() {
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* @method computeAABB
|
|
87
|
+
* @description Writes this shape's world AABB under a transform.
|
|
88
|
+
* @param {AABB} aabb - Output box
|
|
89
|
+
* @param {Transform2} xf - The owning body's transform
|
|
90
|
+
*/
|
|
91
|
+
computeAABB(aabb, xf) {
|
|
92
|
+
const px = xf.q.c * this.p.x - xf.q.s * this.p.y + xf.p.x;
|
|
93
|
+
const py = xf.q.s * this.p.x + xf.q.c * this.p.y + xf.p.y;
|
|
94
|
+
aabb.set(
|
|
95
|
+
px - this.radius,
|
|
96
|
+
py - this.radius,
|
|
97
|
+
px + this.radius,
|
|
98
|
+
py + this.radius
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
/**
|
|
103
|
+
* @method computeMass
|
|
104
|
+
* @description Writes mass, center and rotational inertia for a density.
|
|
105
|
+
* @param {Object} massData - Output `{ mass, center, I }`
|
|
106
|
+
* @param {number} density
|
|
107
|
+
*/
|
|
108
|
+
computeMass(massData, density) {
|
|
109
|
+
massData.mass = density * Math.PI * this.radius * this.radius;
|
|
110
|
+
massData.center = this.p.clone();
|
|
111
|
+
massData.I =
|
|
112
|
+
massData.mass *
|
|
113
|
+
(0.5 * this.radius * this.radius + Vec2.dot(this.p, this.p));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* @method testPoint
|
|
118
|
+
* @description True when a world point is inside the circle.
|
|
119
|
+
* @param {Transform2} xf
|
|
120
|
+
* @param {Object} p - World point
|
|
121
|
+
* @returns {boolean}
|
|
122
|
+
*/
|
|
123
|
+
testPoint(xf, p) {
|
|
124
|
+
const cx = xf.q.c * this.p.x - xf.q.s * this.p.y + xf.p.x;
|
|
125
|
+
const cy = xf.q.s * this.p.x + xf.q.c * this.p.y + xf.p.y;
|
|
126
|
+
const dx = p.x - cx;
|
|
127
|
+
const dy = p.y - cy;
|
|
128
|
+
return dx * dx + dy * dy <= this.radius * this.radius;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* @method rayCast
|
|
133
|
+
* @description Casts a ray against the circle.
|
|
134
|
+
* @param {Object} output - Written as `{ fraction, normal }` on a hit
|
|
135
|
+
* @param {Object} input - `{ p1, p2, maxFraction }` in world space
|
|
136
|
+
* @param {Transform2} xf
|
|
137
|
+
* @returns {boolean} - Whether the ray hit
|
|
138
|
+
*/
|
|
139
|
+
rayCast(output, input, xf) {
|
|
140
|
+
const posX = xf.q.c * this.p.x - xf.q.s * this.p.y + xf.p.x;
|
|
141
|
+
const posY = xf.q.s * this.p.x + xf.q.c * this.p.y + xf.p.y;
|
|
142
|
+
|
|
143
|
+
const sx = input.p1.x - posX;
|
|
144
|
+
const sy = input.p1.y - posY;
|
|
145
|
+
const b = sx * sx + sy * sy - this.radius * this.radius;
|
|
146
|
+
|
|
147
|
+
const rx = input.p2.x - input.p1.x;
|
|
148
|
+
const ry = input.p2.y - input.p1.y;
|
|
149
|
+
const c = sx * rx + sy * ry;
|
|
150
|
+
const rr = rx * rx + ry * ry;
|
|
151
|
+
const sigma = c * c - rr * b;
|
|
152
|
+
|
|
153
|
+
if (sigma < 0 || rr < EPSILON) return false;
|
|
154
|
+
|
|
155
|
+
let a = -(c + Math.sqrt(sigma));
|
|
156
|
+
if (a < 0 || a > input.maxFraction * rr) return false;
|
|
157
|
+
|
|
158
|
+
a /= rr;
|
|
159
|
+
output.fraction = a;
|
|
160
|
+
const nx = sx + a * rx;
|
|
161
|
+
const ny = sy + a * ry;
|
|
162
|
+
const len = Math.hypot(nx, ny) || 1;
|
|
163
|
+
output.normal = new Vec2(nx / len, ny / len);
|
|
164
|
+
return true;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* @class PolygonShape
|
|
170
|
+
* @extends Shape
|
|
171
|
+
* @description A convex polygon with up to `MAX_VERTICES` corners, wound
|
|
172
|
+
* counter-clockwise. Boxes, by far the common case, come from
|
|
173
|
+
* {@link PolygonShape.box}.
|
|
174
|
+
* @param {Array<Object>} [points] - Optional points to build a convex hull from
|
|
175
|
+
*/
|
|
176
|
+
class PolygonShape extends Shape {
|
|
177
|
+
constructor(points = null) {
|
|
178
|
+
super(ShapeType.POLYGON, Settings.polygonRadius);
|
|
179
|
+
this.vertices = [];
|
|
180
|
+
this.normals = [];
|
|
181
|
+
this.centroid = new Vec2(0, 0);
|
|
182
|
+
if (points) this.set(points);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* @method box
|
|
187
|
+
* @description Builds an axis-aligned box from half-extents, optionally
|
|
188
|
+
* offset and rotated in the body's local frame.
|
|
189
|
+
* @param {number} hx - Half width
|
|
190
|
+
* @param {number} hy - Half height
|
|
191
|
+
* @param {Object} [center] - Local center `{ x, y }`
|
|
192
|
+
* @param {number} [angle=0] - Local rotation in radians
|
|
193
|
+
* @returns {PolygonShape}
|
|
194
|
+
*/
|
|
195
|
+
static box(hx, hy, center = null, angle = 0) {
|
|
196
|
+
const shape = new PolygonShape();
|
|
197
|
+
shape.setAsBox(hx, hy, center, angle);
|
|
198
|
+
return shape;
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @method setAsBox
|
|
203
|
+
* @description Turns this polygon into a box in place.
|
|
204
|
+
* @param {number} hx - Half width
|
|
205
|
+
* @param {number} hy - Half height
|
|
206
|
+
* @param {Object} [center] - Local center `{ x, y }`
|
|
207
|
+
* @param {number} [angle=0] - Local rotation in radians
|
|
208
|
+
* @returns {PolygonShape} - this
|
|
209
|
+
*/
|
|
210
|
+
setAsBox(hx, hy, center = null, angle = 0) {
|
|
211
|
+
this.vertices = [
|
|
212
|
+
new Vec2(-hx, -hy),
|
|
213
|
+
new Vec2(hx, -hy),
|
|
214
|
+
new Vec2(hx, hy),
|
|
215
|
+
new Vec2(-hx, hy),
|
|
216
|
+
];
|
|
217
|
+
this.normals = [
|
|
218
|
+
new Vec2(0, -1),
|
|
219
|
+
new Vec2(1, 0),
|
|
220
|
+
new Vec2(0, 1),
|
|
221
|
+
new Vec2(-1, 0),
|
|
222
|
+
];
|
|
223
|
+
this.centroid.setZero();
|
|
224
|
+
|
|
225
|
+
if (center) {
|
|
226
|
+
const c = new Vec2(center);
|
|
227
|
+
const xf = new Transform2();
|
|
228
|
+
xf.set(c, angle);
|
|
229
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
230
|
+
this.vertices[i] = Transform2.mulVec(xf, this.vertices[i]);
|
|
231
|
+
this.normals[i] = Rot.mulVec(xf.q, this.normals[i]);
|
|
232
|
+
}
|
|
233
|
+
this.centroid.copy(c);
|
|
234
|
+
}
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
/**
|
|
239
|
+
* @method set
|
|
240
|
+
* @description Builds the convex hull of a point cloud. Points inside the
|
|
241
|
+
* hull are dropped, so callers can pass a rough outline.
|
|
242
|
+
* @param {Array<Object>} points - `{ x, y }` points
|
|
243
|
+
* @returns {PolygonShape} - this
|
|
244
|
+
*/
|
|
245
|
+
set(points) {
|
|
246
|
+
const hull = computeHull(points);
|
|
247
|
+
if (hull.length < 3) {
|
|
248
|
+
throw new Error("[Physics] A polygon needs at least 3 distinct points.");
|
|
249
|
+
}
|
|
250
|
+
this.vertices = hull;
|
|
251
|
+
this.normals = [];
|
|
252
|
+
for (let i = 0; i < hull.length; i++) {
|
|
253
|
+
const next = hull[(i + 1) % hull.length];
|
|
254
|
+
const edge = Vec2.sub(next, hull[i]);
|
|
255
|
+
if (edge.lengthSquared() <= EPSILON * EPSILON) {
|
|
256
|
+
throw new Error("[Physics] Polygon has a degenerate edge.");
|
|
257
|
+
}
|
|
258
|
+
const normal = new Vec2(edge.y, -edge.x);
|
|
259
|
+
normal.normalize();
|
|
260
|
+
this.normals.push(normal);
|
|
261
|
+
}
|
|
262
|
+
this.centroid = computeCentroid(hull);
|
|
263
|
+
return this;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @method getVertexCount
|
|
268
|
+
* @description Number of corners.
|
|
269
|
+
* @returns {number}
|
|
270
|
+
*/
|
|
271
|
+
getVertexCount() {
|
|
272
|
+
return this.vertices.length;
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* @method getVertex
|
|
277
|
+
* @description Returns a corner by index.
|
|
278
|
+
* @param {number} index
|
|
279
|
+
* @returns {Vec2}
|
|
280
|
+
*/
|
|
281
|
+
getVertex(index) {
|
|
282
|
+
return this.vertices[index];
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/**
|
|
286
|
+
* @method getSupport
|
|
287
|
+
* @description Index of the vertex furthest along a local direction: the
|
|
288
|
+
* support function GJK is built on.
|
|
289
|
+
* @param {Object} d - Local direction
|
|
290
|
+
* @returns {number}
|
|
291
|
+
*/
|
|
292
|
+
getSupport(d) {
|
|
293
|
+
let best = 0;
|
|
294
|
+
let bestValue = this.vertices[0].x * d.x + this.vertices[0].y * d.y;
|
|
295
|
+
for (let i = 1; i < this.vertices.length; i++) {
|
|
296
|
+
const value = this.vertices[i].x * d.x + this.vertices[i].y * d.y;
|
|
297
|
+
if (value > bestValue) {
|
|
298
|
+
bestValue = value;
|
|
299
|
+
best = i;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return best;
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
/**
|
|
306
|
+
* @method computeAABB
|
|
307
|
+
* @description Writes this shape's world AABB under a transform.
|
|
308
|
+
* @param {AABB} aabb - Output box
|
|
309
|
+
* @param {Transform2} xf
|
|
310
|
+
*/
|
|
311
|
+
computeAABB(aabb, xf) {
|
|
312
|
+
let lowerX = Infinity;
|
|
313
|
+
let lowerY = Infinity;
|
|
314
|
+
let upperX = -Infinity;
|
|
315
|
+
let upperY = -Infinity;
|
|
316
|
+
for (const v of this.vertices) {
|
|
317
|
+
const x = xf.q.c * v.x - xf.q.s * v.y + xf.p.x;
|
|
318
|
+
const y = xf.q.s * v.x + xf.q.c * v.y + xf.p.y;
|
|
319
|
+
if (x < lowerX) lowerX = x;
|
|
320
|
+
if (y < lowerY) lowerY = y;
|
|
321
|
+
if (x > upperX) upperX = x;
|
|
322
|
+
if (y > upperY) upperY = y;
|
|
323
|
+
}
|
|
324
|
+
aabb.set(
|
|
325
|
+
lowerX - this.radius,
|
|
326
|
+
lowerY - this.radius,
|
|
327
|
+
upperX + this.radius,
|
|
328
|
+
upperY + this.radius
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* @method computeMass
|
|
334
|
+
* @description Writes mass, center and rotational inertia for a density by
|
|
335
|
+
* summing the triangle fan of the polygon.
|
|
336
|
+
* @param {Object} massData - Output `{ mass, center, I }`
|
|
337
|
+
* @param {number} density
|
|
338
|
+
*/
|
|
339
|
+
computeMass(massData, density) {
|
|
340
|
+
const count = this.vertices.length;
|
|
341
|
+
let area = 0;
|
|
342
|
+
let I = 0;
|
|
343
|
+
const center = new Vec2(0, 0);
|
|
344
|
+
const inv3 = 1 / 3;
|
|
345
|
+
|
|
346
|
+
const ref = this.vertices[0];
|
|
347
|
+
for (let i = 1; i < count - 1; i++) {
|
|
348
|
+
const e1 = Vec2.sub(this.vertices[i], ref);
|
|
349
|
+
const e2 = Vec2.sub(this.vertices[i + 1], ref);
|
|
350
|
+
const D = Vec2.cross(e1, e2);
|
|
351
|
+
const triangleArea = 0.5 * D;
|
|
352
|
+
area += triangleArea;
|
|
353
|
+
|
|
354
|
+
center.x += triangleArea * inv3 * (e1.x + e2.x);
|
|
355
|
+
center.y += triangleArea * inv3 * (e1.y + e2.y);
|
|
356
|
+
|
|
357
|
+
const intx2 = e1.x * e1.x + e2.x * e1.x + e2.x * e2.x;
|
|
358
|
+
const inty2 = e1.y * e1.y + e2.y * e1.y + e2.y * e2.y;
|
|
359
|
+
I += 0.25 * inv3 * D * (intx2 + inty2);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
massData.mass = density * area;
|
|
363
|
+
|
|
364
|
+
if (area > EPSILON) {
|
|
365
|
+
center.mul(1 / area);
|
|
366
|
+
}
|
|
367
|
+
massData.center = Vec2.add(ref, center);
|
|
368
|
+
|
|
369
|
+
I *= density;
|
|
370
|
+
I +=
|
|
371
|
+
massData.mass *
|
|
372
|
+
(Vec2.dot(massData.center, massData.center) - Vec2.dot(center, center));
|
|
373
|
+
massData.I = I;
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
/**
|
|
377
|
+
* @method testPoint
|
|
378
|
+
* @description True when a world point is inside the polygon.
|
|
379
|
+
* @param {Transform2} xf
|
|
380
|
+
* @param {Object} p - World point
|
|
381
|
+
* @returns {boolean}
|
|
382
|
+
*/
|
|
383
|
+
testPoint(xf, p) {
|
|
384
|
+
const local = Transform2.mulTVec(xf, p);
|
|
385
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
386
|
+
const dot =
|
|
387
|
+
this.normals[i].x * (local.x - this.vertices[i].x) +
|
|
388
|
+
this.normals[i].y * (local.y - this.vertices[i].y);
|
|
389
|
+
if (dot > 0) return false;
|
|
390
|
+
}
|
|
391
|
+
return true;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/**
|
|
395
|
+
* @method rayCast
|
|
396
|
+
* @description Casts a ray against the polygon by clipping it against every
|
|
397
|
+
* edge plane and keeping the surviving interval.
|
|
398
|
+
* @param {Object} output - Written as `{ fraction, normal }` on a hit
|
|
399
|
+
* @param {Object} input - `{ p1, p2, maxFraction }` in world space
|
|
400
|
+
* @param {Transform2} xf
|
|
401
|
+
* @returns {boolean}
|
|
402
|
+
*/
|
|
403
|
+
rayCast(output, input, xf) {
|
|
404
|
+
const p1 = Transform2.mulTVec(xf, input.p1);
|
|
405
|
+
const p2 = Transform2.mulTVec(xf, input.p2);
|
|
406
|
+
const d = Vec2.sub(p2, p1);
|
|
407
|
+
|
|
408
|
+
let lower = 0;
|
|
409
|
+
let upper = input.maxFraction;
|
|
410
|
+
let index = -1;
|
|
411
|
+
|
|
412
|
+
for (let i = 0; i < this.vertices.length; i++) {
|
|
413
|
+
const numerator = Vec2.dot(
|
|
414
|
+
this.normals[i],
|
|
415
|
+
Vec2.sub(this.vertices[i], p1)
|
|
416
|
+
);
|
|
417
|
+
const denominator = Vec2.dot(this.normals[i], d);
|
|
418
|
+
|
|
419
|
+
if (denominator === 0) {
|
|
420
|
+
if (numerator < 0) return false;
|
|
421
|
+
} else {
|
|
422
|
+
const t = numerator / denominator;
|
|
423
|
+
if (denominator < 0 && t > lower) {
|
|
424
|
+
lower = t;
|
|
425
|
+
index = i;
|
|
426
|
+
} else if (denominator > 0 && t < upper) {
|
|
427
|
+
upper = t;
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
if (upper < lower) return false;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
if (index >= 0) {
|
|
435
|
+
output.fraction = lower;
|
|
436
|
+
output.normal = Rot.mulVec(xf.q, this.normals[index]);
|
|
437
|
+
return true;
|
|
438
|
+
}
|
|
439
|
+
return false;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* @function computeHull
|
|
445
|
+
* @description Monotone-chain convex hull, counter-clockwise, with duplicate
|
|
446
|
+
* and near-collinear points removed.
|
|
447
|
+
* @param {Array<Object>} points
|
|
448
|
+
* @returns {Array<Vec2>}
|
|
449
|
+
* @private
|
|
450
|
+
*/
|
|
451
|
+
function computeHull(points) {
|
|
452
|
+
const unique = [];
|
|
453
|
+
for (const p of points) {
|
|
454
|
+
const v = new Vec2(p);
|
|
455
|
+
let duplicate = false;
|
|
456
|
+
for (const u of unique) {
|
|
457
|
+
if (Vec2.distanceSquared(u, v) < 0.25 * Settings.linearSlop ** 2) {
|
|
458
|
+
duplicate = true;
|
|
459
|
+
break;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
if (!duplicate) unique.push(v);
|
|
463
|
+
}
|
|
464
|
+
if (unique.length < 3) return unique;
|
|
465
|
+
|
|
466
|
+
unique.sort((a, b) => (a.x === b.x ? a.y - b.y : a.x - b.x));
|
|
467
|
+
|
|
468
|
+
const cross = (o, a, b) =>
|
|
469
|
+
(a.x - o.x) * (b.y - o.y) - (a.y - o.y) * (b.x - o.x);
|
|
470
|
+
|
|
471
|
+
const lower = [];
|
|
472
|
+
for (const p of unique) {
|
|
473
|
+
while (
|
|
474
|
+
lower.length >= 2 &&
|
|
475
|
+
cross(lower[lower.length - 2], lower[lower.length - 1], p) <= 0
|
|
476
|
+
) {
|
|
477
|
+
lower.pop();
|
|
478
|
+
}
|
|
479
|
+
lower.push(p);
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const upper = [];
|
|
483
|
+
for (let i = unique.length - 1; i >= 0; i--) {
|
|
484
|
+
const p = unique[i];
|
|
485
|
+
while (
|
|
486
|
+
upper.length >= 2 &&
|
|
487
|
+
cross(upper[upper.length - 2], upper[upper.length - 1], p) <= 0
|
|
488
|
+
) {
|
|
489
|
+
upper.pop();
|
|
490
|
+
}
|
|
491
|
+
upper.push(p);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
lower.pop();
|
|
495
|
+
upper.pop();
|
|
496
|
+
return lower.concat(upper);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
/**
|
|
500
|
+
* @function computeCentroid
|
|
501
|
+
* @description Area-weighted centroid of a convex polygon.
|
|
502
|
+
* @param {Array<Vec2>} vertices
|
|
503
|
+
* @returns {Vec2}
|
|
504
|
+
* @private
|
|
505
|
+
*/
|
|
506
|
+
function computeCentroid(vertices) {
|
|
507
|
+
const c = new Vec2(0, 0);
|
|
508
|
+
let area = 0;
|
|
509
|
+
const inv3 = 1 / 3;
|
|
510
|
+
const ref = vertices[0];
|
|
511
|
+
|
|
512
|
+
for (let i = 1; i < vertices.length - 1; i++) {
|
|
513
|
+
const e1 = Vec2.sub(vertices[i], ref);
|
|
514
|
+
const e2 = Vec2.sub(vertices[i + 1], ref);
|
|
515
|
+
const triangleArea = 0.5 * Vec2.cross(e1, e2);
|
|
516
|
+
area += triangleArea;
|
|
517
|
+
c.x += triangleArea * inv3 * (e1.x + e2.x);
|
|
518
|
+
c.y += triangleArea * inv3 * (e1.y + e2.y);
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
if (area > EPSILON) c.mul(1 / area);
|
|
522
|
+
return Vec2.add(ref, c);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* @function Box
|
|
527
|
+
* @description Convenience factory for a box polygon.
|
|
528
|
+
* @param {number} hx - Half width in physics units
|
|
529
|
+
* @param {number} hy - Half height in physics units
|
|
530
|
+
* @param {Object} [center] - Local center `{ x, y }`
|
|
531
|
+
* @param {number} [angle=0] - Local rotation in radians
|
|
532
|
+
* @returns {PolygonShape}
|
|
533
|
+
*/
|
|
534
|
+
function Box(hx, hy, center = null, angle = 0) {
|
|
535
|
+
return PolygonShape.box(hx, hy, center, angle);
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/**
|
|
539
|
+
* @function Circle
|
|
540
|
+
* @description Convenience factory for a circle shape.
|
|
541
|
+
* @param {number} radius - Radius in physics units
|
|
542
|
+
* @param {Object} [center] - Local center `{ x, y }`
|
|
543
|
+
* @returns {CircleShape}
|
|
544
|
+
*/
|
|
545
|
+
function Circle(radius, center = null) {
|
|
546
|
+
return new CircleShape(radius, center);
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export { Shape, ShapeType, CircleShape, PolygonShape, Box, Circle, AABB };
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { Vec2, Transform2, EPSILON } from "./Math2D.js";
|
|
2
|
+
import Settings from "./Settings.js";
|
|
3
|
+
import { distance } from "./Distance.js";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* @enum TOIState
|
|
7
|
+
* @description Outcome of a time-of-impact query.
|
|
8
|
+
*/
|
|
9
|
+
const TOIState = {
|
|
10
|
+
/** The shapes never meet within the step. */
|
|
11
|
+
SEPARATED: "separated",
|
|
12
|
+
/** They meet at the returned fraction of the step. */
|
|
13
|
+
TOUCHING: "touching",
|
|
14
|
+
/** They already overlap at the start of the step. */
|
|
15
|
+
OVERLAPPED: "overlapped",
|
|
16
|
+
/** The search ran out of iterations; `t` is still a safe lower bound. */
|
|
17
|
+
FAILED: "failed",
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
const xfA = new Transform2();
|
|
21
|
+
const xfB = new Transform2();
|
|
22
|
+
const output = { distance: 0, pointA: new Vec2(), pointB: new Vec2() };
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* @function timeOfImpact
|
|
26
|
+
* @description Finds the first fraction of a step at which two moving shapes
|
|
27
|
+
* touch, by conservative advancement: measure the gap, work out the fastest the
|
|
28
|
+
* shapes could possibly close it, and skip ahead by exactly that much time,
|
|
29
|
+
* never further. Repeating this converges on the impact without ever stepping
|
|
30
|
+
* past it, which is what stops a fast body from tunnelling through a wall.
|
|
31
|
+
*
|
|
32
|
+
* @param {Object} input - `{ proxyA, proxyB, sweepA, sweepB, tMax }`
|
|
33
|
+
* @returns {{state: string, t: number}} - The outcome and the impact fraction
|
|
34
|
+
*/
|
|
35
|
+
function timeOfImpact(input) {
|
|
36
|
+
const { proxyA, proxyB, sweepA, sweepB } = input;
|
|
37
|
+
const tMax = input.tMax ?? 1;
|
|
38
|
+
|
|
39
|
+
const totalRadius = proxyA.radius + proxyB.radius;
|
|
40
|
+
const target = Math.max(
|
|
41
|
+
Settings.linearSlop,
|
|
42
|
+
totalRadius - 3 * Settings.linearSlop
|
|
43
|
+
);
|
|
44
|
+
const tolerance = 0.25 * Settings.linearSlop;
|
|
45
|
+
|
|
46
|
+
const dxA = sweepA.c.x - sweepA.c0.x;
|
|
47
|
+
const dyA = sweepA.c.y - sweepA.c0.y;
|
|
48
|
+
const dxB = sweepB.c.x - sweepB.c0.x;
|
|
49
|
+
const dyB = sweepB.c.y - sweepB.c0.y;
|
|
50
|
+
const dAngA = Math.abs(sweepA.a - sweepA.a0);
|
|
51
|
+
const dAngB = Math.abs(sweepB.a - sweepB.a0);
|
|
52
|
+
|
|
53
|
+
const approachBound =
|
|
54
|
+
Math.hypot(dxA - dxB, dyA - dyB) +
|
|
55
|
+
dAngA * proxyA.getMaxExtent() +
|
|
56
|
+
dAngB * proxyB.getMaxExtent();
|
|
57
|
+
|
|
58
|
+
if (approachBound < EPSILON) return { state: TOIState.SEPARATED, t: tMax };
|
|
59
|
+
|
|
60
|
+
let t = 0;
|
|
61
|
+
for (let iter = 0; iter < Settings.maxTOIIterations; iter++) {
|
|
62
|
+
sweepA.getTransform(xfA, t);
|
|
63
|
+
sweepB.getTransform(xfB, t);
|
|
64
|
+
|
|
65
|
+
distance(output, {
|
|
66
|
+
proxyA,
|
|
67
|
+
proxyB,
|
|
68
|
+
transformA: xfA,
|
|
69
|
+
transformB: xfB,
|
|
70
|
+
useRadii: false,
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
if (output.distance <= 0) {
|
|
74
|
+
return { state: t === 0 ? TOIState.OVERLAPPED : TOIState.TOUCHING, t };
|
|
75
|
+
}
|
|
76
|
+
if (output.distance < target + tolerance) {
|
|
77
|
+
return { state: TOIState.TOUCHING, t };
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
t += (output.distance - target) / approachBound;
|
|
81
|
+
if (t >= tMax) return { state: TOIState.SEPARATED, t: tMax };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return { state: TOIState.FAILED, t };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export { timeOfImpact, TOIState };
|