@codexo/exojs 0.15.0 → 0.15.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +51 -0
  2. package/dist/esm/audio/Playable.d.ts +6 -1
  3. package/dist/esm/audio/Playable.d.ts.map +1 -1
  4. package/dist/esm/core/BuildInfo.js +2 -2
  5. package/dist/esm/core/SceneNode.d.ts +10 -1
  6. package/dist/esm/core/SceneNode.d.ts.map +1 -1
  7. package/dist/esm/core/SceneNode.js +10 -1
  8. package/dist/esm/core/SceneNode.js.map +1 -1
  9. package/dist/esm/debug/BoundingBoxesLayer.d.ts.map +1 -1
  10. package/dist/esm/debug/BoundingBoxesLayer.js +2 -1
  11. package/dist/esm/debug/BoundingBoxesLayer.js.map +1 -1
  12. package/dist/esm/debug/HitTestLayer.js +4 -4
  13. package/dist/esm/debug/HitTestLayer.js.map +1 -1
  14. package/dist/esm/debug/PerformanceLayer.js +1 -1
  15. package/dist/esm/debug/PerformanceLayer.js.map +1 -1
  16. package/dist/esm/debug/RenderPassInspectorLayer.js +2 -2
  17. package/dist/esm/debug/RenderPassInspectorLayer.js.map +1 -1
  18. package/dist/esm/math/AbstractVector.d.ts +4 -3
  19. package/dist/esm/math/AbstractVector.d.ts.map +1 -1
  20. package/dist/esm/math/AbstractVector.js +5 -4
  21. package/dist/esm/math/AbstractVector.js.map +1 -1
  22. package/dist/esm/math/ObservableVector.d.ts +2 -0
  23. package/dist/esm/math/ObservableVector.d.ts.map +1 -1
  24. package/dist/esm/math/ObservableVector.js +9 -0
  25. package/dist/esm/math/ObservableVector.js.map +1 -1
  26. package/dist/esm/math/Polygon.d.ts.map +1 -1
  27. package/dist/esm/math/Polygon.js +4 -1
  28. package/dist/esm/math/Polygon.js.map +1 -1
  29. package/dist/esm/math/collision-detection.d.ts.map +1 -1
  30. package/dist/esm/math/collision-detection.js +13 -13
  31. package/dist/esm/math/collision-detection.js.map +1 -1
  32. package/dist/esm/math/collision-primitives.d.ts +1 -1
  33. package/dist/esm/math/collision-primitives.d.ts.map +1 -1
  34. package/dist/esm/math/collision-primitives.js +4 -1
  35. package/dist/esm/math/collision-primitives.js.map +1 -1
  36. package/dist/esm/rendering/primitives/Graphics.d.ts +7 -0
  37. package/dist/esm/rendering/primitives/Graphics.d.ts.map +1 -1
  38. package/dist/esm/rendering/primitives/Graphics.js +20 -6
  39. package/dist/esm/rendering/primitives/Graphics.js.map +1 -1
  40. package/dist/esm/rendering/sprite/AnimatedSprite.d.ts +1 -0
  41. package/dist/esm/rendering/sprite/AnimatedSprite.d.ts.map +1 -1
  42. package/dist/esm/rendering/sprite/AnimatedSprite.js +18 -1
  43. package/dist/esm/rendering/sprite/AnimatedSprite.js.map +1 -1
  44. package/dist/esm/rendering/sprite/Sprite.d.ts.map +1 -1
  45. package/dist/esm/rendering/sprite/Sprite.js +8 -0
  46. package/dist/esm/rendering/sprite/Sprite.js.map +1 -1
  47. package/dist/esm/rendering/text/TextLayout.d.ts.map +1 -1
  48. package/dist/esm/rendering/text/TextLayout.js +2 -3
  49. package/dist/esm/rendering/text/TextLayout.js.map +1 -1
  50. package/dist/esm/resources/coreAssetBindings.d.ts +11 -0
  51. package/dist/esm/resources/coreAssetBindings.d.ts.map +1 -1
  52. package/dist/esm/resources/coreAssetBindings.js +10 -2
  53. package/dist/esm/resources/coreAssetBindings.js.map +1 -1
  54. package/dist/exo.debug.esm.js +1 -1
  55. package/dist/exo.debug.esm.js.map +1 -1
  56. package/dist/exo.esm.js +1 -1
  57. package/dist/exo.esm.js.map +1 -1
  58. package/dist/exo.iife.js +104 -33
  59. package/dist/exo.iife.js.map +1 -1
  60. package/dist/exo.iife.min.js +1 -1
  61. package/dist/exo.iife.min.js.map +1 -1
  62. package/package.json +1 -1
