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.
Files changed (208) hide show
  1. package/README.md +2198 -968
  2. package/dist/types/index.d.ts +75 -32
  3. package/dist/types/src/Animator.d.ts +50 -0
  4. package/dist/types/{BitmapText.d.ts → src/BitmapText.d.ts} +19 -21
  5. package/dist/types/src/Camera.d.ts +122 -0
  6. package/dist/types/src/CameraController.d.ts +107 -0
  7. package/dist/types/src/CanvasText.d.ts +91 -0
  8. package/dist/types/src/CollisionLayers.d.ts +58 -0
  9. package/dist/types/{Color.d.ts → src/Color.d.ts} +6 -6
  10. package/dist/types/src/Coroutine.d.ts +111 -0
  11. package/dist/types/src/DebugOverlay.d.ts +86 -0
  12. package/dist/types/src/Drawable.d.ts +271 -0
  13. package/dist/types/src/Easing.d.ts +22 -0
  14. package/dist/types/src/Emerald.d.ts +420 -0
  15. package/dist/types/src/EmeraldDB.d.ts +159 -0
  16. package/dist/types/{FPSCounter.d.ts → src/FPSCounter.d.ts} +1 -3
  17. package/dist/types/src/GLUtils.d.ts +4 -0
  18. package/dist/types/{Instance.d.ts → src/Instance.d.ts} +37 -15
  19. package/dist/types/{InstancedTexture.d.ts → src/InstancedTexture.d.ts} +75 -23
  20. package/dist/types/src/Interpolator.d.ts +66 -0
  21. package/dist/types/src/Material.d.ts +87 -0
  22. package/dist/types/src/MathUtils.d.ts +81 -0
  23. package/dist/types/src/ParticleEmitter.d.ts +131 -0
  24. package/dist/types/src/Physics.d.ts +337 -0
  25. package/dist/types/src/Pool.d.ts +53 -0
  26. package/dist/types/src/PostEffects.d.ts +68 -0
  27. package/dist/types/src/PostProcessor.d.ts +124 -0
  28. package/dist/types/src/RenderTarget.d.ts +56 -0
  29. package/dist/types/src/Scene.d.ts +62 -0
  30. package/dist/types/src/ScreenEffects.d.ts +111 -0
  31. package/dist/types/src/Serializer.d.ts +86 -0
  32. package/dist/types/src/Shaders.d.ts +2 -0
  33. package/dist/types/{Shapes.d.ts → src/Shapes.d.ts} +4 -6
  34. package/dist/types/src/SpatialGrid.d.ts +50 -0
  35. package/dist/types/src/SpriteBatch.d.ts +89 -0
  36. package/dist/types/src/StateMachine.d.ts +59 -0
  37. package/dist/types/src/Storage.d.ts +89 -0
  38. package/dist/types/{Texture.d.ts → src/Texture.d.ts} +3 -4
  39. package/dist/types/src/TextureAtlas.d.ts +55 -0
  40. package/dist/types/src/Tilemap.d.ts +91 -0
  41. package/dist/types/src/Time.d.ts +53 -0
  42. package/dist/types/src/Timer.d.ts +54 -0
  43. package/dist/types/src/Transform.d.ts +94 -0
  44. package/dist/types/src/Tween.d.ts +81 -0
  45. package/dist/types/src/UI.d.ts +121 -0
  46. package/dist/types/src/components/Behaviour.d.ts +71 -0
  47. package/dist/types/{components → src/components}/BoxCollider.d.ts +14 -14
  48. package/dist/types/src/components/BoxColliderDebug.d.ts +15 -0
  49. package/dist/types/{components → src/components}/CircleCollider.d.ts +14 -12
  50. package/dist/types/src/components/CircleColliderDebug.d.ts +15 -0
  51. package/dist/types/src/components/Collider.d.ts +102 -0
  52. package/dist/types/src/components/GameObject.d.ts +135 -0
  53. package/dist/types/src/components/PolygonCollider.d.ts +33 -0
  54. package/dist/types/src/components/RigidBody.d.ts +456 -0
  55. package/dist/types/src/importers/Aseprite.d.ts +79 -0
  56. package/dist/types/src/importers/ForgeLevel.d.ts +97 -0
  57. package/dist/types/src/importers/TiledMap.d.ts +62 -0
  58. package/dist/types/{lights → src/lights}/DirectionalLight.d.ts +7 -9
  59. package/dist/types/{lights → src/lights}/PointLight.d.ts +6 -9
  60. package/dist/types/src/managers/AssetManager.d.ts +116 -0
  61. package/dist/types/src/managers/AudioManager.d.ts +259 -0
  62. package/dist/types/{managers → src/managers}/CameraManager.d.ts +8 -8
  63. package/dist/types/{managers → src/managers}/EventManager.d.ts +46 -32
  64. package/dist/types/src/managers/GLManager.d.ts +84 -0
  65. package/dist/types/src/managers/GLState.d.ts +37 -0
  66. package/dist/types/src/managers/Gamepad.d.ts +102 -0
  67. package/dist/types/src/managers/IDManager.d.ts +31 -0
  68. package/dist/types/src/managers/InputManager.d.ts +381 -0
  69. package/dist/types/src/managers/NetworkManager.d.ts +93 -0
  70. package/dist/types/src/managers/RenderStats.d.ts +34 -0
  71. package/dist/types/src/managers/SceneManager.d.ts +44 -0
  72. package/dist/types/src/managers/ShaderManager.d.ts +55 -0
  73. package/dist/types/src/managers/TextureManager.d.ts +102 -0
  74. package/dist/types/src/particlesystem/Particle.d.ts +64 -0
  75. package/dist/types/{particlesystem → src/particlesystem}/ParticleSettings.d.ts +32 -24
  76. package/dist/types/{particlesystem → src/particlesystem}/Particles.d.ts +20 -12
  77. package/dist/types/src/physics/AABB.d.ts +92 -0
  78. package/dist/types/src/physics/Body.d.ts +435 -0
  79. package/dist/types/src/physics/BodyType.d.ts +6 -0
  80. package/dist/types/src/physics/BroadPhase.d.ts +210 -0
  81. package/dist/types/src/physics/Collision.d.ts +102 -0
  82. package/dist/types/src/physics/Contact.d.ts +206 -0
  83. package/dist/types/src/physics/ContactSolver.d.ts +108 -0
  84. package/dist/types/src/physics/Distance.d.ts +54 -0
  85. package/dist/types/src/physics/DistanceJoint.d.ts +90 -0
  86. package/dist/types/src/physics/Fixture.d.ts +221 -0
  87. package/dist/types/src/physics/Island.d.ts +52 -0
  88. package/dist/types/src/physics/Joint.d.ts +59 -0
  89. package/dist/types/src/physics/Math2D.d.ts +371 -0
  90. package/dist/types/src/physics/RevoluteJoint.d.ts +119 -0
  91. package/dist/types/src/physics/Settings.d.ts +22 -0
  92. package/dist/types/src/physics/Shapes.d.ts +207 -0
  93. package/dist/types/src/physics/TimeOfImpact.d.ts +22 -0
  94. package/dist/types/src/physics/World.d.ts +274 -0
  95. package/dist/types/src/physics/index.d.ts +34 -0
  96. package/index.js +78 -0
  97. package/package.json +73 -60
  98. package/src/Animator.js +95 -0
  99. package/src/BitmapText.js +6 -5
  100. package/src/Camera.js +183 -0
  101. package/src/CameraController.js +192 -0
  102. package/src/CanvasText.js +281 -0
  103. package/src/CollisionLayers.js +86 -0
  104. package/src/Color.js +26 -18
  105. package/src/Coroutine.js +259 -0
  106. package/src/DebugOverlay.js +246 -0
  107. package/src/Drawable.js +842 -582
  108. package/src/Easing.js +57 -0
  109. package/src/Emerald.js +1150 -459
  110. package/src/EmeraldDB.js +328 -0
  111. package/src/FPSCounter.js +43 -43
  112. package/src/GLUtils.js +60 -67
  113. package/src/Instance.js +41 -5
  114. package/src/InstancedTexture.js +308 -127
  115. package/src/Interpolator.js +124 -0
  116. package/src/Material.js +202 -0
  117. package/src/MathUtils.js +134 -0
  118. package/src/ParticleEmitter.js +284 -0
  119. package/src/Physics.js +431 -40
  120. package/src/Pool.js +85 -0
  121. package/src/PostEffects.js +296 -0
  122. package/src/PostProcessor.js +304 -0
  123. package/src/RenderTarget.js +134 -0
  124. package/src/Scene.js +115 -83
  125. package/src/ScreenEffects.js +266 -0
  126. package/src/Serializer.js +131 -0
  127. package/src/Shaders.js +150 -165
  128. package/src/Shapes.js +118 -129
  129. package/src/SpatialGrid.js +111 -0
  130. package/src/SpriteBatch.js +299 -0
  131. package/src/StateMachine.js +82 -0
  132. package/src/Storage.js +175 -47
  133. package/src/Texture.js +58 -67
  134. package/src/TextureAtlas.js +96 -0
  135. package/src/Tilemap.js +274 -0
  136. package/src/Time.js +51 -6
  137. package/src/Timer.js +99 -0
  138. package/src/Transform.js +100 -7
  139. package/src/Tween.js +160 -0
  140. package/src/UI.js +394 -0
  141. package/src/components/Behaviour.js +90 -0
  142. package/src/components/BoxCollider.js +29 -12
  143. package/src/components/BoxColliderDebug.js +3 -4
  144. package/src/components/CircleCollider.js +26 -12
  145. package/src/components/CircleColliderDebug.js +25 -24
  146. package/src/components/Collider.js +150 -34
  147. package/src/components/GameObject.js +130 -21
  148. package/src/components/PolygonCollider.js +55 -0
  149. package/src/components/RigidBody.js +556 -8
  150. package/src/importers/Aseprite.js +142 -0
  151. package/src/importers/ForgeLevel.js +581 -0
  152. package/src/importers/TiledMap.js +158 -0
  153. package/src/lights/DirectionalLight.js +6 -15
  154. package/src/lights/PointLight.js +4 -4
  155. package/src/managers/AssetManager.js +239 -0
  156. package/src/managers/AudioManager.js +565 -146
  157. package/src/managers/EventManager.js +488 -477
  158. package/src/managers/GLManager.js +57 -0
  159. package/src/managers/GLState.js +70 -0
  160. package/src/managers/Gamepad.js +126 -0
  161. package/src/managers/IDManager.js +24 -2
  162. package/src/managers/InputManager.js +905 -0
  163. package/src/managers/NetworkManager.js +178 -0
  164. package/src/managers/RenderStats.js +34 -0
  165. package/src/managers/SceneManager.js +30 -0
  166. package/src/managers/ShaderManager.js +0 -2
  167. package/src/managers/TextureManager.js +218 -0
  168. package/src/particlesystem/Particle.js +82 -7
  169. package/src/particlesystem/ParticleSettings.js +21 -3
  170. package/src/particlesystem/Particles.js +80 -31
  171. package/src/physics/AABB.js +207 -0
  172. package/src/physics/Body.js +862 -0
  173. package/src/physics/BodyType.js +16 -0
  174. package/src/physics/BroadPhase.js +641 -0
  175. package/src/physics/Collision.js +534 -0
  176. package/src/physics/Contact.js +500 -0
  177. package/src/physics/ContactSolver.js +526 -0
  178. package/src/physics/Distance.js +403 -0
  179. package/src/physics/DistanceJoint.js +227 -0
  180. package/src/physics/Fixture.js +346 -0
  181. package/src/physics/Island.js +203 -0
  182. package/src/physics/Joint.js +78 -0
  183. package/src/physics/Math2D.js +573 -0
  184. package/src/physics/RevoluteJoint.js +278 -0
  185. package/src/physics/Settings.js +78 -0
  186. package/src/physics/Shapes.js +549 -0
  187. package/src/physics/TimeOfImpact.js +87 -0
  188. package/src/physics/World.js +731 -0
  189. package/src/physics/index.js +79 -0
  190. package/dist/types/Drawable.d.ts +0 -157
  191. package/dist/types/Emerald.d.ts +0 -73
  192. package/dist/types/GLUtils.d.ts +0 -6
  193. package/dist/types/Physics.d.ts +0 -139
  194. package/dist/types/Scene.d.ts +0 -39
  195. package/dist/types/Shaders.d.ts +0 -4
  196. package/dist/types/Storage.d.ts +0 -46
  197. package/dist/types/Time.d.ts +0 -22
  198. package/dist/types/Transform.d.ts +0 -41
  199. package/dist/types/components/BoxColliderDebug.d.ts +0 -19
  200. package/dist/types/components/CircleColliderDebug.d.ts +0 -19
  201. package/dist/types/components/Collider.d.ts +0 -53
  202. package/dist/types/components/GameObject.d.ts +0 -72
  203. package/dist/types/components/RigidBody.d.ts +0 -104
  204. package/dist/types/managers/AudioManager.d.ts +0 -60
  205. package/dist/types/managers/GLManager.d.ts +0 -47
  206. package/dist/types/managers/IDManager.d.ts +0 -21
  207. package/dist/types/managers/SceneManager.d.ts +0 -22
  208. 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 };