Add linkage annotation overlay toggle and joint labels

This commit is contained in:
2026-02-12 13:51:47 +02:00
parent f9d1b4f530
commit 855f596cfe
5 changed files with 63 additions and 2 deletions

View File

@@ -13,6 +13,7 @@
- body = red
- Play/pause, reset, speed slider, angle scrubber.
- Foot trajectory traces for both sides.
- Optional linkage annotation overlay (joint and link labels).
- Diagnostics panel with closure error and fallback counters.
## File layout
@@ -47,4 +48,3 @@ Then open:
- No collision/ground dynamics and no body translation.
- Side B is driven by `theta + pi`.
- Lower branch of each circle-circle intersection is selected to maintain leg-down posture.

View File

@@ -47,6 +47,11 @@
<span>Show foot traces</span>
</label>
<label class="check">
<input id="show-annotations" type="checkbox" />
<span>Overlay linkage annotations</span>
</label>
<section class="diag">
<h2>Diagnostics</h2>
<pre id="diag">loading…</pre>
@@ -57,4 +62,3 @@
<script type="module" src="./src/main.js"></script>
</body>
</html>

View File

@@ -14,6 +14,7 @@ const state = {
showNear: true,
showFar: true,
showTrace: true,
showAnnotations: false,
fallbackCount: 0,
pose: null,
ground: {

View File

@@ -160,6 +160,52 @@ function drawHUD(ctx, data) {
ctx.restore();
}
function drawLabel(ctx, p, text, dx = 0, dy = 0) {
ctx.save();
ctx.font = '12px ui-monospace, SFMono-Regular, Menlo, monospace';
ctx.textAlign = 'center';
ctx.textBaseline = 'middle';
const x = p.x + dx;
const y = p.y + dy;
const padX = 4;
const padY = 2;
const metrics = ctx.measureText(text);
const w = metrics.width + padX * 2;
const h = 14 + padY * 2;
ctx.fillStyle = 'rgba(10, 12, 18, 0.72)';
ctx.fillRect(x - w * 0.5, y - h * 0.5, w, h);
ctx.fillStyle = '#f4f7ff';
ctx.fillText(text, x, y + 0.5);
ctx.restore();
}
function drawAnnotations(ctx, tf, side) {
if (!side) return;
const A = tf.toScreen(side.A);
const B = tf.toScreen(side.B);
const O = tf.toScreen(side.O);
const C = tf.toScreen(side.C);
const D = tf.toScreen(side.D);
const E = tf.toScreen(side.E);
const F = tf.toScreen(side.F);
const G = tf.toScreen(side.G);
// Joint labels
drawLabel(ctx, A, 'A', -12, -10);
drawLabel(ctx, B, 'B', 12, -10);
drawLabel(ctx, O, 'O', 0, -14);
drawLabel(ctx, C, 'C', 0, 14);
drawLabel(ctx, D, 'D', -12, 10);
drawLabel(ctx, E, 'E', 12, 10);
drawLabel(ctx, F, 'F', -12, 12);
drawLabel(ctx, G, 'G', 12, 12);
}
export function renderScene(canvas, state) {
const ctx = canvas.getContext('2d');
ctx.fillStyle = COLORS.bg;
@@ -208,5 +254,9 @@ export function renderScene(canvas, state) {
drawSide(ctx, tf, state.pose.near, 1, 2.6);
}
if (state.showAnnotations && state.showNear) {
drawAnnotations(ctx, tf, state.pose.near);
}
drawHUD(ctx, state.pose);
}

View File

@@ -10,6 +10,7 @@ export function wireUI(state, hooks) {
const showNear = document.getElementById('show-near');
const showFar = document.getElementById('show-far');
const showTrace = document.getElementById('show-trace');
const showAnnotations = document.getElementById('show-annotations');
speed.value = String(state.speedRps);
speedOut.value = Number(state.speedRps).toFixed(2);
@@ -20,6 +21,7 @@ export function wireUI(state, hooks) {
showNear.checked = state.showNear;
showFar.checked = state.showFar;
showTrace.checked = state.showTrace;
showAnnotations.checked = state.showAnnotations;
speed.addEventListener('input', () => {
const v = Number(speed.value);
@@ -55,6 +57,10 @@ export function wireUI(state, hooks) {
state.showTrace = showTrace.checked;
});
showAnnotations.addEventListener('change', () => {
state.showAnnotations = showAnnotations.checked;
});
return {
syncAngle(theta) {
const deg = radToDeg(theta);