react-action-debug / shift.html
yxma's picture
add mocap time-shift check: images fixed, overlay drawn from pose[frame+shift]
112f82f verified
Raw History Blame Contribute Delete
5.68 kB
<!doctype html><html lang="zh"><head><meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1">
<title>mocap shift — episode_006_seg00</title>
<style>
:root{--bg:#0e1116;--panel:#161b22;--line:#30363d;--fg:#e6edf3;--dim:#8b949e;
--L:#58a6ff;--R:#ff9d4d}
*{box-sizing:border-box} body{margin:0;background:var(--bg);color:var(--fg);
font:13px/1.5 ui-monospace,SFMono-Regular,Menlo,monospace}
header{padding:10px 14px;border-bottom:1px solid var(--line)}
h1{margin:0;font-size:15px;font-weight:600}
.sub{color:var(--dim);font-size:12px;margin-top:2px}
.sub a{color:var(--L)}
#wrap{position:relative;width:100%;max-width:960px;margin:0 auto;background:#000}
#wrap img,#wrap canvas{width:100%;display:block}
#ov{position:absolute;inset:0}
.bar{display:flex;gap:8px;align-items:center;padding:8px 14px;flex-wrap:wrap;
border-bottom:1px solid var(--line)}
button{background:var(--panel);color:var(--fg);border:1px solid var(--line);
border-radius:5px;padding:4px 10px;cursor:pointer;font:inherit}
button:hover{border-color:var(--dim)}
input[type=range]{accent-color:var(--L)}
#srange{width:260px} #frange{flex:1;min-width:200px}
.val{font-variant-numeric:tabular-nums} .tag{color:var(--dim)}
.big{font-size:17px;font-weight:600;color:var(--L);min-width:52px;display:inline-block;text-align:right}
kbd{background:var(--panel);border:1px solid var(--line);border-radius:3px;padding:0 4px}
.note{padding:8px 14px;color:var(--dim);font-size:12px;border-top:1px solid var(--line)}
</style></head><body>
<header>
<h1>mocap 时间偏移检查 · view_middle</h1>
<div class="sub">图像固定,叠加用 <b>pose[frame + shift]</b> 绘制 · <a href="index.html">← 完整面板播放器</a></div>
</header>
<div id="wrap"><img id="img" alt=""><canvas id="ov"></canvas></div>
<div class="bar">
<span class="tag">shift</span><span class="big" id="sv">0</span><span class="tag">帧</span>
<input id="srange" type="range" min="-8" max="8" step="1" value="0">
<button id="s0">归零</button>
<button id="sweep">▶ 扫描 -5…+5</button>
</div>
<div class="bar">
<span class="tag">frame</span><span class="val" id="fv">0</span>
<span class="tag">row</span><span class="val" id="rv">—</span>
<input id="frange" type="range" min="0" value="0">
<button id="prev">◀</button><button id="next">▶</button>
<button id="play">▶ 播放</button>
</div>
<div class="note">
正偏移 = 叠加取<b>更晚</b>的位姿。若 mocap 落后于相机,正偏移会让叠加贴回传感器。
蓝 = 左,橙 = 右。<kbd>←</kbd><kbd>→</kbd> 帧 · <kbd>↑</kbd><kbd>↓</kbd> shift · <kbd>空格</kbd> 播放
</div>
<script>
const $=id=>document.getElementById(id);
let D=null,i=0,sh=0,playing=false,timer=null,sweeping=null;
const pad=n=>String(n).padStart(4,'0');
fetch('data.json').then(r=>r.json()).then(d=>{D=d;$('frange').max=d.n-1;show(0);});
function pt(arr,row){const k=row-D.proj.lo_row;
return (k>=0&&k<arr.length)?arr[k]:null;}
function show(k){
i=Math.max(0,Math.min(D.n-1,k));
$('img').src='mid/m'+pad(i+1)+'.jpg';
$('frange').value=i; $('fv').textContent=i;
$('rv').textContent=D.rows[i]; draw();
}
function draw(){
const c=$('ov'),img=$('img');
const W=img.clientWidth||960, H=img.clientHeight||W*D.proj.h/D.proj.w;
c.width=W*devicePixelRatio; c.height=H*devicePixelRatio;
c.style.height=H+'px';
const g=c.getContext('2d'); g.clearRect(0,0,c.width,c.height);
const sx=c.width/D.proj.w, sy=c.height/D.proj.h;
const row=D.rows[i]+sh;
[['left','#58a6ff'],['right','#ff9d4d']].forEach(([side,col])=>{
const p=pt(D.proj[side],row); if(!p)return;
const x=p[0]*sx,y=p[1]*sy,r=13*devicePixelRatio;
g.strokeStyle=col; g.lineWidth=2.4*devicePixelRatio;
g.beginPath(); g.arc(x,y,r,0,7); g.stroke();
g.beginPath(); g.moveTo(x-r*1.5,y); g.lineTo(x+r*1.5,y);
g.moveTo(x,y-r*1.5); g.lineTo(x,y+r*1.5); g.stroke();
});
// shift != 0 时把原位置画成虚线,便于对比
if(sh!==0){ g.setLineDash([5*devicePixelRatio,4*devicePixelRatio]);
[['left','#58a6ff'],['right','#ff9d4d']].forEach(([side,col])=>{
const p=pt(D.proj[side],D.rows[i]); if(!p)return;
g.strokeStyle=col+'70'; g.lineWidth=1.6*devicePixelRatio;
g.beginPath(); g.arc(p[0]*sx,p[1]*sy,13*devicePixelRatio,0,7); g.stroke();});
g.setLineDash([]); }
}
$('srange').oninput=e=>{sh=+e.target.value;$('sv').textContent=(sh>0?'+':'')+sh;draw();};
$('s0').onclick=()=>{sh=0;$('srange').value=0;$('sv').textContent='0';draw();};
$('sweep').onclick=()=>{
if(sweeping){clearInterval(sweeping);sweeping=null;$('sweep').textContent='▶ 扫描 -5…+5';return;}
let v=-5; $('sweep').textContent='⏸ 停止扫描';
sweeping=setInterval(()=>{sh=v;$('srange').value=v;$('sv').textContent=(v>0?'+':'')+v;draw();
v++; if(v>5)v=-5;},700);};
$('frange').oninput=e=>show(+e.target.value);
$('prev').onclick=()=>show(i-1); $('next').onclick=()=>show(i+1);
$('play').onclick=()=>{playing=!playing;$('play').textContent=playing?'⏸':'▶ 播放';
if(playing)timer=setInterval(()=>show(i>=D.n-1?0:i+1),1000/30); else clearInterval(timer);};
addEventListener('keydown',e=>{
if(e.key==='ArrowLeft'){show(i-1);e.preventDefault();}
if(e.key==='ArrowRight'){show(i+1);e.preventDefault();}
if(e.key==='ArrowUp'){$('srange').value=Math.min(8,sh+1);$('srange').oninput({target:$('srange')});e.preventDefault();}
if(e.key==='ArrowDown'){$('srange').value=Math.max(-8,sh-1);$('srange').oninput({target:$('srange')});e.preventDefault();}
if(e.code==='Space'){$('play').click();e.preventDefault();}});
$('img').onload=draw; addEventListener('resize',draw);
</script></body></html>