Custom Code Experiment •
Slinky (Three,JS)
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>Wireframe Wormhole Tunnel</title>
<style>
body {
margin: 0;
overflow: hidden;
background: #000;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
const scene = new THREE.Scene();
scene.fog = new THREE.Fog(0x000000, 10, 150);
const camera = new THREE.PerspectiveCamera(90, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.z = 0;
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const tunnelSegments = 150;
const ringsPerSegment = 1;
const tunnelLength = 300;
const rings = [];
function getCurvedPosition(progress) {
const z = -progress * tunnelLength;
// Create a winding path that loops seamlessly - much gentler curves
const x = Math.sin(progress * Math.PI * 4) * 3;
const y = Math.cos(progress * Math.PI * 4) * 3;
return { x, y, z };
}
for (let i = 0; i < tunnelSegments; i++) {
const progress = i / tunnelSegments;
const pos = getCurvedPosition(progress);
const radius = 15 + Math.sin(progress * Math.PI * 8) * 2;
const geometry = new THREE.TorusGeometry(radius, 0.15, 8, 32);
const hue = (progress * 0.6 + 0.5) % 1;
const color = new THREE.Color().setHSL(hue, 1, 0.5);
const material = new THREE.MeshBasicMaterial({
color: color,
wireframe: true,
transparent: true,
opacity: 0.8
});
const ring = new THREE.Mesh(geometry, material);
ring.position.set(pos.x, pos.y, pos.z);
if (i < tunnelSegments - 1) {
const nextPos = getCurvedPosition((i + 1) / tunnelSegments);
const direction = new THREE.Vector3(
nextPos.x - pos.x,
nextPos.y - pos.y,
nextPos.z - pos.z
).normalize();
const up = new THREE.Vector3(0, 1, 0);
const right = new THREE.Vector3().crossVectors(up, direction).normalize();
const newUp = new THREE.Vector3().crossVectors(direction, right);
const matrix = new THREE.Matrix4();
matrix.makeBasis(right, newUp, direction);
ring.rotation.setFromRotationMatrix(matrix);
}
scene.add(ring);
rings.push({ mesh: ring, index: i });
}
const starGeometry = new THREE.BufferGeometry();
const starVertices = [];
for (let i = 0; i < 1000; i++) {
const x = (Math.random() - 0.5) * 200;
const y = (Math.random() - 0.5) * 200;
const z = (Math.random() - 0.5) * 200;
starVertices.push(x, y, z);
}
starGeometry.setAttribute('position', new THREE.Float32BufferAttribute(starVertices, 3));
const starMaterial = new THREE.PointsMaterial({ color: 0xffffff, size: 0.1 });
const stars = new THREE.Points(starGeometry, starMaterial);
scene.add(stars);
let speed = 0.004; // Increased speed for better flying sensation
let pathProgress = 0;
let rotationSpeed = 0.002;
let cameraRoll = 0;
function animate() {
requestAnimationFrame(animate);
pathProgress += speed;
if (pathProgress > 1) pathProgress -= 1;
const currentPos = getCurvedPosition(pathProgress);
camera.position.set(currentPos.x, currentPos.y, currentPos.z);
const lookAheadProgress = (pathProgress + 0.02) % 1;
const lookAtPos = getCurvedPosition(lookAheadProgress);
const direction = new THREE.Vector3(
lookAtPos.x - currentPos.x,
lookAtPos.y - currentPos.y,
lookAtPos.z - currentPos.z
).normalize();
const nextProgress = (pathProgress + 0.01) % 1;
const nextPos = getCurvedPosition(nextProgress);
const lateralChange = nextPos.x - currentPos.x;
cameraRoll = -lateralChange * 0.5; // Bank into turns
camera.lookAt(lookAtPos.x, lookAtPos.y, lookAtPos.z);
camera.rotateZ(cameraRoll);
rings.forEach((ringData, idx) => {
const ring = ringData.mesh;
ring.rotation.z += rotationSpeed * (1 + idx * 0.005);
const ringProgress = ringData.index / tunnelSegments;
const relativeProgress = (ringProgress - pathProgress + 1) % 1;
if (relativeProgress > 0.95 || relativeProgress < 0.05) {
const newIndex = Math.floor((pathProgress * tunnelSegments + tunnelSegments * 0.5)) % tunnelSegments;
ringData.index = newIndex;
const newProgress = newIndex / tunnelSegments;
const newPos = getCurvedPosition(newProgress);
const newRadius = 15 + Math.sin(newProgress * Math.PI * 8) * 2;
const newGeometry = new THREE.TorusGeometry(newRadius, 0.15, 8, 32);
ring.geometry.dispose();
ring.geometry = newGeometry;
ring.position.set(newPos.x, newPos.y, newPos.z);
const nextIndex = (newIndex + 1) % tunnelSegments;
const nextPos = getCurvedPosition(nextIndex / tunnelSegments);
const direction = new THREE.Vector3(
nextPos.x - newPos.x,
nextPos.y - newPos.y,
nextPos.z - newPos.z
).normalize();
const up = new THREE.Vector3(0, 1, 0);
const right = new THREE.Vector3().crossVectors(up, direction).normalize();
const newUp = new THREE.Vector3().crossVectors(direction, right);
const matrix = new THREE.Matrix4();
matrix.makeBasis(right, newUp, direction);
ring.rotation.setFromRotationMatrix(matrix);
const hue = (newProgress * 0.6 + 0.5) % 1;
ring.material.color.setHSL(hue, 1, 0.5);
}
});
stars.rotation.z += 0.0001;
renderer.render(scene, camera);
}
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
</script>
</body>
</html>