@laplace.live/pixijs-live2d 0.0.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.
package/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 GuangChen2333
4
+ Copyright (c) 2026 LAPLACE Live!
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,68 @@
1
+ # @laplace.live/pixijs-live2d
2
+
3
+ PixiJS v8 Live2D engine for Cubism 3/4/5 models — motions, expressions, physics, pose, lip
4
+ sync with motion sounds, parallel motions, and hit-area picking. The 2D-object engine of
5
+ [LAPLACE Persona](https://github.com/laplace-live/persona); its WebGPU sibling for three.js is
6
+ [`@laplace.live/three-live2d`](https://www.npmjs.com/package/@laplace.live/three-live2d).
7
+
8
+ Forked from
9
+ [untitled-pixi-live2d-engine](https://github.com/Untitled-Story/untitled-pixi-live2d-engine)
10
+ `v1.3.5` (itself a PixiJS v8 continuation of the pixi-live2d-display line). Diverges
11
+ deliberately: Cubism 2 support removed, the CubismWebFramework mirrored pristine at tag
12
+ `5-r.5`, and moc3 v6 (Cubism 5.3 offscreens + new blend modes) rendering verified.
13
+
14
+ ## Install
15
+
16
+ ```sh
17
+ npm i @laplace.live/pixijs-live2d pixi.js
18
+ npm i @pixi/sound # optional — motion sounds / audio lip sync
19
+ ```
20
+
21
+ `pixi.js` ≥ 8.13 is a peer dependency. `@pixi/sound` is an optional peer, dynamic-imported the
22
+ first time audio is used.
23
+
24
+ ## Cubism Core
25
+
26
+ Live2D Cubism Core (`live2dcubismcore.js`) is Live2D's proprietary runtime and is **not
27
+ bundled**. Download it with the
28
+ [Cubism SDK for Web](https://www.live2d.com/en/sdk/download/web/), serve it yourself, and load
29
+ it **before this package is imported** — the entry throws immediately when
30
+ `window.Live2DCubismCore` is missing. A `<script>` tag ahead of your bundle works; so does
31
+ evaluating Core first and pulling the engine in with a dynamic `import()`. Adding the SDK's
32
+ `live2dcubismcore.d.ts` to your project completes the typings.
33
+
34
+ ## Usage
35
+
36
+ ```ts
37
+ import { Live2DModel } from '@laplace.live/pixijs-live2d'
38
+ import { Application, Ticker } from 'pixi.js'
39
+
40
+ const app = new Application()
41
+ await app.init({ backgroundAlpha: 0 })
42
+
43
+ Live2DModel.registerTicker(Ticker) // drive motion updates from pixi's shared ticker
44
+
45
+ const model = await Live2DModel.from('https://example.com/hiyori/hiyori.model3.json')
46
+ app.stage.addChild(model)
47
+ model.scale.set(0.25)
48
+
49
+ model.on('hit', (hitAreas) => {
50
+ if (hitAreas.includes('Body')) model.motion('TapBody')
51
+ })
52
+ ```
53
+
54
+ `@laplace.live/pixijs-live2d/extra` ships `HitAreaFrames`, a debug overlay that draws the
55
+ model's hit areas.
56
+
57
+ ## License
58
+
59
+ The engine (`src/`) is [MIT](./LICENSE), carrying the upstream fork's copyright alongside ours.
60
+ It compiles in a pristine mirror of
61
+ [Live2D/CubismWebFramework](https://github.com/Live2D/CubismWebFramework), covered by the
62
+ [Live2D Open Software License](https://www.live2d.com/eula/live2d-open-software-license-agreement_en.html)
63
+ — its license text ships in this package at `vendor/cubism/LICENSE.md`. Live2D Cubism Core is
64
+ not included; it is distributed by Live2D under the
65
+ [Live2D Proprietary Software License](https://www.live2d.com/eula/live2d-proprietary-software-license-agreement_en.html),
66
+ and businesses above the revenue threshold also need a
67
+ [Cubism SDK Release License](https://www.live2d.com/en/sdk/license/). Review Live2D's terms
68
+ before shipping a product.
@@ -0,0 +1,16 @@
1
+ import { Graphics, Renderer, Text } from "pixi.js";
2
+ //#region src/tools/HitAreaFrames.d.ts
3
+ declare class HitAreaFrames extends Graphics {
4
+ initialized: boolean;
5
+ texts: Text[];
6
+ strokeWidth: number;
7
+ normalColor: number;
8
+ activeColor: number;
9
+ constructor();
10
+ private readonly handleInit;
11
+ private readonly handlePointerMove;
12
+ protected _render(renderer: Renderer): void;
13
+ }
14
+ //#endregion
15
+ export { HitAreaFrames };
16
+ //# sourceMappingURL=extra.d.ts.map
package/dist/extra.js ADDED
@@ -0,0 +1,75 @@
1
+ import { Graphics, Rectangle, Text, TextStyle } from "pixi.js";
2
+ //#region src/tools/HitAreaFrames.ts
3
+ const tempBounds = new Rectangle();
4
+ var HitAreaFrames = class extends Graphics {
5
+ initialized = false;
6
+ texts = [];
7
+ strokeWidth = 4;
8
+ normalColor = 14883354;
9
+ activeColor = 2017330;
10
+ constructor() {
11
+ super();
12
+ this.eventMode = "static";
13
+ this.on("added", this.handleInit).on("globalpointermove", this.handlePointerMove);
14
+ }
15
+ handleInit = () => {
16
+ const internalModel = this.parent.internalModel;
17
+ const textStyle = new TextStyle({
18
+ fontSize: 24,
19
+ fill: "#ffffff",
20
+ stroke: {
21
+ color: "#000000",
22
+ width: 4
23
+ }
24
+ });
25
+ this.texts = Object.keys(internalModel.hitAreas).map((hitAreaName) => {
26
+ const text = new Text({
27
+ text: hitAreaName,
28
+ style: textStyle
29
+ });
30
+ text.visible = false;
31
+ this.addChild(text);
32
+ return text;
33
+ });
34
+ };
35
+ handlePointerMove = (e) => {
36
+ const hitAreaNames = this.parent.hitTest(e.global.x, e.global.y);
37
+ this.texts.forEach((text) => {
38
+ text.visible = hitAreaNames.includes(text.text);
39
+ });
40
+ };
41
+ _render(renderer) {
42
+ const internalModel = this.parent.internalModel;
43
+ const matrix = this.worldTransform;
44
+ const scale = 1 / Math.sqrt(matrix.a ** 2 + matrix.b ** 2);
45
+ this.texts.forEach((text) => {
46
+ const hitArea = internalModel.hitAreas[text.text];
47
+ if (!hitArea) return;
48
+ let drawIndex = hitArea.index;
49
+ if (drawIndex < 0) {
50
+ drawIndex = internalModel.getDrawableIndex(hitArea.id);
51
+ if (drawIndex < 0) return;
52
+ hitArea.index = drawIndex;
53
+ }
54
+ const bounds = internalModel.getDrawableBounds(drawIndex, tempBounds);
55
+ const transform = internalModel.localTransform;
56
+ bounds.x = bounds.x * transform.a + transform.tx;
57
+ bounds.y = bounds.y * transform.d + transform.ty;
58
+ bounds.width = bounds.width * transform.a;
59
+ bounds.height = bounds.height * transform.d;
60
+ this.setStrokeStyle({
61
+ width: this.strokeWidth * scale,
62
+ color: text.visible ? this.activeColor : this.normalColor
63
+ }).rect(bounds.x, bounds.y, bounds.width, bounds.height);
64
+ text.x = bounds.x + this.strokeWidth * scale;
65
+ text.y = bounds.y + this.strokeWidth * scale;
66
+ text.scale.set(scale);
67
+ });
68
+ Graphics.prototype._render.call(this, renderer);
69
+ this.clear();
70
+ }
71
+ };
72
+ //#endregion
73
+ export { HitAreaFrames };
74
+
75
+ //# sourceMappingURL=extra.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"extra.js","names":[],"sources":["../src/tools/HitAreaFrames.ts"],"sourcesContent":["// noinspection JSUnusedGlobalSymbols\n\nimport type { Renderer } from 'pixi.js'\nimport { type FederatedPointerEvent, Graphics, Rectangle, Text, TextStyle } from 'pixi.js'\n\nimport type { Live2DModel } from '@/Live2DModel'\n\nconst tempBounds = new Rectangle()\n\nexport class HitAreaFrames extends Graphics {\n initialized = false\n\n texts: Text[] = []\n\n strokeWidth = 4\n normalColor = 0xe31a1a\n activeColor = 0x1ec832\n\n constructor() {\n super()\n\n this.eventMode = 'static'\n\n this.on('added', this.handleInit).on('globalpointermove', this.handlePointerMove)\n }\n\n private readonly handleInit = () => {\n const internalModel = (this.parent as Live2DModel).internalModel\n\n const textStyle = new TextStyle({\n fontSize: 24,\n fill: '#ffffff',\n stroke: { color: '#000000', width: 4 },\n })\n\n this.texts = Object.keys(internalModel.hitAreas).map(hitAreaName => {\n const text = new Text({\n text: hitAreaName,\n style: textStyle,\n })\n\n text.visible = false\n\n this.addChild(text)\n\n return text\n })\n }\n\n private readonly handlePointerMove = (e: FederatedPointerEvent) => {\n const hitAreaNames = (this.parent as Live2DModel).hitTest(e.global.x, e.global.y)\n\n this.texts.forEach(text => {\n text.visible = hitAreaNames.includes(text.text)\n })\n }\n\n protected _render(renderer: Renderer): void {\n const internalModel = (this.parent as Live2DModel).internalModel\n\n // extract scale from the transform matrix, and invert it to ease following calculation\n // https://math.stackexchange.com/a/13165\n const matrix = this.worldTransform\n const scale = 1 / Math.sqrt(matrix.a ** 2 + matrix.b ** 2)\n\n this.texts.forEach(text => {\n const hitArea = internalModel.hitAreas[text.text]\n\n if (!hitArea) {\n return\n }\n\n let drawIndex = hitArea.index\n\n if (drawIndex < 0) {\n drawIndex = internalModel.getDrawableIndex(hitArea.id)\n\n if (drawIndex < 0) {\n return\n }\n\n hitArea.index = drawIndex\n }\n\n const bounds = internalModel.getDrawableBounds(drawIndex, tempBounds)\n const transform = internalModel.localTransform\n\n bounds.x = bounds.x * transform.a + transform.tx\n bounds.y = bounds.y * transform.d + transform.ty\n bounds.width = bounds.width * transform.a\n bounds.height = bounds.height * transform.d\n\n this.setStrokeStyle({\n width: this.strokeWidth * scale,\n color: text.visible ? this.activeColor : this.normalColor,\n }).rect(bounds.x, bounds.y, bounds.width, bounds.height)\n\n text.x = bounds.x + this.strokeWidth * scale\n text.y = bounds.y + this.strokeWidth * scale\n text.scale.set(scale)\n })\n\n const render = (\n Graphics.prototype as unknown as {\n _render: (renderer: Renderer) => void\n }\n )._render\n render.call(this, renderer)\n\n this.clear()\n }\n}\n"],"mappings":";;AAOA,MAAM,aAAa,IAAI,UAAU;AAEjC,IAAa,gBAAb,cAAmC,SAAS;CAC1C,cAAc;CAEd,QAAgB,CAAC;CAEjB,cAAc;CACd,cAAc;CACd,cAAc;CAEd,cAAc;EACZ,MAAM;EAEN,KAAK,YAAY;EAEjB,KAAK,GAAG,SAAS,KAAK,UAAU,CAAC,CAAC,GAAG,qBAAqB,KAAK,iBAAiB;CAClF;CAEA,mBAAoC;EAClC,MAAM,gBAAiB,KAAK,OAAuB;EAEnD,MAAM,YAAY,IAAI,UAAU;GAC9B,UAAU;GACV,MAAM;GACN,QAAQ;IAAE,OAAO;IAAW,OAAO;GAAE;EACvC,CAAC;EAED,KAAK,QAAQ,OAAO,KAAK,cAAc,QAAQ,CAAC,CAAC,KAAI,gBAAe;GAClE,MAAM,OAAO,IAAI,KAAK;IACpB,MAAM;IACN,OAAO;GACT,CAAC;GAED,KAAK,UAAU;GAEf,KAAK,SAAS,IAAI;GAElB,OAAO;EACT,CAAC;CACH;CAEA,qBAAsC,MAA6B;EACjE,MAAM,eAAgB,KAAK,OAAuB,QAAQ,EAAE,OAAO,GAAG,EAAE,OAAO,CAAC;EAEhF,KAAK,MAAM,SAAQ,SAAQ;GACzB,KAAK,UAAU,aAAa,SAAS,KAAK,IAAI;EAChD,CAAC;CACH;CAEA,QAAkB,UAA0B;EAC1C,MAAM,gBAAiB,KAAK,OAAuB;EAInD,MAAM,SAAS,KAAK;EACpB,MAAM,QAAQ,IAAI,KAAK,KAAK,OAAO,KAAK,IAAI,OAAO,KAAK,CAAC;EAEzD,KAAK,MAAM,SAAQ,SAAQ;GACzB,MAAM,UAAU,cAAc,SAAS,KAAK;GAE5C,IAAI,CAAC,SACH;GAGF,IAAI,YAAY,QAAQ;GAExB,IAAI,YAAY,GAAG;IACjB,YAAY,cAAc,iBAAiB,QAAQ,EAAE;IAErD,IAAI,YAAY,GACd;IAGF,QAAQ,QAAQ;GAClB;GAEA,MAAM,SAAS,cAAc,kBAAkB,WAAW,UAAU;GACpE,MAAM,YAAY,cAAc;GAEhC,OAAO,IAAI,OAAO,IAAI,UAAU,IAAI,UAAU;GAC9C,OAAO,IAAI,OAAO,IAAI,UAAU,IAAI,UAAU;GAC9C,OAAO,QAAQ,OAAO,QAAQ,UAAU;GACxC,OAAO,SAAS,OAAO,SAAS,UAAU;GAE1C,KAAK,eAAe;IAClB,OAAO,KAAK,cAAc;IAC1B,OAAO,KAAK,UAAU,KAAK,cAAc,KAAK;GAChD,CAAC,CAAC,CAAC,KAAK,OAAO,GAAG,OAAO,GAAG,OAAO,OAAO,OAAO,MAAM;GAEvD,KAAK,IAAI,OAAO,IAAI,KAAK,cAAc;GACvC,KAAK,IAAI,OAAO,IAAI,KAAK,cAAc;GACvC,KAAK,MAAM,IAAI,KAAK;EACtB,CAAC;EAOD,SAJW,UAGT,QACK,KAAK,MAAM,QAAQ;EAE1B,KAAK,MAAM;CACb;AACF"}