deploy(pointcloud): 5d7fccce79 5d7fccce79
This commit is contained in:
parent
2437b75b5f
commit
06fe436f07
|
|
@ -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);
|
||||
|
|
|
|||
Loading…
Reference in New Issue