Files
planet/frontend/public/earth/js/motion-protocol.js
linkong 899e3bce43
Some checks failed
ci / backend (push) Has been cancelled
ci / frontend (push) Has been cancelled
release / images (push) Has been cancelled
ci / delivery (push) Has been cancelled
release: bump version to 0.71.0
2026-06-11 16:47:24 +08:00

98 lines
3.3 KiB
JavaScript

export const MOTION_PROVIDER_BROWSER = "browser_camera";
export const MOTION_PROVIDER_AGENT = "motion_agent";
export const DEFAULT_MOTION_PROVIDER = MOTION_PROVIDER_BROWSER;
export const MOTION_PROVIDERS = new Set([
MOTION_PROVIDER_BROWSER,
MOTION_PROVIDER_AGENT,
]);
export const MOTION_GESTURES = new Set([
"rotate_left",
"rotate_right",
"rotate_up",
"rotate_down",
"zoom_in",
"zoom_out",
"focus_prev",
"focus_next",
"layer_prev",
"layer_next",
"confirm",
]);
export function clamp01(value, fallback = 1) {
const number = Number(value);
if (!Number.isFinite(number)) return fallback;
return Math.max(0, Math.min(1, number));
}
export function normalizeMotionProvider(value, fallback = DEFAULT_MOTION_PROVIDER) {
const normalized = String(value || "").trim();
if (normalized === "browser") return MOTION_PROVIDER_BROWSER;
if (normalized === "agent") return MOTION_PROVIDER_AGENT;
return MOTION_PROVIDERS.has(normalized) ? normalized : fallback;
}
export function normalizeGestureMessage(raw, fallbackSource = "motion-provider") {
if (!raw || typeof raw !== "object") return null;
if (raw.type !== "gesture") return null;
const gesture = String(raw.gesture || "").trim();
if (!MOTION_GESTURES.has(gesture)) return null;
return {
type: "gesture",
gesture,
phase: raw.phase || "discrete",
confidence: clamp01(raw.confidence, 0),
intensity: clamp01(raw.intensity, 1),
timestampMs: Number(raw.timestamp_ms || raw.timestampMs || Date.now()),
seq: Number(raw.seq || 0),
source: raw.source || fallbackSource,
mode: raw.mode || "single",
protocolVersion: raw.protocol_version || raw.protocolVersion || "motion.v1",
cameraId: raw.camera_id || raw.cameraId || "unknown",
inputMode: raw.input_mode || raw.inputMode || raw.mode || "single",
fusion: raw.fusion && typeof raw.fusion === "object" ? raw.fusion : null,
payload: raw.payload && typeof raw.payload === "object" ? raw.payload : {},
};
}
export function normalizeSkeletonMessage(raw, fallbackSource = "motion-provider") {
if (!raw || typeof raw !== "object" || raw.type !== "skeleton") return null;
const joints = Array.isArray(raw.joints)
? raw.joints
.map((joint) => {
const id = String(joint?.id || "").trim();
const x = Number(joint?.x);
const y = Number(joint?.y);
if (!id || !Number.isFinite(x) || !Number.isFinite(y)) return null;
return {
id,
x: clamp01(x, 0),
y: clamp01(y, 0),
confidence: clamp01(joint?.confidence, 1),
};
})
.filter(Boolean)
: [];
const bones = Array.isArray(raw.bones)
? raw.bones
.map((bone) => {
if (!Array.isArray(bone) || bone.length < 2) return null;
const from = String(bone[0] || "").trim();
const to = String(bone[1] || "").trim();
return from && to ? [from, to] : null;
})
.filter(Boolean)
: [];
return {
type: "skeleton",
timestampMs: Number(raw.timestamp_ms || raw.timestampMs || Date.now()),
source: raw.source || fallbackSource,
mode: raw.mode || "single",
cameraId: raw.camera_id || raw.cameraId || "unknown",
matchedGesture: raw.matched_gesture || raw.matchedGesture || null,
confidence: clamp01(raw.confidence, 0),
joints,
bones,
};
}