@rig-c/review 0.6.0 → 0.7.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +206 -12
- package/docs/DESIGN.md +26 -4
- package/docs/REVIEW_PACKAGE.md +113 -0
- package/package.json +1 -1
- package/src/applied-context.ts +285 -0
- package/src/baseline.ts +175 -0
- package/src/bone-anchors.ts +123 -0
- package/src/bones-check.ts +105 -0
- package/src/build-route.ts +109 -0
- package/src/chest-landmarks.ts +122 -0
- package/src/chest-regions.ts +10 -4
- package/src/cli.ts +19 -8
- package/src/config-patch.ts +213 -0
- package/src/cut-edits.ts +184 -0
- package/src/environment.ts +19 -6
- package/src/feedback-export.ts +17 -1
- package/src/front.ts +11 -0
- package/src/handoff.ts +208 -0
- package/src/idle-tracks.ts +401 -0
- package/src/manifest.ts +15 -6
- package/src/mesh-edit.ts +45 -15
- package/src/mesh-entry.ts +351 -0
- package/src/open.ts +12 -5
- package/src/physics-tuning.ts +236 -0
- package/src/producer-files.ts +11 -0
- package/src/producer.ts +158 -0
- package/src/readings-run.ts +129 -0
- package/src/readings.ts +235 -0
- package/src/rebuild.ts +938 -0
- package/src/reference.ts +4 -3
- package/src/requirements-run.ts +160 -0
- package/src/requirements.ts +230 -0
- package/src/retarget.ts +211 -0
- package/src/retune-route.ts +52 -0
- package/src/rig-fields.ts +295 -0
- package/src/server.ts +59 -5
- package/src/session.ts +9 -0
- package/src/setup-mesh.ts +144 -0
- package/src/weights.ts +74 -0
- package/web/bones-panel.ts +230 -0
- package/web/chest-editor.ts +128 -13
- package/web/chest-regions-tool.ts +2 -1
- package/web/cuts-panel.ts +266 -0
- package/web/idle-panel.ts +280 -0
- package/web/index.html +1 -1
- package/web/inspector-blocks.ts +77 -0
- package/web/main.ts +701 -49
- package/web/mesh-editor.ts +73 -38
- package/web/mesh-panel.ts +273 -0
- package/web/physics-panel.ts +151 -0
- package/web/readings-panel.ts +105 -0
- package/web/rebuild-client.ts +216 -0
- package/web/requirements-panel.ts +241 -0
- package/web/retarget-panel.ts +203 -0
- package/web/rig-fields-panel.ts +269 -0
- package/web/rig-overlays.ts +108 -14
- package/web/setup-pose.ts +30 -0
- package/web/styles.css +28 -5
- package/web/weights-view.ts +159 -0
package/web/styles.css
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
/* Theme tokens: site values copied from rig-c.org styles.css (dark); light and --rv-* are review-local derivations. No literal colour outside these blocks. */
|
|
2
|
-
:root{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--font-sans:Inter,system-ui,sans-serif;--font-display:Inter,system-ui,sans-serif;--font-mono:"JetBrains Mono",ui-monospace,monospace;--radius:10px;--bg:#070b18;--fg:#c9d4e8;--fg-strong:#f3f6fc;--fg-soft:#aebbd4;--fg-muted:#7f8ba6;--border:#18213a;--border-strong:#2a3657;--hover-bg:#0f1730;--active-bg:#142042;--link:#7cc8ff;--link-hover:#b3e0ff;--btn-fg:#08101f;--code-bg:#050814;--code-fg:#d6e2f5;--code-border:#18213a;--code-inline-bg:#121a30;--footer-bg:#050813;--pass:#30d158;--warn:#ffcc00;--fail:#ff453a;--rv-bar:#0b1124;--rv-panel:#0d152b;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:var(--active-bg);--rv-field:var(--code-bg);--rv-segmented:var(--code-bg);--rv-pressed-bg:#133733;--rv-pressed-border:#207c44;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link-hover);--rv-backdrop:rgba(7,11,24,.82);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#2a3657;--rv-scrollbar-thumb-hover:#3d4c74;--rv-scrollbar-track:transparent;--rv-separator:#1a2747;--rv-separator-hover:#3f7fb8}
|
|
3
|
-
:root[data-theme=light]{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--bg:#f5f7fb;--fg:#26324a;--fg-strong:#0c1324;--fg-soft:#3b4864;--fg-muted:#5d6982;--border:#d5dcea;--border-strong:#b3bed3;--hover-bg:#e8edf6;--active-bg:#dbe3f3;--link:#1a6fc0;--link-hover:#0f528f;--btn-fg:#ffffff;--code-bg:#eef1f7;--code-fg:#1d2940;--code-border:#d5dcea;--code-inline-bg:#e3e8f2;--footer-bg:#eceff6;--pass:#1e8a3c;--warn:#8a6400;--fail:#b3261e;--rv-bar:#edf1f7;--rv-panel:#e9eef6;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:#d5ddec;--rv-field:#ffffff;--rv-segmented:var(--code-inline-bg);--rv-pressed-bg:#c9ded8;--rv-pressed-border:#6fb286;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link);--rv-backdrop:rgba(12,19,36,.45);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#b3bed3;--rv-scrollbar-thumb-hover:#8e9bb5;--rv-scrollbar-track:transparent;--rv-separator:#c9d6ec;--rv-separator-hover:#5b93cf}
|
|
2
|
+
:root{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--font-sans:Inter,system-ui,sans-serif;--font-display:Inter,system-ui,sans-serif;--font-mono:"JetBrains Mono",ui-monospace,monospace;--radius:10px;--bg:#070b18;--fg:#c9d4e8;--fg-strong:#f3f6fc;--fg-soft:#aebbd4;--fg-muted:#7f8ba6;--border:#18213a;--border-strong:#2a3657;--hover-bg:#0f1730;--active-bg:#142042;--link:#7cc8ff;--link-hover:#b3e0ff;--btn-fg:#08101f;--code-bg:#050814;--code-fg:#d6e2f5;--code-border:#18213a;--code-inline-bg:#121a30;--footer-bg:#050813;--pass:#30d158;--warn:#ffcc00;--fail:#ff453a;--rv-bar:#0b1124;--rv-panel:#0d152b;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:var(--active-bg);--rv-field:var(--code-bg);--rv-segmented:var(--code-bg);--rv-pressed-bg:#133733;--rv-pressed-border:#207c44;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link-hover);--rv-backdrop:rgba(7,11,24,.82);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#2a3657;--rv-scrollbar-thumb-hover:#3d4c74;--rv-scrollbar-track:transparent;--rv-separator:#1a2747;--rv-separator-hover:#3f7fb8;--rv-tick-inspected:#7cc8ff;--rv-tick-scratch:#ff9f43}
|
|
3
|
+
:root[data-theme=light]{--rv-region-a:#ff9f43;--rv-region-a-fill:rgba(255,159,67,.18);--rv-region-b:#4dd6ff;--rv-region-b-fill:rgba(77,214,255,.18);--bg:#f5f7fb;--fg:#26324a;--fg-strong:#0c1324;--fg-soft:#3b4864;--fg-muted:#5d6982;--border:#d5dcea;--border-strong:#b3bed3;--hover-bg:#e8edf6;--active-bg:#dbe3f3;--link:#1a6fc0;--link-hover:#0f528f;--btn-fg:#ffffff;--code-bg:#eef1f7;--code-fg:#1d2940;--code-border:#d5dcea;--code-inline-bg:#e3e8f2;--footer-bg:#eceff6;--pass:#1e8a3c;--warn:#8a6400;--fail:#b3261e;--rv-bar:#edf1f7;--rv-panel:#e9eef6;--rv-raised:var(--code-inline-bg);--rv-control:var(--code-inline-bg);--rv-control-hover:#d5ddec;--rv-field:#ffffff;--rv-segmented:var(--code-inline-bg);--rv-pressed-bg:#c9ded8;--rv-pressed-border:#6fb286;--rv-pressed-fg:var(--fg-strong);--rv-focus:var(--link);--rv-backdrop:rgba(12,19,36,.45);--rv-pane-hi:#142042;--rv-pane-lo:#070b18;--rv-pane-label-bg:rgba(7,11,24,.85);--rv-stage-fg:#aebbd4;--rv-grid-dot:rgba(127,139,166,.2);--rv-media-bg:#0b1020;--rv-scrollbar-thumb:#b3bed3;--rv-scrollbar-thumb-hover:#8e9bb5;--rv-scrollbar-track:transparent;--rv-separator:#c9d6ec;--rv-separator-hover:#5b93cf;--rv-tick-inspected:#1a6fc0;--rv-tick-scratch:#c25e00}
|
|
4
4
|
:root{font-family:var(--font-sans);color:var(--fg);background:var(--bg);font-size:13px;color-scheme:dark;font-synthesis:none}*{box-sizing:border-box}body{margin:0}button,input,select,textarea{font:inherit}button{color:inherit;background:var(--rv-control);border:1px solid var(--border-strong);border-radius:6px;padding:7px 10px;cursor:pointer}button:hover{background:var(--rv-control-hover)}button:disabled{opacity:.45;cursor:default}button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}button.primary{background:var(--link);border-color:var(--link);color:var(--btn-fg);font-weight:600}button.outline{background:transparent}button.block{width:100%;margin-top:10px}a{color:var(--link)}input,select,textarea{color:var(--fg);background:var(--rv-field);border:1px solid var(--border-strong);border-radius:6px;padding:8px;max-width:100%}select,input[type=text],textarea{width:100%}textarea{resize:vertical;line-height:1.6}input[type=range]{accent-color:var(--link);padding:0;min-width:0}input[type=checkbox],input[type=radio]{accent-color:var(--link)}button:focus-visible,a:focus-visible,input:focus-visible,select:focus-visible,textarea:focus-visible{outline:2px solid var(--rv-focus);outline-offset:3px}[hidden]{display:none!important}.topbar{min-height:72px;padding:15px 22px;display:flex;align-items:center;gap:25px;border-bottom:1px solid var(--border);flex-wrap:wrap;background:var(--rv-bar)}.brand{font-size:19px;letter-spacing:-.6px;white-space:nowrap}.brand-mark{display:inline-grid;place-items:center;width:24px;height:24px;border:1px solid var(--link);color:var(--link);border-radius:50%;margin-right:9px;font-size:14px}.topbar strong{font-weight:500;font-size:13px}.topbar #revision{margin-left:9px}.top-actions{display:flex;align-items:center;gap:8px;margin-left:auto;flex-wrap:wrap}.subtle,.hint{color:var(--fg-muted);font-size:11px}.hint{line-height:1.6}.workspaces{display:flex;gap:10px;padding:0 18px;border-bottom:1px solid var(--border);background:var(--rv-bar)}.workspaces button{border:0;border-bottom:2px solid transparent;border-radius:0;background:transparent;padding:15px 13px;font-size:12px;letter-spacing:.06em;color:var(--fg-soft)}.workspaces button[aria-pressed=true]{border-bottom-color:var(--link);color:var(--fg-strong);background:transparent}.workspaces button span{font-size:11px;margin-left:8px;letter-spacing:0;color:var(--fg-muted)}.layout{display:grid;grid-template-columns:190px minmax(250px,1fr) 286px;min-height:calc(100vh - 160px)}aside{background:var(--rv-panel);padding:20px 15px}.left-panel{border-right:1px solid var(--border)}.right-panel{border-left:1px solid var(--border)}.eyebrow{font-size:11px;letter-spacing:.08em;color:var(--fg-muted);margin:0 0 11px}.label{display:block;color:var(--fg-muted);font-size:11px;margin:0 0 7px}.space-top{margin-top:15px}.left-panel section{margin-top:26px}.tool{width:100%;display:flex;justify-content:space-between;align-items:center;text-align:left;gap:5px;border-color:transparent;background:transparent;padding:9px 6px;margin:3px 0}.tool small{font-size:11px;color:var(--fg-muted)}.checkpoint{display:block;width:100%;background:transparent;border:1px solid transparent;text-align:left;margin:4px 0;padding:10px 9px;white-space:normal;line-height:1.5}.checkpoint[aria-current=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border)}.checkpoint small{display:block;font-size:11px;color:var(--fg-muted);margin-top:3px}.source-link{display:block;font-size:11px;overflow-wrap:anywhere;margin:8px 0}main{min-width:0;display:flex;flex-direction:column;background:var(--bg)}.viewbar{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:12px;border-bottom:1px solid var(--border)}.zoom-controls{display:flex;gap:4px;align-items:center}.zoom-controls button{padding:4px 8px;font-size:12px}.zoom-controls output{font:11px var(--font-mono);min-width:39px;text-align:center;color:var(--fg-muted)}.stages{display:flex;gap:10px;padding:12px 12px 0;flex:1;min-height:460px}.stage-pane{position:relative;min-width:0;flex:1;border:1px solid var(--border-strong);border-radius:8px;overflow:hidden;background:radial-gradient(ellipse at 50% 40%,var(--rv-pane-hi),var(--rv-pane-lo) 74%)}.pane-label{position:absolute;left:10px;top:10px;z-index:2;background:var(--rv-pane-label-bg);border-radius:4px;padding:4px 7px;color:var(--rv-stage-fg);font-size:11px;pointer-events:none}.viewport{position:relative;overflow:hidden;width:100%;height:100%;min-height:450px;touch-action:none;background-image:radial-gradient(var(--rv-grid-dot) .7px,transparent .7px);background-size:22px 22px}.empty{position:absolute;inset:65px 22px 30px;display:grid;place-items:center;text-align:center;color:var(--rv-stage-fg);font-size:12px;line-height:1.8}.reference-content,#rig-root{position:absolute;transform-origin:0 0}.reference-content img,.reference-content video{display:block;max-width:none}.reference-content svg{display:block;max-width:none}.reference-content video{object-fit:contain}.stage-footer{display:flex;align-items:center;justify-content:space-between;gap:10px;flex-wrap:wrap;padding:11px 16px;color:var(--fg-muted);font-size:11px}.transport{display:flex;align-items:center;gap:11px;padding:13px;border-top:1px solid var(--border);background:var(--rv-panel)}.transport select{width:auto;max-width:150px;font-size:11px}.transport input{flex:1}.transport output{font:11px var(--font-mono);white-space:nowrap;color:var(--fg-muted)}.sound-panel,.angle-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel)}.row{display:flex;align-items:center;gap:11px;flex-wrap:wrap}.row .eyebrow{margin:0}.sound-panel .row{font-size:11px;margin-bottom:9px}.sound-panel input[type=range]{flex:1;max-width:160px}.cue{display:flex;gap:9px;align-items:center;justify-content:space-between;border-top:1px solid var(--border);padding:9px 0;font-size:11px;flex-wrap:wrap}.cue button{padding:4px 7px;font-size:11px}.angle-panel{display:flex;gap:10px;align-items:center;flex-wrap:wrap;font-size:11px}.angle-panel select{width:auto;max-width:150px}.angle-panel .hint{width:100%;margin:0}.angle-panel input{flex:1}.divider{height:1px;background:var(--border-strong);margin:22px 0}h1{font-size:15px;font-weight:500;line-height:1.6;margin:0 0 7px}.outcome{border:0;padding:0;margin:18px 0 0;display:flex;gap:11px;flex-wrap:wrap}.outcome legend{font-size:11px;color:var(--fg-muted);margin-bottom:8px;padding:0}.outcome label{display:flex;align-items:center;gap:3px;font-size:11px}.proposal-item{border-top:1px solid var(--border);padding:11px 0;margin-top:12px;font-size:11px;overflow-wrap:anywhere}.proposal-item strong{display:block;font-weight:500;margin-bottom:5px}.proposal-item button{font-size:11px;padding:3px 6px;margin-top:7px}footer{display:flex;justify-content:space-between;align-items:center;gap:6px 15px;flex-wrap:nowrap;height:40px;box-sizing:border-box;overflow:hidden;padding:0 18px;border-top:1px solid var(--border);background:var(--footer-bg);font-size:11px;color:var(--fg-soft)}.error{color:var(--fail)!important}.dirty{color:var(--warn)!important}.sr-only{position:absolute;width:1px;height:1px;overflow:hidden;clip-path:inset(50%)}
|
|
5
5
|
@media(min-width:1500px){.layout{grid-template-columns:218px minmax(250px,1fr) 310px}.stages{min-height:600px}}
|
|
6
6
|
@media(max-width:1000px){.layout{grid-template-columns:158px minmax(250px,1fr) 250px}aside{padding:17px 12px}.topbar{gap:14px}.topbar #revision{display:block;margin-left:0}.stages{min-height:400px}.viewport{min-height:400px}.workspaces span{display:none}}
|
|
@@ -227,13 +227,36 @@ footer.status-bar{flex-wrap:nowrap}
|
|
|
227
227
|
.review-dialog .review-summary>label{display:block;font-size:11px;margin:10px 0 5px}
|
|
228
228
|
.review-dialog .report-panel{padding:10px 0}
|
|
229
229
|
@media(max-width:540px){.review-dialog-header .hint{display:none}}
|
|
230
|
-
:root{--rv-bust-r:#00e5ff;--rv-bust-r-fill:rgba(0,229,255,.13);--rv-bust-l:#52ff88;--rv-bust-l-fill:rgba(82,255,136,.13);--rv-bust-overlap:#ff5252;--rv-bust-overlap-fill:rgba(255,82,82,.13);--rv-bust-handle:#ffffff;--rv-bust-ink:#111318;--rv-bust-label-bg:rgba(12,14,18,.82);--rv-bust-flash:#ffcc00}
|
|
231
|
-
:root[data-theme=light]{--rv-bust-r:#00e5ff;--rv-bust-r-fill:rgba(0,229,255,.13);--rv-bust-l:#52ff88;--rv-bust-l-fill:rgba(82,255,136,.13);--rv-bust-overlap:#ff5252;--rv-bust-overlap-fill:rgba(255,82,82,.13);--rv-bust-handle:#ffffff;--rv-bust-ink:#111318;--rv-bust-label-bg:rgba(12,14,18,.82);--rv-bust-flash:#ffcc00}
|
|
230
|
+
:root{--rv-mesh-region:#ff7ae0;--rv-mesh-region-fill:rgba(255,122,224,.12);--rv-mesh-handle:#ffffff;--rv-bust-r:#00e5ff;--rv-bust-r-fill:rgba(0,229,255,.13);--rv-bust-l:#52ff88;--rv-bust-l-fill:rgba(82,255,136,.13);--rv-bust-overlap:#ff5252;--rv-bust-overlap-fill:rgba(255,82,82,.13);--rv-bust-handle:#ffffff;--rv-bust-ink:#111318;--rv-bust-label-bg:rgba(12,14,18,.82);--rv-bust-flash:#ffcc00;--rv-landmark:#ffd34d;--rv-landmark-fill:#ffd34d;--rv-landmark-ink:#111318}
|
|
231
|
+
:root[data-theme=light]{--rv-mesh-region:#c2189b;--rv-mesh-region-fill:rgba(194,24,155,.12);--rv-mesh-handle:#ffffff;--rv-bust-r:#00e5ff;--rv-bust-r-fill:rgba(0,229,255,.13);--rv-bust-l:#52ff88;--rv-bust-l-fill:rgba(82,255,136,.13);--rv-bust-overlap:#ff5252;--rv-bust-overlap-fill:rgba(255,82,82,.13);--rv-bust-handle:#ffffff;--rv-bust-ink:#111318;--rv-bust-label-bg:rgba(12,14,18,.82);--rv-bust-flash:#ffcc00;--rv-landmark:#ffd34d;--rv-landmark-fill:#ffd34d;--rv-landmark-ink:#111318}
|
|
232
232
|
.view-tools{display:flex;gap:4px;flex:none}.view-tool{padding:5px 9px;font-size:11px;white-space:nowrap}
|
|
233
|
-
.chest-editor-bar{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;padding:6px 10px;border-bottom:1px solid var(--border);background:var(--rv-bar);font-size:11px}.chest-editor-bar[hidden]{display:none}.chest-editor-bar .bust-group{display:flex;align-items:center;gap:6px;padding-left:10px;border-left:1px solid var(--border)}.chest-editor-bar .bust-group:first-child{padding-left:0;border-left:0}.chest-editor-bar label{display:flex;align-items:center;gap:4px;color:var(--fg-muted)}.chest-editor-bar input[type=number]{width:58px;padding:3px 5px;font-size:11px}.chest-editor-bar button{padding:4px 8px;font-size:11px}.chest-editor-bar button[aria-disabled=true]{opacity:.6}.bust-tag{font-weight:700}.bust-tag-r{color:var(--rv-bust-r)}.bust-tag-l{color:var(--rv-bust-l)}#chest-editor-status{flex:1 1 220px;min-width:0}
|
|
233
|
+
.chest-editor-bar{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px;padding:6px 10px;border-bottom:1px solid var(--border);background:var(--rv-bar);font-size:11px}.chest-editor-bar[hidden]{display:none}.chest-editor-bar input[type=range]{width:84px}.chest-editor-bar input[type=text]{width:104px;padding:3px 5px;font-size:11px}.chest-editor-bar output{min-width:2.6em;font:10px/1 var(--font-mono);color:var(--fg)}.chest-editor-bar [data-landmark][aria-pressed=true]{outline:2px solid var(--rv-landmark);outline-offset:1px}#lm-derived{flex:1 1 100%;order:9;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:10px/1.4 var(--font-mono)}#lm-derived.error{color:var(--fail)}.chest-editor-bar .bust-group{display:flex;align-items:center;gap:6px;padding-left:10px;border-left:1px solid var(--border)}.chest-editor-bar .bust-group:first-child{padding-left:0;border-left:0}.chest-editor-bar label{display:flex;align-items:center;gap:4px;color:var(--fg-muted)}.chest-editor-bar input[type=number]{width:58px;padding:3px 5px;font-size:11px}.chest-editor-bar button{padding:4px 8px;font-size:11px}.chest-editor-bar button[aria-disabled=true]{opacity:.6}.bust-tag{font-weight:700}.bust-tag-r{color:var(--rv-bust-r)}.bust-tag-l{color:var(--rv-bust-l)}#handoff-line{flex:1 1 100%;order:10;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;font:10px/1.4 var(--font-mono)}#chest-editor-status{flex:1 1 220px;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}.chest-editor-bar{position:relative}.rebuild-log{position:absolute;top:100%;left:10px;right:10px;z-index:6;margin:4px 0 0;max-height:7.5em;overflow:auto;padding:5px 8px;font:10px/1.5 var(--font-mono);white-space:pre-wrap;overflow-wrap:anywhere;background:var(--code-bg);color:var(--code-fg);border:1px solid var(--border);border-radius:5px}
|
|
234
234
|
:root{--rv-mesh-veil:rgba(8,10,14,.55);--rv-mesh-edge:#69f1ff;--rv-mesh-hull:#ffb020;--rv-mesh-constraint:#ff4fd8;--rv-mesh-removed:#ff5252;--rv-mesh-vertex:#ffffff;--rv-mesh-ink:#111318;--rv-mesh-selected:#ffe14d;--rv-mesh-added:#52ff88;--rv-mesh-hover:#ffffff;--rv-mesh-box:#ffe14d;--rv-mesh-box-fill:rgba(255,225,77,.12)}
|
|
235
235
|
:root[data-theme=light]{--rv-mesh-veil:rgba(245,247,251,.6);--rv-mesh-edge:#69f1ff;--rv-mesh-hull:#ffb020;--rv-mesh-constraint:#ff4fd8;--rv-mesh-removed:#ff5252;--rv-mesh-vertex:#ffffff;--rv-mesh-ink:#111318;--rv-mesh-selected:#ffe14d;--rv-mesh-added:#52ff88;--rv-mesh-hover:#ffffff;--rv-mesh-box:#ffe14d;--rv-mesh-box-fill:rgba(255,225,77,.12)}
|
|
236
236
|
.mesh-editor-bar kbd{margin-left:5px;font-size:10px;opacity:.75}#mesh-editor-status{flex:1 1 260px;min-width:0;white-space:nowrap;overflow:hidden;text-overflow:ellipsis}
|
|
237
237
|
#rig-pane[data-mesh-mode] .viewport{cursor:crosshair}#rig-pane[data-mesh-mode=modify][data-mesh-hover] .viewport{cursor:move}#rig-pane[data-mesh-mode=create] .viewport{cursor:copy}#rig-pane[data-mesh-mode=delete][data-mesh-hover] .viewport{cursor:not-allowed}#rig-pane[data-mesh-mode=edges][data-mesh-hover=vertex] .viewport{cursor:cell}
|
|
238
238
|
#rig-pane[data-chest-hover=move] .viewport{cursor:move}#rig-pane[data-chest-hover=r] .viewport,#rig-pane[data-chest-hover=band] .viewport{cursor:ew-resize}#rig-pane[data-chest-hover=band] .viewport{cursor:ns-resize}
|
|
239
239
|
.environment-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel);font-size:11px}.environment-panel .row{margin-bottom:9px}.environment-panel .row label{display:flex;align-items:center;gap:4px;color:var(--fg-muted)}.environment-panel input[type=range]{flex:1;max-width:180px}.environment-panel input[type=number]{width:72px;padding:4px 6px;font-variant-numeric:tabular-nums}.environment-panel button{padding:4px 9px;font-size:11px}.environment-panel output{font:11px var(--font-mono);color:var(--fg-muted)}.environment-panel .hint{margin:0 0 9px}.environment-readout{display:block;margin:0 0 9px;color:var(--fg-strong)!important;overflow-wrap:anywhere}.physics-constraints summary{cursor:pointer;color:var(--fg-soft)}.physics-table-wrap{overflow-x:auto;max-height:260px;overflow-y:auto}.physics-table{width:100%;border-collapse:collapse;font:11px var(--font-mono);font-variant-numeric:tabular-nums}.physics-table th,.physics-table td{padding:3px 6px;border-top:1px solid var(--border);text-align:right;white-space:nowrap}.physics-table th{color:var(--fg-muted);font-weight:500}.physics-table th:first-child,.physics-table td:first-child,.physics-table th:nth-child(2),.physics-table td:nth-child(2){text-align:left}.physics-table .absent{color:var(--fg-muted)}
|
|
240
|
+
/* Animate · Physics tuning (#103): edits are config patch ops; tokens only. */
|
|
241
|
+
.physics-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel);font-size:11px}.physics-panel .row{margin-bottom:9px}.physics-panel .row label{color:var(--fg-muted)}.physics-panel select{width:auto;max-width:100%;padding:4px 6px;font-size:11px}.physics-panel input[type=number]{width:72px;padding:4px 6px;font-variant-numeric:tabular-nums}.physics-panel button{padding:4px 9px;font-size:11px}.physics-panel .hint{margin:0 0 9px}.physics-panel code{font:11px var(--font-mono)}.physics-table .pending{color:var(--warn)}.physics-table tr.selected th,.physics-table tr.selected td{background:var(--rv-pressed-bg)}.physics-row-pick{border:0;background:transparent;padding:0;font:inherit;color:var(--link);text-align:left}.physics-row-pick[aria-pressed=true]{background:transparent;color:var(--fg-strong);font-weight:600}.physics-row-editor{margin:10px 0;padding:9px 10px;border:1px solid var(--border);border-radius:6px}.physics-editor-title{margin:0 0 8px;overflow-wrap:anywhere}.physics-controls{display:flex;flex-direction:column;gap:6px}.physics-control{display:grid;grid-template-columns:58px minmax(0,1fr) 72px auto;align-items:center;gap:6px;color:var(--fg-muted)}.physics-control.pending span,.physics-toggle.pending{color:var(--warn)}.physics-control output{font:11px var(--font-mono);color:var(--fg-muted)}.physics-toggle{display:inline-flex;align-items:center;gap:4px;margin-right:10px;color:var(--fg-muted)}.physics-locked{margin-top:10px}.physics-locked .eyebrow{margin:0 0 6px}.physics-locked-table td.physics-reason,.physics-locked-table th{white-space:normal;text-align:left}.physics-reason{overflow-wrap:anywhere}.physics-pending-list{margin:0 0 9px;padding-left:18px}.physics-pending-list li{margin:3px 0;overflow-wrap:anywhere}.physics-pending-list li.applied{color:var(--fg-soft)}.physics-pending-list button{margin-left:6px;padding:1px 6px}.physics-readout{display:flex;flex-direction:column;gap:4px}.physics-readout-line{display:grid;grid-template-columns:52px minmax(0,1fr);gap:6px;align-items:baseline}.physics-readout-line .eyebrow{margin:0}.physics-readout-line code{overflow-wrap:anywhere;color:var(--fg-strong)}#rig-pane[data-physics-pick=true]{cursor:crosshair}.physics-work{display:grid;grid-template-columns:minmax(0,1.5fr) minmax(260px,1fr);gap:12px;align-items:start}.physics-work .physics-row-editor{margin:0}@media (max-width:900px){.physics-work{grid-template-columns:minmax(0,1fr)}}
|
|
242
|
+
|
|
243
|
+
/* Readings (#108): the block, its worst-frame lines and the two-colour ticks under the transport scrub. */
|
|
244
|
+
.readings-panel{padding:15px;border-top:1px solid var(--border);background:var(--rv-panel);font-size:11px}.readings-panel .row{margin-bottom:9px}.readings-panel .row label{display:flex;align-items:center;gap:4px;color:var(--fg-muted)}.readings-panel .hint{margin:0 0 9px}.readings-panel code{font:11px var(--font-mono)}.readings-panel button{padding:4px 9px;font-size:11px}.readings-pair{flex-wrap:nowrap}.readings-pair label{flex:none;min-width:92px}.readings-pair input[type=text]{flex:1;min-width:0;width:auto;padding:4px 6px;font:11px var(--font-mono)}.readings-worst{list-style:none;margin:0;padding:0;display:flex;flex-direction:column;gap:4px}.readings-line{display:grid;grid-template-columns:8px minmax(0,1fr);gap:6px;align-items:start}.readings-line .tick-swatch{margin-top:5px}.readings-line code,.readings-go{font:11px var(--font-mono);overflow-wrap:anywhere;text-align:left;color:var(--fg-strong)}.readings-go{border:0;background:transparent;padding:0;cursor:pointer}.readings-go:hover{background:transparent;color:var(--link-hover)}.readings-line.error code{color:var(--fail)}.readings-items{grid-column:2;color:var(--fg-muted)}.readings-items summary{cursor:pointer}.readings-items ul{margin:4px 0 0;padding-left:14px;display:flex;flex-direction:column;gap:3px}.tick-swatch{display:inline-block;width:8px;height:8px;border-radius:2px;vertical-align:baseline}.tick-swatch.inspected{background:var(--rv-tick-inspected)}.tick-swatch.scratch{background:var(--rv-tick-scratch)}
|
|
245
|
+
.transport{position:relative}.transport.has-reading-ticks{padding-bottom:24px}.inspector .transport.has-reading-ticks{padding-bottom:22px}.reading-ticks{position:absolute;height:14px;margin-top:2px;pointer-events:none}.reading-tick{position:absolute;width:3px;height:6px;margin-left:-1px;padding:0;border:0;border-radius:1px;pointer-events:auto;cursor:pointer}.reading-tick.inspected{top:0;background:var(--rv-tick-inspected)}.reading-tick.scratch{top:8px;background:var(--rv-tick-scratch)}.reading-tick:hover{background:var(--fg-strong)}.reading-tick:focus-visible{outline-offset:1px}
|
|
246
|
+
.idle-panel .idle-add-row input[type=text]{flex:1;min-width:90px;width:auto;padding:4px 6px;font:11px var(--font-mono)}.idle-preview-label{margin-left:auto;padding:2px 7px;border:1px solid var(--warn);border-radius:4px;color:var(--warn);font-size:11px;overflow-wrap:anywhere}.idle-preview-label.refused{border-style:dashed}.idle-preview-toggle{display:inline-flex;align-items:center;gap:4px;color:var(--fg-muted)}.idle-table tr.pending td,.idle-panel p.pending{color:var(--warn)}.idle-blink{margin:10px 0}.idle-blink .eyebrow{margin:0 0 6px}.idle-period select{width:100%;min-width:0}.idle-precheck{display:flex;flex-direction:column;gap:4px;margin-bottom:9px}.idle-precheck code{font:11px var(--font-mono);overflow-wrap:anywhere}.idle-precheck .idle-note{color:var(--fg-muted)}.transport.has-idle-blink{padding-top:22px}.idle-blink-strip{position:absolute;height:12px;pointer-events:none}.idle-blink-marker{position:absolute;top:0;width:14px;height:12px;margin-left:-7px;padding:0;border:0;background:transparent;color:var(--link);font-size:10px;line-height:12px;pointer-events:auto;cursor:ew-resize;touch-action:none}.idle-blink-marker.pending{color:var(--warn)}.idle-handles{position:absolute;inset:0;pointer-events:none;z-index:3}.idle-handle{position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;padding:0;border:2px solid var(--rv-tick-scratch);border-radius:50%;background:var(--rv-pane-label-bg);pointer-events:auto;cursor:ns-resize;touch-action:none}.idle-handle span{position:absolute;left:16px;top:-3px;white-space:nowrap;padding:1px 4px;border-radius:3px;background:var(--rv-pane-label-bg);color:var(--rv-stage-fg);font:10px var(--font-mono);pointer-events:none}
|
|
247
|
+
.mesh-params{margin:0 0 10px}.mesh-param{grid-template-columns:78px minmax(0,1fr) 72px 18px auto}.mesh-param select{max-width:140px}.mesh-param-toggle{margin-top:2px}.mesh-tools{display:flex;align-items:center;flex-wrap:wrap;gap:6px}.mesh-tools .eyebrow{margin:0 6px 0 0}.mesh-tools button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}.mesh-regions{display:flex;flex-direction:column;gap:6px;margin:0 0 10px}.mesh-region{display:flex;flex-wrap:wrap;align-items:center;gap:4px 8px;margin:0;padding:6px 8px;border:1px solid var(--border);border-radius:6px}.mesh-region.selected{border-color:var(--rv-mesh-region)}.mesh-region legend{padding:0 4px;font:600 11px var(--font-mono);color:var(--rv-mesh-region)}.mesh-region label{display:flex;align-items:center;gap:4px;color:var(--fg-muted)}.mesh-region input[type=number]{width:62px}.mesh-region input[type=text]{width:96px;padding:3px 5px;font-size:11px}.mesh-segments li{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.mesh-ribs{display:inline-flex;align-items:center;gap:4px;color:var(--fg-muted)}.mesh-ribs input{width:64px}.mesh-precheck{margin:0 0 9px;padding-left:18px}.mesh-precheck li{color:var(--fail);overflow-wrap:anywhere}#mesh-counts code.error,#mesh-status.error{color:var(--fail)}
|
|
248
|
+
/* Inspector block row (#124): sticky at the top of each section's scroll (the section pads 12px 15px), one line that scrolls sideways when narrow. */
|
|
249
|
+
.block-row{position:sticky;top:-12px;z-index:3;display:flex;flex-wrap:nowrap;gap:4px;margin:-12px -15px 10px;padding:6px 15px;overflow-x:auto;overscroll-behavior-x:contain;background:var(--rv-panel);border-bottom:1px solid var(--border)}.block-row button{flex:none;padding:3px 9px;font-size:11px;white-space:nowrap;color:var(--fg-muted);background:transparent;border-color:transparent}.block-row button:hover{color:var(--fg-strong);background:var(--rv-control-hover)}.block-row button[aria-current=true]{color:var(--rv-pressed-fg);background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border)}.block-row button:focus-visible{outline:2px solid var(--rv-focus);outline-offset:-2px}
|
|
250
|
+
/* Setup · Weights (#107): the weight ramp (low → half → high) and max influence's bone colours. Drawn on the stage, which stays dark, so both schemes carry the same values. */
|
|
251
|
+
:root{--rv-weight-0:#3d1a78;--rv-weight-half:#e0573a;--rv-weight-1:#fcef6c;--rv-weight-bone-1:#00e5ff;--rv-weight-bone-2:#ff7ae0;--rv-weight-bone-3:#52ff88;--rv-weight-bone-4:#ffb020;--rv-weight-bone-5:#a98bff;--rv-weight-bone-6:#ff5252}
|
|
252
|
+
:root[data-theme=light]{--rv-weight-0:#3d1a78;--rv-weight-half:#e0573a;--rv-weight-1:#fcef6c;--rv-weight-bone-1:#00e5ff;--rv-weight-bone-2:#ff7ae0;--rv-weight-bone-3:#52ff88;--rv-weight-bone-4:#ffb020;--rv-weight-bone-5:#a98bff;--rv-weight-bone-6:#ff5252}
|
|
253
|
+
.weights-controls{display:flex;align-items:center;flex-wrap:wrap;gap:6px 12px}.weights-controls label{display:inline-flex;align-items:center;gap:5px}.weights-controls select{width:auto;max-width:220px}#weights-state.error{color:var(--fail)}.weights-legend{display:flex;align-items:center;flex-wrap:wrap;gap:6px 10px;margin:0 0 8px;font:11px var(--font-mono);color:var(--fg-muted)}.weights-ramp{display:inline-block;width:140px;height:10px;border-radius:3px;background:linear-gradient(90deg,var(--rv-weight-0),var(--rv-weight-half),var(--rv-weight-1))}.weights-swatch{display:inline-flex;align-items:center;gap:5px}.weights-swatch i{display:inline-block;width:10px;height:10px;border-radius:50%}
|
|
254
|
+
:root{--rv-bone-line:#ffe26b;--rv-field-still:#69f1ff;--rv-field-inward:rgba(255,122,224,.28);--rv-field-inward-line:#ff7ae0;--rv-field-outward:rgba(105,241,255,.24);--rv-field-outward-line:#69f1ff;--rv-field-rest:#ffe26b;--rv-field-handle-bg:rgba(12,14,18,.82);--rv-field-ink:#f4f6fa;--rv-bone-origin:#ffe26b;--rv-bone-point:#69f1ff;--rv-bone-tip:#ff7ae0;--rv-bone-pending:#ffb020;--rv-bone-selected:#ffffff;--rv-bone-ink:#111318;--rv-bone-label-bg:rgba(12,14,18,.82)}
|
|
255
|
+
:root[data-theme=light]{--rv-bone-line:#ffe26b;--rv-field-still:#69f1ff;--rv-field-inward:rgba(255,122,224,.28);--rv-field-inward-line:#ff7ae0;--rv-field-outward:rgba(105,241,255,.24);--rv-field-outward-line:#69f1ff;--rv-field-rest:#ffe26b;--rv-field-handle-bg:rgba(12,14,18,.82);--rv-field-ink:#f4f6fa;--rv-bone-origin:#ffe26b;--rv-bone-point:#69f1ff;--rv-bone-tip:#ff7ae0;--rv-bone-pending:#ffb020;--rv-bone-selected:#ffffff;--rv-bone-ink:#111318;--rv-bone-label-bg:rgba(12,14,18,.82)}
|
|
256
|
+
.bones-controls{display:flex;align-items:center;flex-wrap:wrap;gap:6px 10px}.bones-controls button[aria-pressed=true]{background:var(--rv-pressed-bg);border-color:var(--rv-pressed-border);color:var(--rv-pressed-fg)}.bones-controls select{max-width:260px}#bones-panel-state.error{color:var(--fail)}#bones-status.error{color:var(--fail)!important}.bones-anchor-editor{margin:0 0 10px;padding:9px 10px;border:1px solid var(--border);border-radius:6px}.bones-anchor{display:grid;grid-template-columns:minmax(0,1.3fr) minmax(0,1fr) 64px 64px auto;align-items:center;gap:6px;margin:3px 0;color:var(--fg-muted)}.bones-anchor code{overflow-wrap:anywhere}.bones-anchor.pending span,.bones-anchor.pending output{color:var(--warn)}.bones-anchor output{font:11px var(--font-mono)}.bones-lines{display:flex;flex-direction:column;gap:3px;margin-top:8px}.bones-lines .eyebrow{margin:0 0 2px}.bones-lines code{overflow-wrap:anywhere;color:var(--fg-strong)}.bones-lines code.lint{color:var(--warn)}.bones-lines code.error{color:var(--fail)}.bones-coverage summary{cursor:pointer;font:11px var(--font-mono);color:var(--fg-muted)}.bones-coverage code{display:block;margin:2px 0 2px 12px;color:var(--fg-muted)}#rig-pane[data-bones-hover=move]{cursor:move}
|
|
257
|
+
|
|
258
|
+
.rig-fields-group{margin:8px 0 10px}.rig-fields-group .eyebrow{margin:0 0 5px}.rig-fields-group .row{flex-wrap:wrap;gap:6px;align-items:center}.rig-fields-group select{min-width:0;max-width:100%}.rig-fields-group input[type=number]{width:76px}.rig-fields-group .physics-control{display:grid;grid-template-columns:minmax(64px,auto) minmax(0,1fr) 76px auto;gap:6px;align-items:center}
|
|
259
|
+
.rig-fields-stage{position:absolute;inset:0;pointer-events:none;z-index:3}.rig-fields-svg{position:absolute;inset:0;width:100%;height:100%;overflow:visible}.rig-fields-still-line{stroke:var(--rv-field-still);stroke-width:2;stroke-dasharray:6 3}.rig-fields-arc{stroke-width:1.5}.rig-fields-arc.inward{fill:var(--rv-field-inward);stroke:var(--rv-field-inward-line)}.rig-fields-arc.outward{fill:var(--rv-field-outward);stroke:var(--rv-field-outward-line)}.rig-fields-rest{stroke:var(--rv-field-rest);stroke-width:1.5;stroke-dasharray:3 3}
|
|
260
|
+
.rig-fields-handle{position:absolute;width:14px;height:14px;margin:-7px 0 0 -7px;padding:0;border:2px solid var(--rv-field-still);border-radius:50%;background:var(--rv-field-handle-bg);pointer-events:auto;cursor:grab;touch-action:none}.rig-fields-handle.still{cursor:ns-resize}.rig-fields-handle.inward{border-color:var(--rv-field-inward-line)}.rig-fields-handle.outward{border-color:var(--rv-field-outward-line)}.rig-fields-handle span{position:absolute;left:16px;top:-3px;white-space:nowrap;padding:1px 4px;border-radius:3px;background:var(--rv-field-handle-bg);color:var(--rv-field-ink);font:10px var(--font-mono);pointer-events:none}
|
|
261
|
+
.requirements-fields .row{margin-bottom:6px}.requirements-panel select{width:auto;max-width:170px;padding:4px 6px;font-size:11px}.requirements-panel input[type=text]{flex:1;min-width:0;width:auto;padding:4px 6px;font:11px var(--font-mono)}.requirements-panel input[type=number]{width:70px;padding:4px 6px;font-size:11px}.requirements-list{list-style:none;margin:0 0 9px;padding:0;display:flex;flex-direction:column;gap:8px}.requirements-item{border-left:2px solid var(--border-strong);padding-left:8px;display:flex;flex-direction:column;gap:4px}.requirements-item.pass{border-left-color:var(--pass,var(--link))}.requirements-item.fail{border-left-color:var(--fail)}.requirements-item code{font:11px var(--font-mono);overflow-wrap:anywhere}.requirements-bar input[type=range]{flex:1;min-width:90px}.requirements-bar output{font:11px var(--font-mono);color:var(--fg-strong)}.requirements-refusals{list-style:none;margin:6px 0;padding:0;display:flex;flex-direction:column;gap:3px}.requirements-refusals code{font:11px var(--font-mono);color:var(--fail);overflow-wrap:anywhere}
|
|
262
|
+
.retarget-chains{display:flex;flex-direction:column;gap:5px;margin-bottom:9px}.retarget-chain{display:flex;flex-wrap:wrap;align-items:center;gap:4px}.retarget-chain.off{opacity:.55}.retarget-chain label{min-width:70px}.retarget-chain code,.retarget-translate span{font:11px var(--font-mono);color:var(--fg-muted)}.retarget-translate{display:flex;flex-wrap:wrap;align-items:center;gap:4px}.retarget-json textarea{width:100%;font:11px var(--font-mono)}.retarget-panel .row{display:flex;flex-wrap:wrap;align-items:center;gap:6px}.retarget-log{max-height:160px;overflow:auto;font:10px var(--font-mono);white-space:pre-wrap;color:var(--fg-muted)}
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Setup · Weights (#107): a watch layer over the selected slot's setup-pose mesh. Pick a bone and every vertex is
|
|
3
|
+
* coloured by that bone's stored weight on the theme's ramp (0 → 1), or switch to max influence and every vertex takes
|
|
4
|
+
* its dominant bone's colour; hovering a vertex reads its stored value. Weights come from the compiled model document
|
|
5
|
+
* (`src/weights.ts` over the shared reader `src/setup-mesh.ts`); vertex positions come from the published player's
|
|
6
|
+
* setup pose (`web/setup-pose.ts`, a second `RigPlayer` over the same assets that never plays — the same placement
|
|
7
|
+
* Edit Mesh uses). Nothing is edited, applied, recorded or sent.
|
|
8
|
+
*/
|
|
9
|
+
import type {ModelAssets,RenderMode} from '@rig-c/play';
|
|
10
|
+
import {placeSetupMesh} from '../src/setup-mesh.ts';
|
|
11
|
+
import {WEIGHTS_SELECT_SLOT,dominantInfluence,meanColor,meshWeights,rampColor,weightOf,weightText,weightsModel,type MeshWeights} from '../src/weights.ts';
|
|
12
|
+
import {modelBoneNames,readSetupPose} from './setup-pose.ts';
|
|
13
|
+
import type {OverlayPoint,OverlayProject,OverlayWeights} from './rig-overlays.ts';
|
|
14
|
+
|
|
15
|
+
export type WeightsMode='bone'|'max';
|
|
16
|
+
/** Ramp stops, low → high, and the categorical bone colours of max influence: theme token names. */
|
|
17
|
+
export const weightRampTokens=['--rv-weight-0','--rv-weight-half','--rv-weight-1'] as const;
|
|
18
|
+
export const weightBoneTokens=['--rv-weight-bone-1','--rv-weight-bone-2','--rv-weight-bone-3','--rv-weight-bone-4','--rv-weight-bone-5','--rv-weight-bone-6'] as const;
|
|
19
|
+
export const WEIGHTS_SOURCE_MISMATCH='WEIGHTS_SOURCE_MISMATCH';
|
|
20
|
+
export const WEIGHTS_RIG_UNAVAILABLE='WEIGHTS_RIG_UNAVAILABLE';
|
|
21
|
+
|
|
22
|
+
/** The selected slot's mesh at the setup pose: world vertices (one per stored vertex), triangles and identity. */
|
|
23
|
+
export interface SetupMesh {slot:string;vertices:number[];triangles:number[];identity:{skin:string;attachment:string}|null}
|
|
24
|
+
export type SetupMeshResult={ok:true;mesh:SetupMesh;weights:MeshWeights}|{ok:false;code:string};
|
|
25
|
+
/**
|
|
26
|
+
* The slot's attachment is the one the setup pose resolves (`readSetupPose`); its stored vertices are placed through
|
|
27
|
+
* the setup-pose bones (`placeSetupMesh`). The stage player is not touched.
|
|
28
|
+
*/
|
|
29
|
+
export function setupPoseMesh(assets:ModelAssets,model:unknown,slot:string,root:HTMLElement,mode:RenderMode='dom'):SetupMeshResult {
|
|
30
|
+
const available=weightsModel(model);if(!available.ok)return available;
|
|
31
|
+
try{
|
|
32
|
+
const pose=readSetupPose(assets,slot,modelBoneNames(model),root,mode);
|
|
33
|
+
const result=meshWeights(model,slot,pose.identity);if(!result.ok)return result;
|
|
34
|
+
const vertices=placeSetupMesh(result.weights,pose.world);if(!vertices)return {ok:false,code:`${WEIGHTS_SOURCE_MISMATCH}: ${slot} has a non-finite setup-pose vertex`};
|
|
35
|
+
return {ok:true,weights:result.weights,mesh:{slot,vertices,triangles:result.weights.triangles,identity:pose.identity}};
|
|
36
|
+
}catch(error){return {ok:false,code:`${WEIGHTS_RIG_UNAVAILABLE}: ${error instanceof Error?error.message:String(error)}`};}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface WeightsOverlayInput {
|
|
40
|
+
weights:MeshWeights;mesh:SetupMesh;mode:WeightsMode;bone:string|null;hover:number|null;
|
|
41
|
+
/** Token values: the ramp stops low → high, the bone colours, and the overlay's own colours. */
|
|
42
|
+
ramp:readonly string[];boneColors:readonly string[];colors:OverlayWeights['colors'];
|
|
43
|
+
}
|
|
44
|
+
/** The overlay drawing: one entry per setup-pose vertex, its value and colour read from the stored weights. */
|
|
45
|
+
export function weightsOverlay(input:WeightsOverlayInput):{ok:true;overlay:OverlayWeights}|{ok:false;code:string} {
|
|
46
|
+
const {weights,mesh,mode,bone}=input,count=weights.vertices.length;
|
|
47
|
+
if(mesh.vertices.length!==count*2||mesh.triangles.length%3||mesh.triangles.some(i=>!Number.isSafeInteger(i)||i<0||i>=count))return {ok:false,code:`${WEIGHTS_SOURCE_MISMATCH}: ${mesh.slot} setup pose draws ${mesh.vertices.length/2} vertices; the model attachment stores ${count}`};
|
|
48
|
+
if(mode==='bone'&&!bone)return {ok:false,code:'WEIGHTS_SELECT_BONE: pick a bone'};
|
|
49
|
+
const vertices:OverlayWeights['vertices'][number][]=[];
|
|
50
|
+
for(let i=0;i<count;i++){
|
|
51
|
+
const influences=weights.vertices[i]!,at:OverlayPoint={x:mesh.vertices[i*2]!,y:mesh.vertices[i*2+1]!};
|
|
52
|
+
if(mode==='bone'){
|
|
53
|
+
const value=weightOf(influences,bone!),color=rampColor(input.ramp,value);if(!color)return {ok:false,code:'WEIGHTS_RAMP_UNAVAILABLE: the weight ramp tokens are not hex colours'};
|
|
54
|
+
vertices.push({at,index:i,value,color,label:`#${i} ${bone} ${weightText(value)}`});
|
|
55
|
+
}else{
|
|
56
|
+
const top=dominantInfluence(influences)!,color=input.boneColors[weights.bones.indexOf(top.bone)%input.boneColors.length];
|
|
57
|
+
if(!color)return {ok:false,code:'WEIGHTS_RAMP_UNAVAILABLE: no bone colour tokens'};
|
|
58
|
+
vertices.push({at,index:i,value:top.weight,color,label:`#${i} max ${top.bone} ${weightText(top.weight)}`});
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const triangles:OverlayWeights['triangles'][number][]=[];
|
|
62
|
+
for(let t=0;t<mesh.triangles.length;t+=3){const points=[mesh.triangles[t]!,mesh.triangles[t+1]!,mesh.triangles[t+2]!] as const;triangles.push({points,color:meanColor(points.map(i=>vertices[i]!.color))??vertices[points[0]]!.color});}
|
|
63
|
+
const hover=input.hover!==null&&input.hover>=0&&input.hover<count?input.hover:null;
|
|
64
|
+
return {ok:true,overlay:{triangles,vertices,hover,colors:input.colors}};
|
|
65
|
+
}
|
|
66
|
+
/** Nearest projected vertex within `radius` canvas px, or null. */
|
|
67
|
+
export function hitVertex(overlay:OverlayWeights,project:OverlayProject,at:OverlayPoint,radius=10):number|null {
|
|
68
|
+
let best:number|null=null,distance=radius;
|
|
69
|
+
overlay.vertices.forEach((v,i)=>{const p=project(v.at.x,v.at.y),d=Math.hypot(p.x-at.x,p.y-at.y);if(Number.isFinite(d)&&d<=distance){distance=d;best=i;}});
|
|
70
|
+
return best;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export interface WeightsViewHost {
|
|
74
|
+
pane:HTMLElement;canvas:HTMLElement;
|
|
75
|
+
project:()=>OverlayProject|null;
|
|
76
|
+
inspector:()=>string|undefined;
|
|
77
|
+
slot:()=>string|null;
|
|
78
|
+
/** The hash-verified compiled model document the stage draws (null: the package has none; undefined: still loading). */
|
|
79
|
+
model:()=>unknown;
|
|
80
|
+
/** The selected slot's weights and setup-pose mesh (null: no rig loaded). */
|
|
81
|
+
setup:(slot:string)=>SetupMeshResult|null;
|
|
82
|
+
changed:()=>void;
|
|
83
|
+
token:(name:string)=>string;
|
|
84
|
+
}
|
|
85
|
+
export interface WeightsView {overlay:()=>OverlayWeights|null;refresh:()=>void;sync:()=>void;dispose:()=>void;state:()=>{show:boolean;mode:WeightsMode;bone:string|null;code:string|null;hover:number|null}}
|
|
86
|
+
|
|
87
|
+
export function bindWeightsView(host:WeightsViewHost):WeightsView {
|
|
88
|
+
const node=<T extends HTMLElement>(id:string)=>document.getElementById(id) as T;
|
|
89
|
+
const show=node<HTMLButtonElement>('weights-show'),modeSelect=node<HTMLSelectElement>('weights-mode'),boneSelect=node<HTMLSelectElement>('weights-bone');
|
|
90
|
+
const stateText=node<HTMLElement>('weights-state'),legend=node<HTMLElement>('weights-legend'),readout=node<HTMLElement>('weights-hover');
|
|
91
|
+
let on=false,hover:number|null=null,weights:MeshWeights|null=null,mesh:SetupMesh|null=null,loadCode:string|null=null,code:string|null=null,current:OverlayWeights|null=null,loaded:{slot:string|null;model:unknown}|null=null;
|
|
92
|
+
const mode=():WeightsMode=>modeSelect.value==='max'?'max':'bone';
|
|
93
|
+
const option=(value:string,label=value)=>{const o=document.createElement('option');o.value=value;o.textContent=label;return o;};
|
|
94
|
+
const allBones=():string[]=>modelBoneNames(host.model());
|
|
95
|
+
/** Re-read the slot's weights and setup mesh when the slot or the model changed. */
|
|
96
|
+
function load():void {
|
|
97
|
+
const slot=host.slot(),model=host.model();
|
|
98
|
+
if(loaded&&loaded.slot===slot&&loaded.model===model)return;loaded={slot,model};weights=null;mesh=null;hover=null;let code:string|null=null;
|
|
99
|
+
const done=()=>{loadCode=code;fillBones();};
|
|
100
|
+
if(model===undefined){code=`${WEIGHTS_RIG_UNAVAILABLE}: the compiled model document is still loading`;loaded=null;done();return;}
|
|
101
|
+
if(model===null){const result=meshWeights(model,slot,null);code=result.ok?null:result.code;done();return;}
|
|
102
|
+
if(!slot){code=`${WEIGHTS_SELECT_SLOT}: select one mesh slot in the parts list`;done();return;}
|
|
103
|
+
const setup=host.setup(slot);
|
|
104
|
+
if(!setup){code=`${WEIGHTS_RIG_UNAVAILABLE}: no rig loaded`;loaded=null;done();return;}
|
|
105
|
+
if(!setup.ok){code=setup.code;done();return;}
|
|
106
|
+
weights=setup.weights;mesh=setup.mesh;done();
|
|
107
|
+
}
|
|
108
|
+
function fillBones():void {
|
|
109
|
+
const before=boneSelect.value,influencing=weights?.bones??[],rest=allBones().filter(b=>!influencing.includes(b));
|
|
110
|
+
boneSelect.replaceChildren();
|
|
111
|
+
if(influencing.length){const g=document.createElement('optgroup');g.label=`Influencing ${weights!.slot}`;for(const b of influencing)g.append(option(b));boneSelect.append(g);}
|
|
112
|
+
if(rest.length){const g=document.createElement('optgroup');g.label='Other bones';for(const b of rest)g.append(option(b));boneSelect.append(g);}
|
|
113
|
+
const names=[...influencing,...rest];boneSelect.value=names.includes(before)?before:(influencing[0]??names[0]??'');
|
|
114
|
+
boneSelect.disabled=!names.length;
|
|
115
|
+
}
|
|
116
|
+
function build():void {
|
|
117
|
+
current=null;code=loadCode;
|
|
118
|
+
if(!on||!weights||!mesh){render();return;}
|
|
119
|
+
const token=host.token,result=weightsOverlay({weights,mesh,mode:mode(),bone:boneSelect.value||null,hover,ramp:weightRampTokens.map(token),boneColors:weightBoneTokens.map(token),
|
|
120
|
+
colors:{veil:token('--rv-mesh-veil'),edge:token('--rv-mesh-ink'),ink:token('--rv-mesh-ink'),labelBg:token('--rv-bust-label-bg'),hover:token('--rv-mesh-hover')}});
|
|
121
|
+
if(result.ok)current=result.overlay;else code=result.code;
|
|
122
|
+
render();
|
|
123
|
+
}
|
|
124
|
+
function render():void {
|
|
125
|
+
show.setAttribute('aria-pressed',String(on));show.textContent=on?'Hide weights':'Show weights';
|
|
126
|
+
show.disabled=!weights&&!on;boneSelect.disabled=!boneSelect.options.length||mode()==='max';
|
|
127
|
+
stateText.classList.toggle('error',!!code);
|
|
128
|
+
if(code)stateText.textContent=code;
|
|
129
|
+
else if(weights)stateText.textContent=`${weights.slot} / ${weights.skin} / ${weights.attachment}${weights.source.attachment!==weights.attachment||weights.source.slot!==weights.slot?` (linked to ${weights.source.slot} / ${weights.source.attachment})`:''} · ${weights.vertices.length} vertices · ${weights.weighted?`weighted over ${weights.bones.length} bone(s)`:`unweighted: every vertex is ${weights.bones[0]} at 1.0`} · setup pose${on?'':' · Show weights to draw them'}`;
|
|
130
|
+
else stateText.textContent='';
|
|
131
|
+
legend.replaceChildren();
|
|
132
|
+
if(weights&&mode()==='bone'){
|
|
133
|
+
const bar=document.createElement('span');bar.className='weights-ramp';bar.setAttribute('aria-hidden','true');
|
|
134
|
+
const low=document.createElement('span'),high=document.createElement('span');low.textContent='0.0';high.textContent=`1.0 · ${boneSelect.value||'no bone'}`;legend.append(low,bar,high);
|
|
135
|
+
}else if(weights){
|
|
136
|
+
weights.bones.forEach((b,i)=>{const item=document.createElement('span'),dot=document.createElement('i');item.className='weights-swatch';dot.style.background=`var(${weightBoneTokens[i%weightBoneTokens.length]})`;item.append(dot,b);legend.append(item);});
|
|
137
|
+
}
|
|
138
|
+
const h=current&¤t.hover!==null?current.vertices[current.hover]:null;
|
|
139
|
+
readout.textContent=h&&weights?`#${h.index}: ${weights.vertices[h.index]!.map(i=>`${i.bone} ${weightText(i.weight)}`).join(' · ')}`:on&&weights?'Hover a vertex to read its stored weights':'';
|
|
140
|
+
}
|
|
141
|
+
const local=(event:PointerEvent)=>{const rect=host.canvas.getBoundingClientRect();return {x:event.clientX-rect.left,y:event.clientY-rect.top};};
|
|
142
|
+
const move=(event:PointerEvent)=>{
|
|
143
|
+
if(!on||!current)return;const project=host.project();if(!project)return;
|
|
144
|
+
const hit=hitVertex(current,project,local(event));if(hit===hover)return;hover=hit;build();host.changed();
|
|
145
|
+
};
|
|
146
|
+
const leave=()=>{if(hover===null)return;hover=null;build();host.changed();};
|
|
147
|
+
host.pane.addEventListener('pointermove',move);host.pane.addEventListener('pointerleave',leave);
|
|
148
|
+
const toggle=()=>{on=!on;hover=null;load();build();host.changed();};
|
|
149
|
+
const pick=()=>{hover=null;build();host.changed();};
|
|
150
|
+
show.addEventListener('click',toggle);modeSelect.addEventListener('change',pick);boneSelect.addEventListener('change',pick);
|
|
151
|
+
const visible=()=>host.inspector()==='rig';
|
|
152
|
+
return {
|
|
153
|
+
overlay:()=>on&&visible()?current:null,
|
|
154
|
+
refresh(){load();build();if(on)host.changed();},
|
|
155
|
+
sync(){if(!visible()&&hover!==null){hover=null;build();}},
|
|
156
|
+
state:()=>({show:on,mode:mode(),bone:boneSelect.value||null,code,hover}),
|
|
157
|
+
dispose(){host.pane.removeEventListener('pointermove',move);host.pane.removeEventListener('pointerleave',leave);show.removeEventListener('click',toggle);modeSelect.removeEventListener('change',pick);boneSelect.removeEventListener('change',pick);},
|
|
158
|
+
};
|
|
159
|
+
}
|