@doki-land/live2d 0.0.10 → 0.0.12

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 (47) hide show
  1. package/README.md +3 -0
  2. package/dist/index.d.ts +326 -0
  3. package/dist/index.js +1667 -0
  4. package/dist/reexports/core.d.ts +3 -0
  5. package/dist/reexports/core.js +2 -0
  6. package/dist/reexports/loader.d.ts +1 -0
  7. package/dist/reexports/loader.js +2 -0
  8. package/dist/reexports/renderer.d.ts +1 -0
  9. package/dist/reexports/renderer.js +2 -0
  10. package/package.json +61 -43
  11. package/src/create-live2d.ts +39 -0
  12. package/src/focus.ts +53 -0
  13. package/src/index.ts +78 -187
  14. package/src/load-textures.ts +85 -0
  15. package/src/motion/evaluate-curve.ts +119 -0
  16. package/src/motion/index.ts +20 -0
  17. package/src/motion/motion-player.ts +389 -0
  18. package/src/motion/parse-motion3.ts +164 -0
  19. package/src/motion/types.ts +89 -0
  20. package/src/reexports/core.ts +1 -0
  21. package/src/reexports/loader.ts +1 -0
  22. package/src/reexports/renderer.ts +1 -0
  23. package/src/stage/actor-model-slot.ts +429 -0
  24. package/src/stage/actor.ts +188 -0
  25. package/src/stage/index.ts +18 -0
  26. package/src/stage/single-facade.ts +221 -0
  27. package/src/stage/stage.ts +388 -0
  28. package/src/stage/transform.ts +161 -0
  29. package/dist/l2d.umd.js +0 -1208
  30. package/dist/l2d.umd.js.map +0 -1
  31. package/dist/live2d.css +0 -1
  32. package/lib/cubism2.d.ts +0 -179
  33. package/lib/cubism2.min.js +0 -2
  34. package/lib/cubism5.d.ts +0 -367
  35. package/lib/cubism5.min.js +0 -10
  36. package/lib/index.d.ts +0 -7
  37. package/readme.md +0 -60
  38. package/src/fs/index.ts +0 -93
  39. package/src/helper/index.ts +0 -25
  40. package/src/icons/icons.ts +0 -39
  41. package/src/icons/style.css +0 -44
  42. package/src/icons/switch-character.svg +0 -1
  43. package/src/icons/switch-costume.svg +0 -1
  44. package/src/types/Live2dOptions.ts +0 -56
  45. package/src/types/ModelOptions.ts +0 -40
  46. package/src/types/Resolve.ts +0 -16
  47. package/src/types/index.ts +0 -3
