I Hand‑Crafted an Optical Universe That Responds to a Writer’s State
- 3 days ago
- 6 min read
Updated: 23 hours ago
I hand‑crafted an optical universe that responds to a writer’s working state,
and I dedicate this universe to everyone who writes quietly in the dark.
It is a digital cosmos that can be triggered and altered by the keyboard.
Every keystroke ignites a spark of starlight at the edge of space,
and that light travels across the vast structure before fading into the depths of the void.
When you slack off, the universe grows dim and silent;
when you surge with energy, the universe blazes with you.
You can even use a small external screen as a “universe monitor.”
While you write, it stays by your side—
a world that seems to breathe with you.
Sometimes, all a lonely writer needs
is a small joy like this, isn’t it.
And if you come up with better ideas or improvements, I’d love to see them.
Demo Preview
// ===== Keyboard Event =====
document.addEventListener("keydown", fireUserSignal);
// ===== Basic Setup =====
document.body.style.margin = "0";
document.body.style.overflow = "hidden";
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x05030a, 40, 260);
const camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
500
);
camera.position.set(0, 0, 40);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setClearColor(0x050308, 1);
document.body.appendChild(renderer.domElement);
// ===== Color Settings =====
const lineColor = new THREE.Color("#262b3a");
const autoSignalColor = new THREE.Color("#5a6b7a");
const userSignalMain = new THREE.Color("#e8ffff");
const userSignalExtra1 = new THREE.Color("#ff4fa8");
const userSignalExtra2 = new THREE.Color("#ffd45a");
// ===== Lighting =====
scene.add(new THREE.AmbientLight(0x404060, 1.4));
const pointLight = new THREE.PointLight(0x88ccff, 2.5, 320);
scene.add(pointLight);
// ===== Line Structure: Fractal Harmonic Field (Cosmic Flow) + Distribution Refinement =====
const LINE_COUNT = 130;
const AUTO_SIGNAL_COUNT = 80;
const lines = [];
const autoSignals = [];
const userSignals = [];
const lineMat = new THREE.LineBasicMaterial({
color: lineColor,
transparent: true,
opacity: 0.85
});
// Fractal Harmonic parameters (avoid stacking, maintain flow)
const FH_FREQ1 = 4.2;
const FH_FREQ2 = 7.9;
const FH_FREQ3 = 13.3;
const FH_AMP1 = 3.2;
const FH_AMP2 = 2.1;
const FH_AMP3 = 1.4;
for (let i = 0; i < LINE_COUNT; i++) {
const points = [];
// Base angle: evenly distributed + slight nonlinear offset to avoid empty regions
const angleBase =
(i / LINE_COUNT) * Math.PI * 2 + Math.sin(i * 0.7) * 0.2;
const baseRadius = 50;
const depth = 260;
for (let j = 0; j <= 320; j++) {
const t = j / 320;
// Radius: stable ring at the front, nonlinear contraction toward the back
let radius;
if (t < 0.03) {
radius = baseRadius;
} else {
const radialMain = baseRadius * Math.pow(1.0 - t, 1.18);
const radialShell = 8 * (1.0 - t);
radius = radialMain + radialShell;
}
// Angle: directional twist of the Cosmic Flow
let angle = angleBase;
if (t > 0.03) {
angle += t * 1.1;
angle += Math.sin(t * 3.5 + i * 0.27) * 0.18;
angle += Math.cos(t * 4.7 - i * 0.19) * 0.13;
}
let x = Math.cos(angle) * radius;
let y = Math.sin(angle) * radius;
// Fractal Harmonic: multi-frequency, multi-scale flow
if (t > 0.03) {
const fh1 =
Math.sin(t * FH_FREQ1 + i * 0.31) * FH_AMP1 +
Math.cos(t * FH_FREQ1 * 0.7 - i * 0.23) * (FH_AMP1 * 0.6);
const fh2 =
Math.sin(t * FH_FREQ2 + i * 0.41) * FH_AMP2 +
Math.cos(t * FH_FREQ2 * 0.8 - i * 0.37) * (FH_AMP2 * 0.7);
const fh3 =
Math.sin(t * FH_FREQ3 + i * 0.53) * FH_AMP3 +
Math.cos(t * FH_FREQ3 * 0.9 - i * 0.29) * (FH_AMP3 * 0.8);
const flow = fh1 * 0.7 + fh2 * 0.6 + fh3 * 0.5;
x += flow * 0.9;
y += flow * 0.7;
}
// Depth: nonlinear + slight offset per line to avoid heavy intersections
const z =
-Math.pow(t, 1.22) * depth +
40 +
Math.sin(i * 0.3) * 2.5;
points.push(new THREE.Vector3(x, y, z));
}
const geo = new THREE.BufferGeometry().setFromPoints(points);
const line = new THREE.Line(geo, lineMat);
scene.add(line);
lines.push({ line, points });
}
// ===== Background Signals (dim, slow) =====
const autoGeo = new THREE.SphereGeometry(0.18, 12, 12);
function createAutoSignal() {
const mat = new THREE.MeshBasicMaterial({
color: autoSignalColor,
transparent: true,
opacity: 0.4
});
const mesh = new THREE.Mesh(autoGeo, mat);
scene.add(mesh);
const lineIndex = Math.floor(Math.random() * lines.length);
const speed = 0.005 + Math.random() * 0.004;
return {
mesh,
lineIndex,
t: Math.random(),
speed
};
}
for (let i = 0; i < AUTO_SIGNAL_COUNT; i++) {
autoSignals.push(createAutoSignal());
}
// ===== User Signals (light beam + trail) =====
const userGeo = new THREE.CylinderGeometry(0.05, 0.05, 1.8, 12);
const TRAIL_POINTS = 16;
function fireUserSignal() {
if (!lines.length) return;
let color = userSignalMain;
const r = Math.random();
if (r < 0.25) color = userSignalExtra1;
else if (r < 0.5) color = userSignalExtra2;
const mat = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: 1,
blending: THREE.AdditiveBlending
});
const mesh = new THREE.Mesh(userGeo, mat);
mesh.rotation.x = Math.PI / 2;
scene.add(mesh);
const lineIndex = Math.floor(Math.random() * lines.length);
const lineData = lines[lineIndex];
if (!lineData || !lineData.points || lineData.points.length < 2) {
scene.remove(mesh);
return;
}
const startPoint = lineData.points[0];
mesh.position.copy(startPoint);
const baseSpeed = 0.0012 + Math.random() * 0.0015;
const trailPositions = new Float32Array(TRAIL_POINTS * 3);
for (let i = 0; i < TRAIL_POINTS; i++) {
trailPositions[i * 3] = startPoint.x;
trailPositions[i * 3 + 1] = startPoint.y;
trailPositions[i * 3 + 2] = startPoint.z;
}
const trailGeo = new THREE.BufferGeometry();
trailGeo.setAttribute(
"position",
new THREE.BufferAttribute(trailPositions, 3)
);
const trailMat = new THREE.PointsMaterial({
color,
size: 0.22,
transparent: true,
opacity: 0.55,
blending: THREE.AdditiveBlending,
depthWrite: false
});
const trailPoints = new THREE.Points(trailGeo, trailMat);
scene.add(trailPoints);
userSignals.push({
mesh,
lineIndex,
t: 0,
baseSpeed,
trailPositions,
trailGeo,
trailPoints
});
}
window.addEventListener("keydown", () => {
fireUserSignal();
});
// ===== Starfield Distribution =====
const starGeo = new THREE.BufferGeometry();
const starCount = 2000;
const starPositions = new Float32Array(starCount * 3);
const starColors = new Float32Array(starCount * 3);
const colorOptions = [
new THREE.Color(0x99ccff),
new THREE.Color(0xffffff),
new THREE.Color(0xffddaa),
new THREE.Color(0xff99cc)
];
for (let i = 0; i < starCount; i++) {
// Slightly reduced radius, concentrated within view range
const r = 140 + Math.random() * 80;
const theta = Math.random() * Math.PI * 2;
const phi = Math.acos(2 * Math.random() - 1);
// Stars mainly distributed around Z ≈ -90
const zOffset = -90;
const x = r * Math.sin(phi) * Math.cos(theta);
const y = r * Math.sin(phi) * Math.sin(theta);
const z = r * Math.cos(phi) + zOffset;
starPositions[i * 3] = x;
starPositions[i * 3 + 1] = y;
starPositions[i * 3 + 2] = z;
const c = colorOptions[Math.floor(Math.random() * colorOptions.length)];
starColors[i * 3] = c.r;
starColors[i * 3 + 1] = c.g;
starColors[i * 3 + 2] = c.b;
}
starGeo.setAttribute("position", new THREE.BufferAttribute(starPositions, 3));
starGeo.setAttribute("color", new THREE.BufferAttribute(starColors, 3));
const starMat = new THREE.PointsMaterial({
size: 0.9,
transparent: true,
opacity: 1.0,
vertexColors: true,
blending: THREE.AdditiveBlending,
depthWrite: false,
depthTest: false
});
const stars = new THREE.Points(starGeo, starMat);
scene.add(stars);
// ===== Window Resize =====
window.addEventListener("resize", () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// ===== Animation =====
let t = 0;
function animate() {
requestAnimationFrame(animate);
t += 0.0018;
// Camera motion
camera.position.x = Math.sin(t * 1.4) * 6;
camera.position.y = Math.sin(t * 0.9) * 4;
camera.position.z = 40 + Math.sin(t * 0.6) * 4;
camera.lookAt(0, 0, -90);
// Starfield: slow rotation
stars.rotation.z += 0.00025;
// Background signals
autoSignals.forEach((s) => {
s.t += s.speed;
if (s.t > 1) s.t = 0;
const lineData = lines[s.lineIndex];
const idx = Math.floor(s.t * (lineData.points.length - 1));
const p = lineData.points[idx];
s.mesh.position.copy(p);
s.mesh.material.opacity = 0.2 + Math.pow(1.0 - s.t, 2.0) * 0.3;
});
// User signals
for (let i = userSignals.length - 1; i >= 0; i--) {
const s = userSignals[i];
// Fixed speed: each beam keeps its own baseSpeed
s.t += s.baseSpeed;
if (s.t > 1) {
scene.remove(s.mesh);
scene.remove(s.trailPoints);
userSignals.splice(i, 1);
continue;
}
const lineData = lines[s.lineIndex];
const idx = Math.floor(s.t * (lineData.points.length - 1));
const p = lineData.points[idx];
s.mesh.position.copy(p);
const pulse = 0.12 * Math.sin(performance.now() * 0.01 + i);
s.mesh.material.opacity =
1.0 + Math.pow(1.0 - s.t, 2.0) * 1.1 + pulse;
const tp = s.trailPositions;
for (let k = TRAIL_POINTS - 1; k > 0; k--) {
tp[k * 3] = tp[(k - 1) * 3];
tp[k * 3 + 1] = tp[(k - 1) * 3 + 1];
tp[k * 3 + 2] = tp[(k - 1) * 3 + 2];
}
tp[0] = p.x;
tp[1] = p.y;
tp[2] = p.z;
s.trailGeo.attributes.position.needsUpdate = true;
}
renderer.render(scene, camera);
}
animate();By VON(壹叔瘋神)



Comments