spine-html 0.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 firejune (Joon Kyoung)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/NOTICE.md ADDED
@@ -0,0 +1,19 @@
1
+ # Third-party notices
2
+
3
+ ## Spine Runtimes
4
+
5
+ This project depends on `@esotericsoftware/spine-core`, part of the
6
+ [Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes),
7
+ Copyright (c) 2013-2025 Esoteric Software LLC, licensed under the
8
+ [Spine Runtimes License Agreement](https://esotericsoftware.com/spine-runtimes-license).
9
+
10
+ Key obligation that propagates to users of this project: integration of the Spine
11
+ Runtimes into software (including via this renderer) is permitted **provided that each
12
+ user of the resulting product obtains their own Spine Editor license**, and any
13
+ redistribution includes the Spine Runtimes license and copyright notice.
14
+
15
+ ## Example assets
16
+
17
+ The spineboy skeleton, atlas, and images used by the demo are owned by Esoteric
18
+ Software. They are **not redistributed** in this repository; `scripts/fetch-assets.sh`
19
+ downloads them from the official spine-runtimes repository for local evaluation.
package/README.md ADDED
@@ -0,0 +1,124 @@
1
+ # spine-html
2
+
3
+ Render [Spine](https://esotericsoftware.com/) skeletal animations as **plain DOM** — one
4
+ absolutely-positioned `<img>` per slot, posed with a single CSS `matrix()` write per frame.
5
+ No canvas, no WebGL for the rigid tier.
6
+
7
+ ## Why
8
+
9
+ Every Spine web runtime rasterizes into a canvas. But `spine-core` is fully
10
+ renderer-independent: it computes bone world transforms — and even deformed mesh
11
+ vertices — on the CPU. For **region attachments** (the rigid parts of a skeleton), a bone
12
+ transform is a plain affine map, and CSS `transform: matrix()` expresses that *exactly*.
13
+ So the rigid tier of a Spine skeleton can live in the DOM, composited by the browser,
14
+ inspectable in devtools, styled with CSS.
15
+
16
+ The idea has been floated on the official forum three times since 2014 and was never
17
+ built — the blockers named were meshes, clipping, and DOM update overhead. This project
18
+ is the experiment: how far does the DOM actually go, and how cheap is it?
19
+
20
+ Measured so far (PoC, Chromium on Apple silicon):
21
+
22
+ - Rigid only (spineboy-ess): 10 skeletons / 180 slot images at **~0.03 ms skeleton math
23
+ + ~0.22 ms DOM writes per frame** — about 1.5% of a 60 fps frame budget.
24
+ - With meshes (spineboy-pro): 1 skeleton = **~0.05 ms + ~0.5 ms render** (8 mesh
25
+ canvases, 323 triangles); 10 running skeletons = **~4.4 ms/frame** total.
26
+
27
+ ## Status
28
+
29
+ **Proof of concept.**
30
+
31
+ - ✅ Region attachments (rigid parts): exact affine mapping, draw-order via `z-index`,
32
+ attachment swaps, alpha
33
+ - ✅ Atlas unpacking at load time (90°-packed regions restored), so the rigid per-frame
34
+ path never touches a canvas
35
+ - ✅ Mesh attachments (deform tier): small per-part canvases sized to the mesh's world
36
+ bounds, interleaved with the rigid `<img>` slots in one stacking context — the DOM
37
+ handles the bones, a rasterizer handles the warps
38
+ - ✅ Blend modes via `mix-blend-mode` (additive = `plus-lighter`)
39
+ - ✅ Crack-free mesh seams on clip-antialiasing browsers (Safari): each triangle's
40
+ clip polygon is expanded 0.5px from its centroid so neighbours overlap — the
41
+ texture is continuous across shared edges, so the overlap is invisible
42
+ (`?expand=0` shows the cracks for comparison)
43
+ - ⬜ Clipping — deliberately unsupported (counted and skipped); layered transparent
44
+ parts + `overflow: hidden` cover the practical cases
45
+ - ⬜ RGB tinting (alpha works); DPR-aware mesh canvas backing store
46
+ - ⬜ WebKit mesh-canvas cost: Safari rasterizes the per-triangle path noticeably
47
+ slower than Chromium (measured ~7.6ms vs ~0.5ms per frame headless) — dirty-check
48
+ skipping and/or an optional WebGL blit backend are the candidates
49
+
50
+ ## Install
51
+
52
+ ```bash
53
+ npm i spine-html @esotericsoftware/spine-core
54
+ ```
55
+
56
+ ```ts
57
+ import { TextureAtlas, AtlasAttachmentLoader, SkeletonJson, Skeleton,
58
+ AnimationState, AnimationStateData, Physics } from '@esotericsoftware/spine-core';
59
+ import { SpineHtmlRenderer, DomTexture, unpackRegions } from 'spine-html';
60
+
61
+ // Load: parse the atlas, attach page images, unpack per-region bitmaps once.
62
+ const atlas = new TextureAtlas(atlasText);
63
+ const pageImages = new Map<string, HTMLImageElement>();
64
+ for (const page of atlas.pages) {
65
+ const image = await loadImage(page.name); // your loader
66
+ page.setTexture(new DomTexture(image));
67
+ pageImages.set(page.name, image);
68
+ }
69
+ const regionImages = await unpackRegions(atlas, pageImages);
70
+ const data = new SkeletonJson(new AtlasAttachmentLoader(atlas)).readSkeletonData(jsonText);
71
+
72
+ // A positioned element becomes the skeleton origin (Spine is Y-up: the
73
+ // skeleton grows upward from it).
74
+ const skeleton = new Skeleton(data);
75
+ const state = new AnimationState(new AnimationStateData(data));
76
+ state.setAnimation(0, 'walk', true);
77
+ const renderer = new SpineHtmlRenderer(rootElement, regionImages);
78
+
79
+ function frame(delta: number) {
80
+ state.update(delta);
81
+ state.apply(skeleton);
82
+ skeleton.update(delta);
83
+ skeleton.updateWorldTransform(Physics.update);
84
+ renderer.render(skeleton);
85
+ }
86
+ ```
87
+
88
+ `spine-html` is a third-party renderer and is not affiliated with or endorsed by
89
+ Esoteric Software.
90
+
91
+ ## Demo (this repository)
92
+
93
+ ```bash
94
+ bun install
95
+ bun run dev
96
+ ```
97
+
98
+ The official spineboy example assets are downloaded automatically on first `dev`/`build`
99
+ (they are owned by Esoteric Software and not redistributed in this repository — see
100
+ [NOTICE.md](NOTICE.md)); `bun run fetch-assets` runs the same idempotent step manually.
101
+
102
+ ## How it works
103
+
104
+ 1. `@esotericsoftware/spine-core` loads the skeleton and drives `AnimationState`,
105
+ constraints, and physics — all CPU-side, renderer-agnostic.
106
+ 2. At load time each atlas region is cut into its own bitmap (rotation restored), one
107
+ blob URL per region.
108
+ 3. Per frame, for each slot in draw order: `computeWorldVertices` yields the region's
109
+ four corners in world space (order **BL, UL, UR, BR** — note the comments inside
110
+ `computeWorldVertices` are stale). Flip Y (Spine is Y-up), derive the affine from
111
+ three corners, write one `matrix()` string. That's the whole render path.
112
+
113
+ ## License
114
+
115
+ The code in this repository is MIT licensed.
116
+
117
+ It depends on the [Spine Runtimes](https://github.com/EsotericSoftware/spine-runtimes)
118
+ (`@esotericsoftware/spine-core`), which are licensed under the
119
+ [Spine Runtimes License](https://esotericsoftware.com/spine-runtimes-license):
120
+ **integrating the Spine Runtimes — including through this project — requires each user
121
+ to have their own valid Spine Editor license.** See [NOTICE.md](NOTICE.md).
122
+
123
+ Example assets (spineboy) are owned by Esoteric Software and are fetched from the
124
+ official repository at setup time, not redistributed here.
@@ -0,0 +1,29 @@
1
+ import { Texture, TextureAtlas, type TextureFilter, type TextureWrap } from '@esotericsoftware/spine-core';
2
+ /**
3
+ * Minimal Texture implementation for the DOM renderer. spine-core only needs
4
+ * it as a handle attached to atlas pages; filtering/wrapping are GPU concepts
5
+ * with no DOM equivalent, so they are no-ops.
6
+ */
7
+ export declare class DomTexture extends Texture {
8
+ setFilters(_minFilter: TextureFilter, _magFilter: TextureFilter): void;
9
+ setWraps(_uWrap: TextureWrap, _vWrap: TextureWrap): void;
10
+ dispose(): void;
11
+ }
12
+ export interface RegionImage {
13
+ /** Blob URL of the unpacked (rotation-restored) region pixels. */
14
+ url: string;
15
+ /** Unpacked width in atlas pixels. */
16
+ width: number;
17
+ /** Unpacked height in atlas pixels. */
18
+ height: number;
19
+ }
20
+ /**
21
+ * Cuts every atlas region out of the page image into its own bitmap once at
22
+ * load time, restoring 90° packing rotation, so the per-frame path never
23
+ * touches a canvas. Returns blob URLs keyed by region name.
24
+ *
25
+ * This is a loading-pipeline step, not a rendering step: after this runs,
26
+ * rendering is pure DOM (one <img> per slot, one CSS matrix write per frame).
27
+ */
28
+ export declare function unpackRegions(atlas: TextureAtlas, pageImages: Map<string, HTMLImageElement>): Promise<Map<string, RegionImage>>;
29
+ //# sourceMappingURL=DomTexture.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DomTexture.d.ts","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,EACP,YAAY,EAEZ,KAAK,aAAa,EAClB,KAAK,WAAW,EACjB,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,qBAAa,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAU,EAAE,aAAa,EAAE,UAAU,EAAE,aAAa,GAAG,IAAI;IACtE,QAAQ,CAAC,MAAM,EAAE,WAAW,EAAE,MAAM,EAAE,WAAW,GAAG,IAAI;IACxD,OAAO,IAAI,IAAI;CAChB;AAED,MAAM,WAAW,WAAW;IAC1B,kEAAkE;IAClE,GAAG,EAAE,MAAM,CAAC;IACZ,sCAAsC;IACtC,KAAK,EAAE,MAAM,CAAC;IACd,uCAAuC;IACvC,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;GAOG;AACH,wBAAsB,aAAa,CACjC,KAAK,EAAE,YAAY,EACnB,UAAU,EAAE,GAAG,CAAC,MAAM,EAAE,gBAAgB,CAAC,GACxC,OAAO,CAAC,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC,CAAC,CAiCnC"}
@@ -0,0 +1,52 @@
1
+ import { Texture, } from '@esotericsoftware/spine-core';
2
+ /**
3
+ * Minimal Texture implementation for the DOM renderer. spine-core only needs
4
+ * it as a handle attached to atlas pages; filtering/wrapping are GPU concepts
5
+ * with no DOM equivalent, so they are no-ops.
6
+ */
7
+ export class DomTexture extends Texture {
8
+ setFilters(_minFilter, _magFilter) { }
9
+ setWraps(_uWrap, _vWrap) { }
10
+ dispose() { }
11
+ }
12
+ /**
13
+ * Cuts every atlas region out of the page image into its own bitmap once at
14
+ * load time, restoring 90° packing rotation, so the per-frame path never
15
+ * touches a canvas. Returns blob URLs keyed by region name.
16
+ *
17
+ * This is a loading-pipeline step, not a rendering step: after this runs,
18
+ * rendering is pure DOM (one <img> per slot, one CSS matrix write per frame).
19
+ */
20
+ export async function unpackRegions(atlas, pageImages) {
21
+ const result = new Map();
22
+ for (const region of atlas.regions) {
23
+ const atlasRegion = region;
24
+ const image = pageImages.get(atlasRegion.page.name);
25
+ if (!image)
26
+ throw new Error(`Missing page image: ${atlasRegion.page.name}`);
27
+ const w = region.width;
28
+ const h = region.height;
29
+ const canvas = document.createElement('canvas');
30
+ canvas.width = w;
31
+ canvas.height = h;
32
+ const ctx = canvas.getContext('2d');
33
+ if (!ctx)
34
+ throw new Error('2d context unavailable');
35
+ if (region.degrees === 90) {
36
+ // The region is packed rotated: it occupies an h×w rect in the page.
37
+ // Rotate it back so the bitmap is in artwork orientation.
38
+ ctx.translate(0, h);
39
+ ctx.rotate(-Math.PI / 2);
40
+ ctx.drawImage(image, atlasRegion.x, atlasRegion.y, h, w, 0, 0, h, w);
41
+ }
42
+ else {
43
+ ctx.drawImage(image, atlasRegion.x, atlasRegion.y, w, h, 0, 0, w, h);
44
+ }
45
+ const blob = await new Promise((resolve, reject) => {
46
+ canvas.toBlob((b) => (b ? resolve(b) : reject(new Error('toBlob failed'))), 'image/png');
47
+ });
48
+ result.set(atlasRegion.name, { url: URL.createObjectURL(blob), width: w, height: h });
49
+ }
50
+ return result;
51
+ }
52
+ //# sourceMappingURL=DomTexture.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DomTexture.js","sourceRoot":"","sources":["../src/DomTexture.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,OAAO,GAKR,MAAM,8BAA8B,CAAC;AAEtC;;;;GAIG;AACH,MAAM,OAAO,UAAW,SAAQ,OAAO;IACrC,UAAU,CAAC,UAAyB,EAAE,UAAyB,IAAS,CAAC;IACzE,QAAQ,CAAC,MAAmB,EAAE,MAAmB,IAAS,CAAC;IAC3D,OAAO,KAAU,CAAC;CACnB;AAWD;;;;;;;GAOG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CACjC,KAAmB,EACnB,UAAyC;IAEzC,MAAM,MAAM,GAAG,IAAI,GAAG,EAAuB,CAAC;IAE9C,KAAK,MAAM,MAAM,IAAI,KAAK,CAAC,OAAO,EAAE,CAAC;QACnC,MAAM,WAAW,GAAG,MAA4B,CAAC;QACjD,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACpD,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,KAAK,CAAC,uBAAuB,WAAW,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;QAE5E,MAAM,CAAC,GAAG,MAAM,CAAC,KAAK,CAAC;QACvB,MAAM,CAAC,GAAG,MAAM,CAAC,MAAM,CAAC;QACxB,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa,CAAC,QAAQ,CAAC,CAAC;QAChD,MAAM,CAAC,KAAK,GAAG,CAAC,CAAC;QACjB,MAAM,CAAC,MAAM,GAAG,CAAC,CAAC;QAClB,MAAM,GAAG,GAAG,MAAM,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QACpC,IAAI,CAAC,GAAG;YAAE,MAAM,IAAI,KAAK,CAAC,wBAAwB,CAAC,CAAC;QAEpD,IAAI,MAAM,CAAC,OAAO,KAAK,EAAE,EAAE,CAAC;YAC1B,qEAAqE;YACrE,0DAA0D;YAC1D,GAAG,CAAC,SAAS,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;YACpB,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;YACzB,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvE,CAAC;aAAM,CAAC;YACN,GAAG,CAAC,SAAS,CAAC,KAAK,EAAE,WAAW,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC;QACvE,CAAC;QAED,MAAM,IAAI,GAAG,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;YACvD,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,EAAE,WAAW,CAAC,CAAC;QAC3F,CAAC,CAAC,CAAC;QACH,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,EAAE,GAAG,EAAE,GAAG,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,CAAC,CAAC;IACxF,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
@@ -0,0 +1,67 @@
1
+ import { type Skeleton } from '@esotericsoftware/spine-core';
2
+ import type { RegionImage } from './DomTexture';
3
+ /**
4
+ * Renders a spine-core Skeleton as DOM, split by slot type:
5
+ *
6
+ * - Region attachments (rigid parts) become one absolutely-positioned <img>
7
+ * posed with a single CSS matrix() write per frame — exact, since bone
8
+ * transforms are affine.
9
+ * - Mesh attachments (deform parts) each get a small per-part <canvas> sized
10
+ * to the mesh's world bounding box, redrawn per frame with the standard
11
+ * per-triangle clip+transform+drawImage mapping.
12
+ *
13
+ * Both element kinds share one stacking context, so draw order interleaves
14
+ * freely via z-index (rear hair canvas < torso img < front hair canvas).
15
+ * Clipping attachments are deliberately unsupported (transparent layered
16
+ * parts make them unnecessary); they are counted in clipSkipCount.
17
+ *
18
+ * Coordinate mapping: Spine is Y-up, CSS is Y-down — world Y is negated.
19
+ * spine-core computes everything (bones, constraints, physics, deformed
20
+ * vertices) on the CPU; this class only draws.
21
+ */
22
+ export declare class SpineHtmlRenderer {
23
+ private readonly root;
24
+ private readonly regionImages;
25
+ /** Clipping attachments encountered (visible but unsupported). */
26
+ clipSkipCount: number;
27
+ /** Mesh canvases drawn last frame. */
28
+ meshCount: number;
29
+ /** Triangles rasterized last frame. */
30
+ triangleCount: number;
31
+ /**
32
+ * How far (px) each triangle's clip polygon is expanded from its centroid.
33
+ * Closes antialiased-clip cracks between adjacent triangles (Safari);
34
+ * set to 0 to see them.
35
+ */
36
+ triangleExpand: number;
37
+ private readonly views;
38
+ private meshVertices;
39
+ /**
40
+ * @param root Positioned element (e.g. position:absolute) that becomes the
41
+ * skeleton origin. The renderer only appends slot elements to it — layout
42
+ * and scaling of the root belong to the caller.
43
+ * @param regionImages Unpacked per-region bitmaps (for the rigid tier).
44
+ */
45
+ constructor(root: HTMLElement, regionImages: Map<string, RegionImage>);
46
+ render(skeleton: Skeleton): void;
47
+ dispose(): void;
48
+ private renderRegion;
49
+ private renderMesh;
50
+ /**
51
+ * Standard canvas triangle texture mapping (same math as the official
52
+ * spine-canvas renderer): derive the affine that sends the triangle's
53
+ * texture-space corners to its screen-space corners, clip, draw the page.
54
+ *
55
+ * The clip polygon is expanded outward from the centroid by a fraction of
56
+ * a pixel. Browsers that antialias clip paths (Safari) otherwise leave
57
+ * hairline cracks between adjacent triangles; the expanded clips overlap
58
+ * into the neighbouring triangle, and since the texture is continuous
59
+ * across the shared edge the overlap draws the same pixels — cracks close
60
+ * with no visible cost. The texture-mapping affine itself stays exact.
61
+ */
62
+ private drawTriangle;
63
+ private applyCommon;
64
+ private view;
65
+ private hide;
66
+ }
67
+ //# sourceMappingURL=SpineHtmlRenderer.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpineHtmlRenderer.d.ts","sourceRoot":"","sources":["../src/SpineHtmlRenderer.ts"],"names":[],"mappings":"AAAA,OAAO,EAML,KAAK,QAAQ,EAId,MAAM,8BAA8B,CAAC;AACtC,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAqChD;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,iBAAiB;IAwB1B,OAAO,CAAC,QAAQ,CAAC,IAAI;IACrB,OAAO,CAAC,QAAQ,CAAC,YAAY;IAxB/B,kEAAkE;IAClE,aAAa,SAAK;IAClB,sCAAsC;IACtC,SAAS,SAAK;IACd,uCAAuC;IACvC,aAAa,SAAK;IAClB;;;;OAIG;IACH,cAAc,SAAO;IAErB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,YAAY,CAAyB;IAE7C;;;;;OAKG;gBAEgB,IAAI,EAAE,WAAW,EACjB,YAAY,EAAE,GAAG,CAAC,MAAM,EAAE,WAAW,CAAC;IAGzD,MAAM,CAAC,QAAQ,EAAE,QAAQ,GAAG,IAAI;IA2BhC,OAAO,IAAI,IAAI;IAOf,OAAO,CAAC,YAAY;IA8CpB,OAAO,CAAC,UAAU;IAiFlB;;;;;;;;;;;OAWG;IACH,OAAO,CAAC,YAAY;IAwCpB,OAAO,CAAC,WAAW;IAqBnB,OAAO,CAAC,IAAI;IAiCZ,OAAO,CAAC,IAAI;CAOb"}
@@ -0,0 +1,306 @@
1
+ import { BlendMode, ClippingAttachment, MeshAttachment, RegionAttachment, } from '@esotericsoftware/spine-core';
2
+ const regionVertices = new Float32Array(8);
3
+ /** Push a point away from (cx, cy) by `amount` pixels. */
4
+ function expandPoint(x, y, cx, cy, amount) {
5
+ const dx = x - cx;
6
+ const dy = y - cy;
7
+ const len = Math.sqrt(dx * dx + dy * dy);
8
+ if (len < 1e-6)
9
+ return [x, y];
10
+ const s = amount / len;
11
+ return [x + dx * s, y + dy * s];
12
+ }
13
+ const BLEND_CSS = {
14
+ [BlendMode.Normal]: '',
15
+ [BlendMode.Additive]: 'plus-lighter',
16
+ [BlendMode.Multiply]: 'multiply',
17
+ [BlendMode.Screen]: 'screen',
18
+ };
19
+ /**
20
+ * Renders a spine-core Skeleton as DOM, split by slot type:
21
+ *
22
+ * - Region attachments (rigid parts) become one absolutely-positioned <img>
23
+ * posed with a single CSS matrix() write per frame — exact, since bone
24
+ * transforms are affine.
25
+ * - Mesh attachments (deform parts) each get a small per-part <canvas> sized
26
+ * to the mesh's world bounding box, redrawn per frame with the standard
27
+ * per-triangle clip+transform+drawImage mapping.
28
+ *
29
+ * Both element kinds share one stacking context, so draw order interleaves
30
+ * freely via z-index (rear hair canvas < torso img < front hair canvas).
31
+ * Clipping attachments are deliberately unsupported (transparent layered
32
+ * parts make them unnecessary); they are counted in clipSkipCount.
33
+ *
34
+ * Coordinate mapping: Spine is Y-up, CSS is Y-down — world Y is negated.
35
+ * spine-core computes everything (bones, constraints, physics, deformed
36
+ * vertices) on the CPU; this class only draws.
37
+ */
38
+ export class SpineHtmlRenderer {
39
+ root;
40
+ regionImages;
41
+ /** Clipping attachments encountered (visible but unsupported). */
42
+ clipSkipCount = 0;
43
+ /** Mesh canvases drawn last frame. */
44
+ meshCount = 0;
45
+ /** Triangles rasterized last frame. */
46
+ triangleCount = 0;
47
+ /**
48
+ * How far (px) each triangle's clip polygon is expanded from its centroid.
49
+ * Closes antialiased-clip cracks between adjacent triangles (Safari);
50
+ * set to 0 to see them.
51
+ */
52
+ triangleExpand = 0.5;
53
+ views = new Map();
54
+ meshVertices = new Float32Array(256);
55
+ /**
56
+ * @param root Positioned element (e.g. position:absolute) that becomes the
57
+ * skeleton origin. The renderer only appends slot elements to it — layout
58
+ * and scaling of the root belong to the caller.
59
+ * @param regionImages Unpacked per-region bitmaps (for the rigid tier).
60
+ */
61
+ constructor(root, regionImages) {
62
+ this.root = root;
63
+ this.regionImages = regionImages;
64
+ }
65
+ render(skeleton) {
66
+ this.clipSkipCount = 0;
67
+ this.meshCount = 0;
68
+ this.triangleCount = 0;
69
+ const drawOrder = skeleton.drawOrder.appliedPose;
70
+ const skeletonAlpha = skeleton.color.a;
71
+ for (let i = 0, n = drawOrder.length; i < n; i++) {
72
+ const slot = drawOrder[i];
73
+ const pose = slot.appliedPose;
74
+ const attachment = pose.attachment;
75
+ if (!slot.bone.active) {
76
+ this.hide(slot);
77
+ continue;
78
+ }
79
+ if (attachment instanceof RegionAttachment) {
80
+ this.renderRegion(slot, pose, attachment, i, skeletonAlpha);
81
+ }
82
+ else if (attachment instanceof MeshAttachment) {
83
+ this.renderMesh(skeleton, slot, pose, attachment, i, skeletonAlpha);
84
+ }
85
+ else {
86
+ if (attachment instanceof ClippingAttachment)
87
+ this.clipSkipCount++;
88
+ this.hide(slot);
89
+ }
90
+ }
91
+ }
92
+ dispose() {
93
+ for (const view of this.views.values())
94
+ view.el.remove();
95
+ this.views.clear();
96
+ }
97
+ // --- rigid tier -----------------------------------------------------------
98
+ renderRegion(slot, pose, attachment, zIndex, skeletonAlpha) {
99
+ const sequence = attachment.sequence;
100
+ const region = sequence.regions[sequence.resolveIndex(pose)];
101
+ const regionImage = region && this.regionImages.get(region.name);
102
+ if (!regionImage) {
103
+ this.hide(slot);
104
+ return;
105
+ }
106
+ const view = this.view(slot, 'image');
107
+ const img = view.el;
108
+ if (view.regionName !== region.name) {
109
+ view.regionName = region.name;
110
+ img.src = regionImage.url;
111
+ img.width = regionImage.width;
112
+ img.height = regionImage.height;
113
+ }
114
+ attachment.computeWorldVertices(slot, attachment.getOffsets(pose), regionVertices, 0, 2);
115
+ // Corner order from spine-core is BL, UL, UR, BR — derived from
116
+ // computeUVs, whose per-vertex UVs are (u,v2), (u,v), (u2,v), (u2,v2).
117
+ // (The br/bl/ul/ur comments inside computeWorldVertices are stale.)
118
+ // Flip Y for CSS (Spine is Y-up).
119
+ const blx = regionVertices[0], bly = -regionVertices[1];
120
+ const ulx = regionVertices[2], uly = -regionVertices[3];
121
+ const urx = regionVertices[4], ury = -regionVertices[5];
122
+ const w = regionImage.width;
123
+ const h = regionImage.height;
124
+ const a = (urx - ulx) / w;
125
+ const b = (ury - uly) / w;
126
+ const c = (blx - ulx) / h;
127
+ const d = (bly - uly) / h;
128
+ img.style.transform = `matrix(${a},${b},${c},${d},${ulx},${uly})`;
129
+ this.applyCommon(view, slot, zIndex, skeletonAlpha * pose.color.a * attachment.color.a);
130
+ }
131
+ // --- deform tier ----------------------------------------------------------
132
+ renderMesh(skeleton, slot, pose, attachment, zIndex, skeletonAlpha) {
133
+ const sequence = attachment.sequence;
134
+ const sequenceIndex = sequence.resolveIndex(pose);
135
+ const region = sequence.regions[sequenceIndex];
136
+ const page = region?.texture?.getImage();
137
+ if (!page) {
138
+ this.hide(slot);
139
+ return;
140
+ }
141
+ const count = attachment.worldVerticesLength;
142
+ if (this.meshVertices.length < count)
143
+ this.meshVertices = new Float32Array(count);
144
+ const vertices = this.meshVertices;
145
+ attachment.computeWorldVertices(skeleton, slot, 0, count, vertices, 0, 2);
146
+ // World bounds (in CSS coords: Y negated).
147
+ let minX = Infinity, minY = Infinity, maxX = -Infinity, maxY = -Infinity;
148
+ for (let v = 0; v < count; v += 2) {
149
+ const x = vertices[v];
150
+ const y = -vertices[v + 1];
151
+ vertices[v + 1] = y;
152
+ if (x < minX)
153
+ minX = x;
154
+ if (x > maxX)
155
+ maxX = x;
156
+ if (y < minY)
157
+ minY = y;
158
+ if (y > maxY)
159
+ maxY = y;
160
+ }
161
+ const pad = 1;
162
+ minX = Math.floor(minX) - pad;
163
+ minY = Math.floor(minY) - pad;
164
+ const w = Math.ceil(maxX) + pad - minX;
165
+ const h = Math.ceil(maxY) + pad - minY;
166
+ if (w <= 0 || h <= 0) {
167
+ this.hide(slot);
168
+ return;
169
+ }
170
+ const view = this.view(slot, 'canvas');
171
+ const canvas = view.el;
172
+ // NOTE: 1x backing store for the PoC; the example atlas is 0.5-scale
173
+ // anyway. DPR-aware backing is a follow-up.
174
+ if (view.canvasW !== w || view.canvasH !== h) {
175
+ view.canvasW = w;
176
+ view.canvasH = h;
177
+ canvas.width = w;
178
+ canvas.height = h;
179
+ }
180
+ canvas.style.transform = `translate(${minX}px,${minY}px)`;
181
+ const ctx = canvas.getContext('2d');
182
+ if (!ctx)
183
+ return;
184
+ ctx.clearRect(0, 0, w, h);
185
+ const uvs = sequence.getUVs(sequenceIndex);
186
+ const triangles = attachment.triangles;
187
+ const uw = page.width - 1;
188
+ const uh = page.height - 1;
189
+ for (let t = 0; t < triangles.length; t += 3) {
190
+ const i0 = triangles[t] * 2;
191
+ const i1 = triangles[t + 1] * 2;
192
+ const i2 = triangles[t + 2] * 2;
193
+ this.drawTriangle(ctx, page, vertices[i0] - minX, vertices[i0 + 1] - minY, uvs[i0] * uw, uvs[i0 + 1] * uh, vertices[i1] - minX, vertices[i1 + 1] - minY, uvs[i1] * uw, uvs[i1 + 1] * uh, vertices[i2] - minX, vertices[i2 + 1] - minY, uvs[i2] * uw, uvs[i2 + 1] * uh);
194
+ }
195
+ this.meshCount++;
196
+ this.triangleCount += triangles.length / 3;
197
+ this.applyCommon(view, slot, zIndex, skeletonAlpha * pose.color.a * attachment.color.a);
198
+ }
199
+ /**
200
+ * Standard canvas triangle texture mapping (same math as the official
201
+ * spine-canvas renderer): derive the affine that sends the triangle's
202
+ * texture-space corners to its screen-space corners, clip, draw the page.
203
+ *
204
+ * The clip polygon is expanded outward from the centroid by a fraction of
205
+ * a pixel. Browsers that antialias clip paths (Safari) otherwise leave
206
+ * hairline cracks between adjacent triangles; the expanded clips overlap
207
+ * into the neighbouring triangle, and since the texture is continuous
208
+ * across the shared edge the overlap draws the same pixels — cracks close
209
+ * with no visible cost. The texture-mapping affine itself stays exact.
210
+ */
211
+ drawTriangle(ctx, img, x0, y0, u0, v0, x1, y1, u1, v1, x2, y2, u2, v2) {
212
+ const cx = (x0 + x1 + x2) / 3;
213
+ const cy = (y0 + y1 + y2) / 3;
214
+ const expand = this.triangleExpand;
215
+ ctx.beginPath();
216
+ ctx.moveTo(...expandPoint(x0, y0, cx, cy, expand));
217
+ ctx.lineTo(...expandPoint(x1, y1, cx, cy, expand));
218
+ ctx.lineTo(...expandPoint(x2, y2, cx, cy, expand));
219
+ ctx.closePath();
220
+ x1 -= x0;
221
+ y1 -= y0;
222
+ x2 -= x0;
223
+ y2 -= y0;
224
+ u1 -= u0;
225
+ v1 -= v0;
226
+ u2 -= u0;
227
+ v2 -= v0;
228
+ let det = u1 * v2 - u2 * v1;
229
+ if (det === 0)
230
+ return;
231
+ det = 1 / det;
232
+ const a = (v2 * x1 - v1 * x2) * det;
233
+ const b = (v2 * y1 - v1 * y2) * det;
234
+ const c = (u1 * x2 - u2 * x1) * det;
235
+ const d = (u1 * y2 - u2 * y1) * det;
236
+ const e = x0 - a * u0 - c * v0;
237
+ const f = y0 - b * u0 - d * v0;
238
+ ctx.save();
239
+ ctx.transform(a, b, c, d, e, f);
240
+ ctx.clip();
241
+ ctx.drawImage(img, 0, 0);
242
+ ctx.restore();
243
+ }
244
+ // --- shared plumbing -------------------------------------------------------
245
+ applyCommon(view, slot, zIndex, alpha) {
246
+ if (view.zIndex !== zIndex) {
247
+ view.zIndex = zIndex;
248
+ view.el.style.zIndex = String(zIndex);
249
+ }
250
+ // Alpha only; RGB tinting has no cheap DOM equivalent (out of scope).
251
+ if (view.opacity !== alpha) {
252
+ view.opacity = alpha;
253
+ view.el.style.opacity = alpha === 1 ? '' : String(alpha);
254
+ }
255
+ const blendMode = slot.data.blendMode;
256
+ if (view.blendMode !== blendMode) {
257
+ view.blendMode = blendMode;
258
+ view.el.style.mixBlendMode = BLEND_CSS[blendMode];
259
+ }
260
+ if (!view.visible) {
261
+ view.visible = true;
262
+ view.el.style.display = '';
263
+ }
264
+ }
265
+ view(slot, kind) {
266
+ let view = this.views.get(slot);
267
+ if (view && view.kind !== kind) {
268
+ view.el.remove();
269
+ view = undefined;
270
+ }
271
+ if (!view) {
272
+ const el = kind === 'image' ? document.createElement('img') : document.createElement('canvas');
273
+ if (el instanceof HTMLImageElement)
274
+ el.draggable = false;
275
+ el.style.position = 'absolute';
276
+ el.style.left = '0';
277
+ el.style.top = '0';
278
+ el.style.transformOrigin = '0 0';
279
+ el.style.pointerEvents = 'none';
280
+ el.style.userSelect = 'none';
281
+ el.style.display = 'none';
282
+ view = {
283
+ kind,
284
+ el,
285
+ regionName: '',
286
+ visible: false,
287
+ zIndex: -1,
288
+ opacity: 1,
289
+ blendMode: BlendMode.Normal,
290
+ canvasW: 0,
291
+ canvasH: 0,
292
+ };
293
+ this.views.set(slot, view);
294
+ this.root.appendChild(el);
295
+ }
296
+ return view;
297
+ }
298
+ hide(slot) {
299
+ const view = this.views.get(slot);
300
+ if (view && view.visible) {
301
+ view.visible = false;
302
+ view.el.style.display = 'none';
303
+ }
304
+ }
305
+ }
306
+ //# sourceMappingURL=SpineHtmlRenderer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"SpineHtmlRenderer.js","sourceRoot":"","sources":["../src/SpineHtmlRenderer.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ export { SpineHtmlRenderer } from './SpineHtmlRenderer';
2
+ export { DomTexture, unpackRegions, type RegionImage } from './DomTexture';
3
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAE,KAAK,WAAW,EAAE,MAAM,cAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,3 @@
1
+ export { SpineHtmlRenderer } from './SpineHtmlRenderer';
2
+ export { DomTexture, unpackRegions } from './DomTexture';
3
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,EAAE,UAAU,EAAE,aAAa,EAAoB,MAAM,cAAc,CAAC"}
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "spine-html",
3
+ "version": "0.1.0",
4
+ "description": "Render Spine skeletal animations as plain DOM — rigid slots as <img> posed by CSS matrix, deform meshes on small per-part canvases, interleaved in one stacking context.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "firejune",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/firejune/spine-html.git"
11
+ },
12
+ "homepage": "https://github.com/firejune/spine-html#readme",
13
+ "bugs": "https://github.com/firejune/spine-html/issues",
14
+ "keywords": [
15
+ "spine",
16
+ "spine2d",
17
+ "skeletal-animation",
18
+ "mesh-deformation",
19
+ "dom",
20
+ "css-transform",
21
+ "renderer",
22
+ "2d",
23
+ "animation",
24
+ "character-animation"
25
+ ],
26
+ "main": "./dist/index.js",
27
+ "types": "./dist/index.d.ts",
28
+ "exports": {
29
+ ".": {
30
+ "types": "./dist/index.d.ts",
31
+ "import": "./dist/index.js"
32
+ }
33
+ },
34
+ "files": [
35
+ "dist",
36
+ "NOTICE.md"
37
+ ],
38
+ "sideEffects": false,
39
+ "scripts": {
40
+ "predev": "bash scripts/fetch-assets.sh",
41
+ "dev": "vite",
42
+ "prebuild": "bash scripts/fetch-assets.sh",
43
+ "build": "tsc --noEmit && vite build",
44
+ "build:lib": "rm -rf dist && tsc -p tsconfig.build.json",
45
+ "prepublishOnly": "npm run build:lib",
46
+ "preview": "vite preview",
47
+ "fetch-assets": "bash scripts/fetch-assets.sh"
48
+ },
49
+ "peerDependencies": {
50
+ "@esotericsoftware/spine-core": "^4.3.0"
51
+ },
52
+ "devDependencies": {
53
+ "@esotericsoftware/spine-core": "^4.3.13",
54
+ "typescript": "^5.6.0",
55
+ "vite": "^6.0.0"
56
+ }
57
+ }