@@ -0,0 +1,3 @@
1
+ export * from '@doki-land/live2d-core';
2
+ import '@doki-land/live2d-loader';
3
+ import '@doki-land/live2d-renderer';
@@ -0,0 +1,2 @@
1
+ // src/reexports/core.ts
2
+ export * from "@doki-land/live2d-core";
@@ -0,0 +1 @@
1
+ export * from '@doki-land/live2d-loader';
@@ -0,0 +1,2 @@
1
+ // src/reexports/loader.ts
2
+ export * from "@doki-land/live2d-loader";
@@ -0,0 +1 @@
1
+ export * from '@doki-land/live2d-renderer';
@@ -0,0 +1,2 @@
1
+ // src/reexports/renderer.ts
2
+ export * from "@doki-land/live2d-renderer";
package/package.json CHANGED
@@ -1,47 +1,65 @@
1
1
  {
2
- "name": "@doki-land/live2d",
3
- "version": "0.0.10",
4
- "keywords": [
5
- "live2d",
6
- "pixi",
7
- "typescript"
8
- ],
9
- "description": "Live2D TypeScript runtime based on pixi",
10
- "author": "Aster <192608617@qq.com>",
11
- "license": "LGPL-3.0-or-later",
12
- "type": "module",
2
+ "name": "@doki-land/live2d",
3
+ "version": "0.0.12",
4
+ "description": "Live2D TypeScript library — public facade for Doki Land",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "author": "Doki Land",
8
+ "homepage": "https://github.com/doki-land/live2d.ts",
9
+ "repository": {
10
+ "type": "git",
11
+ "url": "git+https://github.com/doki-land/live2d.ts.git"
12
+ },
13
+ "keywords": [
14
+ "live2d",
15
+ "webgpu",
16
+ "webgl2",
17
+ "doki-land"
18
+ ],
19
+ "main": "./src/index.ts",
20
+ "types": "./src/index.ts",
21
+ "exports": {
22
+ ".": "./src/index.ts",
23
+ "./core": "./src/reexports/core.ts",
24
+ "./loader": "./src/reexports/loader.ts",
25
+ "./renderer": "./src/reexports/renderer.ts"
26
+ },
27
+ "files": [
28
+ "dist",
29
+ "src"
30
+ ],
31
+ "publishConfig": {
32
+ "access": "public",
33
+ "main": "./dist/index.js",
34
+ "types": "./dist/index.d.ts",
13
35
  "exports": {
14
- ".": {
15
- "types": "./src/index.ts",
16
- "import": "./src/index.ts",
17
- "require": "./dist/l2d.umd.js"
18
- },
19
- "./*": {
20
- "types": "./src/*/index.ts",
21
- "import": "./src/*/index.ts"
22
- }
23
- },
24
- "files": [
25
- "dist",
26
- "lib",
27
- "src"
28
- ],
29
- "publishConfig": {
30
- "registry": "https://registry.npmjs.org",
31
- "access": "public"
32
- },
33
- "scripts": {
34
- "dev": "vite",
35
- "build": "vite build",
36
- "preview": "vite preview",
37
- "prepublishOnly": "pnpm build"
38
- },
39
- "dependencies": {
40
- "@pixi/core": "^7.4.3",
41
- "@pixi/display": "^7.4.3",
42
- "@pixi/ticker": "^7.4.3",
43
- "minimatch": "^10.0.1",
44
- "pixi-live2d-display-lipsyncpatch": "0.5.0-ls-7",
45
- "pixi.js": "^7.4.3"
36
+ ".": {
37
+ "types": "./dist/index.d.ts",
38
+ "import": "./dist/index.js"
39
+ },
40
+ "./core": {
41
+ "types": "./dist/reexports/core.d.ts",
42
+ "import": "./dist/reexports/core.js"
43
+ },
44
+ "./loader": {
45
+ "types": "./dist/reexports/loader.d.ts",
46
+ "import": "./dist/reexports/loader.js"
47
+ },
48
+ "./renderer": {
49
+ "types": "./dist/reexports/renderer.d.ts",
50
+ "import": "./dist/reexports/renderer.js"
51
+ }
46
52
  }
53
+ },
54
+ "scripts": {
55
+ "build": "tsup src/index.ts src/reexports/core.ts src/reexports/loader.ts src/reexports/renderer.ts --format esm --dts",
56
+ "typecheck": "tsc --noEmit",
57
+ "test": "vitest run --passWithNoTests"
58
+ },
59
+ "dependencies": {
60
+ "@doki-land/live2d-core": "0.0.12",
61
+ "@doki-land/live2d-loader": "0.0.12",
62
+ "@doki-land/live2d-renderer": "0.0.12"
63
+ },
64
+ "sideEffects": false
47
65
  }
