rig-play 1.8.1 → 1.9.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 CHANGED
@@ -38,7 +38,21 @@ links to either earlier name keep working.
38
38
 
39
39
  ### Measured
40
40
 
41
- Measured on the 0.x line, on the spineboy exports it then played (PoC, Apple silicon —
41
+ **The element mode's mesh backend is `'webgl'` by default since 1.9 (#109)**, read off
42
+ macOS Safari on 2026-10-13 (1.8.2, gallery rig `look-packed`: 4 meshes, 464
43
+ triangles, `sweep`; the demo's stats line after a 30 s settle; the Mac was also
44
+ serving the builds, so absolute fps is low and only the comparison is the point —
45
+ readings verbatim on #25):
46
+
47
+ | macOS Safari | 1 rig | 10 rigs (4,640 triangles) |
48
+ | --- | --: | --: |
49
+ | canvas2d mesh backend | 21–26 fps | 2–3 fps |
50
+ | webgl mesh backend | 60 fps | 58 fps |
51
+
52
+ Nothing is claimed here for mobile browsers: no phone or tablet has been measured
53
+ on the 1.x line.
54
+
55
+ The numbers below are older: measured on the 0.x line, on the spineboy exports it then played (PoC, Apple silicon —
42
56
  headless and on-device numbers are labeled, they do **not** substitute for each other).
43
57
  The renderer tiers they describe carried over; they have not been re-measured since the
44
58
  player moved to rigc's model (#60):
@@ -103,9 +117,12 @@ default stays `dom`.
103
117
  bounds, interleaved with the rigid `<img>` slots in one stacking context — the DOM
104
118
  handles the bones, a rasterizer handles the warps
105
119
  - ✅ Blend modes via `mix-blend-mode` (additive = `plus-lighter`)
106
- - ✅ Crack-free mesh seams on clip-antialiasing browsers (Safari): each triangle's
107
- clip polygon is expanded 0.5px from its centroid so neighbours overlap — the
108
- texture is continuous across shared edges, so the overlap is invisible
120
+ - ✅ Crack-free mesh seams on the canvas2d backend: canvas2d antialiases each
121
+ triangle's clip edge, so two triangles sharing an edge each leave it partly
122
+ transparent and the background shows through as a hairline — plainest over a
123
+ light background behind light art (#104). Each edge of a triangle's clip
124
+ polygon is moved 0.5px outward along its own normal so neighbours overlap —
125
+ the texture is continuous across shared edges, so the overlap is invisible
109
126
  (`?expand=0` shows the cracks for comparison)
110
127
  - ✅ RGB tinting (skeleton × slot × attachment color) via an SVG `feColorMatrix`
111
128
  reference filter per element — an exact channel multiply that works identically on
@@ -147,8 +164,8 @@ default stays `dom`.
147
164
  37 fps, meshed ×10 = 3–4 fps with JS flat at ~4–5 ms — put the real cost in the
148
165
  GPU process: Safari antialiases canvas2d **clip paths**, so the per-triangle clip
149
166
  mapping pays a per-triangle AA-mask tax (the same AA that caused the seam cracks)
150
- - ✅ Optional WebGL blit backend for the mesh tier: `renderer.meshBackend =
151
- 'webgl'` (default stays `'canvas2d'`). All dirty meshes are shelf-packed into
167
+ - ✅ WebGL blit backend for the mesh tier, the default since 1.9 (#109;
168
+ `renderer.meshBackend = 'canvas2d'` keeps the 2d raster). All dirty meshes are shelf-packed into
152
169
  **one shared offscreen WebGL context** (module-level — browsers cap contexts at
153
170
  ~16), drawn as textured triangles with premultiplied alpha, then rect-blitted
154
171
  onto the same per-part canvases with an unclipped `drawImage` — cheap on Safari.
@@ -583,12 +600,13 @@ onto shared pages instead. Both play here, unchanged:
583
600
  (Σ width × height), computed on demand. This is what `pixelRatio` moves
584
601
  quadratically, and the cheapest way to see an oversampling stage; the demo
585
602
  prints it in the stats line as `backing N Mpx`.
586
- - `renderer.meshBackend` — `'canvas2d'` (default) or `'webgl'`; same output, but
587
- heavy deforming scenes on Safari want `'webgl'` (see Measured above). Falls back
588
- to canvas2d automatically when WebGL is unavailable. Switching re-rasters every
603
+ - `renderer.meshBackend` — `'webgl'` (default since 1.9) or `'canvas2d'`; same
604
+ output, and on macOS Safari webgl holds 58–60 fps where canvas2d reads 2–26
605
+ (see Measured above). Falls back to canvas2d automatically when WebGL is
606
+ unavailable or the shared context is lost; `meshBackendActive` says which drew. Switching re-rasters every
589
607
  mesh once (no reallocation), so it is fine to expose as a user setting.
590
- - `renderer.triangleExpand` — clip overdraw in px that closes antialiased mesh
591
- seams (default 0.5). Also re-rasters every mesh once when changed.
608
+ - `renderer.triangleExpand` — how far (canvas px) each triangle edge's clip is
609
+ moved outward to close antialiased mesh seams (default 0.5). Also re-rasters every mesh once when changed.
592
610
  - `renderer.clipping` — apply the clips the frame carries (default `true`).
593
611
  Which slots a clip covers is decided where the frame is made — the player's
594
612
  adapter walks the draw order the way the runtime's draw loop does (one clip in
@@ -640,6 +658,30 @@ on-device (Chromium, dpr 2, stage under `scale(0.25)`, ~2.2 M CSS px on screen):
640
658
  | 92 meshes | `devicePixelRatio` (2) | 141.1 Mpx | 36–39 |
641
659
  | 92 meshes | `dpr × zoom` (0.5) | 9.1 Mpx | 60–61 |
642
660
 
661
+ ## Coming from 1.8: meshes draw through WebGL by default
662
+
663
+ From 1.9 the element mode rasterizes its meshes through the one shared WebGL
664
+ context unless told otherwise (`DomRenderer.meshBackend` defaults to `'webgl'`,
665
+ #109). The picture is the same within the parity suite's bounds, the API is
666
+ unchanged, and canvas2d is still drawn wherever WebGL is unavailable or the
667
+ context is lost. What a caller can notice:
668
+
669
+ - The page holds one more WebGL context (one for every mesh of every player, not
670
+ one per player). A page already near the browser's cap of about 16 live
671
+ contexts can lose its oldest one when this one is created.
672
+ - `meshBackendActive` reads `'webgl'` where it read `'canvas2d'`.
673
+ - Frame cost moves from the page to the GPU blit (see *Measured*).
674
+
675
+ To keep 1.8's raster, set it back, up front or on a live player:
676
+
677
+ ```ts
678
+ const player = new RigPlayer(root, assets, { meshBackend: 'canvas2d' });
679
+ // or, on a renderer you hold:
680
+ renderer.meshBackend = 'canvas2d';
681
+ ```
682
+
683
+ The canvas mode was already webgl by default and does not change.
684
+
643
685
  ## Coming from spine-html 0.8
644
686
 
645
687
  This is not a drop-in upgrade. 0.8 drew a Spine export (`.json` or `.skel`, plus its
@@ -692,7 +734,7 @@ name, and unloading is still elements first, bitmaps second.
692
734
  | `renderer.pixelRatio` | `player.renderer.pixelRatio` | On both renderers. Carried over when `player.mode` changes. |
693
735
  | `renderer.syncPixelRatio()` | `player.renderer.syncPixelRatio()` | On both renderers, the same meaning and the same deadband (since 1.3.0; before it, DOM mode only). |
694
736
  | `renderer.meshBackingPixels` | `DomRenderer.meshBackingPixels` | DOM mode only. |
695
- | `renderer.meshBackend`, `meshBackendActive`, type `MeshBackend` | `DomRenderer.meshBackend`, `meshBackendActive`, type `MeshBackend` | Same values (`'canvas2d'` default, `'webgl'`). Also settable up front as `RigPlayerOptions.meshBackend`. |
737
+ | `renderer.meshBackend`, `meshBackendActive`, type `MeshBackend` | `DomRenderer.meshBackend`, `meshBackendActive`, type `MeshBackend` | Same values; the default is `'webgl'` since 1.9 (`'canvas2d'` before). Also settable up front as `RigPlayerOptions.meshBackend`. |
696
738
  | `renderer.triangleExpand` | `DomRenderer.triangleExpand` | Same default (0.5). |
697
739
  | `renderer.clipping` | `player.renderer.clipping` | On both renderers. Carried over when `player.mode` changes. |
698
740
  | `clipCount`, `clipSkipCount`, `clipWriteCount` | same names on `DomRenderer` | `clipSkipCount` counts less than it did: a second clip met while one is in force, and a clip on an inactive bone, no longer reach the frame, so they are not counted. |
@@ -82,9 +82,10 @@ export declare class DomRenderer {
82
82
  /** Triangles rasterized last frame. */
83
83
  triangleCount: number;
84
84
  /**
85
- * How far (px) each triangle's clip polygon is expanded from its centroid.
86
- * Closes antialiased-clip cracks between adjacent triangles (Safari);
87
- * set to 0 to see them.
85
+ * How far (canvas px) each edge of a triangle's clip polygon is moved
86
+ * outward along its normal. Closes the antialiased-clip cracks between
87
+ * adjacent triangles, through which the background shows (#104); set to 0
88
+ * to see them.
88
89
  */
89
90
  triangleExpand: number;
90
91
  /**
@@ -96,9 +97,9 @@ export declare class DomRenderer {
96
97
  */
97
98
  pixelRatio: number;
98
99
  /**
99
- * Rasterizer for the mesh (deform) tier. 'canvas2d' (default) maps each
100
- * triangle with clip+transform+drawImage directly on the per-part canvas.
101
- * 'webgl' rasterizes every dirty mesh into one shared offscreen WebGL
100
+ * Rasterizer for the mesh (deform) tier. 'canvas2d' maps each triangle
101
+ * with clip+transform+drawImage directly on the per-part canvas. 'webgl'
102
+ * (default) rasterizes every dirty mesh into one shared offscreen WebGL
102
103
  * canvas and rect-blits each mesh back onto its per-part canvas — the DOM
103
104
  * structure and all element-level behavior (z-index interleave, tint
104
105
  * filter, mix-blend-mode, dirty-skip) are identical, only the raster step
@@ -107,7 +108,8 @@ export declare class DomRenderer {
107
108
  * process that rAF-limits heavy scenes; GL rasterizes shared edges
108
109
  * seamlessly (no clip, no crack overdraw) and the blit is an unclipped
109
110
  * rect copy. Falls back to 'canvas2d' when WebGL is unavailable or the
110
- * shared context is lost — see meshBackendActive.
111
+ * shared context is lost — see meshBackendActive. The default since 1.9
112
+ * (#109); before it, 'canvas2d'. Set 'canvas2d' to keep the 2d raster.
111
113
  */
112
114
  meshBackend: MeshBackend;
113
115
  /** Backend that actually rasterized the mesh tier during the last render(). */
@@ -294,9 +296,10 @@ export declare class DomRenderer {
294
296
  * texel of bilinear support is added, and the result is floor/ceil'd to texel
295
297
  * boundaries (so the sub-rect itself resamples nothing) and clamped to the
296
298
  * page. A fixed pad would not do: the texture-space reach of the expansion is
297
- * `triangleExpand` times the local texels-per-canvas-unit, which on the
298
- * demo's own meshes ranges from ~0.26 texels on a typical triangle to ~8.6 on
299
- * a foreshortened one. Under-padding lets the rim of the overdraw sample
299
+ * `triangleExpand` times the local texels-per-canvas-unit (up to
300
+ * `MITRE_LIMIT` times that at a sharp corner), which on the demo's own meshes
301
+ * ranged from ~0.26 texels on a typical triangle to ~8.6 on a foreshortened
302
+ * one. Under-padding lets the rim of the overdraw sample
300
303
  * nothing, which brings back the very seams the overdraw exists to close.
301
304
  *
302
305
  * No half-texel offset is applied to the incoming u/v: the `(i + 0.5) / size`
@@ -1 +1 @@
1
- {"version":3,"file":"DomRenderer.d.ts","sourceRoot":"","sources":["../src/DomRenderer.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DomRenderer.d.ts","sourceRoot":"","sources":["../src/DomRenderer.ts"],"names":[],"mappings":"AACA,OAAO,EAAmB,KAAK,WAAW,EAAuB,MAAM,iBAAiB,CAAC;AAEzF,OAAO,KAAK,EAAwB,UAAU,EAAqC,MAAM,iBAAiB,CAAC;AAE3G,kDAAkD;AAClD,MAAM,MAAM,WAAW,GAAG,UAAU,GAAG,OAAO,CAAC;AA4J/C;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,qBAAa,WAAW;IAqIpB,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAC7B,OAAO,CAAC,QAAQ,CAAC,KAAK;IAtIxB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,QAAQ,UAAQ;IAChB,8EAA8E;IAC9E,SAAS,SAAK;IACd;;;;;OAKG;IACH,aAAa,SAAK;IAClB;;;;OAIG;IACH,cAAc,SAAK;IACnB,2CAA2C;IAC3C,SAAS,SAAK;IACd,kEAAkE;IAClE,cAAc,SAAK;IACnB;;;;OAIG;IACH,kBAAkB,SAAK;IACvB,uCAAuC;IACvC,aAAa,SAAK;IAClB;;;;;OAKG;IACH,cAAc,SAAO;IACrB;;;;;;OAMG;IACH,UAAU,SAA+D;IACzE;;;;;;;;;;;;;;OAcG;IACH,WAAW,EAAE,WAAW,CAAW;IACnC,+EAA+E;IAC/E,iBAAiB,EAAE,WAAW,CAAW;IAEzC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA+B;IACrD,6FAA6F;IAC7F,OAAO,CAAC,WAAW,CAAK;IACxB,OAAO,CAAC,QAAQ,CAAC,WAAW,CAAqB;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAkB;IAC/C;;;;OAIG;IACH,OAAO,CAAC,OAAO,CAAsC;IACrD;;;;;;OAMG;IACH,OAAO,CAAC,eAAe,CAAyB;IAChD,OAAO,CAAC,QAAQ,CAA8B;IAC9C,iEAAiE;IACjE,OAAO,CAAC,UAAU,CAA0B;IAC5C,gFAAgF;IAChF,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAwB;IAClD,OAAO,CAAC,WAAW,CAAS;IAC5B,yEAAyE;IACzE,OAAO,CAAC,UAAU,CAAS;IAC3B,0EAA0E;IAC1E,OAAO,CAAC,WAAW,CAAuB;IAC1C,OAAO,CAAC,iBAAiB,CAAK;IAC9B;;;;OAIG;IACH,OAAO,CAAC,aAAa,CAAuB;IAC5C,6EAA6E;IAC7E,OAAO,CAAC,eAAe,CAAM;IAE7B;;;;;;;;;OASG;gBAEgB,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,WAAW,CAAC,MAAM,EAAE,WAAW,CAAC,EAC9C,KAAK,GAAE,WAAW,CAAC,MAAM,EAAE,gBAAgB,CAAa;IAG3E;;;;;;;;;;;OAWG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAM9B;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OAmCG;IACH,cAAc,IAAI,MAAM;IAKxB;;;;OAIG;IACH,MAAM,CAAC,KAAK,EAAE,UAAU,GAAG,IAAI;IAqE/B;;;;;;;;;;;;;;;;;OAiBG;IACH,OAAO,IAAI,IAAI;IAiBf,OAAO,CAAC,YAAY;IAwCpB,OAAO,CAAC,UAAU;IAiJlB;;;;;;OAMG;IACH,OAAO,CAAC,UAAU;IAWlB,sEAAsE;IACtE,OAAO,CAAC,eAAe;IAuCvB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA2DG;IACH,OAAO,CAAC,YAAY;IAWpB;;;;;;;;;;;;;OAaG;IACH,OAAO,CAAC,OAAO;IAmCf;;;;;;;;;;OAUG;IACH,OAAO,CAAC,SAAS;IAoDjB,OAAO,CAAC,WAAW;IAOnB,yEAAyE;IACzE,OAAO,CAAC,YAAY;IAUpB,OAAO,CAAC,aAAa;IAUrB,OAAO,CAAC,eAAe;IAUvB,OAAO,CAAC,YAAY;IAOpB,OAAO,CAAC,WAAW;IA4CnB,OAAO,CAAC,aAAa;IAyBrB,OAAO,CAAC,IAAI;IAmDZ,OAAO,CAAC,IAAI;CAWb"}
package/dist/index.js CHANGED
@@ -6845,13 +6845,41 @@ function getMeshGlBlitter() {
6845
6845
  const regionVertices = new Float32Array(8);
6846
6846
  const SVG_NS = "http://www.w3.org/2000/svg";
6847
6847
  let tintFilterSeq = 0;
6848
- function expandPoint(x, y, cx, cy, amount) {
6849
- const dx = x - cx;
6850
- const dy = y - cy;
6851
- const len = Math.sqrt(dx * dx + dy * dy);
6852
- if (len < 1e-6) return [x, y];
6853
- const s = amount / len;
6854
- return [x + dx * s, y + dy * s];
6848
+ const MITRE_LIMIT = 4;
6849
+ const offsetCorners = new Float64Array(6);
6850
+ function offsetTriangle(x0, y0, x1, y1, x2, y2, amount) {
6851
+ const out = offsetCorners;
6852
+ out[0] = x0;
6853
+ out[1] = y0;
6854
+ out[2] = x1;
6855
+ out[3] = y1;
6856
+ out[4] = x2;
6857
+ out[5] = y2;
6858
+ const area = (x1 - x0) * (y2 - y0) - (x2 - x0) * (y1 - y0);
6859
+ if (amount === 0 || area === 0) return out;
6860
+ const sign = area > 0 ? 1 : -1;
6861
+ let len = Math.hypot(x1 - x0, y1 - y0);
6862
+ const n0x = (y1 - y0) / len * sign, n0y = -(x1 - x0) / len * sign;
6863
+ len = Math.hypot(x2 - x1, y2 - y1);
6864
+ const n1x = (y2 - y1) / len * sign, n1y = -(x2 - x1) / len * sign;
6865
+ len = Math.hypot(x0 - x2, y0 - y2);
6866
+ const n2x = (y0 - y2) / len * sign, n2y = -(x0 - x2) / len * sign;
6867
+ offsetCorner(out, 0, n2x, n2y, n0x, n0y, amount);
6868
+ offsetCorner(out, 2, n0x, n0y, n1x, n1y, amount);
6869
+ offsetCorner(out, 4, n1x, n1y, n2x, n2y, amount);
6870
+ return out;
6871
+ }
6872
+ function offsetCorner(out, i, ax, ay, bx, by, amount) {
6873
+ const k = amount / (1 + ax * bx + ay * by);
6874
+ let dx = (ax + bx) * k, dy = (ay + by) * k;
6875
+ const reach = Math.hypot(dx, dy);
6876
+ const limit = MITRE_LIMIT * amount;
6877
+ if (reach > limit) {
6878
+ dx *= limit / reach;
6879
+ dy *= limit / reach;
6880
+ }
6881
+ out[i] += dx;
6882
+ out[i + 1] += dy;
6855
6883
  }
6856
6884
  function clipCoord(v) {
6857
6885
  return `${clipNum(v)}px`;
@@ -6931,9 +6959,10 @@ class DomRenderer {
6931
6959
  /** Triangles rasterized last frame. */
6932
6960
  triangleCount = 0;
6933
6961
  /**
6934
- * How far (px) each triangle's clip polygon is expanded from its centroid.
6935
- * Closes antialiased-clip cracks between adjacent triangles (Safari);
6936
- * set to 0 to see them.
6962
+ * How far (canvas px) each edge of a triangle's clip polygon is moved
6963
+ * outward along its normal. Closes the antialiased-clip cracks between
6964
+ * adjacent triangles, through which the background shows (#104); set to 0
6965
+ * to see them.
6937
6966
  */
6938
6967
  triangleExpand = 0.5;
6939
6968
  /**
@@ -6945,9 +6974,9 @@ class DomRenderer {
6945
6974
  */
6946
6975
  pixelRatio = typeof devicePixelRatio === "number" ? devicePixelRatio : 1;
6947
6976
  /**
6948
- * Rasterizer for the mesh (deform) tier. 'canvas2d' (default) maps each
6949
- * triangle with clip+transform+drawImage directly on the per-part canvas.
6950
- * 'webgl' rasterizes every dirty mesh into one shared offscreen WebGL
6977
+ * Rasterizer for the mesh (deform) tier. 'canvas2d' maps each triangle
6978
+ * with clip+transform+drawImage directly on the per-part canvas. 'webgl'
6979
+ * (default) rasterizes every dirty mesh into one shared offscreen WebGL
6951
6980
  * canvas and rect-blits each mesh back onto its per-part canvas — the DOM
6952
6981
  * structure and all element-level behavior (z-index interleave, tint
6953
6982
  * filter, mix-blend-mode, dirty-skip) are identical, only the raster step
@@ -6956,11 +6985,12 @@ class DomRenderer {
6956
6985
  * process that rAF-limits heavy scenes; GL rasterizes shared edges
6957
6986
  * seamlessly (no clip, no crack overdraw) and the blit is an unclipped
6958
6987
  * rect copy. Falls back to 'canvas2d' when WebGL is unavailable or the
6959
- * shared context is lost — see meshBackendActive.
6988
+ * shared context is lost — see meshBackendActive. The default since 1.9
6989
+ * (#109); before it, 'canvas2d'. Set 'canvas2d' to keep the 2d raster.
6960
6990
  */
6961
- meshBackend = "canvas2d";
6991
+ meshBackend = "webgl";
6962
6992
  /** Backend that actually rasterized the mesh tier during the last render(). */
6963
- meshBackendActive = "canvas2d";
6993
+ meshBackendActive = "webgl";
6964
6994
  views = /* @__PURE__ */ new Map();
6965
6995
  /** Counts render() calls: the stamp a view drawn this frame carries (`SlotView.drawnIn`). */
6966
6996
  frameNumber = 0;
@@ -7365,9 +7395,10 @@ class DomRenderer {
7365
7395
  * texel of bilinear support is added, and the result is floor/ceil'd to texel
7366
7396
  * boundaries (so the sub-rect itself resamples nothing) and clamped to the
7367
7397
  * page. A fixed pad would not do: the texture-space reach of the expansion is
7368
- * `triangleExpand` times the local texels-per-canvas-unit, which on the
7369
- * demo's own meshes ranges from ~0.26 texels on a typical triangle to ~8.6 on
7370
- * a foreshortened one. Under-padding lets the rim of the overdraw sample
7398
+ * `triangleExpand` times the local texels-per-canvas-unit (up to
7399
+ * `MITRE_LIMIT` times that at a sharp corner), which on the demo's own meshes
7400
+ * ranged from ~0.26 texels on a typical triangle to ~8.6 on a foreshortened
7401
+ * one. Under-padding lets the rim of the overdraw sample
7371
7402
  * nothing, which brings back the very seams the overdraw exists to close.
7372
7403
  *
7373
7404
  * No half-texel offset is applied to the incoming u/v: the `(i + 0.5) / size`
@@ -7624,11 +7655,10 @@ class DomRenderer {
7624
7655
  }
7625
7656
  }
7626
7657
  function drawTexturedTriangle(ctx, img, expand, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2) {
7627
- const cx = (x0 + x1 + x2) / 3;
7628
- const cy = (y0 + y1 + y2) / 3;
7629
- const [px0, py0] = expandPoint(x0, y0, cx, cy, expand);
7630
- const [px1, py1] = expandPoint(x1, y1, cx, cy, expand);
7631
- const [px2, py2] = expandPoint(x2, y2, cx, cy, expand);
7658
+ const corners = offsetTriangle(x0, y0, x1, y1, x2, y2, expand);
7659
+ const px0 = corners[0], py0 = corners[1];
7660
+ const px1 = corners[2], py1 = corners[3];
7661
+ const px2 = corners[4], py2 = corners[5];
7632
7662
  ctx.beginPath();
7633
7663
  ctx.moveTo(px0, py0);
7634
7664
  ctx.lineTo(px1, py1);