organs.js: Eyes at full detail, the evidence astrolabe
Black-glass biconvex lens with a cyan caustic edge, blue-metal aperture ring with graduations, six log-spiral aperture petals that narrow as the stop nears (unknowns widen, confidence steadies), a proof lattice of meridians and latitudes, two astrolabe gimbals, a pale scan arc, and cyan evidence sparks that appear only as evidence arrives. After Aquarium's Eyes entry in epiphany-agent-sdf-visual-language.md, palette as-is. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
f311698f25
commit
e83f4fa869
1 changed files with 83 additions and 15 deletions
98
prototype/organs.js
vendored
98
prototype/organs.js
vendored
|
|
@ -207,7 +207,88 @@ export function life() {
|
|||
};
|
||||
}
|
||||
|
||||
// ---------- Epiphany-only organs (Face, Eyes, Body): lower detail, they live in her chamber ----------
|
||||
// Eyes: evidence astrolabe. A black-glass lens on its +z axis, held in blue-metal gimbals,
|
||||
// behind six spiral aperture petals; a proof lattice of meridians and latitudes over the lens
|
||||
// hemisphere, a pale scan arc, and cyan evidence sparks that appear only as evidence arrives (k).
|
||||
// Unknowns widen the aperture; confidence narrows and steadies it.
|
||||
export function eyes() {
|
||||
const group = new THREE.Group();
|
||||
const lensGeo = lathe([[0, 0.34], [0.3, 0.3], [0.55, 0.18], [0.7, 0], [0.55, -0.18], [0.3, -0.3], [0, -0.34]], 12);
|
||||
lensGeo.rotateX(Math.PI / 2);
|
||||
const lens = new THREE.Mesh(lensGeo, ps1({ color: 0x07090c, rim: 0x5ff2ff }));
|
||||
const caustic = new THREE.Mesh(new THREE.TorusGeometry(0.71, 0.035, 3, 20), ps1({ color: 0x5ff2ff, emissive: true }));
|
||||
const metal = ps1({ color: 0x4a6a8f, rim: 0x9fc8ff });
|
||||
const ring = new THREE.Mesh(new THREE.TorusGeometry(0.86, 0.09, 4, 20), metal);
|
||||
group.add(lens, caustic, ring);
|
||||
const ticks = [];
|
||||
for (let i = 0; i < 12; i++) { // graduations on the aperture ring
|
||||
const a = i / 12 * Math.PI * 2, r = 0.86, l = i % 3 ? 0.08 : 0.16;
|
||||
ticks.push(V(Math.cos(a) * r, Math.sin(a) * r, 0.1), V(Math.cos(a) * (r + l), Math.sin(a) * (r + l), 0.1));
|
||||
}
|
||||
ring.add(segs(ticks, 0xcfe2f0));
|
||||
|
||||
// Aperture petals: log-spiral shutters pivoting on the rim, in the lens's tangent disk.
|
||||
const petalMat = ps1({ color: 0x3a5576, rim: 0x9fc8ff, side: THREE.DoubleSide });
|
||||
const petals = [...Array(6)].map((_, i) => {
|
||||
const a0 = i / 6 * Math.PI * 2, pivot = V(Math.cos(a0) * 0.8, Math.sin(a0) * 0.8, 0.36);
|
||||
const pos = [], idx = [], N = 6;
|
||||
for (let k = 0; k <= N; k++) {
|
||||
const s = k / N, rho = 0.8 * Math.exp(-1.3 * s), th = a0 + 1.2 * s, w = 0.17 * (1 - 0.7 * s) + 0.02;
|
||||
const c = V(Math.cos(th) * rho, Math.sin(th) * rho, 0), side = V(Math.cos(th), Math.sin(th), 0);
|
||||
const pa = c.clone().addScaledVector(side, w).sub(pivot), pb = c.clone().addScaledVector(side, -w).sub(pivot);
|
||||
pos.push(pa.x, pa.y, 0, pb.x, pb.y, 0);
|
||||
if (k < N) { const b = k * 2; idx.push(b, b + 1, b + 2, b + 1, b + 3, b + 2); }
|
||||
}
|
||||
const geo = new THREE.BufferGeometry(); geo.setAttribute('position', new THREE.Float32BufferAttribute(pos, 3)); geo.setIndex(idx); geo.computeVertexNormals();
|
||||
const holder = new THREE.Group(); holder.position.copy(pivot); holder.add(new THREE.Mesh(geo, petalMat)); group.add(holder);
|
||||
// Opening swings the inner tip away from the axis; pick the rotation sign that does that.
|
||||
const tip = V((pos[N * 6] + pos[N * 6 + 3]) / 2, (pos[N * 6 + 1] + pos[N * 6 + 4]) / 2, 0);
|
||||
const out = (sgn) => tip.clone().applyAxisAngle(V(0, 0, 1), sgn * 0.3).add(pivot).setZ(0).length();
|
||||
holder.userData.sign = out(1) > out(-1) ? 1 : -1;
|
||||
return holder;
|
||||
});
|
||||
|
||||
// Proof lattice: constraint lines over the lens hemisphere (meridians through the axis, two latitudes).
|
||||
const lattice = new THREE.Group(); group.add(lattice);
|
||||
const R = 1.0, arc = (f) => [...Array(13)].map((_, j) => f(j / 12 * Math.PI / 2));
|
||||
for (let m = 0; m < 3; m++) {
|
||||
const phi = m / 3 * Math.PI;
|
||||
for (const sgn of [1, -1]) lattice.add(lines(arc(th => V(Math.cos(phi) * Math.sin(th) * R * sgn, Math.sin(phi) * Math.sin(th) * R * sgn, Math.cos(th) * R)), 0xcfe2f0));
|
||||
}
|
||||
for (const th of [0.6, 1.15]) {
|
||||
const lat = lines(circlePts(Math.sin(th) * R, 18), 0xa8c4dc); lat.rotation.x = Math.PI / 2; lat.position.z = Math.cos(th) * R; lattice.add(lat);
|
||||
}
|
||||
|
||||
// Gimbals: the astrolabe's outer rings.
|
||||
const gimbals = [1.16, 1.3].map((r, i) => {
|
||||
const g = new THREE.Mesh(new THREE.TorusGeometry(r, 0.035, 3, 24), metal);
|
||||
g.rotation.set(i ? 0 : Math.PI / 2, i ? Math.PI / 2 : 0, 0); group.add(g); return g;
|
||||
});
|
||||
|
||||
// Scan arc: a pale third of a circle sweeping the tangent disk, masked by the aperture.
|
||||
const scan = lines([...Array(9)].map((_, j) => { const a = j / 8 * Math.PI * 0.66; return V(Math.cos(a) * 0.5, Math.sin(a) * 0.5, 0.4); }), 0xe8f6ff);
|
||||
group.add(scan);
|
||||
|
||||
// Evidence sparks, snapped to fixed source directions on the hemisphere. No evidence, no sparks.
|
||||
const sparks = [[0.5, 0.3], [1.9, 0.8], [3.0, 0.45], [4.2, 1.0], [5.1, 0.65], [0.9, 1.2], [3.6, 1.25]].map(([phi, th]) => {
|
||||
const s = new THREE.Mesh(new THREE.BoxGeometry(0.08, 0.08, 0.08), ps1({ color: 0x6ff7ff, emissive: true }));
|
||||
s.position.set(Math.cos(phi) * Math.sin(th) * R, Math.sin(phi) * Math.sin(th) * R, Math.cos(th) * R); group.add(s); return s;
|
||||
});
|
||||
return {
|
||||
group,
|
||||
update(t, k) {
|
||||
const open = 0.95 - 0.55 * k + (1 - k) * 0.08 * Math.sin(t * 5.3);
|
||||
petals.forEach(p => { p.rotation.z = p.userData.sign * 1.1 * open; });
|
||||
lattice.rotation.z = t * 0.12;
|
||||
gimbals[0].rotation.y = t * 0.3; gimbals[1].rotation.x = t * 0.21;
|
||||
ring.rotation.z = -t * 0.08;
|
||||
scan.rotation.z = t * 1.4; scan.scale.setScalar(0.5 + 0.7 * open);
|
||||
sparks.forEach((s, i) => { s.visible = k > (i + 1) / 9; s.rotation.y = t * 2; });
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
// ---------- organs only Epiphany had (Face, Body): lower detail, they drift in her chamber ----------
|
||||
|
||||
// Face: speech lantern. Translucent bell, warm throat, pearls around the lip.
|
||||
export function face() {
|
||||
|
|
@ -219,19 +300,6 @@ export function face() {
|
|||
return { group, update(t) { pearls.forEach((p, i) => { const a = t * 0.6 + i * 1.26, r = 0.9 + 0.25 * Math.sin(t + i); p.position.set(Math.cos(a) * r, -0.4 + 0.1 * Math.sin(t * 2 + i), Math.sin(a) * r); }); } };
|
||||
}
|
||||
|
||||
// Eyes: evidence astrolabe. Black glass lens, blue-metal aperture ring, proof meridians, cyan sparks.
|
||||
export function eyes() {
|
||||
const group = new THREE.Group();
|
||||
const lens = new THREE.Mesh(new THREE.SphereGeometry(0.7, 8, 6), ps1({ color: 0x07090c, rim: 0x5ff2ff })); lens.scale.z = 0.45; group.add(lens);
|
||||
const ring = new THREE.Mesh(new THREE.TorusGeometry(0.82, 0.09, 4, 16), ps1({ color: 0x4a6a8f, rim: 0x9fc8ff })); group.add(ring);
|
||||
const meridians = [0, 1].map(i => { const m = lines(circlePts(0.98, 20), 0xcfe2f0); m.rotation.x = Math.PI / 2 * i; group.add(m); return m; });
|
||||
for (let i = 0; i < 5; i++) {
|
||||
const s = new THREE.Mesh(new THREE.BoxGeometry(0.07, 0.07, 0.07), ps1({ color: 0x6ff7ff, emissive: true }));
|
||||
const a = i * 1.3; s.position.set(Math.cos(a) * 0.5, Math.sin(a) * 0.5, 0.3); group.add(s);
|
||||
}
|
||||
return { group, update(t) { meridians.forEach((m, i) => { m.rotation.y = t * (0.3 + i * 0.2); }); ring.rotation.z = t * 0.1; } };
|
||||
}
|
||||
|
||||
// Body: load-bearing graph stone. Soft geode mass, enamel stress ribs, checkpoint pearls.
|
||||
export function body() {
|
||||
const group = new THREE.Group();
|
||||
|
|
@ -247,7 +315,7 @@ export function body() {
|
|||
return { group, update(t) { stone.scale.y = 0.72 + Math.sin(t * 0.9) * 0.02; group.rotation.y = t * 0.05; } };
|
||||
}
|
||||
|
||||
// Scaffolding: Epiphany is still growing bones.
|
||||
// Scaffolding: what stands of Epiphany after Eureka absorbed her.
|
||||
export function scaffold(w, h) {
|
||||
const x = w / 2, pts = [];
|
||||
const c = [V(-x, 0, -x), V(x, 0, -x), V(x, 0, x), V(-x, 0, x)];
|
||||
|
|
|
|||
Loading…
Reference in a new issue