@@ -0,0 +1,39 @@
1
+ import {
2
+ createMoc2Backend,
3
+ createMoc3Backend,
4
+ createRenderer,
5
+ } from "@doki-land/live2d-renderer";
6
+ import { allocateActorId } from "./stage/actor.js";
7
+ import {
8
+ type CreateLive2DOptions,
9
+ createSingleActorFacade,
10
+ type Live2DRuntime,
11
+ } from "./stage/single-facade.js";
12
+ import { createLive2dStage } from "./stage/stage.js";
13
+
14
+ export type {
15
+ CreateLive2DOptions,
16
+ Live2DRuntime,
17
+ } from "./stage/single-facade.js";
18
+ export {
19
+ MotionPriority,
20
+ type PlayMotionOptions,
21
+ } from "./stage/single-facade.js";
22
+
23
+ /** Wire moc backends and a renderer into one single-actor session. */
24
+ export function createLive2D(options: CreateLive2DOptions = {}): Live2DRuntime {
25
+ const backends = options.backends ?? [
26
+ createMoc2Backend(),
27
+ createMoc3Backend(),
28
+ ];
29
+ const stage = createLive2dStage({
30
+ backends,
31
+ renderer:
32
+ options.renderer ?? createRenderer({ prefer: options.prefer }),
33
+ updateMode: options.updateMode ?? "manual",
34
+ }) as import("./stage/stage.js").Live2dStageImpl;
35
+ const actor = stage.createActor({
36
+ id: allocateActorId("default"),
37
+ }) as import("./stage/actor.js").Live2dActorImpl;
38
+ return createSingleActorFacade(stage, actor, backends);
39
+ }
package/src/focus.ts ADDED
@@ -0,0 +1,53 @@
1
+ import type { ParameterBinding } from "@doki-land/live2d-renderer";
2
+
3
+ /**
4
+ * Map canvas focus (-1..1, Y-up) onto standard Cubism drag parameters.
5
+ *
6
+ * Matches the Cubism sample / community widget weights:
7
+ * ANGLE_X/Y full range, ANGLE_Z = -dragX*dragY, BODY_ANGLE_X, EYE_BALL_*.
8
+ * Missing ids on a given model are skipped.
9
+ */
10
+ export function focusParameterUpdates(
11
+ parameters: readonly ParameterBinding[],
12
+ dragX: number,
13
+ dragY: number,
14
+ ): Array<{ id: string; value: number }> {
15
+ const byId = new Map(parameters.map((p) => [p.id, p]));
16
+ const x = clampUnit(dragX);
17
+ const y = clampUnit(dragY);
18
+ const out: Array<{ id: string; value: number }> = [];
19
+
20
+ const set = (id: string, normalized: number) => {
21
+ const binding = byId.get(id);
22
+ if (!binding) return;
23
+ out.push({ id, value: valueFromNormalized(binding, normalized) });
24
+ };
25
+
26
+ set("PARAM_ANGLE_X", x);
27
+ set("PARAM_ANGLE_Y", y);
28
+ set("PARAM_ANGLE_Z", clampUnit(x * y * -1));
29
+ // Official sample uses dragX*10 vs ANGLE's *30; BODY range is typically ±10,
30
+ // so full-range normalized drag still matches that relative weight.
31
+ set("PARAM_BODY_ANGLE_X", x);
32
+ set("PARAM_BODY_ANGLE_Y", y);
33
+ set("PARAM_EYE_BALL_X", x);
34
+ set("PARAM_EYE_BALL_Y", y);
35
+
36
+ return out;
37
+ }
38
+
39
+ function clampUnit(n: number): number {
40
+ if (n > 1) return 1;
41
+ if (n < -1) return -1;
42
+ return n;
43
+ }
44
+
45
+ function valueFromNormalized(
46
+ binding: ParameterBinding,
47
+ normalized: number,
48
+ ): number {
49
+ const n = clampUnit(normalized);
50
+ return n >= 0
51
+ ? binding.defaultValue + (binding.max - binding.defaultValue) * n
52
+ : binding.defaultValue + (binding.defaultValue - binding.min) * n;
53
+ }
package/src/index.ts CHANGED
@@ -1,190 +1,81 @@
1
- import '../lib/cubism2.min.js';
2
- import '../lib/cubism5.min.js';
3
- import * as PIXI from 'pixi.js';
4
- import {Application} from 'pixi.js';
5
- import {Ticker} from '@pixi/ticker';
6
- import {Live2DModel, MotionManager, SoundManager} from 'pixi-live2d-display-lipsyncpatch';
7
- import './icons/style.css';
8
- import {Live2dOptions} from '@doki-land/live2d/types';
9
-
10
- export {MotionManager, SoundManager, Live2DModel, PIXI};
11
-
12
- /**
13
- * 创建Live2D模型
14
- * @param options 模型配置选项
15
- * @returns Promise<Live2DModel> 加载完成的Live2D模型实例
16
- */
17
- export async function createLive2D(options: Live2dOptions): Promise<Live2DModel> {
18
- const {
19
- element_id = 'live2d-canvas',
20
- models,
21
- width = 300,
22
- height = 300,
23
- auto_fit = true,
24
- auto_motion = true,
25
- mouse_tracking = true
26
- } = options;
27
- // 创建画布
28
- let element = findOrCreateCanvas(element_id, options)
29
- // 初始化PIXI应用
30
- const app = new Application({
31
- view: element,
32
- width: width,
33
- height: height,
34
- // resolution: 1,
35
- backgroundAlpha: 0,
36
- autoDensity: true,
37
- antialias: true,
38
- eventMode: 'static',
39
- eventFeatures: {
40
- move: true,
41
- globalMove: true,
42
- click: true,
43
- wheel: true
44
- }
45
- });
46
-
47
- // 加载模型
48
- const model = await Live2DModel.from(models[0].model_url, {
49
- ticker: Ticker.system,
50
- });
51
- // 添加模型到舞台
52
- app.stage.addChild(model);
53
-
54
- // 自动适应大小
55
- if (auto_fit) {
56
- // 修复auto_fit功能,确保模型不被截断且保持正确比例
57
- const scale = Math.min(width / model.width, height / model.height);
58
- model.scale.set(scale);
59
-
60
- // 居中显示,确保模型完全可见
61
- model.x = (width - model.width * scale) / 2;
62
- model.y = (height - model.height * scale) / 2;
63
- }
64
-
65
- // 启用鼠标跟踪
66
- if (mouse_tracking) {
67
- enableMouseTracking(model, app.view);
68
- }
69
-
70
- // 自动开始动画
71
- if (auto_motion) {
72
- await startIdleAnimation(model);
73
- }
74
-
75
- // 创建右侧图标栏
76
- // createIconPanel(model, element);
77
-
78
- return model;
79
- }
80
-
81
- function findOrCreateCanvas(id: string, options: Live2dOptions): HTMLCanvasElement {
82
- let canvas = document.getElementById(id) as HTMLCanvasElement;
83
- if (!canvas) {
84
- canvas = document.createElement('canvas');
85
- canvas.id = id;
86
- canvas.classList.add('live2d-canvas')
87
- canvas.style.bottom = `${options.spacing_y || 0}px`;
88
- canvas.style[options.position === 'left' ? 'left' : 'right'] = `${options.spacing_x || 0}px`;
89
- document.body.appendChild(canvas);
90
- }
91
- return canvas;
92
- }
93
-
94
-
95
- /**
96
- * 启用鼠标跟踪功能
97
- * @param model Live2D模型实例
98
- * @param canvas 画布元素
99
- */
100
- function enableMouseTracking(model: Live2DModel, canvas: any) {
101
- // 跟踪鼠标移动
102
- document.addEventListener('mousemove', (event) => {
103
- const rect = canvas.getBoundingClientRect();
104
- const centerX = rect.left + rect.width / 2;
105
- const centerY = rect.top + rect.height / 2;
106
-
107
- // 计算鼠标位置相对于画布中心的偏移
108
- const mouseX = (event.clientX - centerX) / rect.width;
109
- // 修正Y轴方向,使用负值来反转方向
110
- const mouseY = -(event.clientY - centerY) / rect.height;
111
-
112
- // 更新模型的视线方向
113
- if (model.internalModel) {
114
- model.internalModel.focusController?.focus(mouseX, mouseY);
115
- }
116
- });
117
-
118
- // 添加点击模型触发动作的交互
119
- canvas.addEventListener('click', () => {
120
- // 尝试播放tap_body动作,如果没有则尝试其他可用动作
121
- try {
122
- const motions = model.internalModel?.motionManager.definitions;
123
- if (motions) {
124
- if (motions.tap_body) {
125
- model.motion('tap_body');
126
- } else if (motions.tap) {
127
- model.motion('tap');
128
- } else {
129
- // 使用第一个可用的动作组
130
- const firstMotionGroup = Object.keys(motions)[0];
131
- if (firstMotionGroup) {
132
- model.motion(firstMotionGroup);
133
- }
134
- }
135
- }
136
- } catch (error) {
137
- console.warn('Failed to play tap animation:', error);
138
- }
139
- });
140
- }
141
-
142
- /**
143
- * 开始空闲动画
144
- * @param model Live2D模型实例
145
- */
146
- async function startIdleAnimation(model: Live2DModel) {
147
- // 尝试播放内置的动作组
148
- try {
149
- // 获取模型支持的动作组
150
- const motions = model.internalModel?.motionManager.definitions;
151
-
152
- if (motions) {
153
- // 优先使用Idle动作组
154
- if (motions.idle) {
155
- await model.motion('idle');
156
- } else if (motions.tap_body) {
157
- // 如果没有idle动作,尝试使用 tap_body 动作
158
- await model.motion('tap_body');
159
- } else {
160
- // 使用第一个可用的动作组
161
- const firstMotionGroup = Object.keys(motions)[0];
162
- if (firstMotionGroup) {
163
- await model.motion(firstMotionGroup);
164
- }
165
- }
166
- }
167
- } catch (error) {
168
- console.warn('Failed to start idle animation:', error);
169
- }
170
- }
171
-
172
1
  /**
173
- * 加载Cubism SDK
174
- * 在使用Live2D功能前必须调用此函数
175
- * @param cubism2 可选的CubismCore对象,如果在非浏览器环境中使用,需要传入
176
- * @param cubism5
2
+ * `@doki-land/live2d` — public facade.
3
+ *
4
+ * ```ts
5
+ * import { createLive2D } from "@doki-land/live2d";
6
+ * ```
177
7
  */
