This commit is contained in:
github-actions[bot] 2026-04-29 23:51:31 +00:00
parent 2437b75b5f
commit 06fe436f07
1 changed files with 200 additions and 75 deletions

View File

@ -2,6 +2,9 @@
<html>
<head>
<title>RuView — Camera + WiFi CSI Point Cloud</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta http-equiv="Cache-Control" content="no-cache, no-store, must-revalidate">
<meta name="ruview-viewer-version" content="0.2.0-face-mesh">
<style>
body { margin: 0; background: #0a0a0a; color: #e8a634; font-family: monospace; }
canvas { display: block; }
@ -22,15 +25,16 @@
</head>
<body>
<div id="info">
<h3 style="margin:0 0 8px 0">RuView Point Cloud</h3>
<h3 style="margin:0 0 4px 0">RuView · Seldon Vault</h3>
<div style="font-size: 11px; color: #888; margin-bottom: 8px; max-width: 240px; line-height: 1.4; font-style: italic;">"Psychohistory deals with reactions of human conglomerates to fixed social and economic stimuli." — Hari Seldon</div>
<div id="stats">Loading...</div>
</div>
<button id="cam-cta">Enable camera — render your face as a point cloud</button>
<button id="cam-cta">Project Subject — render your face into the Vault</button>
<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, 0.5, -1.5);
var camera = new THREE.PerspectiveCamera(72, window.innerWidth/window.innerHeight, 0.1, 200);
camera.position.set(0, 0.2, -3.5);
camera.lookAt(0, 0, 2);
var renderer = new THREE.WebGLRenderer({ antialias: true });
@ -179,49 +183,185 @@
}
}
// ---- Foundation-inspired galactic context (Asimov / Trantor / Seldon) ----
// Shared between face-mesh and synthetic-fallback paths. The subject (face
// or procedural figure) is the foreground; this function paints the Seldon
// time-vault around it: holographic surveyor grid underfoot, slow galactic
// spiral receding into the distance, distant starfield, and a halo ring.
function pushFoundationContext(splats) {
var t = (Date.now() - demoStartMs) / 1000.0;
// 1. Holographic surveyor grid — amber lattice at y=+1.4 (renders below
// the subject because the renderer flips y to Three.js Y-up).
var gx, gz;
for (gx = -10; gx <= 10; gx++) {
for (gz = 0; gz <= 30; gz++) {
var alpha = 0.35 + 0.15 * Math.sin(t * 0.5 + gz * 0.2);
splats.push({
center: [gx * 0.5, 1.4, gz * 0.4],
color: [0.40 * alpha, 0.28 * alpha, 0.10 * alpha],
opacity: 1.0,
scale: [0.018, 0.018, 0.018]
});
}
}
for (gz = 0; gz <= 30; gz += 2) {
for (gx = -20; gx <= 20; gx++) {
splats.push({
center: [gx * 0.25, 1.4, gz * 0.4 + 0.1],
color: [0.30, 0.22, 0.08],
opacity: 1.0,
scale: [0.014, 0.014, 0.014]
});
}
}
// 2. Galactic spiral — Trantor recedes behind the subject. ~640 stars
// across two logarithmic arms, slowly rotating. Warmer at the core,
// cooler at the edges (Hertzsprung-Russell-ish).
var arm, k, theta_arm, r_arm, sx, sz, twist, arm_color;
for (arm = 0; arm < 2; arm++) {
for (k = 0; k < 320; k++) {
var prog = k / 320;
theta_arm = arm * Math.PI + prog * 6.0 + t * 0.05;
r_arm = 4.0 + prog * 14.0;
twist = Math.sin(prog * 8.0) * 0.4;
sx = Math.cos(theta_arm) * r_arm + twist;
sz = Math.sin(theta_arm) * r_arm + 12.0;
var coreFade = Math.max(0.15, 1.0 - prog);
arm_color = [
coreFade * 0.85 + 0.15 * (1 - prog),
coreFade * 0.70 + 0.20,
coreFade * 0.55 + 0.45 * prog
];
splats.push({
center: [sx, -2.5 + Math.sin(prog * 12) * 0.3, sz],
color: arm_color,
opacity: 1.0,
scale: [0.025, 0.025, 0.025]
});
}
}
// 3. Distant starfield — 800 deterministic stars on a spherical shell.
// Fixed LCG seed so visitors don't see noise flicker between frames.
var seed = 42;
function nextRand() {
seed = (seed * 1664525 + 1013904223) >>> 0;
return seed / 4294967296;
}
var s, r_s, phi, costheta, sinphi;
for (s = 0; s < 800; s++) {
phi = nextRand() * Math.PI * 2;
costheta = nextRand() * 2 - 1;
sinphi = Math.sqrt(1 - costheta * costheta);
r_s = 22 + nextRand() * 8;
var brightness = 0.4 + nextRand() * 0.6;
var hue = nextRand();
splats.push({
center: [
Math.cos(phi) * sinphi * r_s,
costheta * r_s * 0.5 - 1.0,
Math.sin(phi) * sinphi * r_s + 5.0
],
color: hue > 0.85
? [brightness, brightness * 0.85, brightness * 0.6]
: (hue > 0.3
? [brightness * 0.9, brightness * 0.95, brightness]
: [brightness * 0.5, brightness * 0.7, brightness]),
opacity: 1.0,
scale: [0.020, 0.020, 0.020]
});
}
// 4. Holographic projection halo around the subject — Seldon vault
// projections always had a faint encircling ring of particles.
var ring;
for (ring = 0; ring < 60; ring++) {
var rt = ring / 60 * Math.PI * 2 + t * 0.3;
splats.push({
center: [
Math.cos(rt) * 1.6,
Math.sin(rt) * 1.2 - 0.2,
2.0 + Math.sin(rt * 3 + t * 0.5) * 0.3
],
color: [0.95, 0.55, 0.15],
opacity: 1.0,
scale: [0.014, 0.014, 0.014]
});
}
}
// Map a single landmark to world coords. Coord conventions:
// x: 0.5 - lm.x → mirror so left-of-screen = your left side (selfie)
// y: lm.y - 0.5 → keep MediaPipe's y-DOWN convention; the renderer's
// existing -y flip in updateSplats does the single flip
// to Three.js Y-up. Pre-flipping here would double-flip
// and the face would render upside down.
// z: 2.0 + lm.z*8 → amplify lm.z (~[-0.1,+0.1]) so the nose/eye-socket
// depth is visible from an oblique camera angle.
function lmToCenter(lm) {
return [
(0.5 - lm.x) * 4.0,
(lm.y - 0.5) * 3.0,
2.0 + lm.z * 8.0
];
}
function pushFaceSplat(splats, center, alpha) {
splats.push({
center: center,
color: [0.95 * alpha, 0.65 * alpha, 0.20 * alpha],
opacity: 1.0,
scale: [0.006, 0.006, 0.006]
});
}
// FACEMESH_TESSELATION is a flat array [a0,b0, a1,b1, ...] of vertex indices
// forming edges of the triangulated face mesh. ~1300 edges × 2 = 2600 entries.
// We interpolate 6 splats per edge → ~8000 splats per face vs 478 vertices.
var FACE_EDGES = (typeof FACEMESH_TESSELATION !== "undefined") ? FACEMESH_TESSELATION : null;
function faceMeshFrame() {
if (faceMeshState !== "running" || !latestFaceLandmarks) return null;
var lms = latestFaceLandmarks;
var splats = [];
var i, lm, x, y, z;
// 468 (or 478 with refined landmarks) face points → splats. MediaPipe's
// selfie convention has x mirrored; we mirror back so left-of-screen = your
// left side. z is depth-relative-to-face-center, ~[-0.1,+0.1] in practice.
var i, lm;
// 1. Original 478 vertices — bright, slightly larger to anchor features
for (i = 0; i < lms.length; i++) {
lm = lms[i];
x = (0.5 - lm.x) * 4.0;
y = (0.5 - lm.y) * 3.0;
z = 2.0 + lm.z * 4.0;
splats.push({
center: [x, y, z],
color: [0.95, 0.65, 0.20],
center: lmToCenter(lms[i]),
color: [1.0, 0.72, 0.25],
opacity: 1.0,
scale: [0.012, 0.012, 0.012]
scale: [0.010, 0.010, 0.010]
});
}
// Procedural floor + back wall for spatial context — same density as the
// local demo's room scaffold.
var gx, gz;
for (gx = -4; gx <= 4; gx++) {
for (gz = 1; gz <= 8; gz++) {
splats.push({
center: [gx * 0.4, -1.4, gz * 0.4],
color: [0.15, 0.18, 0.22],
opacity: 1.0,
scale: [0.05, 0.05, 0.05]
});
}
}
for (gx = -4; gx <= 4; gx += 2) {
for (var wy = -1; wy <= 2; wy++) {
splats.push({
center: [gx * 0.4, wy * 0.5, 4.0],
color: [0.12, 0.20, 0.28],
opacity: 1.0,
scale: [0.05, 0.05, 0.05]
});
// 2. Edge interpolation — 6 splats per FACEMESH_TESSELATION edge
if (FACE_EDGES) {
var edgeCount = FACE_EDGES.length;
var SAMPLES = 6;
var e, a, b, t, f, ax, ay, az, bx, by, bz, cx, cy, cz;
for (e = 0; e < edgeCount; e += 2) {
a = lms[FACE_EDGES[e]];
b = lms[FACE_EDGES[e + 1]];
if (!a || !b) continue;
var aPos = lmToCenter(a);
var bPos = lmToCenter(b);
ax = aPos[0]; ay = aPos[1]; az = aPos[2];
bx = bPos[0]; by = bPos[1]; bz = bPos[2];
for (t = 1; t <= SAMPLES; t++) {
f = t / (SAMPLES + 1);
cx = ax * (1 - f) + bx * f;
cy = ay * (1 - f) + by * f;
cz = az * (1 - f) + bz * f;
pushFaceSplat(splats, [cx, cy, cz], 0.85);
}
}
}
pushFoundationContext(splats);
demoFrameNum += 1;
return {
splats: splats,
@ -245,53 +385,35 @@
}
function syntheticFrame() {
// Deterministic synthetic point cloud: floor grid, two walls, and
// a standing figure that breathes/sways. Resembles the live API
// payload so the same render path drives both modes.
// Used when camera permission is denied / unavailable. Renders a
// procedural standing figure inside the Seldon vault context.
// y-down convention: head at small/negative y, feet at large/positive y;
// the renderer flips y so head ends up at the top of the screen.
var t = (Date.now() - demoStartMs) / 1000.0;
var sway = Math.sin(t * 0.8) * 0.05;
var breath = Math.sin(t * 1.2) * 0.015;
var splats = [];
// Floor — 12x12 grid at y=-1
var gx, gz;
for (gx = -6; gx <= 6; gx++) {
for (gz = 0; gz <= 12; gz++) {
// Standing figure — 240 points in a vertical cylinder, denser than
// before to feel like a holographic projection.
var ring, k_ring, theta, r, py;
for (ring = 0; ring < 30; ring++) {
py = -1.0 + (ring / 30) * 2.2; // head (-1.0) → feet (+1.2) in y-down
r = 0.20 + breath * (py < 0 ? 1.5 : 0); // chest expands more on inhale
for (k_ring = 0; k_ring < 16; k_ring++) {
theta = (k_ring / 16) * Math.PI * 2;
splats.push({
center: [gx * 0.4, -1.0, gz * 0.4],
color: [0.15, 0.18, 0.22],
center: [
sway + Math.cos(theta) * r,
py,
2.3 + Math.sin(theta) * r
],
color: [0.91, 0.65, 0.20],
opacity: 1.0,
scale: [0.05, 0.05, 0.05]
scale: [0.018, 0.018, 0.018]
});
}
}
// Back wall + side walls — sparse vertical strips
var wy, wx;
for (wy = -1; wy <= 2; wy++) {
for (wx = -6; wx <= 6; wx += 2) {
splats.push({
center: [wx * 0.4, wy * 0.5, 4.8],
color: [0.12, 0.20, 0.28],
opacity: 1.0,
scale: [0.05, 0.05, 0.05]
});
}
splats.push({ center: [-2.4, wy * 0.5, 0.5 + (wy + 1) * 0.8], color: [0.12, 0.20, 0.28], opacity: 1.0, scale: [0.05, 0.05, 0.05] });
splats.push({ center: [ 2.4, wy * 0.5, 0.5 + (wy + 1) * 0.8], color: [0.12, 0.20, 0.28], opacity: 1.0, scale: [0.05, 0.05, 0.05] });
}
// Standing figure — 60 points in a vertical cylinder
var i, theta, r, py;
for (i = 0; i < 60; i++) {
theta = (i / 60) * Math.PI * 2;
py = -0.6 + (i / 60) * 1.6;
r = 0.18 + breath * (py > 0 ? 1 : 0);
splats.push({
center: [sway + Math.cos(theta) * r, py, 2.3 + Math.sin(theta) * r],
color: [0.91, 0.65, 0.20],
opacity: 1.0,
scale: [0.04, 0.04, 0.04]
});
}
// 17 COCO keypoints in normalized [0,1] image coords (matches live shape)
var headY = 0.18;
@ -315,6 +437,9 @@
[0.41, 0.92, 0.88] // 16 rightAnkle
];
// Wrap the figure in the Seldon-vault context (grid, spiral, starfield, halo)
pushFoundationContext(splats);
demoFrameNum += 1;
return {
splats: splats,
@ -459,13 +584,13 @@
return;
}
btn.addEventListener("click", function() {
btn.textContent = "Starting camera…";
btn.textContent = "Initializing the Vault…";
startFaceMesh();
});
})();
fetchCloud();
setInterval(fetchCloud, 250); // 4 Hz — enough for face mesh, light on the network
setInterval(fetchCloud, 100); // 10 Hz — denser updates so face mesh feels live and the spiral animates smoothly
function updateSplats(splats) {
if (pointsMesh) scene.remove(pointsMesh);