modelstage 0.0.0-stage → 0.5.24
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 +21 -0
- package/README.md +267 -2
- package/THIRD_PARTY_NOTICES.md +90 -0
- package/bin/modelstage.mjs +101 -0
- package/config/diag-upload.example.json +7 -0
- package/config/models/live2d.assetbundls.json +1 -0
- package/config/models/live2d.azurlane.json +1 -0
- package/config/models/live2d.azurlane_bundle.json +1 -0
- package/config/models/live2d.azurlane_spine.json +1 -0
- package/config/models/live2d.bd2.json +1 -0
- package/config/models/live2d.bluearchive.json +1 -0
- package/config/models/live2d.live2d.json +1 -0
- package/config/models/live2d.nikke.json +1 -0
- package/config/models/live2d.nikke_2.json +1 -0
- package/config/models/live2d.npc.json +1 -0
- package/config/models/spine.assetbundls.json +1 -0
- package/config/models/spine.azurlane.json +1 -0
- package/config/models/spine.azurlane_bundle.json +1 -0
- package/config/models/spine.azurlane_spine.json +1 -0
- package/config/models/spine.bd2.json +1 -0
- package/config/models/spine.bluearchive.json +1 -0
- package/config/models/spine.live2d.json +1 -0
- package/config/models/spine.nikke.json +1 -0
- package/config/models/spine.nikke_2.json +1 -0
- package/config/models/spine.npc.json +1 -0
- package/config/names/NIKKE.json +1058 -0
- package/config/names/README.md +47 -0
- package/config/names//345/244/251/345/221/275/344/271/213/345/255/220.json +42897 -0
- package/config/names//346/243/225/350/211/262/345/260/230/345/237/2032.json +4016 -0
- package/config/names//347/242/247/350/223/235/350/210/252/347/272/277.json +38342 -0
- package/config/plugins.json +13 -0
- package/config/resources.example.json +40 -0
- package/package.json +58 -4
- package/plugins/unity-live2d-bundle/README.md +98 -0
- package/plugins/unity-live2d-bundle/index.js +891 -0
- package/plugins/unity-live2d-bundle/lib/anim-to-motion.js +402 -0
- package/plugins/unity-live2d-bundle/lib/bundle-probe.js +244 -0
- package/plugins/unity-live2d-bundle/lib/bytes.js +189 -0
- package/plugins/unity-live2d-bundle/lib/common-strings.js +46 -0
- package/plugins/unity-live2d-bundle/lib/dxt.js +197 -0
- package/plugins/unity-live2d-bundle/lib/errors.js +71 -0
- package/plugins/unity-live2d-bundle/lib/etc2.js +371 -0
- package/plugins/unity-live2d-bundle/lib/extract.js +242 -0
- package/plugins/unity-live2d-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
- package/plugins/unity-live2d-bundle/lib/lzma-d-min.js +34 -0
- package/plugins/unity-live2d-bundle/lib/model3.js +137 -0
- package/plugins/unity-live2d-bundle/lib/png.js +424 -0
- package/plugins/unity-live2d-bundle/lib/serialized.js +233 -0
- package/plugins/unity-live2d-bundle/lib/texdecode.js +190 -0
- package/plugins/unity-live2d-bundle/lib/typetree.js +215 -0
- package/plugins/unity-live2d-bundle/lib/unityfs.js +396 -0
- package/plugins/unity-live2d-bundle/plugin.json +81 -0
- package/plugins/unity-live2d-bundle/profiles/unityfs-live2d.json +26 -0
- package/plugins/unity-spine-bundle/index.js +535 -0
- package/plugins/unity-spine-bundle/lib/bytes.js +189 -0
- package/plugins/unity-spine-bundle/lib/common-strings.js +46 -0
- package/plugins/unity-spine-bundle/lib/dxt.js +197 -0
- package/plugins/unity-spine-bundle/lib/errors.js +79 -0
- package/plugins/unity-spine-bundle/lib/etc2.js +371 -0
- package/plugins/unity-spine-bundle/lib/extract.js +242 -0
- package/plugins/unity-spine-bundle/lib/lzma-d-min.LICENSE.txt +21 -0
- package/plugins/unity-spine-bundle/lib/lzma-d-min.js +34 -0
- package/plugins/unity-spine-bundle/lib/png.js +424 -0
- package/plugins/unity-spine-bundle/lib/serialized.js +233 -0
- package/plugins/unity-spine-bundle/lib/texdecode.js +190 -0
- package/plugins/unity-spine-bundle/lib/typetree.js +215 -0
- package/plugins/unity-spine-bundle/lib/unityfs.js +396 -0
- package/plugins/unity-spine-bundle/plugin.json +75 -0
- package/src/server/README.md +69 -0
- package/src/server/assets/browse.mjs +56 -0
- package/src/server/assets/bundle-probe.mjs +94 -0
- package/src/server/assets/probe.mjs +184 -0
- package/src/server/core/config.mjs +173 -0
- package/src/server/core/env-num.mjs +60 -0
- package/src/server/core/env.mjs +49 -0
- package/src/server/core/fs-portable.mjs +37 -0
- package/src/server/core/log.mjs +107 -0
- package/src/server/core/paths.mjs +44 -0
- package/src/server/data/catalog.mjs +1091 -0
- package/src/server/data/entry-states.mjs +499 -0
- package/src/server/data/live2d.mjs +272 -0
- package/src/server/data/manifest-cache.mjs +58 -0
- package/src/server/data/manifest-stamp.mjs +135 -0
- package/src/server/data/model-meta.mjs +114 -0
- package/src/server/data/names.mjs +106 -0
- package/src/server/data/shape-probe-worker.mjs +33 -0
- package/src/server/data/shape-probe.mjs +626 -0
- package/src/server/data/spine.mjs +336 -0
- package/src/server/data/tree.mjs +126 -0
- package/src/server/http/atomic-json.mjs +145 -0
- package/src/server/http/background-solids.mjs +270 -0
- package/src/server/http/backgrounds.mjs +388 -0
- package/src/server/http/diag-upload.mjs +264 -0
- package/src/server/http/diag.mjs +173 -0
- package/src/server/http/ready.mjs +167 -0
- package/src/server/http/static.mjs +312 -0
- package/src/server/http/write.mjs +647 -0
- package/src/server/index.mjs +114 -0
- package/src/server/plugins/builtin-providers.json +30 -0
- package/src/server/plugins/registry.mjs +650 -0
- package/src/server/plugins/slot-catalog.mjs +286 -0
- package/src/server/routes/background-solids.mjs +56 -0
- package/src/server/routes/backgrounds.mjs +77 -0
- package/src/server/routes/config.mjs +38 -0
- package/src/server/routes/data.mjs +313 -0
- package/src/server/routes/diag.mjs +37 -0
- package/src/server/routes/entries.mjs +170 -0
- package/src/server/routes/ready.mjs +122 -0
- package/src/server/serve.mjs +334 -0
- package/src/server/unpack/capacity.mjs +217 -0
- package/src/server/unpack/mem-probe.mjs +27 -0
- package/src/server/unpack/plugin-runner.mjs +529 -0
- package/src/server/unpack/plugin-store.mjs +736 -0
- package/src/server/unpack/probe-store.mjs +73 -0
- package/src/server/unpack/routes.mjs +359 -0
- package/src/server/unpack/runtime/exec.mjs +558 -0
- package/src/server/unpack/runtime/index.mjs +141 -0
- package/src/server/unpack/runtime/node-vm.mjs +111 -0
- package/src/server/unpack/unpack-worker.mjs +105 -0
- package/src/server/unpack/worker-client.mjs +184 -0
- package/src/server/unpack/write-result.mjs +84 -0
- package/src/shared/bundle-shape.mjs +83 -0
- package/src/shared/entry-reasons.mjs +119 -0
- package/src/shared/names.mjs +45 -0
- package/src/web/README.md +193 -0
- package/src/web/app/anim-list.js +222 -0
- package/src/web/app/background-solids.js +150 -0
- package/src/web/app/backgrounds.js +152 -0
- package/src/web/app/camera-log.js +28 -0
- package/src/web/app/context.js +72 -0
- package/src/web/app/dir-picker.js +242 -0
- package/src/web/app/entry-report.js +71 -0
- package/src/web/app/memory-bars.js +91 -0
- package/src/web/app/memory-budget.js +228 -0
- package/src/web/app/memory-guard.js +204 -0
- package/src/web/app/model-actions.js +682 -0
- package/src/web/app/renderer-setting.js +70 -0
- package/src/web/app/root-manager.js +254 -0
- package/src/web/app/status.js +138 -0
- package/src/web/app/topbar-actions.js +357 -0
- package/src/web/app/transport-actions.js +297 -0
- package/src/web/app/ui-state.js +559 -0
- package/src/web/app/url-params.js +171 -0
- package/src/web/app/view-settings.js +186 -0
- package/src/web/components.css +465 -0
- package/src/web/debug/diag-hud.js +95 -0
- package/src/web/debug/drag.js +369 -0
- package/src/web/debug/log.js +320 -0
- package/src/web/debug/model-cmp.js +292 -0
- package/src/web/debug/panel.css +474 -0
- package/src/web/debug/panel.js +712 -0
- package/src/web/debug/race-probe.js +82 -0
- package/src/web/debug/upload.js +130 -0
- package/src/web/engine/adapters/atlas.js +47 -0
- package/src/web/engine/adapters/index.js +22 -0
- package/src/web/engine/adapters/renderer.js +223 -0
- package/src/web/engine/adapters/skeleton.js +105 -0
- package/src/web/engine/adapters/texture.js +64 -0
- package/src/web/engine/animation.js +181 -0
- package/src/web/engine/appearance.js +136 -0
- package/src/web/engine/camera-adapter.js +212 -0
- package/src/web/engine/effect-chain.js +138 -0
- package/src/web/engine/engine-state.js +120 -0
- package/src/web/engine/filters.js +224 -0
- package/src/web/engine/fit.js +197 -0
- package/src/web/engine/frame-gate.js +33 -0
- package/src/web/engine/gl-driver.js +56 -0
- package/src/web/engine/gl.js +77 -0
- package/src/web/engine/index.js +20 -0
- package/src/web/engine/loader.js +55 -0
- package/src/web/engine/model-info.js +26 -0
- package/src/web/engine/pipeline.js +200 -0
- package/src/web/engine/player.js +427 -0
- package/src/web/engine/pma/blend.js +76 -0
- package/src/web/engine/pma/detect.js +118 -0
- package/src/web/engine/pma/index.js +8 -0
- package/src/web/engine/pma/resolve.js +112 -0
- package/src/web/engine/pma/upload.js +127 -0
- package/src/web/engine/pma.js +5 -0
- package/src/web/engine/render-frame.js +93 -0
- package/src/web/engine/runtime-capabilities.js +209 -0
- package/src/web/engine/runtime.js +37 -0
- package/src/web/engine/skins.js +165 -0
- package/src/web/engine/textures.js +174 -0
- package/src/web/index.html +115 -0
- package/src/web/infra/api.js +531 -0
- package/src/web/infra/diag.js +509 -0
- package/src/web/infra/document.js +269 -0
- package/src/web/infra/dom.js +31 -0
- package/src/web/infra/events.mjs +27 -0
- package/src/web/infra/log.js +153 -0
- package/src/web/infra/mem-stats.js +112 -0
- package/src/web/infra/plugin-host-registration.mjs +285 -0
- package/src/web/infra/plugin-host-report.mjs +32 -0
- package/src/web/infra/plugin-host-unload.mjs +79 -0
- package/src/web/infra/plugin-host-unpack.mjs +899 -0
- package/src/web/infra/plugin-host-warmup.mjs +180 -0
- package/src/web/infra/plugin-host.js +628 -0
- package/src/web/infra/ready.js +461 -0
- package/src/web/infra/schedule.js +51 -0
- package/src/web/infra/shortcuts.js +129 -0
- package/src/web/infra/slot-registry.js +402 -0
- package/src/web/infra/vfs.js +247 -0
- package/src/web/live2d/bridge.js +400 -0
- package/src/web/live2d/model.js +321 -0
- package/src/web/live2d/overlay.js +142 -0
- package/src/web/live2d/pixi-app.js +111 -0
- package/src/web/live2d/playback-probe.js +92 -0
- package/src/web/live2d/probe-geometry.js +329 -0
- package/src/web/live2d/probe.js +119 -0
- package/src/web/live2d/release.js +66 -0
- package/src/web/live2d/runtime-adapter.js +289 -0
- package/src/web/live2d/stage-ctx.js +152 -0
- package/src/web/live2d/stage.js +204 -0
- package/src/web/live2d/vfs-settings.js +174 -0
- package/src/web/live2d/view.js +89 -0
- package/src/web/main.js +629 -0
- package/src/web/panel/background-list.js +373 -0
- package/src/web/panel/bg-source.js +291 -0
- package/src/web/panel/color-picker.css +199 -0
- package/src/web/panel/color-picker.js +278 -0
- package/src/web/panel/dialog.css +116 -0
- package/src/web/panel/dialog.js +173 -0
- package/src/web/panel/dom.js +35 -0
- package/src/web/panel/icons.js +58 -0
- package/src/web/panel/memory-bar.js +195 -0
- package/src/web/panel/panel-settings.js +1263 -0
- package/src/web/panel/panel.css +1304 -0
- package/src/web/panel/render-choice.js +104 -0
- package/src/web/panel/schema.js +784 -0
- package/src/web/panel/settings-io.js +291 -0
- package/src/web/panel/shell-controls.js +274 -0
- package/src/web/panel/slider.js +210 -0
- package/src/web/panel/toolbar-schema.js +111 -0
- package/src/web/panel/toolbar.js +195 -0
- package/src/web/panel/transport-picker.js +375 -0
- package/src/web/panel/transport.js +321 -0
- package/src/web/panel/view.js +553 -0
- package/src/web/plugin/unpack-worker.js +185 -0
- package/src/web/render/camera/constants.js +60 -0
- package/src/web/render/camera/index.js +123 -0
- package/src/web/render/camera/spine.js +124 -0
- package/src/web/render/capture.js +30 -0
- package/src/web/render/interaction.js +527 -0
- package/src/web/render/live2d.js +627 -0
- package/src/web/render/playback.js +47 -0
- package/src/web/render/reset-view.js +29 -0
- package/src/web/render/spine.js +386 -0
- package/src/web/render/surface-switch.js +176 -0
- package/src/web/render/surface.js +571 -0
- package/src/web/render/surfaces.js +70 -0
- package/src/web/roster/filter.js +31 -0
- package/src/web/roster/list.js +276 -0
- package/src/web/roster/persist.js +52 -0
- package/src/web/roster/reveal.js +77 -0
- package/src/web/roster/rows.js +248 -0
- package/src/web/roster/snapshot.js +37 -0
- package/src/web/roster/view.js +670 -0
- package/src/web/spine/canvas-api-groups.js +267 -0
- package/src/web/spine/canvas-api.js +97 -0
- package/src/web/spine/canvas-document.js +80 -0
- package/src/web/spine/canvas-engine.js +193 -0
- package/src/web/spine/canvas-entry.js +28 -0
- package/src/web/spine/canvas-pixels.js +36 -0
- package/src/web/spine/canvas-runtime.js +127 -0
- package/src/web/spine/canvas-view.js +108 -0
- package/src/web/spine/canvas.js +7 -0
- package/src/web/spine/runtime-hint.js +73 -0
- package/src/web/spine/runtimes.js +212 -0
- package/src/web/style.css +493 -0
- package/tool/lib/bundle-probe-node.mjs +121 -0
- package/tool/lib/check-perms.mjs +114 -0
- package/tool/lib/contract-checks.mjs +582 -0
- package/tool/lib/cubism-core.mjs +122 -0
- package/tool/lib/export-bundle.mjs +328 -0
- package/tool/lib/paths.mjs +20 -0
- package/tool/lib/runtime-guides.mjs +65 -0
- package/tool/lib/runtime-package.mjs +403 -0
- package/tool/lib/spine-meta.mjs +227 -0
- package/tool/lib/spine-motions.mjs +104 -0
- package/tool/lib/spine-runtime.mjs +131 -0
- package/tool/lib/test-lock.mjs +178 -0
- package/tool/lib/test-report.mjs +133 -0
- package/tool/parse-jsonc.mjs +86 -0
- package/tool/resolve-live2d-runtimes.mjs +276 -0
- package/tool/spine-motion-rules.json +101 -0
|
@@ -0,0 +1,292 @@
|
|
|
1
|
+
// model-cmp.js —— 逐模型差异采集(诊断专用,不改任何渲染行为)
|
|
2
|
+
//
|
|
3
|
+
// 依据:docs/设计/相机.md 第 4 节。
|
|
4
|
+
// 为什么用 extract 而不是 readPixels:Live2D 的 PIXI 画布是 preserveDrawingBuffer=false,
|
|
5
|
+
// 直接 readPixels 全 0,不能当证据;extract.canvas 走离屏渲染,拿到的是"实际画出来的结果"。
|
|
6
|
+
//
|
|
7
|
+
// 判定思路(本轮核心):把画布四边内侧与"美术包围盒之外、画布之内"的 alpha 采出来 ——
|
|
8
|
+
// outsideArtAlphaRatio > 0.01 ⇒ 画布被我们涂满了(渲染侧问题);
|
|
9
|
+
// = 0 ⇒ 边缘本身就是美术内容。
|
|
10
|
+
|
|
11
|
+
/** 判定「有内容」的 alpha 阈值(0-255) */
|
|
12
|
+
const ALPHA_ART = 8;
|
|
13
|
+
/** 像素分析的最大边长(降采样上限,控制单次 getImageData 的内存) */
|
|
14
|
+
const MAX_ANALYZE = 1024;
|
|
15
|
+
/** 四边内侧采样带宽(像素) */
|
|
16
|
+
const EDGE_BAND = 2;
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* 把 extract 出来的 canvas 画到离屏 2D 画布上并读像素(extract 的 canvas 可能不可直接 getImageData)。
|
|
20
|
+
* @param {HTMLCanvasElement} cv
|
|
21
|
+
* @returns {{data: Uint8ClampedArray, w: number, h: number}|{error: string}}
|
|
22
|
+
*/
|
|
23
|
+
function readPixelsOf(cv) {
|
|
24
|
+
const w = Math.max(1, Math.round(cv.width || 0));
|
|
25
|
+
const h = Math.max(1, Math.round(cv.height || 0));
|
|
26
|
+
// 降采样后再读像素:6000×6000 的画布一次 getImageData 就是 144MB,多状态会拖死页面。
|
|
27
|
+
// 只缩不放(≤ MAX_ANALYZE),比值类指标(opaqueRatio / outsideArtAlphaRatio / 四边 alpha)不受影响;
|
|
28
|
+
// 长度类指标按 scaleBack 换回原画布单位,保证不同模型之间可比。
|
|
29
|
+
const scale = Math.min(1, MAX_ANALYZE / Math.max(w, h));
|
|
30
|
+
const aw = Math.max(1, Math.round(w * scale));
|
|
31
|
+
const ah = Math.max(1, Math.round(h * scale));
|
|
32
|
+
try {
|
|
33
|
+
const off = document.createElement('canvas');
|
|
34
|
+
off.width = aw;
|
|
35
|
+
off.height = ah;
|
|
36
|
+
const ctx = off.getContext('2d', { willReadFrequently: true });
|
|
37
|
+
ctx.clearRect(0, 0, aw, ah);
|
|
38
|
+
ctx.drawImage(cv, 0, 0, w, h, 0, 0, aw, ah);
|
|
39
|
+
const data = ctx.getImageData(0, 0, aw, ah).data;
|
|
40
|
+
// 立刻释放大画布:6000×6000 的 backing store 约 144MB,多模型多状态不释放会拖死标签页
|
|
41
|
+
try { cv.width = 1; cv.height = 1; } catch { /* 忽略 */ }
|
|
42
|
+
return { data, w: aw, h: ah, srcW: w, srcH: h, scaleBack: w / aw };
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { error: String((e && e.message) || e) };
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/** 不透明像素包围盒 + 不透明占比 */
|
|
49
|
+
function measureBBox(data, w, h) {
|
|
50
|
+
let minX = w;
|
|
51
|
+
let minY = h;
|
|
52
|
+
let maxX = -1;
|
|
53
|
+
let maxY = -1;
|
|
54
|
+
let opaque = 0;
|
|
55
|
+
for (let y = 0; y < h; y += 1) {
|
|
56
|
+
const row = y * w * 4;
|
|
57
|
+
for (let x = 0; x < w; x += 1) {
|
|
58
|
+
if (data[row + x * 4 + 3] > ALPHA_ART) {
|
|
59
|
+
opaque += 1;
|
|
60
|
+
if (x < minX) minX = x;
|
|
61
|
+
if (x > maxX) maxX = x;
|
|
62
|
+
if (y < minY) minY = y;
|
|
63
|
+
if (y > maxY) maxY = y;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
const empty = maxX < minX || maxY < minY;
|
|
68
|
+
return {
|
|
69
|
+
bboxX: empty ? 0 : minX,
|
|
70
|
+
bboxY: empty ? 0 : minY,
|
|
71
|
+
bboxW: empty ? 0 : maxX - minX + 1,
|
|
72
|
+
bboxH: empty ? 0 : maxY - minY + 1,
|
|
73
|
+
opaqueRatio: +(opaque / (w * h)).toFixed(4),
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/** 四边内侧 alpha 均值(判断"边缘是否被涂色") */
|
|
78
|
+
function measureEdges(data, w, h) {
|
|
79
|
+
const avgOf = (x0, x1, y0, y1) => {
|
|
80
|
+
let sum = 0;
|
|
81
|
+
let n = 0;
|
|
82
|
+
for (let y = y0; y < y1; y += 1) {
|
|
83
|
+
for (let x = x0; x < x1; x += 1) {
|
|
84
|
+
sum += data[(y * w + x) * 4 + 3];
|
|
85
|
+
n += 1;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
return n ? +(sum / n / 255).toFixed(4) : 0;
|
|
89
|
+
};
|
|
90
|
+
const bx = Math.min(EDGE_BAND, w);
|
|
91
|
+
const by = Math.min(EDGE_BAND, h);
|
|
92
|
+
return {
|
|
93
|
+
top: avgOf(0, w, 0, by),
|
|
94
|
+
bottom: avgOf(0, w, h - by, h),
|
|
95
|
+
left: avgOf(0, bx, 0, h),
|
|
96
|
+
right: avgOf(w - bx, w, 0, h),
|
|
97
|
+
};
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/** 美术包围盒之外、画布之内的平均 alpha(本轮的关键指标) */
|
|
101
|
+
function measureOutsideArt(data, w, h, bbox) {
|
|
102
|
+
const { bboxX, bboxY, bboxW, bboxH } = bbox;
|
|
103
|
+
let sum = 0;
|
|
104
|
+
let n = 0;
|
|
105
|
+
for (let y = 0; y < h; y += 1) {
|
|
106
|
+
const inY = y >= bboxY && y < bboxY + bboxH;
|
|
107
|
+
const row = y * w * 4;
|
|
108
|
+
for (let x = 0; x < w; x += 1) {
|
|
109
|
+
if (inY && x >= bboxX && x < bboxX + bboxW) continue;
|
|
110
|
+
sum += data[row + x * 4 + 3];
|
|
111
|
+
n += 1;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return n ? +(sum / n / 255).toFixed(4) : 0;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 分析一张 extract 出来的画布。
|
|
119
|
+
* @param {HTMLCanvasElement} cv
|
|
120
|
+
* @returns {object} { w, h, bboxW, bboxH, bboxX, bboxY, opaqueRatio, edgeAlpha, outsideArtAlphaRatio } 或 { *_error }
|
|
121
|
+
*/
|
|
122
|
+
export function analyzeExtractCanvas(cv) {
|
|
123
|
+
const px = readPixelsOf(cv);
|
|
124
|
+
if (px.error) return { w: cv.width, h: cv.height, extract_error: px.error };
|
|
125
|
+
const bbox = measureBBox(px.data, px.w, px.h);
|
|
126
|
+
const back = px.scaleBack || 1;
|
|
127
|
+
return {
|
|
128
|
+
w: px.srcW,
|
|
129
|
+
h: px.srcH,
|
|
130
|
+
bboxX: Math.round(bbox.bboxX * back),
|
|
131
|
+
bboxY: Math.round(bbox.bboxY * back),
|
|
132
|
+
bboxW: Math.round(bbox.bboxW * back),
|
|
133
|
+
bboxH: Math.round(bbox.bboxH * back),
|
|
134
|
+
opaqueRatio: bbox.opaqueRatio,
|
|
135
|
+
edgeAlpha: measureEdges(px.data, px.w, px.h),
|
|
136
|
+
outsideArtAlphaRatio: measureOutsideArt(px.data, px.w, px.h, bbox),
|
|
137
|
+
analyzeW: px.w,
|
|
138
|
+
analyzeH: px.h,
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/** 落盘通道:POST /api/diag,tag=MODELCMP(失败静默,绝不影响页面) */
|
|
143
|
+
function postDiag(rec) {
|
|
144
|
+
try {
|
|
145
|
+
// **刻意不加超时信号**(这不是漏了):这是一条 fire-and-forget 的诊断落盘,
|
|
146
|
+
// 失败本来就被 .catch 吞掉、不影响页面任何功能。
|
|
147
|
+
// 加 AbortSignal.timeout 反而有害:页面卸载/切到后台时超时会抛 AbortError,
|
|
148
|
+
// 而这类"We don't care 的旁路请求"被中断属于正常现象,会污染控制台与诊断日志。
|
|
149
|
+
fetch('/api/diag', {
|
|
150
|
+
method: 'POST',
|
|
151
|
+
headers: { 'Content-Type': 'application/json' },
|
|
152
|
+
// 带上本页 sid:否则这些条目落到 anonymous 会话,GET /api/diag?sid=… 取不到
|
|
153
|
+
body: JSON.stringify({ sid: (typeof window !== 'undefined' && window.__DIAG__ && window.__DIAG__.sid) || undefined, entries: [{ ts: Date.now(), level: 'info', tag: 'MODELCMP', message: JSON.stringify(rec) }] }),
|
|
154
|
+
}).catch(() => {});
|
|
155
|
+
} catch { /* 忽略 */ }
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* 写一条采集记录:控制台单行 + `__WEB__.modelCmp` 数组 + diag 落盘。
|
|
160
|
+
* @param {object} rec
|
|
161
|
+
* @returns {object} 同一条记录
|
|
162
|
+
*/
|
|
163
|
+
export function recordModelCmp(rec) {
|
|
164
|
+
const bag = (window.__WEB__ = window.__WEB__ || {});
|
|
165
|
+
if (!Array.isArray(bag.modelCmp)) bag.modelCmp = [];
|
|
166
|
+
bag.modelCmp.push(rec);
|
|
167
|
+
try { console.log('[MODELCMP] ' + JSON.stringify(rec)); } catch { /* 忽略 */ }
|
|
168
|
+
postDiag(rec);
|
|
169
|
+
return rec;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// ---------------------------------------------------------------- 记录构建
|
|
173
|
+
// 字段名 / 单位 / 采集时机三处统一,不同模型之间才可比(见 doc 第 4 节)。
|
|
174
|
+
|
|
175
|
+
/** 安全取值:失败时返回 { <key>_error },不丢弃整条记录(否则 diff 会误判为"缺失即不同") */
|
|
176
|
+
function safePick(key, fn) {
|
|
177
|
+
try {
|
|
178
|
+
return fn();
|
|
179
|
+
} catch (e) {
|
|
180
|
+
return { [`${key}_error`]: String((e && e.message) || e) };
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/** Cubism 内部尺寸(模型自身坐标系) */
|
|
185
|
+
function cmpInternalSize(internal) {
|
|
186
|
+
if (!internal) return null;
|
|
187
|
+
return { w: internal.width, h: internal.height, originalW: internal.originalWidth, originalH: internal.originalHeight };
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Cubism 画布尺寸(内部单位,不是 CSS 像素) */
|
|
191
|
+
function cmpCanvasSize(core) {
|
|
192
|
+
if (!core) return null;
|
|
193
|
+
const w = typeof core.getCanvasWidth === 'function' ? core.getCanvasWidth() : null;
|
|
194
|
+
const h = typeof core.getCanvasHeight === 'function' ? core.getCanvasHeight() : null;
|
|
195
|
+
return { w, h };
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** PIXI 显示对象(width/height 是未缩放尺寸,scale 是当前缩放) */
|
|
199
|
+
function cmpDisplay(model) {
|
|
200
|
+
return {
|
|
201
|
+
w: model.width,
|
|
202
|
+
h: model.height,
|
|
203
|
+
x: model.x,
|
|
204
|
+
y: model.y,
|
|
205
|
+
scale: model.scale ? model.scale.x : null,
|
|
206
|
+
rotation: model.rotation,
|
|
207
|
+
pivot: model.pivot ? { x: model.pivot.x, y: model.pivot.y } : null,
|
|
208
|
+
anchor: model.anchor ? { x: model.anchor.x, y: model.anchor.y } : null,
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/** 世界坐标系包围盒(含当前缩放/位移) */
|
|
213
|
+
function cmpBounds(model) {
|
|
214
|
+
const b = model.getBounds();
|
|
215
|
+
return { x: b.x, y: b.y, w: b.width, h: b.height };
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/** 纹理尺寸(判断是否被压缩/裁剪) */
|
|
219
|
+
function cmpTextures(model) {
|
|
220
|
+
return (model.textures || []).map((t) => {
|
|
221
|
+
const bt = t && t.baseTexture ? t.baseTexture : null;
|
|
222
|
+
return { w: bt ? bt.width : (t ? t.width : null), h: bt ? bt.height : (t ? t.height : null) };
|
|
223
|
+
});
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* 我们算出来的适配值(纯算术还原,不重置模型):
|
|
228
|
+
* basisW/H = 当前包围盒 ÷ 当前缩放(等价于 scale=1 时的包围盒)
|
|
229
|
+
*/
|
|
230
|
+
function cmpFit(model, size) {
|
|
231
|
+
const b = model.getBounds();
|
|
232
|
+
const scale = model.scale ? model.scale.x || 1 : 1;
|
|
233
|
+
const basisW = b.width / scale;
|
|
234
|
+
const basisH = b.height / scale;
|
|
235
|
+
const W = size ? size.width : null;
|
|
236
|
+
const H = size ? size.height : null;
|
|
237
|
+
const fitScale = W && H && basisW && basisH ? Math.min(W / basisW, H / basisH) : null;
|
|
238
|
+
return {
|
|
239
|
+
basis: 'bounds',
|
|
240
|
+
basisW: +basisW.toFixed(2),
|
|
241
|
+
basisH: +basisH.toFixed(2),
|
|
242
|
+
fitScale: fitScale == null ? null : +(fitScale * 0.98).toFixed(4),
|
|
243
|
+
appliedScale: +Number(scale).toFixed(4),
|
|
244
|
+
offsetX: +Number(model.x).toFixed(2),
|
|
245
|
+
offsetY: +Number(model.y).toFixed(2),
|
|
246
|
+
margin: 0.98,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/** model3 结构特征(版本 / 布局 / 动作组 / 物理 / 姿势) */
|
|
251
|
+
function cmpModel3(internal, meta) {
|
|
252
|
+
const settings = internal && internal.settings ? internal.settings : null;
|
|
253
|
+
const json = settings && (settings.json || settings) ? (settings.json || settings) : null;
|
|
254
|
+
const refs = json && json.FileReferences ? json.FileReferences : null;
|
|
255
|
+
const motions = refs && refs.Motions ? Object.keys(refs.Motions) : [];
|
|
256
|
+
return {
|
|
257
|
+
version: (internal && internal.cubismVersion) || (meta && meta.cubism) || (settings && settings.version) || null,
|
|
258
|
+
hasLayout: !!(json && json.Layout),
|
|
259
|
+
motionGroups: motions.length,
|
|
260
|
+
physics: !!(refs && refs.Physics) || !!(meta && meta.physics),
|
|
261
|
+
pose: !!(refs && refs.Pose),
|
|
262
|
+
expressions: refs && Array.isArray(refs.Expressions) ? refs.Expressions.length : 0,
|
|
263
|
+
};
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* 构建并落盘一条 Live2D 的 MODELCMP 记录(只读;不重置视图)。
|
|
268
|
+
* @param {{app: object, model: object, internal: object, size: object, dpr: number,
|
|
269
|
+
* meta: object, modelId: string, zoomRatio: number, stageMode: string, tag: string}} ctx
|
|
270
|
+
* @returns {object} 记录
|
|
271
|
+
*/
|
|
272
|
+
export function collectLive2DModelCmp(ctx) {
|
|
273
|
+
const { app, model, internal, size, dpr, meta, modelId, zoomRatio, stageMode, tag } = ctx || {};
|
|
274
|
+
const rec = { ts: Date.now(), tag, stageMode: stageMode || null };
|
|
275
|
+
if (!model || !app || !app.renderer) {
|
|
276
|
+
rec.collect_error = 'no model/app';
|
|
277
|
+
return recordModelCmp(rec);
|
|
278
|
+
}
|
|
279
|
+
rec.id = modelId;
|
|
280
|
+
rec.name = (meta && meta.name) || modelId;
|
|
281
|
+
rec.internalSize = safePick('internalSize', () => cmpInternalSize(internal));
|
|
282
|
+
rec.canvasSize = safePick('canvasSize', () => cmpCanvasSize(internal && internal.coreModel));
|
|
283
|
+
rec.display = safePick('display', () => cmpDisplay(model));
|
|
284
|
+
rec.bounds = safePick('bounds', () => cmpBounds(model));
|
|
285
|
+
rec.extract = safePick('extract', () => analyzeExtractCanvas(app.renderer.extract.canvas(model)));
|
|
286
|
+
rec.fit = safePick('fit', () => cmpFit(model, size));
|
|
287
|
+
rec.textures = safePick('textures', () => cmpTextures(model));
|
|
288
|
+
rec.model3 = safePick('model3', () => cmpModel3(internal, meta));
|
|
289
|
+
rec.viewport = safePick('viewport', () => ({ w: size ? size.width : null, h: size ? size.height : null, dpr: dpr == null ? null : dpr }));
|
|
290
|
+
rec.camera = { scale: +(Number(zoomRatio) || 1).toFixed(4) };
|
|
291
|
+
return recordModelCmp(rec);
|
|
292
|
+
}
|