package/dist/exo.iife.js CHANGED
@@ -1769,12 +1769,13 @@ var Exo = (function (exports) {
1769
1769
  */
1770
1770
  class AbstractVector {
1771
1771
  /**
1772
- * Angle of this vector in radians, measured from the positive Y-axis
1773
- * (clockwise). Setting this rotates the vector to the new angle while
1774
- * preserving its {@link length}. Mutates in place.
1772
+ * Angle of this vector in radians, measured from the positive X-axis
1773
+ * (the same convention as `PolarVector.phi`). Setting this rotates the
1774
+ * vector to the new angle while preserving its {@link length}. Mutates in
1775
+ * place.
1775
1776
  */
1776
1777
  get angle() {
1777
- return Math.atan2(this.x, this.y);
1778
+ return Math.atan2(this.y, this.x);
1778
1779
  }
1779
1780
  set angle(angle) {
1780
1781
  const length = this.length;
@@ -2089,7 +2090,10 @@ var Exo = (function (exports) {
2089
2090
  const normY = (y1 - y2) / ry;
2090
2091
  return normX * normX + normY * normY <= 1;
2091
2092
  };
2092
- const intersectionPointPoly$1 = (point, { points }) => polygonContainsPoint(point, points);
2093
+ const intersectionPointPoly$1 = (point, { x: offsetX, y: offsetY, points }) =>
2094
+ // Shift the point into the polygon's local space so its x/y position
2095
+ // offset is honoured (the local `points` never carry the offset).
2096
+ polygonContainsPoint({ x: point.x - offsetX, y: point.y - offsetY }, points);
2093
2097
  const intersectionLineLineSegments = (a1, a2, b1, b2) => {
2094
2098
  const denominator = (a2.x - a1.x) * (b2.y - b1.y) - (b2.x - b1.x) * (a2.y - a1.y);
2095
2099
  if (Math.abs(denominator) <= epsilon$1) {
@@ -2317,8 +2321,13 @@ var Exo = (function (exports) {
2317
2321
  if (getVoronoiRegion(edgeX, edgeY, pointX, pointY) !== VoronoiRegion.right) {
2318
2322
  return false;
2319
2323
  }
2320
- const region = getVoronoiRegion(nextEdge.x, nextEdge.y, circleX - nextPoint.x, circleY - nextPoint.y);
2321
- return region === VoronoiRegion.left && getVectorLength(pointX, pointY) > radius;
2324
+ // In the right region the closest polygon feature is the *next* vertex, so
2325
+ // both the region refinement and the distance test use `circle - nextPoint`
2326
+ // (mirrors the right-region branch of getCollisionPolygonCircle).
2327
+ const positionBx = circleX - nextPoint.x;
2328
+ const positionBy = circleY - nextPoint.y;
2329
+ const region = getVoronoiRegion(nextEdge.x, nextEdge.y, positionBx, positionBy);
2330
+ return region === VoronoiRegion.left && getVectorLength(positionBx, positionBy) > radius;
2322
2331
  };
2323
2332
  const shouldExcludeMiddleVoronoi = (pointX, pointY, radius, edgeX, edgeY) => {
2324
2333
  const normalX = edgeY;
@@ -2331,15 +2340,10 @@ var Exo = (function (exports) {
2331
2340
  return distance > 0 && Math.abs(distance) > radius;
2332
2341
  };
2333
2342
  const intersectionCirclePoly = ({ x: cx, y: cy, radius }, { x: px, y: py, points, edges }) => {
2334
- // Frame transform: express the circle's position relative to the polygon's
2335
- // local space, but with the sign inverted (poly.position - circle.position
2336
- // rather than the natural circle.position - poly.position). The poly's
2337
- // `points` are then in their local coordinates and the Voronoi-region tests
2338
- // below combine them with the negated-offset circle position to reach the
2339
- // same value as a positively-offset frame would. Don't flip without
2340
- // re-deriving the Voronoi math.
2341
- const circleX = px - cx;
2342
- const circleY = py - cy;
2343
+ // Frame transform: the circle centre expressed in the polygon's local space
2344
+ // (circle.position - poly.position), matching getCollisionPolygonCircle.
2345
+ const circleX = cx - px;
2346
+ const circleY = cy - py;
2343
2347
  const len = points.length;
2344
2348
  for (let i = 0; i < len; i++) {
2345
2349
  // i, prev, next are all valid indices into the parallel length-`len`
@@ -2428,8 +2432,8 @@ var Exo = (function (exports) {
2428
2432
  difference.destroy();
2429
2433
  return null;
2430
2434
  }
2431
- const projectionN = difference.clone().normalize();
2432
- const projectionV = difference.multiply(overlap);
2435
+ const projectionN = difference.normalize();
2436
+ const projectionV = projectionN.clone().multiply(overlap);
2433
2437
  return {
2434
2438
  shapeA: circleA,
2435
2439
  shapeB: circleB,
@@ -2942,10 +2946,19 @@ var Exo = (function (exports) {
2942
2946
  this._owner?._onObservableChange(this._channel);
2943
2947
  }
2944
2948
  }
2949
+ // The getters must be redeclared alongside the setter overrides: a
2950
+ // setter-only accessor on the subclass prototype would shadow the whole
2951
+ // inherited accessor pair, leaving `get` undefined.
2952
+ get angle() {
2953
+ return Math.atan2(this._y, this._x);
2954
+ }
2945
2955
  set angle(angle) {
2946
2956
  const length = this.length;
2947
2957
  this.set(Math.cos(angle) * length, Math.sin(angle) * length);
2948
2958
  }
2959
+ get length() {
2960
+ return Math.sqrt(this._x * this._x + this._y * this._y);
2961
+ }
2949
2962
  set length(magnitude) {
2950
2963
  const angle = this.angle;
2951
2964
  this.set(Math.cos(angle) * magnitude, Math.sin(angle) * magnitude);
@@ -6607,6 +6620,9 @@ var Exo = (function (exports) {
6607
6620
  const length = Math.sqrt(axis.x * axis.x + axis.y * axis.y) || 1;
6608
6621
  const nx = axis.x / length;
6609
6622
  const ny = axis.y / length;
6623
+ // World-space projection: the polygon's x/y offset shifts every vertex by
6624
+ // the same amount, so it contributes a constant term along the axis.
6625
+ const offset = nx * this._position.x + ny * this._position.y;
6610
6626
  const points = this._points;
6611
6627
  let min = Infinity;
6612
6628
  let max = -Infinity;
@@ -6619,7 +6635,7 @@ var Exo = (function (exports) {
6619
6635
  if (projection > max)
6620
6636
  max = projection;
6621
6637
  }
6622
- return result.set(min, max);
6638
+ return result.set(min + offset, max + offset);
6623
6639
  }
6624
6640
  contains(x, y) {
6625
6641
  return intersectionPointPoly(Vector.temp.set(x, y), this);
@@ -7289,9 +7305,18 @@ var Exo = (function (exports) {
7289
7305
  this._invalidateSubtreeTransform();
7290
7306
  this._invalidateBoundsCascade();
7291
7307
  }
7308
+ /**
7309
+ * Re-derive `origin` from the fractional anchor and the CURRENT local
7310
+ * bounds. Uses local (untransformed) bounds on purpose: the transform
7311
+ * multiplies the origin by scale itself, so deriving from world bounds
7312
+ * would double-apply scale whenever the anchor is set after scaling.
7313
+ * Subclasses whose local bounds change after construction (e.g. a sprite
7314
+ * switching to a texture sub-frame) must call this to keep an anchored
7315
+ * node anchored.
7316
+ */
7292
7317
  _updateOrigin() {
7293
7318
  const { x, y } = this._anchor;
7294
- const { width, height } = this.getBounds();
7319
+ const { width, height } = this.getLocalBounds();
7295
7320
  this.setOrigin(width * x, height * y);
7296
7321
  }
7297
7322
  }
@@ -16342,6 +16367,14 @@ var Exo = (function (exports) {
16342
16367
  this.width = width;
16343
16368
  this.height = height;
16344
16369
  }
16370
+ // The local bounds changed size — re-derive the origin from the
16371
+ // fractional anchor, or an anchored sprite keeps the OLD bounds' pixel
16372
+ // origin and renders offset by the size difference (an anchored sprite
16373
+ // switching from the full atlas to its first animation frame used to
16374
+ // land hundreds of pixels off-canvas).
16375
+ if (this.anchor.x !== 0 || this.anchor.y !== 0) {
16376
+ this._updateOrigin();
16377
+ }
16345
16378
  this.invalidateCache();
16346
16379
  return this;
16347
16380
  }
@@ -16618,13 +16651,12 @@ var Exo = (function (exports) {
16618
16651
  const extraPerGap = (maxLineWidth - line.width) / gaps;
16619
16652
  let wordIdx = -1;
16620
16653
  let prevWasSpace = true;
16621
- const spaceAdv = provider.getGlyph(' ', fontSize).advance;
16622
16654
  for (const entry of line.placements) {
16623
- if (prevWasSpace && entry.info.advance !== spaceAdv) {
16655
+ if (prevWasSpace && entry.char !== ' ') {
16624
16656
  wordIdx++;
16625
16657
  prevWasSpace = false;
16626
16658
  }
16627
- else if (!prevWasSpace && entry.info.advance === spaceAdv) {
16659
+ else if (!prevWasSpace && entry.char === ' ') {
16628
16660
  prevWasSpace = true;
16629
16661
  }
16630
16662
  result.push({
@@ -33408,14 +33440,22 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
33408
33440
  * parent's source. `new URL(ref, source)` only works when `source` is an
33409
33441
  * absolute URL; loaders are frequently called with relative paths (e.g.
33410
33442
  * `assets/demo/fonts/x.fnt`), so fall back to a synthetic base and strip it.
33443
+ * A root-absolute source (`/assets/demo/fonts/x.fnt`) must yield a
33444
+ * root-absolute result again — dropping the leading slash would make the
33445
+ * browser re-resolve the page image against the document base URL.
33446
+ * @internal exported for tests
33411
33447
  */
33412
33448
  function resolveSubAssetPath(ref, source) {
33449
+ if (/^(?:[a-z][a-z\d+.-]*:|\/\/|\/)/i.test(ref)) {
33450
+ return ref;
33451
+ }
33413
33452
  try {
33414
33453
  return new URL(ref, source).href;
33415
33454
  }
33416
33455
  catch {
33417
33456
  const base = 'https://exojs.invalid/';
33418
- return new URL(ref, base + source.replace(/^\/+/, '')).href.slice(base.length);
33457
+ const resolved = new URL(ref, base + source.replace(/^\/+/, '')).href.slice(base.length);
33458
+ return source.startsWith('/') ? `/${resolved}` : resolved;
33419
33459
  }
33420
33460
  }
33421
33461
  // ---------------------------------------------------------------------------
@@ -36661,12 +36701,26 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36661
36701
  this.addChild(this._createStrokeMesh(data));
36662
36702
  return this;
36663
36703
  }
36704
+ /**
36705
+ * Stroke a shape's perimeter. Shape builders return their outline OPEN
36706
+ * (first point not repeated), but `buildPath` only closes a path whose
36707
+ * first and last points coincide — so the closing segment (e.g. a
36708
+ * rectangle's left edge) went missing. Repeat the first point here.
36709
+ */
36710
+ _strokeClosedOutline(points) {
36711
+ if (points.length >= 4) {
36712
+ this.drawPath([...points, points[0], points[1]]);
36713
+ }
36714
+ else {
36715
+ this.drawPath(points);
36716
+ }
36717
+ }
36664
36718
  /** Fill a closed polygon defined by `[x0,y0, x1,y1, ...]` and optionally stroke its outline. */
36665
36719
  drawPolygon(path) {
36666
36720
  const data = buildPolygon(path);
36667
36721
  this._appendFill(data);
36668
36722
  if (this._lineWidth > 0) {
36669
- this.drawPath(data.points);
36723
+ this._strokeClosedOutline(data.points);
36670
36724
  }
36671
36725
  return this;
36672
36726
  }
@@ -36675,7 +36729,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36675
36729
  const data = buildCircle(centerX, centerY, radius);
36676
36730
  this._appendFill(data);
36677
36731
  if (this._lineWidth > 0) {
36678
- this.drawPath(data.points);
36732
+ this._strokeClosedOutline(data.points);
36679
36733
  }
36680
36734
  return this;
36681
36735
  }
@@ -36684,7 +36738,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36684
36738
  const data = buildEllipse(centerX, centerY, radiusX, radiusY);
36685
36739
  this._appendFill(data);
36686
36740
  if (this._lineWidth > 0) {
36687
- this.drawPath(data.points);
36741
+ this._strokeClosedOutline(data.points);
36688
36742
  }
36689
36743
  return this;
36690
36744
  }
@@ -36693,7 +36747,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36693
36747
  const data = buildRectangle(x, y, width, height);
36694
36748
  this._appendFill(data);
36695
36749
  if (this._lineWidth > 0) {
36696
- this.drawPath(data.points);
36750
+ this._strokeClosedOutline(data.points);
36697
36751
  }
36698
36752
  return this;
36699
36753
  }
@@ -36706,7 +36760,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36706
36760
  const data = buildRoundedRectangle(x, y, width, height, radius);
36707
36761
  this._appendFill(data);
36708
36762
  if (this._lineWidth > 0) {
36709
- this.drawPath(data.points);
36763
+ this._strokeClosedOutline(data.points);
36710
36764
  }
36711
36765
  return this;
36712
36766
  }
@@ -36718,7 +36772,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36718
36772
  const data = buildStar(centerX, centerY, points, radius, innerRadius, rotation);
36719
36773
  this._appendFill(data);
36720
36774
  if (this._lineWidth > 0) {
36721
- this.drawPath(data.points);
36775
+ this._strokeClosedOutline(data.points);
36722
36776
  }
36723
36777
  return this;
36724
36778
  }
@@ -36885,6 +36939,7 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
36885
36939
  _clips = new Map();
36886
36940
  _currentClipName = null;
36887
36941
  _currentFrameIndex = 0;
36942
+ _hasAppliedFrame = false;
36888
36943
  _playing = false;
36889
36944
  _repeatOverride = null;
36890
36945
  _elapsedFrameTimeMs = 0;
@@ -37165,7 +37220,23 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
37165
37220
  */
37166
37221
  _applyFrame(clip, frameIndex) {
37167
37222
  // In-bounds by every call site's own guard.
37168
- this.setTextureFrame(clip.frames[frameIndex], false);
37223
+ if (this._hasAppliedFrame) {
37224
+ // Frame-to-frame advance: keep the current pixel size so differently
37225
+ // sized frames don't visibly pop.
37226
+ this.setTextureFrame(clip.frames[frameIndex], false);
37227
+ }
37228
+ else {
37229
+ // First application: the sprite still shows the full source texture
37230
+ // (usually the whole atlas), so "keep the pixel size" would inflate the
37231
+ // scale by atlasSize/frameSize and the sprite would render blown up far
37232
+ // beyond the canvas. Snap the logical size to the frame instead — while
37233
+ // preserving the user's scale, which `resetSize` would reset to 1.
37234
+ const scaleX = this.scale.x;
37235
+ const scaleY = this.scale.y;
37236
+ this.setTextureFrame(clip.frames[frameIndex], true);
37237
+ this.scale.set(scaleX, scaleY);
37238
+ this._hasAppliedFrame = true;
37239
+ }
37169
37240
  const offset = clip.frameOffsets?.[frameIndex];
37170
37241
  if (offset) {
37171
37242
  this.getLocalBounds().setPosition(offset.x, offset.y);
@@ -40139,8 +40210,8 @@ fn fragmentMain(input: VertexOutput) -> @location(0) vec4<f32> {
40139
40210
  }
40140
40211
 
40141
40212
  const buildInfo = Object.freeze({
40142
- version: "0.15.0",
40143
- revision: "18cadeb",
40213
+ version: "0.15.1",
40214
+ revision: "6f825a8",
40144
40215
  development: false,
40145
40216
  });
40146
40217