@doki-land/live2d 0.0.11 → 0.0.13
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 +1 -1
- package/dist/index.d.ts +118 -23
- package/dist/index.js +938 -339
- package/package.json +4 -4
- package/src/create-live2d.ts +23 -575
- package/src/index.ts +15 -2
- package/src/stage/actor-model-slot.ts +429 -0
- package/src/stage/actor.ts +188 -0
- package/src/stage/index.ts +18 -0
- package/src/stage/single-facade.ts +221 -0
- package/src/stage/stage.ts +388 -0
- package/src/stage/transform.ts +161 -0
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type { ActorTransform } from "@doki-land/live2d-core";
|
|
2
|
+
import { DEFAULT_ACTOR_TRANSFORM } from "@doki-land/live2d-core";
|
|
3
|
+
import type { DrawableMesh } from "@doki-land/live2d-renderer";
|
|
4
|
+
|
|
5
|
+
/** Merge partial transform with defaults. */
|
|
6
|
+
export function resolveActorTransform(
|
|
7
|
+
patch?: Partial<ActorTransform>,
|
|
8
|
+
): ActorTransform {
|
|
9
|
+
const base = DEFAULT_ACTOR_TRANSFORM;
|
|
10
|
+
const scale = patch?.scale ?? base.scale ?? 1;
|
|
11
|
+
return {
|
|
12
|
+
x: patch?.x ?? base.x,
|
|
13
|
+
y: patch?.y ?? base.y,
|
|
14
|
+
scale,
|
|
15
|
+
scaleX: patch?.scaleX ?? patch?.scale ?? scale,
|
|
16
|
+
scaleY: patch?.scaleY ?? patch?.scale ?? scale,
|
|
17
|
+
rotation: patch?.rotation ?? base.rotation ?? 0,
|
|
18
|
+
anchorX: patch?.anchorX ?? base.anchorX ?? 0.5,
|
|
19
|
+
anchorY: patch?.anchorY ?? base.anchorY ?? 1,
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Map model-space NDC (Y-up, -1..1) to normalized stage space (0..1, Y-down).
|
|
25
|
+
*
|
|
26
|
+
* Model width/height of 2 NDC units map to `scale` stage units; anchor aligns
|
|
27
|
+
* the model bbox to `(x, y)` in stage space.
|
|
28
|
+
*/
|
|
29
|
+
export function modelNdcToStage(
|
|
30
|
+
modelX: number,
|
|
31
|
+
modelY: number,
|
|
32
|
+
transform: ActorTransform,
|
|
33
|
+
): { stageX: number; stageY: number } {
|
|
34
|
+
const scaleX = transform.scaleX ?? transform.scale ?? 1;
|
|
35
|
+
const scaleY = transform.scaleY ?? transform.scale ?? 1;
|
|
36
|
+
const anchorX = transform.anchorX ?? 0.5;
|
|
37
|
+
const anchorY = transform.anchorY ?? 1;
|
|
38
|
+
const rotation = transform.rotation ?? 0;
|
|
39
|
+
|
|
40
|
+
const anchorModelX = -1 + anchorX * 2;
|
|
41
|
+
const anchorModelY = 1 - anchorY * 2;
|
|
42
|
+
|
|
43
|
+
let localX = (modelX - anchorModelX) * scaleX * 0.5;
|
|
44
|
+
let localY = (anchorModelY - modelY) * scaleY * 0.5;
|
|
45
|
+
|
|
46
|
+
if (rotation !== 0) {
|
|
47
|
+
const c = Math.cos(rotation);
|
|
48
|
+
const s = Math.sin(rotation);
|
|
49
|
+
const rx = localX * c - localY * s;
|
|
50
|
+
const ry = localX * s + localY * c;
|
|
51
|
+
localX = rx;
|
|
52
|
+
localY = ry;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return {
|
|
56
|
+
stageX: transform.x + localX,
|
|
57
|
+
stageY: transform.y + localY,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** Inverse of {@link modelNdcToStage} for a given transform (ignores rotation). */
|
|
62
|
+
export function stageToModelNdc(
|
|
63
|
+
stageX: number,
|
|
64
|
+
stageY: number,
|
|
65
|
+
transform: ActorTransform,
|
|
66
|
+
): { modelX: number; modelY: number } {
|
|
67
|
+
const scaleX = transform.scaleX ?? transform.scale ?? 1;
|
|
68
|
+
const scaleY = transform.scaleY ?? transform.scale ?? 1;
|
|
69
|
+
const anchorX = transform.anchorX ?? 0.5;
|
|
70
|
+
const anchorY = transform.anchorY ?? 1;
|
|
71
|
+
const anchorModelX = -1 + anchorX * 2;
|
|
72
|
+
const anchorModelY = 1 - anchorY * 2;
|
|
73
|
+
|
|
74
|
+
const localX = (stageX - transform.x) / (scaleX * 0.5);
|
|
75
|
+
const localY = (stageY - transform.y) / (scaleY * 0.5);
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
modelX: localX + anchorModelX,
|
|
79
|
+
modelY: anchorModelY - localY,
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/** Canvas client coordinates → normalized stage space (0..1). */
|
|
84
|
+
export function clientToStage(
|
|
85
|
+
clientX: number,
|
|
86
|
+
clientY: number,
|
|
87
|
+
canvas: HTMLCanvasElement,
|
|
88
|
+
): { stageX: number; stageY: number } {
|
|
89
|
+
const rect = canvas.getBoundingClientRect();
|
|
90
|
+
const x = rect.width > 0 ? (clientX - rect.left) / rect.width : 0;
|
|
91
|
+
const y = rect.height > 0 ? (clientY - rect.top) / rect.height : 0;
|
|
92
|
+
return {
|
|
93
|
+
stageX: Math.min(1, Math.max(0, x)),
|
|
94
|
+
stageY: Math.min(1, Math.max(0, y)),
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** Focus drag vector in -1..1 from actor center toward a stage point. */
|
|
99
|
+
export function stageFocusDrag(
|
|
100
|
+
stageX: number,
|
|
101
|
+
stageY: number,
|
|
102
|
+
transform: ActorTransform,
|
|
103
|
+
): { dragX: number; dragY: number } {
|
|
104
|
+
const dx = stageX - transform.x;
|
|
105
|
+
const dy = transform.y - stageY;
|
|
106
|
+
const dragX = Math.min(1, Math.max(-1, dx * 2));
|
|
107
|
+
const dragY = Math.min(1, Math.max(-1, dy * 2));
|
|
108
|
+
return { dragX, dragY };
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/** Layer name → sort key (lower draws first). */
|
|
112
|
+
export function layerOrderIndex(
|
|
113
|
+
layer: string,
|
|
114
|
+
definedLayers: readonly string[],
|
|
115
|
+
): number {
|
|
116
|
+
const idx = definedLayers.indexOf(layer);
|
|
117
|
+
return idx >= 0 ? idx : definedLayers.length;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/** Sort actors for draw / hit-test (back to front for hit). */
|
|
121
|
+
export function compareActorsForDraw<
|
|
122
|
+
T extends { layer: string; order: number; creationIndex: number },
|
|
123
|
+
>(a: T, b: T, definedLayers: readonly string[]): number {
|
|
124
|
+
const la = layerOrderIndex(a.layer, definedLayers);
|
|
125
|
+
const lb = layerOrderIndex(b.layer, definedLayers);
|
|
126
|
+
return la - lb || a.order - b.order || a.creationIndex - b.creationIndex;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/** Front-to-back order for hit testing. */
|
|
130
|
+
export function compareActorsForHit<
|
|
131
|
+
T extends { layer: string; order: number; creationIndex: number },
|
|
132
|
+
>(a: T, b: T, definedLayers: readonly string[]): number {
|
|
133
|
+
return compareActorsForDraw(b, a, definedLayers);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/** Bake actor stage placement into drawable vertices (renderer stays single-pass). */
|
|
137
|
+
export function transformDrawablesForStage(
|
|
138
|
+
drawables: readonly DrawableMesh[],
|
|
139
|
+
transform: ActorTransform,
|
|
140
|
+
opacity: number,
|
|
141
|
+
): DrawableMesh[] {
|
|
142
|
+
const alpha = Math.min(1, Math.max(0, opacity));
|
|
143
|
+
return drawables.map((d) => {
|
|
144
|
+
if (!d.visible || alpha <= 0) return { ...d, visible: false };
|
|
145
|
+
const pos = new Float32Array(d.vertexPositions.length);
|
|
146
|
+
for (let i = 0; i < pos.length; i += 2) {
|
|
147
|
+
const { stageX, stageY } = modelNdcToStage(
|
|
148
|
+
d.vertexPositions[i]!,
|
|
149
|
+
d.vertexPositions[i + 1]!,
|
|
150
|
+
transform,
|
|
151
|
+
);
|
|
152
|
+
pos[i] = stageX * 2 - 1;
|
|
153
|
+
pos[i + 1] = 1 - stageY * 2;
|
|
154
|
+
}
|
|
155
|
+
return {
|
|
156
|
+
...d,
|
|
157
|
+
vertexPositions: pos,
|
|
158
|
+
opacity: d.opacity * alpha,
|
|
159
|
+
};
|
|
160
|
+
});
|
|
161
|
+
}
|