Custom Code Experiment •
ThreeJs: Spider 1
Source Code View HTML / CSS / JavaScript
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Aether — Spider</title>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { background: #000; overflow: hidden; height: 100vh; }
#bgCanvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 1; }
#animationCanvas { position: absolute; top: 0; left: 0; width: 100%; height: 100%; z-index: 2; }
</style>
</head>
<body>
<canvas id="bgCanvas"></canvas>
<canvas id="animationCanvas"></canvas>
<script>
const colors = {
primary: 0x4db8d4,
dark1: 0x0d2a3a,
dark3: 0x000000,
light: 0xaee8f5
};
// ── Background ──
const bgCanvas = document.getElementById('bgCanvas');
const bgCtx = bgCanvas.getContext('2d');
const pixelSize = 8;
let cols, rows, pixels = [];
function initBackground() {
bgCanvas.width = window.innerWidth;
bgCanvas.height = window.innerHeight;
cols = Math.ceil(bgCanvas.width / pixelSize);
rows = Math.ceil(bgCanvas.height / pixelSize);
pixels = [];
for (let i = 0; i < cols * rows; i++)
pixels.push({ brightness: Math.random() * 0.3, speed: 0.001 + Math.random() * 0.003 });
}
function animateBackground() {
for (let y = 0; y < rows; y++) {
for (let x = 0; x < cols; x++) {
const p = pixels[y * cols + x];
p.brightness += p.speed;
if (p.brightness > 0.4) p.brightness = 0;
bgCtx.fillStyle = `rgb(${Math.floor(p.brightness*10)},${Math.floor(p.brightness*30)},${Math.floor(p.brightness*50)})`;
bgCtx.fillRect(x * pixelSize, y * pixelSize, pixelSize, pixelSize);
}
}
requestAnimationFrame(animateBackground);
}
// ── Three.js ──
const renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('animationCanvas'), antialias: true, alpha: true });
let scene, camera, spider;
function updateSize() {
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
}
function updateCamera() {
const w = window.innerWidth;
if (w < 480) { camera.position.set(0, 4.0, 5.0); camera.fov = 58; }
else if (w < 768) { camera.position.set(0, 3.6, 4.5); camera.fov = 54; }
else { camera.position.set(0, 3.2, 4.0); camera.fov = 50; }
camera.lookAt(0, 0, 0);
camera.updateProjectionMatrix();
}
function mat(color) {
return new THREE.MeshBasicMaterial({ color, wireframe: true });
}
function tube(p0, p1, radius, color, mid) {
const pts = mid ? [p0, mid, p1] : [p0, p1];
const curve = new THREE.CatmullRomCurve3(pts);
return new THREE.Mesh(new THREE.TubeGeometry(curve, 8, radius, 6, false), mat(color));
}
function ball(r, color, x, y, z, sx=1, sy=1, sz=1) {
const m = new THREE.Mesh(new THREE.SphereGeometry(r, 20, 20), mat(color));
m.position.set(x, y, z);
m.scale.set(sx, sy, sz);
return m;
}
function createSpider() {
const g = new THREE.Group();
// ── ABDOMEN ── large ovoid, rear section
g.add(ball(0.72, colors.primary, 0, 0.05, -0.95, 0.88, 0.76, 1.22));
// Dorsal chevron markings
for (let i = 0; i < 3; i++) {
const z = -0.72 - i * 0.28;
const w = 0.32 - i * 0.07;
[-1,1].forEach(s => {
const mk = new THREE.Mesh(new THREE.SphereGeometry(0.1, 10, 10), mat(colors.dark1));
mk.position.set(s * w * 0.55, 0.48, z);
mk.scale.set(0.5, 0.22, 0.7);
g.add(mk);
});
}
// Spinnerets
[-1,1].forEach(s => {
const sp = new THREE.Mesh(new THREE.CylinderGeometry(0.022, 0.03, 0.12, 8), mat(colors.light));
sp.position.set(s * 0.07, -0.08, -1.74);
sp.rotation.x = Math.PI * 0.38;
g.add(sp);
});
// ── PEDICEL ── narrow waist
const pedCurve = new THREE.CatmullRomCurve3([
new THREE.Vector3(0, 0.02, -0.28),
new THREE.Vector3(0, 0.05, -0.08),
new THREE.Vector3(0, 0.02, 0.1),
]);
g.add(new THREE.Mesh(new THREE.TubeGeometry(pedCurve, 8, 0.068, 8, false), mat(colors.dark1)));
// ── CEPHALOTHORAX ── forward section, slightly raised
g.add(ball(0.48, colors.primary, 0, 0.06, 0.4, 0.95, 0.7, 1.12));
// Carapace ridge line
const ridgeCurve = new THREE.CatmullRomCurve3([
new THREE.Vector3(0, 0.37, 0.68),
new THREE.Vector3(0, 0.40, 0.38),
new THREE.Vector3(0, 0.34, 0.08),
]);
g.add(new THREE.Mesh(new THREE.TubeGeometry(ridgeCurve, 8, 0.02, 6, false), mat(colors.light)));
// Fovea diamond
const fov = new THREE.Mesh(new THREE.OctahedronGeometry(0.065, 0), mat(colors.light));
fov.position.set(0, 0.38, 0.28); fov.scale.set(0.8, 0.35, 1.1);
g.add(fov);
// ── EYES ── 8 eyes on front face
const eyeDefs = [
[-0.09, 0.28, 0.84, 0.056], // AME L (large)
[ 0.09, 0.28, 0.84, 0.056], // AME R (large)
[-0.23, 0.21, 0.77, 0.036], // ALE L
[ 0.23, 0.21, 0.77, 0.036], // ALE R
[-0.12, 0.36, 0.66, 0.030], // PME L
[ 0.12, 0.36, 0.66, 0.030], // PME R
[-0.26, 0.30, 0.60, 0.026], // PLE L
[ 0.26, 0.30, 0.60, 0.026], // PLE R
];
eyeDefs.forEach(([x,y,z,r], i) => {
g.add(ball(r, i < 2 ? colors.light : colors.primary, x, y, z));
});
// ── CHELICERAE ──
[-1,1].forEach(s => {
const c0 = new THREE.Vector3(s*0.11, 0.1, 0.83);
const c1 = new THREE.Vector3(s*0.12, -0.05, 0.90);
const c2 = new THREE.Vector3(s*0.11, -0.18, 0.86);
g.add(tube(c0, c1, 0.052, colors.dark1));
g.add(tube(c1, c2, 0.024, colors.light));
});
// ── PEDIPALPS ──
[-1,1].forEach(s => {
const p0 = new THREE.Vector3(s*0.21, 0.1, 0.75);
const p1 = new THREE.Vector3(s*0.34, 0.03, 0.86);
const p2 = new THREE.Vector3(s*0.44,-0.04, 0.96);
const curve = new THREE.CatmullRomCurve3([p0,p1,p2]);
g.add(new THREE.Mesh(new THREE.TubeGeometry(curve, 6, 0.018, 6, false), mat(colors.primary)));
g.add(ball(0.036, colors.light, s*0.44, -0.04, 0.96));
});
// ── LEGS ──
// 4 pairs. Each leg fans outward from the side of the cephalothorax.
// Legs are built in the XZ plane (horizontal) then drop at tibia.
// attachZ: where on the body the leg connects (front=+Z, back=-Z)
// fanAngle: angle outward from the body's forward axis (radians)
// front legs fan forward, rear legs fan backward
const pairs = [
{ attachZ: 0.58, fanAngle: Math.PI * 0.20 }, // pair 1 — sweeps forward
{ attachZ: 0.30, fanAngle: Math.PI * 0.38 }, // pair 2
{ attachZ: 0.04, fanAngle: Math.PI * 0.62 }, // pair 3
{ attachZ: -0.20, fanAngle: Math.PI * 0.80 }, // pair 4 — sweeps back
];
pairs.forEach((pair, pi) => {
[-1, 1].forEach(side => {
// All angles measured from +Z axis (forward), rotating toward ±X
// side=1 → right side, side=-1 → left side
const fa = pair.fanAngle;
// Outward unit vector in XZ plane for this leg
const outX = side * Math.sin(fa);
const outZ = -Math.cos(fa); // negative = forward component
// ── Coxa: body edge → first joint
const cox0 = new THREE.Vector3(side * 0.44, 0.0, pair.attachZ);
const cox1 = new THREE.Vector3(
cox0.x + side * 0.20,
0.02,
cox0.z + outZ * 0.14
);
g.add(tube(cox0, cox1, 0.030, colors.dark1));
// Trochanter node
g.add(ball(0.038, colors.light, cox1.x, cox1.y, cox1.z));
// ── Femur: rises up and out
const femLen = 0.80;
const fem1 = new THREE.Vector3(
cox1.x + outX * femLen * 0.85,
cox1.y + 0.24,
cox1.z + outZ * femLen * 0.45
);
g.add(tube(cox1, fem1, 0.026, colors.primary));
// Knee node
g.add(ball(0.044, colors.light, fem1.x, fem1.y, fem1.z));
// ── Tibia: angles strongly downward and continues outward
const tibLen = 0.78;
const tib1 = new THREE.Vector3(
fem1.x + outX * tibLen * 0.7,
fem1.y - 0.52,
fem1.z + outZ * tibLen * 0.35
);
g.add(tube(fem1, tib1, 0.020, colors.primary));
// ── Metatarsus: continues downward
const metLen = 0.55;
const met1 = new THREE.Vector3(
tib1.x + outX * metLen * 0.5,
tib1.y - 0.40,
tib1.z + outZ * metLen * 0.25
);
g.add(tube(tib1, met1, 0.014, colors.dark1));
// ── Tarsus + claws
const tarLen = 0.28;
const tar1 = new THREE.Vector3(
met1.x + outX * tarLen * 0.4,
met1.y - 0.22,
met1.z + outZ * tarLen * 0.2
);
g.add(tube(met1, tar1, 0.009, colors.dark1));
// Two claw tips
[-1,1].forEach(claw => {
const cl = new THREE.Vector3(
tar1.x + claw * 0.04,
tar1.y - 0.055,
tar1.z + 0.02
);
g.add(tube(tar1, cl, 0.006, colors.light));
});
});
});
return g;
}
function init() {
initBackground();
animateBackground();
updateSize();
scene = new THREE.Scene();
scene.background = new THREE.Color(colors.dark3);
camera = new THREE.PerspectiveCamera(50, window.innerWidth / window.innerHeight, 0.1, 1000);
updateCamera();
spider = createSpider();
scene.add(spider);
scene.add(new THREE.AmbientLight(colors.light, 0.5));
const pl1 = new THREE.PointLight(colors.primary, 1.5, 100);
pl1.position.set(4, 6, 4); scene.add(pl1);
const pl2 = new THREE.PointLight(colors.light, 0.8, 100);
pl2.position.set(-5, 2, 3); scene.add(pl2);
const pl3 = new THREE.PointLight(colors.primary, 0.5, 100);
pl3.position.set(0, -3, -6); scene.add(pl3);
animate();
}
function animate() {
requestAnimationFrame(animate);
const t = Date.now();
if (spider) {
spider.rotation.y += 0.003;
spider.position.y = Math.sin(t * 0.0012) * 0.06;
spider.rotation.x = Math.sin(t * 0.0008) * 0.03;
spider.rotation.z = Math.sin(t * 0.0015) * 0.01;
}
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
updateSize(); initBackground();
if (camera) { camera.aspect = window.innerWidth / window.innerHeight; updateCamera(); }
});
window.addEventListener('load', init);
</script>
</body>
</html>