178
- export async function initializeLive2D() {
179
- // 首先初始化 Cubism2 SDK
180
- // console.log('sdk2:', Cubism2);
181
- // console.log('sdk5:', Cubism5);
182
- // @ts-ignore
183
- // window.Live2D = cubism2 || Cubism2;
184
- // 然后初始化 Cubism5 SDK
185
- // @ts-ignore
186
- // window.Live2DCubismCore = cubism5 || Cubism5().Live2DCubismCore;
187
8
 
188
- // 注册 InteractionManager 以支持 Live2D 模型的自动交互
189
- // PIXI.extensions.add(InteractionManager);
190
- }
9
+ export type {
10
+ ActorHit,
11
+ ActorTransform,
12
+ AssetResolver,
13
+ CreateActorOptions,
14
+ CreateLive2dStageOptions,
15
+ FrameProfile,
16
+ FrameSnapshot,
17
+ InternalModel,
18
+ Live2DSession,
19
+ Live2dActor,
20
+ Live2dStage,
21
+ LoadProgress,
22
+ LoadProgressStage,
23
+ ModelFormat,
24
+ ModelInstance,
25
+ ModelProgram,
26
+ ModelSettings,
27
+ ModelSource,
28
+ PointerTrackingMode,
29
+ PointerTrackingPolicy,
30
+ SessionPhase,
31
+ SessionState,
32
+ StagePointerEvent,
33
+ StageUpdateMode,
34
+ } from "@doki-land/live2d-core";
35
+ export { DEFAULT_ACTOR_TRANSFORM, EventEmitter } from "@doki-land/live2d-core";
36
+ export {
37
+ DEFAULT_NPM_CDN,
38
+ resolveModelSourceUrl,
39
+ resolveNpmSpecifier,
40
+ } from "@doki-land/live2d-loader";
41
+ export {
42
+ createCanvas2DRenderer,
43
+ createMoc2Backend,
44
+ createMoc3Backend,
45
+ createQuadProgram,
46
+ createRenderer,
47
+ createWebGl2Renderer,
48
+ createWebGpuRenderer,
49
+ decodeMoc3,
50
+ evaluateFrame,
51
+ fingerprintSnapshot,
52
+ type ModelBackend,
53
+ type ParameterBinding,
54
+ parseCpuProgram,
55
+ type Renderer,
56
+ type RendererKind,
57
+ serializeCpuProgram,
58
+ } from "@doki-land/live2d-renderer";
59
+ export {
60
+ type CreateLive2DOptions,
61
+ createLive2D,
62
+ type Live2DRuntime,
63
+ MotionPriority,
64
+ type PlayMotionOptions,
65
+ } from "./create-live2d.js";
66
+ export { focusParameterUpdates } from "./focus.js";
67
+ export {
68
+ blendMotionLayers,
69
+ evaluateCurve,
70
+ evaluateMotion3,
71
+ type Motion3Clip,
72
+ type MotionApplySample,
73
+ MotionPlayer,
74
+ parseMotion3,
75
+ } from "./motion/index.js";
76
+ export {
77
+ type CreateLive2dStageFullOptions,
78
+ createLive2dStage,
79
+ } from "./stage/stage.js";
80
+
81
+ export const LIVE2D_VERSION = "0.0.0" as const;
@@ -0,0 +1,85 @@
1
+ /**
2
+ * Fetch model texture images via AssetResolver → TextureData for draw passes.
3
+ */
4
+
5
+ import type { AssetResolver } from "@doki-land/live2d-core";
6
+ import type { TextureData } from "@doki-land/live2d-renderer";
7
+
8
+ export interface LoadTexturesOptions {
9
+ onProgress?: (update: {
10
+ index: number;
11
+ total: number;
12
+ key: string;
13
+ bytesLoaded: number;
14
+ bytesTotal: number | null;
15
+ }) => void;
16
+ }
17
+
18
+ function guessMime(path: string): string {
19
+ const lower = path.toLowerCase();
20
+ if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg";
21
+ if (lower.endsWith(".webp")) return "image/webp";
22
+ if (lower.endsWith(".gif")) return "image/gif";
23
+ return "image/png";
24
+ }
25
+
26
+ async function bytesToImageBitmap(
27
+ bytes: ArrayBuffer,
28
+ path: string,
29
+ ): Promise<ImageBitmap> {
30
+ if (typeof createImageBitmap !== "function") {
31
+ throw new Error(
32
+ "@doki-land/live2d: createImageBitmap is not available in this environment",
33
+ );
34
+ }
35
+ const blob = new Blob([new Uint8Array(bytes)], {
36
+ type: guessMime(path),
37
+ });
38
+ return createImageBitmap(blob);
39
+ }
40
+
41
+ /** Load texture paths from a resolver into GPU-uploadable TextureData. */
42
+ export async function loadTextureData(
43
+ resolver: AssetResolver,
44
+ paths: readonly string[],
45
+ options: LoadTexturesOptions = {},
46
+ ): Promise<TextureData[]> {
47
+ const out: TextureData[] = [];
48
+ const total = paths.length;
49
+ for (let i = 0; i < paths.length; i++) {
50
+ const key = paths[i]!;
51
+ options.onProgress?.({
52
+ index: i,
53
+ total,
54
+ key,
55
+ bytesLoaded: 0,
56
+ bytesTotal: null,
57
+ });
58
+ const bytes = await resolver.fetchBytes(key);
59
+ options.onProgress?.({
60
+ index: i,
61
+ total,
62
+ key,
63
+ bytesLoaded: bytes.byteLength,
64
+ bytesTotal: bytes.byteLength,
65
+ });
66
+ const image = await bytesToImageBitmap(bytes, key);
67
+ out.push({
68
+ index: i,
69
+ image,
70
+ width: image.width,
71
+ height: image.height,
72
+ });
73
+ }
74
+ return out;
75
+ }
76
+
77
+ /** Close ImageBitmaps previously passed to setTextures. */
78
+ export function releaseTextureData(textures: readonly TextureData[]): void {
79
+ for (const t of textures) {
80
+ const img = t.image;
81
+ if (typeof ImageBitmap !== "undefined" && img instanceof ImageBitmap) {
82
+ img.close();
83
+ }
84
+ }
85
+ }