mirror of
https://github.com/ruvnet/RuView
synced 2026-07-31 18:51:42 +00:00
f49c722764
The Rust port lived two directories deep (rust-port/wifi-densepose-rs/) without any sibling under rust-port/ that warranted the extra level. Move the whole workspace up to v2/ to match v1/ (Python) at the same depth and shorten every cd / build command across the repo. git mv preserves history for all tracked files. 60 files updated for path references (CI workflows, ADRs, docs, scripts, READMEs, internal .claude-flow state). Two manual fixes for relative-cd paths in CLAUDE.md and ADR-043 that became wrong after the depth change (cd ../.. → cd ..). Validated: - cargo check --workspace --no-default-features → clean (after target/ nuke; the gitignored target/ was carried by the OS rename and had hard-coded old paths in build scripts) - cargo test --workspace --no-default-features → 1,539 passed, 0 failed, 8 ignored (same totals as pre-rename) - ESP32-S3 on COM7 → still streaming live CSI (cb #40300, RSSI -64 dBm) After-merge follow-up: contributors should `rm -rf v2/target` once and let cargo regenerate from the new path.
230 lines
9.5 KiB
HTML
230 lines
9.5 KiB
HTML
<!DOCTYPE html>
|
|
<html>
|
|
<head>
|
|
<title>RuView — Camera + WiFi CSI Point Cloud</title>
|
|
<style>
|
|
body { margin: 0; background: #0a0a0a; color: #e8a634; font-family: monospace; }
|
|
canvas { display: block; }
|
|
#info { position: absolute; top: 10px; left: 10px; padding: 12px; background: rgba(0,0,0,0.85); border: 1px solid #e8a634; border-radius: 6px; min-width: 240px; font-size: 13px; line-height: 1.5; }
|
|
.live { color: #4f4; } .demo { color: #f44; }
|
|
.section { margin-top: 6px; padding-top: 6px; border-top: 1px solid #333; }
|
|
.label { color: #888; }
|
|
</style>
|
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
|
|
<script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/OrbitControls.js"></script>
|
|
</head>
|
|
<body>
|
|
<div id="info">
|
|
<h3 style="margin:0 0 8px 0">RuView Point Cloud</h3>
|
|
<div id="stats">Loading...</div>
|
|
</div>
|
|
<script>
|
|
var scene = new THREE.Scene();
|
|
scene.background = new THREE.Color(0x0a0a0a);
|
|
var camera = new THREE.PerspectiveCamera(75, window.innerWidth/window.innerHeight, 0.1, 100);
|
|
camera.position.set(0, 2, -4);
|
|
camera.lookAt(0, 0, 2);
|
|
|
|
var renderer = new THREE.WebGLRenderer({ antialias: true });
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
document.body.appendChild(renderer.domElement);
|
|
|
|
var controls = new THREE.OrbitControls(camera, renderer.domElement);
|
|
controls.enableDamping = true;
|
|
controls.target.set(0, 0, 2);
|
|
|
|
var pointsMesh = null;
|
|
var lastFrame = -1;
|
|
var skeletonGroup = null;
|
|
var prevTimestamp = 0;
|
|
var frameRateVal = 0;
|
|
|
|
// COCO skeleton connections: pairs of keypoint indices
|
|
// 0=nose 1=leftEye 2=rightEye 3=leftEar 4=rightEar
|
|
// 5=leftShoulder 6=rightShoulder 7=leftElbow 8=rightElbow
|
|
// 9=leftWrist 10=rightWrist 11=leftHip 12=rightHip
|
|
// 13=leftKnee 14=rightKnee 15=leftAnkle 16=rightAnkle
|
|
var COCO_BONES = [
|
|
[0,1],[0,2],[1,3],[2,4],
|
|
[5,6],[5,7],[7,9],[6,8],[8,10],
|
|
[5,11],[6,12],[11,12],
|
|
[11,13],[13,15],[12,14],[14,16]
|
|
];
|
|
|
|
function clearSkeleton() {
|
|
if (skeletonGroup) {
|
|
scene.remove(skeletonGroup);
|
|
skeletonGroup.traverse(function(obj) {
|
|
if (obj.geometry) obj.geometry.dispose();
|
|
if (obj.material) obj.material.dispose();
|
|
});
|
|
skeletonGroup = null;
|
|
}
|
|
}
|
|
|
|
function drawSkeleton(keypoints) {
|
|
clearSkeleton();
|
|
if (!keypoints || keypoints.length < 17) return;
|
|
skeletonGroup = new THREE.Group();
|
|
|
|
// Map keypoints from [0,1] to scene coords
|
|
// x: [-2, 2], y: [2, -2] (flip y), z: fixed at 2
|
|
var sphereGeo = new THREE.SphereGeometry(0.04, 8, 8);
|
|
var sphereMat = new THREE.MeshBasicMaterial({ color: 0xffff00 });
|
|
var positions3D = [];
|
|
var i, kp, sx, sy;
|
|
for (i = 0; i < 17; i++) {
|
|
kp = keypoints[i];
|
|
if (!kp) { positions3D.push(null); continue; }
|
|
sx = (kp[0] - 0.5) * 4;
|
|
sy = (0.5 - kp[1]) * 4;
|
|
positions3D.push([sx, sy, 2]);
|
|
var sphere = new THREE.Mesh(sphereGeo, sphereMat);
|
|
sphere.position.set(sx, sy, 2);
|
|
skeletonGroup.add(sphere);
|
|
}
|
|
|
|
// Draw bones as white lines
|
|
var lineMat = new THREE.LineBasicMaterial({ color: 0xffffff, linewidth: 2 });
|
|
var b, a, bIdx;
|
|
for (b = 0; b < COCO_BONES.length; b++) {
|
|
a = COCO_BONES[b][0];
|
|
bIdx = COCO_BONES[b][1];
|
|
if (!positions3D[a] || !positions3D[bIdx]) continue;
|
|
var lineGeo = new THREE.BufferGeometry();
|
|
var verts = new Float32Array([
|
|
positions3D[a][0], positions3D[a][1], positions3D[a][2],
|
|
positions3D[bIdx][0], positions3D[bIdx][1], positions3D[bIdx][2]
|
|
]);
|
|
lineGeo.setAttribute("position", new THREE.BufferAttribute(verts, 3));
|
|
var line = new THREE.Line(lineGeo, lineMat);
|
|
skeletonGroup.add(line);
|
|
}
|
|
|
|
scene.add(skeletonGroup);
|
|
}
|
|
|
|
async function fetchCloud() {
|
|
try {
|
|
var resp = await fetch("/api/splats");
|
|
var data = await resp.json();
|
|
if (data.splats && data.frame !== lastFrame) {
|
|
// Compute CSI frame rate
|
|
var now = Date.now();
|
|
if (prevTimestamp > 0) {
|
|
var dt = (now - prevTimestamp) / 1000.0;
|
|
if (dt > 0) frameRateVal = (1.0 / dt).toFixed(1);
|
|
}
|
|
prevTimestamp = now;
|
|
lastFrame = data.frame;
|
|
updateSplats(data.splats);
|
|
|
|
// Draw skeleton if available
|
|
var pipe = data.pipeline;
|
|
if (pipe && pipe.skeleton && pipe.skeleton.keypoints) {
|
|
drawSkeleton(pipe.skeleton.keypoints);
|
|
} else {
|
|
clearSkeleton();
|
|
}
|
|
|
|
// Build info panel
|
|
var mode = data.live
|
|
? '<span class="live">● LIVE</span>'
|
|
: '<span class="demo">● DEMO</span>';
|
|
var html = mode + " Camera + CSI<br>"
|
|
+ "Splats: " + data.count + "<br>"
|
|
+ "Frame: " + data.frame;
|
|
|
|
// CSI frame rate
|
|
html += '<div class="section">'
|
|
+ '<span class="label">CSI Rate:</span> '
|
|
+ frameRateVal + " fps</div>";
|
|
|
|
// Skeleton confidence
|
|
if (pipe && pipe.skeleton && pipe.skeleton.confidence !== undefined) {
|
|
var conf = (pipe.skeleton.confidence * 100).toFixed(0);
|
|
html += '<div class="section">'
|
|
+ '<span class="label">Skeleton:</span> '
|
|
+ conf + "% confidence</div>";
|
|
}
|
|
|
|
// Weather data
|
|
if (pipe && pipe.weather) {
|
|
var w = pipe.weather;
|
|
html += '<div class="section">'
|
|
+ '<span class="label">Weather:</span> ';
|
|
if (w.temperature !== undefined) {
|
|
html += w.temperature + "°C";
|
|
}
|
|
if (w.conditions) {
|
|
html += " " + w.conditions;
|
|
}
|
|
html += "</div>";
|
|
}
|
|
|
|
// Building count from geo
|
|
if (pipe && pipe.geo && pipe.geo.building_count !== undefined) {
|
|
html += '<div class="section">'
|
|
+ '<span class="label">Buildings:</span> '
|
|
+ pipe.geo.building_count + "</div>";
|
|
}
|
|
|
|
// Vitals
|
|
if (pipe && pipe.vitals) {
|
|
var v = pipe.vitals;
|
|
html += '<div class="section">'
|
|
+ '<span class="label">Vitals:</span> ';
|
|
if (v.breathing_rate !== undefined) {
|
|
html += "BR " + v.breathing_rate + "/min";
|
|
}
|
|
if (v.motion_score !== undefined) {
|
|
html += " Motion " + (v.motion_score * 100).toFixed(0) + "%";
|
|
}
|
|
html += "</div>";
|
|
}
|
|
|
|
document.getElementById("stats").innerHTML = html;
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
fetchCloud();
|
|
setInterval(fetchCloud, 500);
|
|
|
|
function updateSplats(splats) {
|
|
if (pointsMesh) scene.remove(pointsMesh);
|
|
var geometry = new THREE.BufferGeometry();
|
|
var positions = new Float32Array(splats.length * 3);
|
|
var colors = new Float32Array(splats.length * 3);
|
|
var i, s;
|
|
for (i = 0; i < splats.length; i++) {
|
|
s = splats[i];
|
|
positions[i*3] = s.center[0];
|
|
positions[i*3+1] = -s.center[1];
|
|
positions[i*3+2] = s.center[2];
|
|
colors[i*3] = s.color[0];
|
|
colors[i*3+1] = s.color[1];
|
|
colors[i*3+2] = s.color[2];
|
|
}
|
|
geometry.setAttribute("position", new THREE.BufferAttribute(positions, 3));
|
|
geometry.setAttribute("color", new THREE.BufferAttribute(colors, 3));
|
|
pointsMesh = new THREE.Points(geometry, new THREE.PointsMaterial({
|
|
size: 0.02, vertexColors: true, sizeAttenuation: true
|
|
}));
|
|
scene.add(pointsMesh);
|
|
}
|
|
|
|
function animate() {
|
|
requestAnimationFrame(animate);
|
|
controls.update();
|
|
renderer.render(scene, camera);
|
|
}
|
|
animate();
|
|
window.addEventListener("resize", function() {
|
|
camera.aspect = window.innerWidth / window.innerHeight;
|
|
camera.updateProjectionMatrix();
|
|
renderer.setSize(window.innerWidth, window.innerHeight);
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|