@doki-land/live2d-renderer 0.0.16 → 0.0.18
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 +36 -8
- package/dist/index.d.ts +142 -139
- package/dist/index.js +98 -98
- package/package.json +2 -2
- package/src/backends/canvas2d.ts +7 -4
- package/src/backends/webgl2.ts +3 -3
- package/src/backends/webgpu.ts +3 -3
- package/src/index.ts +43 -40
- package/src/moc/decode.ts +2 -2
- package/src/moc/drawable-flags.ts +1 -1
- package/src/moc/moc2-deform.ts +1 -1
- package/src/moc/moc2-reader.ts +1 -1
- package/src/moc/moc2.ts +1 -1
- package/src/moc/moc3-deform.ts +1 -1
- package/src/moc/moc3-layout.ts +3 -6
- package/src/moc/moc3-reader.ts +1 -1
- package/src/moc/moc3-to-program.ts +3 -5
- package/src/moc/moc3.ts +3 -3
- package/src/{blend.ts → render/blend.ts} +1 -1
- package/src/{create-renderer.ts → runtime/create-renderer.ts} +4 -4
- package/src/{model-runtime.ts → runtime/model-runtime.ts} +2 -2
- package/src/{shared-compile.ts → runtime/shared-compile.ts} +3 -3
- /package/src/{detect-format.ts → format/detect-format.ts} +0 -0
- /package/src/{clipping.ts → render/clipping.ts} +0 -0
- /package/src/{coords.ts → render/coords.ts} +0 -0
- /package/src/{preview-style.ts → render/preview-style.ts} +0 -0
package/README.md
CHANGED
|
@@ -19,12 +19,15 @@ Applications should normally use these capabilities through `@doki-land/live2d`.
|
|
|
19
19
|
|
|
20
20
|
## 🧭 Package Role
|
|
21
21
|
|
|
22
|
-
The source tree separates
|
|
22
|
+
The source tree separates execution concerns:
|
|
23
23
|
|
|
24
24
|
```text
|
|
25
|
-
src/moc/ model formats, deformation, and ModelBackend implementations
|
|
26
|
-
src/cpu/ model program parsing and CPU frame evaluation
|
|
27
25
|
src/backends/ WebGPU, WebGL2, and Canvas2D graphics backends
|
|
26
|
+
src/moc/ MOC2/MOC3 decode, deformation, and ModelBackend implementations
|
|
27
|
+
src/cpu/ model program parsing and CPU frame evaluation
|
|
28
|
+
src/render/ blend modes, clipping, coords, preview styling
|
|
29
|
+
src/runtime/ renderer factory and model-runtime glue
|
|
30
|
+
src/format/ moc binary and settings format peek helpers
|
|
28
31
|
```
|
|
29
32
|
|
|
30
33
|
MOC2 and MOC3 are model formats, not graphics backends. WebGPU, WebGL2, and Canvas2D are graphics backends, not model
|
|
@@ -42,10 +45,10 @@ pipelines.
|
|
|
42
45
|
## 🚀 Creating a Renderer
|
|
43
46
|
|
|
44
47
|
```ts
|
|
45
|
-
import {
|
|
48
|
+
import {createRenderer} from "@doki-land/live2d-renderer";
|
|
46
49
|
|
|
47
50
|
const renderer = createRenderer({
|
|
48
|
-
|
|
51
|
+
prefer: ["webgpu", "webgl2", "canvas2d"],
|
|
49
52
|
});
|
|
50
53
|
|
|
51
54
|
await renderer.initialize(canvas);
|
|
@@ -152,9 +155,34 @@ contract justifies it.
|
|
|
152
155
|
|
|
153
156
|
## 🤝 Contributing
|
|
154
157
|
|
|
155
|
-
|
|
156
|
-
|
|
158
|
+
### Renderer expectations
|
|
159
|
+
|
|
160
|
+
- Keep hot paths allocation-aware.
|
|
161
|
+
- Preserve deterministic CPU behavior.
|
|
162
|
+
- Document the visual invariant behind renderer changes.
|
|
163
|
+
- Add structural, pixel, or browser evidence appropriate to the change.
|
|
164
|
+
- Rebuild generated bundles from source instead of editing them manually.
|
|
157
165
|
|
|
158
166
|
## 📄 License
|
|
159
167
|
|
|
160
|
-
|
|
168
|
+
### Clean-room rendering
|
|
169
|
+
|
|
170
|
+
- WGSL and GLSL shaders, graphics pipelines, mask and blend paths, model execution, Canvas2D mesh rendering, resource
|
|
171
|
+
lifecycle, and GPU submission optimizations are independently designed and handwritten.
|
|
172
|
+
- No implementation is copied, translated, ported, derived from, or linked against an official renderer, Core binary,
|
|
173
|
+
SDK wrapper, shader source, pipeline implementation, or internal API.
|
|
174
|
+
- External format names identify interoperability targets only.
|
|
175
|
+
- The maintainers and contributors are independent and are not employed by, affiliated with, sponsored by, endorsed by,
|
|
176
|
+
or acting on behalf of the official Cubism SDK vendor.
|
|
177
|
+
|
|
178
|
+
### Contribution boundary
|
|
179
|
+
|
|
180
|
+
- Do not submit official SDK or shader source, disassembly-derived implementations, mechanically translated code, or
|
|
181
|
+
dependencies on an official runtime.
|
|
182
|
+
- Support compatibility patches with a public format fact, neutral fixture, reproducible independent observation, or
|
|
183
|
+
independently authored technical rationale.
|
|
184
|
+
|
|
185
|
+
### Terms
|
|
186
|
+
|
|
187
|
+
- See the repository license for source-code terms.
|
|
188
|
+
- Model assets used for local testing may have separate terms.
|
package/dist/index.d.ts
CHANGED
|
@@ -138,123 +138,6 @@ declare class WebGpuRendererImpl implements WebGpuRenderer {
|
|
|
138
138
|
declare function createWebGpuRenderer(options?: WebGpuRendererOptions): WebGpuRenderer;
|
|
139
139
|
declare function isWebGpuAvailable(): boolean;
|
|
140
140
|
|
|
141
|
-
/**
|
|
142
|
-
* Map Live2D blend modes to WebGL / Canvas compositing.
|
|
143
|
-
*/
|
|
144
|
-
|
|
145
|
-
/** Apply premultiplied-friendly Live2D blend factors on a WebGL2 context. */
|
|
146
|
-
declare function applyWebGl2BlendMode(gl: WebGL2RenderingContext, mode: BlendMode): void;
|
|
147
|
-
/** Canvas2D globalCompositeOperation for Live2D blend modes. */
|
|
148
|
-
declare function canvasCompositeForBlendMode(mode: BlendMode): GlobalCompositeOperation;
|
|
149
|
-
/** WebGPU blend state for Live2D modes (straight alpha draw path). */
|
|
150
|
-
declare function webGpuBlendState(mode: BlendMode): GPUBlendState;
|
|
151
|
-
|
|
152
|
-
/**
|
|
153
|
-
* Clipping-mask context grouping + mask atlas layout.
|
|
154
|
-
*
|
|
155
|
-
* Two packing modes:
|
|
156
|
-
* - `uv-grid`: √N UV cells, alpha channel only (Canvas2D / simple path)
|
|
157
|
-
* - `rgba`: Cubism-style channel × UV packing (≤4 → full UV on R/G/B/A)
|
|
158
|
-
*/
|
|
159
|
-
interface ClippingDrawableRef {
|
|
160
|
-
readonly index: number;
|
|
161
|
-
readonly maskIndices: readonly number[];
|
|
162
|
-
readonly invertedMask: boolean;
|
|
163
|
-
}
|
|
164
|
-
interface ClippingContext {
|
|
165
|
-
/** Stable key: invert flag + sorted mask drawable indices. */
|
|
166
|
-
readonly key: string;
|
|
167
|
-
readonly maskIndices: readonly number[];
|
|
168
|
-
readonly clippedIndices: readonly number[];
|
|
169
|
-
readonly invertedMask: boolean;
|
|
170
|
-
}
|
|
171
|
-
/** UV-space rectangle inside the shared mask atlas (0..1). */
|
|
172
|
-
interface MaskLayoutRect {
|
|
173
|
-
readonly x: number;
|
|
174
|
-
readonly y: number;
|
|
175
|
-
readonly width: number;
|
|
176
|
-
readonly height: number;
|
|
177
|
-
}
|
|
178
|
-
/** R/G/B/A write/sample selector (Cubism channelFlag). */
|
|
179
|
-
type MaskChannelFlag = readonly [number, number, number, number];
|
|
180
|
-
declare const MASK_CHANNEL_FLAGS: readonly MaskChannelFlag[];
|
|
181
|
-
type MaskAtlasMode = "uv-grid" | "rgba";
|
|
182
|
-
interface MaskAtlasOptions {
|
|
183
|
-
/** Packing strategy. Default `rgba` (Cubism density). */
|
|
184
|
-
readonly mode?: MaskAtlasMode;
|
|
185
|
-
/** Cell inset for `uv-grid` only (reduces neighbour bleed). */
|
|
186
|
-
readonly inset?: number;
|
|
187
|
-
/** Render-texture count for `rgba` (default 1 → max 36). */
|
|
188
|
-
readonly renderTextureCount?: number;
|
|
189
|
-
}
|
|
190
|
-
interface LaidOutClippingContext extends ClippingContext {
|
|
191
|
-
readonly layout: MaskLayoutRect;
|
|
192
|
-
/** 0=R … 3=A. `uv-grid` always uses A (3). */
|
|
193
|
-
readonly channelIndex: number;
|
|
194
|
-
readonly channelFlag: MaskChannelFlag;
|
|
195
|
-
/** Cubism multi-RT buffer index; currently always 0. */
|
|
196
|
-
readonly bufferIndex: number;
|
|
197
|
-
/**
|
|
198
|
-
* Expanded AABB of clipped drawables in vertex/NDC space.
|
|
199
|
-
* Mask write/sample map this rect into `layout` (Cubism bounds-fit).
|
|
200
|
-
* Default before `fitClippingContexts`: full NDC (-1..1)².
|
|
201
|
-
*/
|
|
202
|
-
readonly modelBounds: MaskLayoutRect;
|
|
203
|
-
}
|
|
204
|
-
/** Full clip-space quad used when no valid clipped bounds exist. */
|
|
205
|
-
declare const FULL_NDC_BOUNDS: MaskLayoutRect;
|
|
206
|
-
interface ClippingPartition {
|
|
207
|
-
readonly contexts: readonly LaidOutClippingContext[];
|
|
208
|
-
/** Drawables that consume a mask (drawn after mask pass). */
|
|
209
|
-
readonly clipped: ReadonlySet<number>;
|
|
210
|
-
/** Drawables used as masks (mask buffer only, not color target). */
|
|
211
|
-
readonly maskOnly: ReadonlySet<number>;
|
|
212
|
-
}
|
|
213
|
-
/**
|
|
214
|
-
* Group drawables that consume clipping masks into shared contexts.
|
|
215
|
-
* Drawables with empty maskIndices are omitted.
|
|
216
|
-
*/
|
|
217
|
-
declare function buildClippingContexts(drawables: readonly ClippingDrawableRef[]): ClippingContext[];
|
|
218
|
-
/**
|
|
219
|
-
* Pack clipping contexts into a square-ish UV grid on one atlas (alpha only).
|
|
220
|
-
*/
|
|
221
|
-
declare function layoutMaskAtlasUvGrid(contexts: readonly ClippingContext[], options?: {
|
|
222
|
-
inset?: number;
|
|
223
|
-
}): LaidOutClippingContext[];
|
|
224
|
-
/**
|
|
225
|
-
* Cubism `setupLayoutBounds`: pack across R/G/B/A then UV cells.
|
|
226
|
-
* Single RT → max 36 (4×9); multi-RT → 32 per sheet.
|
|
227
|
-
*/
|
|
228
|
-
declare function layoutMaskAtlasRgba(contexts: readonly ClippingContext[], options?: {
|
|
229
|
-
renderTextureCount?: number;
|
|
230
|
-
}): LaidOutClippingContext[];
|
|
231
|
-
/** Pack clipping contexts (default: Cubism RGBA density). */
|
|
232
|
-
declare function layoutMaskAtlas(contexts: readonly ClippingContext[], options?: MaskAtlasOptions): LaidOutClippingContext[];
|
|
233
|
-
/** Partition drawables and assign atlas layouts. */
|
|
234
|
-
declare function partitionForClipping(drawables: readonly ClippingDrawableRef[], options?: MaskAtlasOptions): ClippingPartition;
|
|
235
|
-
/** Axis-aligned bounds of interleaved xy vertex positions. */
|
|
236
|
-
declare function calcVertexBounds(positions: Float32Array): MaskLayoutRect | null;
|
|
237
|
-
/** Expand bounds by a relative margin on each axis (Cubism uses 0.05). */
|
|
238
|
-
declare function expandBounds(bounds: MaskLayoutRect, margin?: number): MaskLayoutRect;
|
|
239
|
-
interface MaskBoundsMeshRef {
|
|
240
|
-
readonly index: number;
|
|
241
|
-
readonly vertexPositions: Float32Array;
|
|
242
|
-
}
|
|
243
|
-
/**
|
|
244
|
-
* Union AABB of clipped drawables, expanded by margin (Cubism
|
|
245
|
-
* `calcClippedDrawableTotalBounds` + 0.05 expand).
|
|
246
|
-
*/
|
|
247
|
-
declare function calcClippedDrawableBounds(clippedIndices: readonly number[], byIndex: ReadonlyMap<number, MaskBoundsMeshRef>, margin?: number): MaskLayoutRect;
|
|
248
|
-
/**
|
|
249
|
-
* Attach per-context `modelBounds` so mask write/sample fit the clipped
|
|
250
|
-
* drawable AABB into the atlas cell (higher mask texel density).
|
|
251
|
-
*/
|
|
252
|
-
declare function fitClippingContexts(contexts: readonly LaidOutClippingContext[], byIndex: ReadonlyMap<number, MaskBoundsMeshRef>, margin?: number): LaidOutClippingContext[];
|
|
253
|
-
/** Flatten layout to `[x, y, w, h]` for GPU uniforms. */
|
|
254
|
-
declare function maskLayoutVec4(layout: MaskLayoutRect): Float32Array;
|
|
255
|
-
/** Flatten channel flag to `[r, g, b, a]` for GPU uniforms. */
|
|
256
|
-
declare function maskChannelVec4(flag: MaskChannelFlag): Float32Array;
|
|
257
|
-
|
|
258
141
|
declare const CPU_PROGRAM_KIND: "cpu-program";
|
|
259
142
|
interface CpuProgramFile {
|
|
260
143
|
readonly kind: typeof CPU_PROGRAM_KIND;
|
|
@@ -299,22 +182,6 @@ declare function evaluateFrame(instance: ModelInstance): FrameSnapshot;
|
|
|
299
182
|
/** Stable fingerprint for golden tests (positions + topology). */
|
|
300
183
|
declare function fingerprintSnapshot(snapshot: FrameSnapshot): string;
|
|
301
184
|
|
|
302
|
-
interface CreateRendererOptions {
|
|
303
|
-
/**
|
|
304
|
-
* Backend try order at initialize time.
|
|
305
|
-
* Default: `["webgpu", "webgl2", "canvas2d"]`.
|
|
306
|
-
*/
|
|
307
|
-
prefer?: RendererKind[];
|
|
308
|
-
webgpu?: WebGpuRendererOptions;
|
|
309
|
-
webgl2?: WebGl2RendererOptions;
|
|
310
|
-
canvas2d?: Canvas2DRendererOptions;
|
|
311
|
-
}
|
|
312
|
-
/**
|
|
313
|
-
* Create a renderer provider. Actual backend is chosen on `initialize`
|
|
314
|
-
* (WebGPU → WebGL2 → Canvas2D by default).
|
|
315
|
-
*/
|
|
316
|
-
declare function createRenderer(options?: CreateRendererOptions): Renderer;
|
|
317
|
-
|
|
318
185
|
/**
|
|
319
186
|
* moc binary format peek. Settings JSON detection lives in live2d-core
|
|
320
187
|
* (`detectModelSettingsFormat`) so loader and renderer share one helper.
|
|
@@ -335,10 +202,10 @@ interface DecodedMoc3 {
|
|
|
335
202
|
readonly format: "moc3";
|
|
336
203
|
readonly program: ModelProgram;
|
|
337
204
|
}
|
|
338
|
-
/** Decode
|
|
205
|
+
/** Decode binary moc2 (`.moc`) bytes into a default-pose ModelProgram. */
|
|
339
206
|
declare function decodeMoc2(bytes: ArrayBuffer): Promise<DecodedMoc2>;
|
|
340
207
|
/**
|
|
341
|
-
* Decode
|
|
208
|
+
* Decode binary moc3 bytes into a ModelProgram.
|
|
342
209
|
* Applies keyform blending + warp/rotation deformer chains at the given pose
|
|
343
210
|
* (defaults when called via decodeMoc3).
|
|
344
211
|
*/
|
|
@@ -347,7 +214,7 @@ declare function decodeMoc3(bytes: ArrayBuffer): Promise<DecodedMoc3>;
|
|
|
347
214
|
/**
|
|
348
215
|
* Shared moc2/moc3 drawable constant-flag decode → blend / invert-mask.
|
|
349
216
|
*/
|
|
350
|
-
/**
|
|
217
|
+
/** Drawable flag bits decoded from the supported MOC3 art-mesh records. */
|
|
351
218
|
declare const Moc3DrawableFlag: {
|
|
352
219
|
readonly BlendAdditive: number;
|
|
353
220
|
readonly BlendMultiplicative: number;
|
|
@@ -391,7 +258,7 @@ interface ParameterBinding {
|
|
|
391
258
|
/** Shared read-only decode payload (renderer-internal; set by stage asset cache). */
|
|
392
259
|
interface SharedModelCompile {
|
|
393
260
|
readonly mocBytes: ArrayBuffer;
|
|
394
|
-
/**
|
|
261
|
+
/** Parsed binary `.moc3` document. */
|
|
395
262
|
readonly moc3Doc?: unknown;
|
|
396
263
|
/** moc3 CPU `.program.json` program. */
|
|
397
264
|
readonly cpuProgram?: _doki_land_live2d_core.ModelProgram;
|
|
@@ -431,7 +298,7 @@ declare class Moc2Backend implements ModelBackend {
|
|
|
431
298
|
}
|
|
432
299
|
declare function createMoc2Backend(): Moc2Backend;
|
|
433
300
|
|
|
434
|
-
/** moc3 model backend
|
|
301
|
+
/** moc3 model backend for binary `.moc3` input or CPU `.program.json` fixtures. */
|
|
435
302
|
declare class Moc3Backend implements ModelBackend {
|
|
436
303
|
readonly format: "moc3";
|
|
437
304
|
canHandle(json: unknown): boolean;
|
|
@@ -468,7 +335,7 @@ interface Moc3KeyTables {
|
|
|
468
335
|
}
|
|
469
336
|
|
|
470
337
|
/**
|
|
471
|
-
* Parse
|
|
338
|
+
* Parse binary MOC3 input into typed section tables (no deform evaluation).
|
|
472
339
|
*/
|
|
473
340
|
interface Moc3CanvasInfo {
|
|
474
341
|
readonly pixelsPerUnit: number;
|
|
@@ -522,6 +389,123 @@ declare function applyMoc3Glues(positionsByMesh: Map<number, Float32Array>, glue
|
|
|
522
389
|
/** Mean Euclidean distance between glued vertex pairs (for tests). */
|
|
523
390
|
declare function meanGlueSeamDistance(positionsByMesh: ReadonlyMap<number, Float32Array>, glue: Moc3GlueDef): number;
|
|
524
391
|
|
|
392
|
+
/**
|
|
393
|
+
* Map Live2D blend modes to WebGL / Canvas compositing.
|
|
394
|
+
*/
|
|
395
|
+
|
|
396
|
+
/** Apply premultiplied-friendly Live2D blend factors on a WebGL2 context. */
|
|
397
|
+
declare function applyWebGl2BlendMode(gl: WebGL2RenderingContext, mode: BlendMode): void;
|
|
398
|
+
/** Canvas2D globalCompositeOperation for Live2D blend modes. */
|
|
399
|
+
declare function canvasCompositeForBlendMode(mode: BlendMode): GlobalCompositeOperation;
|
|
400
|
+
/** WebGPU blend state for Live2D modes (straight alpha draw path). */
|
|
401
|
+
declare function webGpuBlendState(mode: BlendMode): GPUBlendState;
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Clipping-mask context grouping + mask atlas layout.
|
|
405
|
+
*
|
|
406
|
+
* Two packing modes:
|
|
407
|
+
* - `uv-grid`: √N UV cells, alpha channel only (Canvas2D / simple path)
|
|
408
|
+
* - `rgba`: Cubism-style channel × UV packing (≤4 → full UV on R/G/B/A)
|
|
409
|
+
*/
|
|
410
|
+
interface ClippingDrawableRef {
|
|
411
|
+
readonly index: number;
|
|
412
|
+
readonly maskIndices: readonly number[];
|
|
413
|
+
readonly invertedMask: boolean;
|
|
414
|
+
}
|
|
415
|
+
interface ClippingContext {
|
|
416
|
+
/** Stable key: invert flag + sorted mask drawable indices. */
|
|
417
|
+
readonly key: string;
|
|
418
|
+
readonly maskIndices: readonly number[];
|
|
419
|
+
readonly clippedIndices: readonly number[];
|
|
420
|
+
readonly invertedMask: boolean;
|
|
421
|
+
}
|
|
422
|
+
/** UV-space rectangle inside the shared mask atlas (0..1). */
|
|
423
|
+
interface MaskLayoutRect {
|
|
424
|
+
readonly x: number;
|
|
425
|
+
readonly y: number;
|
|
426
|
+
readonly width: number;
|
|
427
|
+
readonly height: number;
|
|
428
|
+
}
|
|
429
|
+
/** R/G/B/A write/sample selector (Cubism channelFlag). */
|
|
430
|
+
type MaskChannelFlag = readonly [number, number, number, number];
|
|
431
|
+
declare const MASK_CHANNEL_FLAGS: readonly MaskChannelFlag[];
|
|
432
|
+
type MaskAtlasMode = "uv-grid" | "rgba";
|
|
433
|
+
interface MaskAtlasOptions {
|
|
434
|
+
/** Packing strategy. Default `rgba` (Cubism density). */
|
|
435
|
+
readonly mode?: MaskAtlasMode;
|
|
436
|
+
/** Cell inset for `uv-grid` only (reduces neighbour bleed). */
|
|
437
|
+
readonly inset?: number;
|
|
438
|
+
/** Render-texture count for `rgba` (default 1 → max 36). */
|
|
439
|
+
readonly renderTextureCount?: number;
|
|
440
|
+
}
|
|
441
|
+
interface LaidOutClippingContext extends ClippingContext {
|
|
442
|
+
readonly layout: MaskLayoutRect;
|
|
443
|
+
/** 0=R … 3=A. `uv-grid` always uses A (3). */
|
|
444
|
+
readonly channelIndex: number;
|
|
445
|
+
readonly channelFlag: MaskChannelFlag;
|
|
446
|
+
/** Cubism multi-RT buffer index; currently always 0. */
|
|
447
|
+
readonly bufferIndex: number;
|
|
448
|
+
/**
|
|
449
|
+
* Expanded AABB of clipped drawables in vertex/NDC space.
|
|
450
|
+
* Mask write/sample map this rect into `layout` (Cubism bounds-fit).
|
|
451
|
+
* Default before `fitClippingContexts`: full NDC (-1..1)².
|
|
452
|
+
*/
|
|
453
|
+
readonly modelBounds: MaskLayoutRect;
|
|
454
|
+
}
|
|
455
|
+
/** Full clip-space quad used when no valid clipped bounds exist. */
|
|
456
|
+
declare const FULL_NDC_BOUNDS: MaskLayoutRect;
|
|
457
|
+
interface ClippingPartition {
|
|
458
|
+
readonly contexts: readonly LaidOutClippingContext[];
|
|
459
|
+
/** Drawables that consume a mask (drawn after mask pass). */
|
|
460
|
+
readonly clipped: ReadonlySet<number>;
|
|
461
|
+
/** Drawables used as masks (mask buffer only, not color target). */
|
|
462
|
+
readonly maskOnly: ReadonlySet<number>;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Group drawables that consume clipping masks into shared contexts.
|
|
466
|
+
* Drawables with empty maskIndices are omitted.
|
|
467
|
+
*/
|
|
468
|
+
declare function buildClippingContexts(drawables: readonly ClippingDrawableRef[]): ClippingContext[];
|
|
469
|
+
/**
|
|
470
|
+
* Pack clipping contexts into a square-ish UV grid on one atlas (alpha only).
|
|
471
|
+
*/
|
|
472
|
+
declare function layoutMaskAtlasUvGrid(contexts: readonly ClippingContext[], options?: {
|
|
473
|
+
inset?: number;
|
|
474
|
+
}): LaidOutClippingContext[];
|
|
475
|
+
/**
|
|
476
|
+
* Cubism `setupLayoutBounds`: pack across R/G/B/A then UV cells.
|
|
477
|
+
* Single RT → max 36 (4×9); multi-RT → 32 per sheet.
|
|
478
|
+
*/
|
|
479
|
+
declare function layoutMaskAtlasRgba(contexts: readonly ClippingContext[], options?: {
|
|
480
|
+
renderTextureCount?: number;
|
|
481
|
+
}): LaidOutClippingContext[];
|
|
482
|
+
/** Pack clipping contexts (default: Cubism RGBA density). */
|
|
483
|
+
declare function layoutMaskAtlas(contexts: readonly ClippingContext[], options?: MaskAtlasOptions): LaidOutClippingContext[];
|
|
484
|
+
/** Partition drawables and assign atlas layouts. */
|
|
485
|
+
declare function partitionForClipping(drawables: readonly ClippingDrawableRef[], options?: MaskAtlasOptions): ClippingPartition;
|
|
486
|
+
/** Axis-aligned bounds of interleaved xy vertex positions. */
|
|
487
|
+
declare function calcVertexBounds(positions: Float32Array): MaskLayoutRect | null;
|
|
488
|
+
/** Expand bounds by a relative margin on each axis (Cubism uses 0.05). */
|
|
489
|
+
declare function expandBounds(bounds: MaskLayoutRect, margin?: number): MaskLayoutRect;
|
|
490
|
+
interface MaskBoundsMeshRef {
|
|
491
|
+
readonly index: number;
|
|
492
|
+
readonly vertexPositions: Float32Array;
|
|
493
|
+
}
|
|
494
|
+
/**
|
|
495
|
+
* Union AABB of clipped drawables, expanded by margin (Cubism
|
|
496
|
+
* `calcClippedDrawableTotalBounds` + 0.05 expand).
|
|
497
|
+
*/
|
|
498
|
+
declare function calcClippedDrawableBounds(clippedIndices: readonly number[], byIndex: ReadonlyMap<number, MaskBoundsMeshRef>, margin?: number): MaskLayoutRect;
|
|
499
|
+
/**
|
|
500
|
+
* Attach per-context `modelBounds` so mask write/sample fit the clipped
|
|
501
|
+
* drawable AABB into the atlas cell (higher mask texel density).
|
|
502
|
+
*/
|
|
503
|
+
declare function fitClippingContexts(contexts: readonly LaidOutClippingContext[], byIndex: ReadonlyMap<number, MaskBoundsMeshRef>, margin?: number): LaidOutClippingContext[];
|
|
504
|
+
/** Flatten layout to `[x, y, w, h]` for GPU uniforms. */
|
|
505
|
+
declare function maskLayoutVec4(layout: MaskLayoutRect): Float32Array;
|
|
506
|
+
/** Flatten channel flag to `[r, g, b, a]` for GPU uniforms. */
|
|
507
|
+
declare function maskChannelVec4(flag: MaskChannelFlag): Float32Array;
|
|
508
|
+
|
|
525
509
|
/** Shared untextured preview colors (Canvas2D / WebGL2 / WebGPU parity). */
|
|
526
510
|
declare const PREVIEW_FILL: {
|
|
527
511
|
readonly r: number;
|
|
@@ -538,6 +522,22 @@ declare const PREVIEW_STROKE: {
|
|
|
538
522
|
/** Expand triangle indices into a line-list (each edge twice-wound). */
|
|
539
523
|
declare function triangleEdgesToLineList(indices: Uint16Array): Uint16Array;
|
|
540
524
|
|
|
525
|
+
interface CreateRendererOptions {
|
|
526
|
+
/**
|
|
527
|
+
* Backend try order at initialize time.
|
|
528
|
+
* Default: `["webgpu", "webgl2", "canvas2d"]`.
|
|
529
|
+
*/
|
|
530
|
+
prefer?: RendererKind[];
|
|
531
|
+
webgpu?: WebGpuRendererOptions;
|
|
532
|
+
webgl2?: WebGl2RendererOptions;
|
|
533
|
+
canvas2d?: Canvas2DRendererOptions;
|
|
534
|
+
}
|
|
535
|
+
/**
|
|
536
|
+
* Create a renderer provider. Actual backend is chosen on `initialize`
|
|
537
|
+
* (WebGPU → WebGL2 → Canvas2D by default).
|
|
538
|
+
*/
|
|
539
|
+
declare function createRenderer(options?: CreateRendererOptions): Renderer;
|
|
540
|
+
|
|
541
541
|
/** One-time decode of moc bytes for stage-level {@link ModelAsset} sharing. */
|
|
542
542
|
declare function compileSharedModelCompile(settings: ModelSettings, mocBytes: ArrayBuffer): SharedModelCompile;
|
|
543
543
|
|
|
@@ -548,6 +548,9 @@ declare function compileSharedModelCompile(settings: ModelSettings, mocBytes: Ar
|
|
|
548
548
|
* - `backends/` — graphics only (webgpu / webgl2 / canvas2d)
|
|
549
549
|
* - `moc/` — moc2 / moc3 decode + model runtimes (not graphics backends)
|
|
550
550
|
* - `cpu/` — CPU evaluate + cpu-program fixture codec
|
|
551
|
+
* - `render/` — blend, clipping, coords, preview styling helpers
|
|
552
|
+
* - `runtime/` — renderer factory + model runtime glue
|
|
553
|
+
* - `format/` — moc binary / settings format peek (shared with core)
|
|
551
554
|
* - `tests/` — all vitest suites (package root)
|
|
552
555
|
*/
|
|
553
556
|
|
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var BlendMode = {
|
|
|
5
5
|
Multiplicative: 2
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
// src/blend.ts
|
|
8
|
+
// src/render/blend.ts
|
|
9
9
|
function applyWebGl2BlendMode(gl, mode) {
|
|
10
10
|
gl.enable(gl.BLEND);
|
|
11
11
|
switch (mode) {
|
|
@@ -84,7 +84,7 @@ function webGpuBlendState(mode) {
|
|
|
84
84
|
}
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
// src/clipping.ts
|
|
87
|
+
// src/render/clipping.ts
|
|
88
88
|
var MASK_CHANNEL_FLAGS = [
|
|
89
89
|
[1, 0, 0, 0],
|
|
90
90
|
[0, 1, 0, 0],
|
|
@@ -329,7 +329,7 @@ function maskChannelVec4(flag) {
|
|
|
329
329
|
return new Float32Array([flag[0], flag[1], flag[2], flag[3]]);
|
|
330
330
|
}
|
|
331
331
|
|
|
332
|
-
// src/coords.ts
|
|
332
|
+
// src/render/coords.ts
|
|
333
333
|
function modelYUpToCanvasPixelY(modelY, canvasHeight) {
|
|
334
334
|
return (1 - modelY) * (canvasHeight / 2);
|
|
335
335
|
}
|
|
@@ -337,7 +337,7 @@ function modelXToCanvasPixelX(modelX, canvasWidth) {
|
|
|
337
337
|
return (modelX + 1) * (canvasWidth / 2);
|
|
338
338
|
}
|
|
339
339
|
|
|
340
|
-
// src/preview-style.ts
|
|
340
|
+
// src/render/preview-style.ts
|
|
341
341
|
var PREVIEW_FILL = {
|
|
342
342
|
r: 91 / 255,
|
|
343
343
|
g: 141 / 255,
|
|
@@ -2620,98 +2620,7 @@ function fingerprintSnapshot(snapshot) {
|
|
|
2620
2620
|
return parts.join("|");
|
|
2621
2621
|
}
|
|
2622
2622
|
|
|
2623
|
-
// src/
|
|
2624
|
-
var DEFAULT_PREFER = ["webgpu", "webgl2", "canvas2d"];
|
|
2625
|
-
var INIT_TIMEOUT_MS = {
|
|
2626
|
-
webgpu: 2500,
|
|
2627
|
-
webgl2: 1500,
|
|
2628
|
-
canvas2d: 1e3
|
|
2629
|
-
};
|
|
2630
|
-
function instantiate(kind, options) {
|
|
2631
|
-
if (kind === "webgpu") return createWebGpuRenderer(options.webgpu);
|
|
2632
|
-
if (kind === "webgl2") return createWebGl2Renderer(options.webgl2);
|
|
2633
|
-
return createCanvas2DRenderer(options.canvas2d);
|
|
2634
|
-
}
|
|
2635
|
-
async function initializeWithTimeout(kind, candidate, canvas) {
|
|
2636
|
-
const ms = INIT_TIMEOUT_MS[kind];
|
|
2637
|
-
let timer;
|
|
2638
|
-
try {
|
|
2639
|
-
await Promise.race([
|
|
2640
|
-
candidate.initialize(canvas),
|
|
2641
|
-
new Promise((_, reject) => {
|
|
2642
|
-
timer = setTimeout(() => {
|
|
2643
|
-
reject(
|
|
2644
|
-
new Error(
|
|
2645
|
-
`@doki-land/live2d-renderer: ${kind} initialize timed out after ${ms}ms`
|
|
2646
|
-
)
|
|
2647
|
-
);
|
|
2648
|
-
}, ms);
|
|
2649
|
-
})
|
|
2650
|
-
]);
|
|
2651
|
-
} finally {
|
|
2652
|
-
if (timer !== void 0) clearTimeout(timer);
|
|
2653
|
-
}
|
|
2654
|
-
}
|
|
2655
|
-
var FallbackRenderer = class {
|
|
2656
|
-
#inner = null;
|
|
2657
|
-
#kind = "webgpu";
|
|
2658
|
-
#prefer;
|
|
2659
|
-
#options;
|
|
2660
|
-
constructor(prefer, options) {
|
|
2661
|
-
this.#prefer = prefer;
|
|
2662
|
-
this.#options = options;
|
|
2663
|
-
}
|
|
2664
|
-
get kind() {
|
|
2665
|
-
return this.#inner?.kind ?? this.#kind;
|
|
2666
|
-
}
|
|
2667
|
-
async initialize(canvas) {
|
|
2668
|
-
const errors = [];
|
|
2669
|
-
for (const kind of this.#prefer) {
|
|
2670
|
-
const candidate = instantiate(kind, this.#options);
|
|
2671
|
-
try {
|
|
2672
|
-
await initializeWithTimeout(kind, candidate, canvas);
|
|
2673
|
-
this.#inner = candidate;
|
|
2674
|
-
this.#kind = candidate.kind;
|
|
2675
|
-
return;
|
|
2676
|
-
} catch (err) {
|
|
2677
|
-
candidate.destroy();
|
|
2678
|
-
errors.push(
|
|
2679
|
-
`${kind}: ${err instanceof Error ? err.message : String(err)}`
|
|
2680
|
-
);
|
|
2681
|
-
}
|
|
2682
|
-
}
|
|
2683
|
-
throw new Error(
|
|
2684
|
-
`@doki-land/live2d-renderer: no renderer initialized (${errors.join("; ")})`
|
|
2685
|
-
);
|
|
2686
|
-
}
|
|
2687
|
-
createModelDrawPass() {
|
|
2688
|
-
if (!this.#inner) {
|
|
2689
|
-
throw new Error(
|
|
2690
|
-
"@doki-land/live2d-renderer: renderer not initialized"
|
|
2691
|
-
);
|
|
2692
|
-
}
|
|
2693
|
-
return this.#inner.createModelDrawPass();
|
|
2694
|
-
}
|
|
2695
|
-
beginFrame() {
|
|
2696
|
-
this.#inner?.beginFrame();
|
|
2697
|
-
}
|
|
2698
|
-
endFrame() {
|
|
2699
|
-
this.#inner?.endFrame();
|
|
2700
|
-
}
|
|
2701
|
-
resize(width, height) {
|
|
2702
|
-
this.#inner?.resize(width, height);
|
|
2703
|
-
}
|
|
2704
|
-
destroy() {
|
|
2705
|
-
this.#inner?.destroy();
|
|
2706
|
-
this.#inner = null;
|
|
2707
|
-
}
|
|
2708
|
-
};
|
|
2709
|
-
function createRenderer(options = {}) {
|
|
2710
|
-
const prefer = options.prefer?.length ? options.prefer : DEFAULT_PREFER;
|
|
2711
|
-
return new FallbackRenderer(prefer, options);
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2714
|
-
// src/detect-format.ts
|
|
2623
|
+
// src/format/detect-format.ts
|
|
2715
2624
|
import { detectModelSettingsFormat } from "@doki-land/live2d-core";
|
|
2716
2625
|
function detectMocBinaryFormat(bytes) {
|
|
2717
2626
|
if (bytes.byteLength < 4) return null;
|
|
@@ -5512,7 +5421,98 @@ function createMoc3Backend() {
|
|
|
5512
5421
|
return new Moc3Backend();
|
|
5513
5422
|
}
|
|
5514
5423
|
|
|
5515
|
-
// src/
|
|
5424
|
+
// src/runtime/create-renderer.ts
|
|
5425
|
+
var DEFAULT_PREFER = ["webgpu", "webgl2", "canvas2d"];
|
|
5426
|
+
var INIT_TIMEOUT_MS = {
|
|
5427
|
+
webgpu: 2500,
|
|
5428
|
+
webgl2: 1500,
|
|
5429
|
+
canvas2d: 1e3
|
|
5430
|
+
};
|
|
5431
|
+
function instantiate(kind, options) {
|
|
5432
|
+
if (kind === "webgpu") return createWebGpuRenderer(options.webgpu);
|
|
5433
|
+
if (kind === "webgl2") return createWebGl2Renderer(options.webgl2);
|
|
5434
|
+
return createCanvas2DRenderer(options.canvas2d);
|
|
5435
|
+
}
|
|
5436
|
+
async function initializeWithTimeout(kind, candidate, canvas) {
|
|
5437
|
+
const ms = INIT_TIMEOUT_MS[kind];
|
|
5438
|
+
let timer;
|
|
5439
|
+
try {
|
|
5440
|
+
await Promise.race([
|
|
5441
|
+
candidate.initialize(canvas),
|
|
5442
|
+
new Promise((_, reject) => {
|
|
5443
|
+
timer = setTimeout(() => {
|
|
5444
|
+
reject(
|
|
5445
|
+
new Error(
|
|
5446
|
+
`@doki-land/live2d-renderer: ${kind} initialize timed out after ${ms}ms`
|
|
5447
|
+
)
|
|
5448
|
+
);
|
|
5449
|
+
}, ms);
|
|
5450
|
+
})
|
|
5451
|
+
]);
|
|
5452
|
+
} finally {
|
|
5453
|
+
if (timer !== void 0) clearTimeout(timer);
|
|
5454
|
+
}
|
|
5455
|
+
}
|
|
5456
|
+
var FallbackRenderer = class {
|
|
5457
|
+
#inner = null;
|
|
5458
|
+
#kind = "webgpu";
|
|
5459
|
+
#prefer;
|
|
5460
|
+
#options;
|
|
5461
|
+
constructor(prefer, options) {
|
|
5462
|
+
this.#prefer = prefer;
|
|
5463
|
+
this.#options = options;
|
|
5464
|
+
}
|
|
5465
|
+
get kind() {
|
|
5466
|
+
return this.#inner?.kind ?? this.#kind;
|
|
5467
|
+
}
|
|
5468
|
+
async initialize(canvas) {
|
|
5469
|
+
const errors = [];
|
|
5470
|
+
for (const kind of this.#prefer) {
|
|
5471
|
+
const candidate = instantiate(kind, this.#options);
|
|
5472
|
+
try {
|
|
5473
|
+
await initializeWithTimeout(kind, candidate, canvas);
|
|
5474
|
+
this.#inner = candidate;
|
|
5475
|
+
this.#kind = candidate.kind;
|
|
5476
|
+
return;
|
|
5477
|
+
} catch (err) {
|
|
5478
|
+
candidate.destroy();
|
|
5479
|
+
errors.push(
|
|
5480
|
+
`${kind}: ${err instanceof Error ? err.message : String(err)}`
|
|
5481
|
+
);
|
|
5482
|
+
}
|
|
5483
|
+
}
|
|
5484
|
+
throw new Error(
|
|
5485
|
+
`@doki-land/live2d-renderer: no renderer initialized (${errors.join("; ")})`
|
|
5486
|
+
);
|
|
5487
|
+
}
|
|
5488
|
+
createModelDrawPass() {
|
|
5489
|
+
if (!this.#inner) {
|
|
5490
|
+
throw new Error(
|
|
5491
|
+
"@doki-land/live2d-renderer: renderer not initialized"
|
|
5492
|
+
);
|
|
5493
|
+
}
|
|
5494
|
+
return this.#inner.createModelDrawPass();
|
|
5495
|
+
}
|
|
5496
|
+
beginFrame() {
|
|
5497
|
+
this.#inner?.beginFrame();
|
|
5498
|
+
}
|
|
5499
|
+
endFrame() {
|
|
5500
|
+
this.#inner?.endFrame();
|
|
5501
|
+
}
|
|
5502
|
+
resize(width, height) {
|
|
5503
|
+
this.#inner?.resize(width, height);
|
|
5504
|
+
}
|
|
5505
|
+
destroy() {
|
|
5506
|
+
this.#inner?.destroy();
|
|
5507
|
+
this.#inner = null;
|
|
5508
|
+
}
|
|
5509
|
+
};
|
|
5510
|
+
function createRenderer(options = {}) {
|
|
5511
|
+
const prefer = options.prefer?.length ? options.prefer : DEFAULT_PREFER;
|
|
5512
|
+
return new FallbackRenderer(prefer, options);
|
|
5513
|
+
}
|
|
5514
|
+
|
|
5515
|
+
// src/runtime/model-runtime.ts
|
|
5516
5516
|
function selectModelBackend(backends, json) {
|
|
5517
5517
|
const hit = backends.find((b) => b.canHandle(json));
|
|
5518
5518
|
if (!hit) {
|
|
@@ -5523,7 +5523,7 @@ function selectModelBackend(backends, json) {
|
|
|
5523
5523
|
return hit;
|
|
5524
5524
|
}
|
|
5525
5525
|
|
|
5526
|
-
// src/shared-compile.ts
|
|
5526
|
+
// src/runtime/shared-compile.ts
|
|
5527
5527
|
function compileSharedModelCompile(settings, mocBytes) {
|
|
5528
5528
|
if (settings.format === "moc2") {
|
|
5529
5529
|
return {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@doki-land/live2d-renderer",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.18",
|
|
4
4
|
"description": "Live2D rendering — moc2/moc3 decode + WebGPU, WebGL2, Canvas2D backends for live2d.ts.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "MIT",
|
|
@@ -45,7 +45,7 @@
|
|
|
45
45
|
"test": "vitest run"
|
|
46
46
|
},
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@doki-land/live2d-core": "0.0.
|
|
48
|
+
"@doki-land/live2d-core": "0.0.18"
|
|
49
49
|
},
|
|
50
50
|
"sideEffects": false
|
|
51
51
|
}
|
package/src/backends/canvas2d.ts
CHANGED
|
@@ -1,11 +1,14 @@
|
|
|
1
|
-
import { canvasCompositeForBlendMode } from "../blend.js";
|
|
1
|
+
import { canvasCompositeForBlendMode } from "../render/blend.js";
|
|
2
2
|
import {
|
|
3
3
|
fitClippingContexts,
|
|
4
4
|
type MaskLayoutRect,
|
|
5
5
|
partitionForClipping,
|
|
6
|
-
} from "../clipping.js";
|
|
7
|
-
import {
|
|
8
|
-
|
|
6
|
+
} from "../render/clipping.js";
|
|
7
|
+
import {
|
|
8
|
+
modelXToCanvasPixelX,
|
|
9
|
+
modelYUpToCanvasPixelY,
|
|
10
|
+
} from "../render/coords.js";
|
|
11
|
+
import { PREVIEW_FILL, PREVIEW_STROKE } from "../render/preview-style.js";
|
|
9
12
|
import type {
|
|
10
13
|
DrawableMesh,
|
|
11
14
|
ModelDrawPass,
|
package/src/backends/webgl2.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* multiply drawable alpha by the mask (or 1−mask when inverted).
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { applyWebGl2BlendMode } from "../blend.js";
|
|
8
|
+
import { applyWebGl2BlendMode } from "../render/blend.js";
|
|
9
9
|
import {
|
|
10
10
|
fitClippingContexts,
|
|
11
11
|
type LaidOutClippingContext,
|
|
@@ -13,12 +13,12 @@ import {
|
|
|
13
13
|
maskChannelVec4,
|
|
14
14
|
maskLayoutVec4,
|
|
15
15
|
partitionForClipping,
|
|
16
|
-
} from "../clipping.js";
|
|
16
|
+
} from "../render/clipping.js";
|
|
17
17
|
import {
|
|
18
18
|
PREVIEW_FILL,
|
|
19
19
|
PREVIEW_STROKE,
|
|
20
20
|
triangleEdgesToLineList,
|
|
21
|
-
} from "../preview-style.js";
|
|
21
|
+
} from "../render/preview-style.js";
|
|
22
22
|
import type {
|
|
23
23
|
DrawableMesh,
|
|
24
24
|
ModelDrawPass,
|
package/src/backends/webgpu.ts
CHANGED
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
* atlas layout; endFrame submits.
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
|
-
import { webGpuBlendState } from "../blend.js";
|
|
9
|
+
import { webGpuBlendState } from "../render/blend.js";
|
|
10
10
|
import {
|
|
11
11
|
fitClippingContexts,
|
|
12
12
|
type LaidOutClippingContext,
|
|
@@ -14,12 +14,12 @@ import {
|
|
|
14
14
|
maskChannelVec4,
|
|
15
15
|
maskLayoutVec4,
|
|
16
16
|
partitionForClipping,
|
|
17
|
-
} from "../clipping.js";
|
|
17
|
+
} from "../render/clipping.js";
|
|
18
18
|
import {
|
|
19
19
|
PREVIEW_FILL,
|
|
20
20
|
PREVIEW_STROKE,
|
|
21
21
|
triangleEdgesToLineList,
|
|
22
|
-
} from "../preview-style.js";
|
|
22
|
+
} from "../render/preview-style.js";
|
|
23
23
|
import type {
|
|
24
24
|
DrawableMesh,
|
|
25
25
|
ModelDrawPass,
|
package/src/index.ts
CHANGED
|
@@ -5,6 +5,9 @@
|
|
|
5
5
|
* - `backends/` — graphics only (webgpu / webgl2 / canvas2d)
|
|
6
6
|
* - `moc/` — moc2 / moc3 decode + model runtimes (not graphics backends)
|
|
7
7
|
* - `cpu/` — CPU evaluate + cpu-program fixture codec
|
|
8
|
+
* - `render/` — blend, clipping, coords, preview styling helpers
|
|
9
|
+
* - `runtime/` — renderer factory + model runtime glue
|
|
10
|
+
* - `format/` — moc binary / settings format peek (shared with core)
|
|
8
11
|
* - `tests/` — all vitest suites (package root)
|
|
9
12
|
*/
|
|
10
13
|
|
|
@@ -26,34 +29,6 @@ export {
|
|
|
26
29
|
WebGpuRendererImpl,
|
|
27
30
|
type WebGpuRendererOptions,
|
|
28
31
|
} from "./backends/webgpu.js";
|
|
29
|
-
export {
|
|
30
|
-
applyWebGl2BlendMode,
|
|
31
|
-
canvasCompositeForBlendMode,
|
|
32
|
-
webGpuBlendState,
|
|
33
|
-
} from "./blend.js";
|
|
34
|
-
export {
|
|
35
|
-
buildClippingContexts,
|
|
36
|
-
type ClippingContext,
|
|
37
|
-
type ClippingDrawableRef,
|
|
38
|
-
type ClippingPartition,
|
|
39
|
-
calcClippedDrawableBounds,
|
|
40
|
-
calcVertexBounds,
|
|
41
|
-
expandBounds,
|
|
42
|
-
FULL_NDC_BOUNDS,
|
|
43
|
-
fitClippingContexts,
|
|
44
|
-
type LaidOutClippingContext,
|
|
45
|
-
layoutMaskAtlas,
|
|
46
|
-
layoutMaskAtlasRgba,
|
|
47
|
-
layoutMaskAtlasUvGrid,
|
|
48
|
-
MASK_CHANNEL_FLAGS,
|
|
49
|
-
type MaskAtlasMode,
|
|
50
|
-
type MaskAtlasOptions,
|
|
51
|
-
type MaskChannelFlag,
|
|
52
|
-
type MaskLayoutRect,
|
|
53
|
-
maskChannelVec4,
|
|
54
|
-
maskLayoutVec4,
|
|
55
|
-
partitionForClipping,
|
|
56
|
-
} from "./clipping.js";
|
|
57
32
|
export {
|
|
58
33
|
CPU_PROGRAM_KIND,
|
|
59
34
|
type CpuProgramFile,
|
|
@@ -68,14 +43,10 @@ export {
|
|
|
68
43
|
fingerprintSnapshot,
|
|
69
44
|
setParameterValue,
|
|
70
45
|
} from "./cpu/evaluate.js";
|
|
71
|
-
export {
|
|
72
|
-
type CreateRendererOptions,
|
|
73
|
-
createRenderer,
|
|
74
|
-
} from "./create-renderer.js";
|
|
75
46
|
export {
|
|
76
47
|
detectMocBinaryFormat,
|
|
77
48
|
detectModelSettingsFormat,
|
|
78
|
-
} from "./detect-format.js";
|
|
49
|
+
} from "./format/detect-format.js";
|
|
79
50
|
export {
|
|
80
51
|
type DecodedMoc2,
|
|
81
52
|
type DecodedMoc3,
|
|
@@ -96,19 +67,51 @@ export {
|
|
|
96
67
|
type Moc3GluePair,
|
|
97
68
|
meanGlueSeamDistance,
|
|
98
69
|
} from "./moc/moc3-glue.js";
|
|
70
|
+
export {
|
|
71
|
+
applyWebGl2BlendMode,
|
|
72
|
+
canvasCompositeForBlendMode,
|
|
73
|
+
webGpuBlendState,
|
|
74
|
+
} from "./render/blend.js";
|
|
75
|
+
export {
|
|
76
|
+
buildClippingContexts,
|
|
77
|
+
type ClippingContext,
|
|
78
|
+
type ClippingDrawableRef,
|
|
79
|
+
type ClippingPartition,
|
|
80
|
+
calcClippedDrawableBounds,
|
|
81
|
+
calcVertexBounds,
|
|
82
|
+
expandBounds,
|
|
83
|
+
FULL_NDC_BOUNDS,
|
|
84
|
+
fitClippingContexts,
|
|
85
|
+
type LaidOutClippingContext,
|
|
86
|
+
layoutMaskAtlas,
|
|
87
|
+
layoutMaskAtlasRgba,
|
|
88
|
+
layoutMaskAtlasUvGrid,
|
|
89
|
+
MASK_CHANNEL_FLAGS,
|
|
90
|
+
type MaskAtlasMode,
|
|
91
|
+
type MaskAtlasOptions,
|
|
92
|
+
type MaskChannelFlag,
|
|
93
|
+
type MaskLayoutRect,
|
|
94
|
+
maskChannelVec4,
|
|
95
|
+
maskLayoutVec4,
|
|
96
|
+
partitionForClipping,
|
|
97
|
+
} from "./render/clipping.js";
|
|
98
|
+
export {
|
|
99
|
+
PREVIEW_FILL,
|
|
100
|
+
PREVIEW_STROKE,
|
|
101
|
+
triangleEdgesToLineList,
|
|
102
|
+
} from "./render/preview-style.js";
|
|
103
|
+
export {
|
|
104
|
+
type CreateRendererOptions,
|
|
105
|
+
createRenderer,
|
|
106
|
+
} from "./runtime/create-renderer.js";
|
|
99
107
|
export {
|
|
100
108
|
type ModelBackend,
|
|
101
109
|
type ModelBackendOptions,
|
|
102
110
|
type ParameterBinding,
|
|
103
111
|
type SharedModelCompile,
|
|
104
112
|
selectModelBackend,
|
|
105
|
-
} from "./model-runtime.js";
|
|
106
|
-
export {
|
|
107
|
-
PREVIEW_FILL,
|
|
108
|
-
PREVIEW_STROKE,
|
|
109
|
-
triangleEdgesToLineList,
|
|
110
|
-
} from "./preview-style.js";
|
|
111
|
-
export { compileSharedModelCompile } from "./shared-compile.js";
|
|
113
|
+
} from "./runtime/model-runtime.js";
|
|
114
|
+
export { compileSharedModelCompile } from "./runtime/shared-compile.js";
|
|
112
115
|
export {
|
|
113
116
|
BlendMode,
|
|
114
117
|
type DrawableMesh,
|
package/src/moc/decode.ts
CHANGED
|
@@ -23,7 +23,7 @@ function readMagic(bytes: ArrayBuffer): string {
|
|
|
23
23
|
return String.fromCharCode(...new Uint8Array(bytes, 0, 4));
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
/** Decode
|
|
26
|
+
/** Decode binary moc2 (`.moc`) bytes into a default-pose ModelProgram. */
|
|
27
27
|
export async function decodeMoc2(bytes: ArrayBuffer): Promise<DecodedMoc2> {
|
|
28
28
|
if (bytes.byteLength < 4) {
|
|
29
29
|
throw new Error("@doki-land/live2d-renderer: truncated moc2 header");
|
|
@@ -48,7 +48,7 @@ export async function decodeMoc2(bytes: ArrayBuffer): Promise<DecodedMoc2> {
|
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
51
|
-
* Decode
|
|
51
|
+
* Decode binary moc3 bytes into a ModelProgram.
|
|
52
52
|
* Applies keyform blending + warp/rotation deformer chains at the given pose
|
|
53
53
|
* (defaults when called via decodeMoc3).
|
|
54
54
|
*/
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
|
|
5
5
|
import { FrameBlendMode } from "@doki-land/live2d-core";
|
|
6
6
|
|
|
7
|
-
/**
|
|
7
|
+
/** Drawable flag bits decoded from the supported MOC3 art-mesh records. */
|
|
8
8
|
export const Moc3DrawableFlag = {
|
|
9
9
|
BlendAdditive: 1 << 0,
|
|
10
10
|
BlendMultiplicative: 1 << 1,
|
package/src/moc/moc2-deform.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* moc2 default-pose deformer bake: affine + mesh-warp parent chain.
|
|
3
|
-
*
|
|
3
|
+
* Evaluates the parsed keyform tables into drawable-space positions.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { interpolateKeyforms, resolveKeyformBlend } from "./moc2-keyforms.js";
|
package/src/moc/moc2-reader.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* moc2 binary stream reader (7-bit varints, big-endian ints/floats).
|
|
3
|
-
*
|
|
3
|
+
* Covers the record layout exercised by the repository's supported fixtures.
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
export const MOC2_REF_TYPE = 33;
|
package/src/moc/moc2.ts
CHANGED
|
@@ -15,7 +15,7 @@ import type {
|
|
|
15
15
|
ModelBackend,
|
|
16
16
|
ModelBackendOptions,
|
|
17
17
|
ParameterBinding,
|
|
18
|
-
} from "../model-runtime.js";
|
|
18
|
+
} from "../runtime/model-runtime.js";
|
|
19
19
|
import type { BlendMode, DrawableMesh } from "../types.js";
|
|
20
20
|
import { type Moc2ModelImpl, Moc2Parser } from "./moc2-objects.js";
|
|
21
21
|
import {
|
package/src/moc/moc3-deform.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* moc3 deformer bake: warp grids + rotation affines along parent chains.
|
|
3
3
|
*
|
|
4
|
-
* Coordinate contract
|
|
4
|
+
* Coordinate contract covered by the Wanko regression fixture:
|
|
5
5
|
* - Root rotations author scale ≈ 1/pixelsPerUnit; children stay in pixels.
|
|
6
6
|
* - Nested rotation under rotation: mulAffine; origins/children share parent units.
|
|
7
7
|
* - Rotation under warp: origin is warp UV; fold 1/ppu into the local linear
|
package/src/moc/moc3-layout.ts
CHANGED
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
* Layout order follows public MOC3 memory-map descriptions (OpenL2D hexpat /
|
|
6
|
-
* independent format notes). Deformer evaluation is deferred — default pose
|
|
7
|
-
* uses each art mesh's first keyform only.
|
|
2
|
+
* MOC3 section layout used by the decoder: a 64-byte header, a section-offset
|
|
3
|
+
* table of 160 u32 entries, and the referenced body arrays. Deformer evaluation
|
|
4
|
+
* is deferred; the initial pose uses each art mesh's first keyform.
|
|
8
5
|
*/
|
|
9
6
|
|
|
10
7
|
export const MOC3_MAGIC = "MOC3";
|
package/src/moc/moc3-reader.ts
CHANGED
|
@@ -38,11 +38,9 @@ function normalizePositions(
|
|
|
38
38
|
canvasHeight: number,
|
|
39
39
|
pixelsPerUnit: number,
|
|
40
40
|
): Float32Array {
|
|
41
|
-
//
|
|
42
|
-
//
|
|
43
|
-
//
|
|
44
|
-
// Y-up NDC (head / bowl toward +Y). Negate Y once here — same contract as
|
|
45
|
-
// moc2 after top-left pixel normalization.
|
|
41
|
+
// Deformer output uses logical canvas/ppu units around the model center.
|
|
42
|
+
// Convert the decoded authoring orientation once into the renderer's shared
|
|
43
|
+
// Y-up NDC contract, matching the moc2 path after top-left normalization.
|
|
46
44
|
const ppu = pixelsPerUnit > 0 ? pixelsPerUnit : 1;
|
|
47
45
|
const hw = canvasWidth > 0 ? (canvasWidth / ppu) * 0.5 : 0.5;
|
|
48
46
|
const hh = canvasHeight > 0 ? (canvasHeight / ppu) * 0.5 : 0.5;
|
package/src/moc/moc3.ts
CHANGED
|
@@ -17,7 +17,7 @@ import type {
|
|
|
17
17
|
ModelBackend,
|
|
18
18
|
ModelBackendOptions,
|
|
19
19
|
ParameterBinding,
|
|
20
|
-
} from "../model-runtime.js";
|
|
20
|
+
} from "../runtime/model-runtime.js";
|
|
21
21
|
import type { BlendMode, DrawableMesh } from "../types.js";
|
|
22
22
|
import { cascadedPartOpacity, readMoc3PartTables } from "./moc3-parts.js";
|
|
23
23
|
import { type Moc3Document, parseMoc3Document } from "./moc3-reader.js";
|
|
@@ -49,7 +49,7 @@ function paramFingerprint(values: ArrayLike<number>): string {
|
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
interface Moc3State {
|
|
52
|
-
/** Present for
|
|
52
|
+
/** Present for binary MOC3 input; null for cpu-program fixtures. */
|
|
53
53
|
doc: Moc3Document | null;
|
|
54
54
|
instance: ModelInstance;
|
|
55
55
|
lastFrame: FrameSnapshot | null;
|
|
@@ -82,7 +82,7 @@ function bakePose(state: Moc3State): void {
|
|
|
82
82
|
state.lastFrame = evaluateFrame(next);
|
|
83
83
|
}
|
|
84
84
|
|
|
85
|
-
/** moc3 model backend
|
|
85
|
+
/** moc3 model backend for binary `.moc3` input or CPU `.program.json` fixtures. */
|
|
86
86
|
export class Moc3Backend implements ModelBackend {
|
|
87
87
|
readonly format = "moc3" as const;
|
|
88
88
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Map Live2D blend modes to WebGL / Canvas compositing.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import { BlendMode } from "
|
|
5
|
+
import { BlendMode } from "../types.js";
|
|
6
6
|
|
|
7
7
|
/** Apply premultiplied-friendly Live2D blend factors on a WebGL2 context. */
|
|
8
8
|
export function applyWebGl2BlendMode(
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import {
|
|
2
2
|
type Canvas2DRendererOptions,
|
|
3
3
|
createCanvas2DRenderer,
|
|
4
|
-
} from "
|
|
4
|
+
} from "../backends/canvas2d.js";
|
|
5
5
|
import {
|
|
6
6
|
createWebGl2Renderer,
|
|
7
7
|
type WebGl2RendererOptions,
|
|
8
|
-
} from "
|
|
8
|
+
} from "../backends/webgl2.js";
|
|
9
9
|
import {
|
|
10
10
|
createWebGpuRenderer,
|
|
11
11
|
type WebGpuRendererOptions,
|
|
12
|
-
} from "
|
|
13
|
-
import type { Renderer, RendererKind } from "
|
|
12
|
+
} from "../backends/webgpu.js";
|
|
13
|
+
import type { Renderer, RendererKind } from "../types.js";
|
|
14
14
|
|
|
15
15
|
export interface CreateRendererOptions {
|
|
16
16
|
/**
|
|
@@ -5,7 +5,7 @@ import type {
|
|
|
5
5
|
ModelFormat,
|
|
6
6
|
ModelSettings,
|
|
7
7
|
} from "@doki-land/live2d-core";
|
|
8
|
-
import type { DrawableMesh, Renderer } from "
|
|
8
|
+
import type { DrawableMesh, Renderer } from "../types.js";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* Model-format runtime contract (moc2 / moc3 / cpu-program).
|
|
@@ -34,7 +34,7 @@ export interface ParameterBinding {
|
|
|
34
34
|
/** Shared read-only decode payload (renderer-internal; set by stage asset cache). */
|
|
35
35
|
export interface SharedModelCompile {
|
|
36
36
|
readonly mocBytes: ArrayBuffer;
|
|
37
|
-
/**
|
|
37
|
+
/** Parsed binary `.moc3` document. */
|
|
38
38
|
readonly moc3Doc?: unknown;
|
|
39
39
|
/** moc3 CPU `.program.json` program. */
|
|
40
40
|
readonly cpuProgram?: import("@doki-land/live2d-core").ModelProgram;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { ModelProgram, ModelSettings } from "@doki-land/live2d-core";
|
|
2
|
-
import { isCpuProgramBytes, parseCpuProgram } from "
|
|
3
|
-
import { Moc2Parser } from "
|
|
4
|
-
import { parseMoc3Document } from "
|
|
2
|
+
import { isCpuProgramBytes, parseCpuProgram } from "../cpu/cpu-program.js";
|
|
3
|
+
import { Moc2Parser } from "../moc/moc2-objects.js";
|
|
4
|
+
import { parseMoc3Document } from "../moc/moc3-reader.js";
|
|
5
5
|
import type { SharedModelCompile } from "./model-runtime.js";
|
|
6
6
|
|
|
7
7
|
/** One-time decode of moc bytes for stage-level {@link ModelAsset} sharing. */
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|