HEXAGONAL PLANE - TERRAIN ANIMATION SOURCE CODE
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>RETROSCAPE — Enhanced Infinite Flyover</title>
<style>
* { margin:0; padding:0; box-sizing:border-box; }
html, body {
width:100%; height:100%;
overflow:hidden;
background:#000;
}
#container { width:100%; height:100vh; position:relative; }
canvas { display:block; }
#scanlines {
position:absolute; top:0; left:0; width:100%; height:100%;
pointer-events:none; z-index:10;
background: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0,0,0,0.15) 2px,
rgba(0,0,0,0.15) 4px
);
}
#vignette {
position:absolute; top:0; left:0; width:100%; height:100%;
pointer-events:none; z-index:9;
background: radial-gradient(ellipse at center, transparent 55%, rgba(0,0,0,0.65) 100%);
}
@keyframes flicker {
0%,100% { opacity:1; }
92% { opacity:1; }
93% { opacity:0.7; }
94% { opacity:1; }
96% { opacity:0.85; }
97% { opacity:1; }
}
#container { animation: flicker 4s infinite; }
</style>
</head>
<body>
<div id="container">
<div id="scanlines"></div>
<div id="vignette"></div>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
(function(){
const PLANE_SIZE = 40000;
const SEGMENTS = 120;
const SPEED = 65;
const WAVE_AMP = 1800;
const SUN_RADIUS = 1200;
const container = document.getElementById('container');
const W = window.innerWidth, H = window.innerHeight;
const renderer = new THREE.WebGLRenderer({ antialias: false, alpha: false });
renderer.setSize(W, H);
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setClearColor(0x0a0010, 1);
container.appendChild(renderer.domElement);
const scene = new THREE.Scene();
scene.fog = new THREE.FogExp2(0x0a0010, 0.000035);
const camera = new THREE.PerspectiveCamera(55, W / H, 1, 120000);
camera.position.set(0, 4800, 0);
camera.rotation.x = -Math.PI / 2.6;
// ---------- THEME SYNC ----------
function syncTheme() {
try {
var isDark = true;
if (window.parent && window.parent.document) {
isDark = window.parent.document.documentElement.classList.contains('dark');
} else {
var theme = localStorage.getItem('theme');
isDark = theme === 'dark' || (!theme && window.matchMedia('(prefers-color-scheme: dark)').matches);
}
if (!isDark) {
document.body.style.backgroundColor = '#f0f4f8';
if (typeof renderer !== 'undefined' && typeof scene !== 'undefined') {
renderer.setClearColor(0xf0f4f8, 1);
scene.fog.color.setHex(0xf0f4f8);
}
} else {
document.body.style.backgroundColor = '#0a0010';
if (typeof renderer !== 'undefined' && typeof scene !== 'undefined') {
renderer.setClearColor(0x0a0010, 1);
scene.fog.color.setHex(0x0a0010);
}
}
} catch (e) {
console.warn("Could not sync theme", e);
}
}
syncTheme();
if (window.parent && window.parent.document) {
try {
var observer = new MutationObserver(syncTheme);
observer.observe(window.parent.document.documentElement, { attributes: true, attributeFilter: ['class'] });
} catch(e) {}
}
window.addEventListener('storage', syncTheme);
let clock = { t: 0 };
function createHexagonalGrid(size, hexRadius, color, opacity) {
const hexHeight = hexRadius * Math.sqrt(3);
const hexWidth = hexRadius * 2;
const horizontalSpacing = hexWidth * 0.75;
const verticalSpacing = hexHeight;
const group = new THREE.Group();
const cols = Math.ceil(size / horizontalSpacing / 2);
const rows = Math.ceil(size / verticalSpacing / 2);
for (let row = -rows/2; row < rows/2; row++) {
for (let col = -cols/2; col < cols/2; col++) {
const x = col * horizontalSpacing;
const y = row * verticalSpacing + (col % 2 === 0 ? 0 : verticalSpacing / 2);
const hexShape = new THREE.Shape();
for (let i = 0; i < 6; i++) {
const angle = (Math.PI / 3) * i;
const px = Math.cos(angle) * hexRadius;
const py = Math.sin(angle) * hexRadius;
if (i === 0) hexShape.moveTo(px, py);
else hexShape.lineTo(px, py);
}
hexShape.closePath();
const hexGeo = new THREE.ShapeGeometry(hexShape);
const hexMat = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: opacity * 0.3,
side: THREE.DoubleSide
});
const hexMesh = new THREE.Mesh(hexGeo, hexMat);
hexMesh.position.set(x, y, 0);
hexMesh.userData.baseX = x;
hexMesh.userData.baseY = y;
const edgesGeo = new THREE.EdgesGeometry(hexGeo);
const edgesMat = new THREE.LineBasicMaterial({ color, opacity, transparent: true });
const edges = new THREE.LineSegments(edgesGeo, edgesMat);
hexMesh.add(edges);
group.add(hexMesh);
}
}
return group;
}
function makePlane(color, opacity, y) {
const hexGrid = createHexagonalGrid(PLANE_SIZE, 400, color, opacity);
hexGrid.rotation.x = -Math.PI / 2;
hexGrid.position.set(0, y, 0);
hexGrid.userData.hexagons = [];
hexGrid.traverse((child) => {
if (child.isMesh && child.geometry.type === 'ShapeGeometry') {
hexGrid.userData.hexagons.push(child);
}
});
return hexGrid;
}
const plainLayers = [
{ color: 0xff00ff, opacity: 1.0, y: 0, wave: 1.0, name: 'main' },
{ color: 0x00ffff, opacity: 0.4, y: -800, wave: 0.7, name: 'cyan' },
{ color: 0xff6600, opacity: 0.35, y: -1600, wave: 0.5, name: 'orange' },
{ color: 0xffff00, opacity: 0.3, y: 600, wave: 0.6, name: 'yellow' }
];
const groundPlanes = [];
plainLayers.forEach(layer => {
for (let i = 0; i < 3; i++) {
const p = makePlane(layer.color, layer.opacity, layer.y);
p.position.z = i * PLANE_SIZE;
p.userData.layer = layer.name;
p.userData.waveIntensity = layer.wave;
p.userData.baseColor = new THREE.Color(layer.color);
p.userData.baseY = layer.y;
scene.add(p);
groundPlanes.push(p);
}
});
const mountainLayers = [
{ color: 0x1a0030, z: -18000, scale: 1.0 },
{ color: 0x2a0050, z: -16000, scale: 0.8 },
{ color: 0x3a0070, z: -14000, scale: 0.6 }
];
mountainLayers.forEach(mLayer => {
const mtnGeo = new THREE.PlaneGeometry(PLANE_SIZE * 2, 8000 * mLayer.scale, 160, 1);
const mtnMat = new THREE.MeshBasicMaterial({ color: mLayer.color, transparent: true, opacity: 0.7 });
const mtn = new THREE.Mesh(mtnGeo, mtnMat);
mtn.rotation.x = -Math.PI / 2;
mtn.position.set(0, -200, mLayer.z);
scene.add(mtn);
const mtnPos = mtnGeo.attributes.position;
for (let i = 0; i < mtnPos.count; i++) {
const x = mtnPos.getX(i);
const nx = x / (PLANE_SIZE);
const h = Math.sin(nx * 3.1) * 2200 * mLayer.scale
+ Math.sin(nx * 7.3) * 1200 * mLayer.scale
+ Math.sin(nx * 15.7) * 600 * mLayer.scale
+ Math.sin(nx * 31) * 300 * mLayer.scale;
mtnPos.setZ(i, Math.max(h, -500));
}
mtnPos.needsUpdate = true;
const mtnWireGeo = mtnGeo.clone();
const wireColor = new THREE.Color(mLayer.color).multiplyScalar(2);
const mtnWire = new THREE.Mesh(mtnWireGeo, new THREE.MeshBasicMaterial({
color: wireColor, wireframe: true, transparent: true, opacity: 0.3
}));
mtnWire.rotation.x = -Math.PI / 2;
mtnWire.position.copy(mtn.position);
mtnWire.userData = { baseZ: mLayer.z };
scene.add(mtnWire);
});
const sunGeo = new THREE.CircleGeometry(SUN_RADIUS, 64);
const sunMat = new THREE.MeshBasicMaterial({ color: 0xffddaa });
const sun = new THREE.Mesh(sunGeo, sunMat);
sun.position.set(0, 2200, -16000);
scene.add(sun);
const stripeCount = 12;
const stripes = [];
for (let i = 0; i < stripeCount; i++) {
const frac = i / stripeCount;
const y0 = -SUN_RADIUS + frac * SUN_RADIUS * 1.85;
const y1 = y0 + (SUN_RADIUS * 0.055) * (1 - frac * 0.7);
const halfW = Math.sqrt(Math.max(0, SUN_RADIUS * SUN_RADIUS - y0 * y0));
const sGeo = new THREE.PlaneGeometry(halfW * 2 + 40, y1 - y0);
const sMat = new THREE.MeshBasicMaterial({ color: 0x0a0010 });
const sMesh = new THREE.Mesh(sGeo, sMat);
sMesh.position.set(0, (y0 + y1) / 2, 0.5);
sun.add(sMesh);
stripes.push(sMesh);
}
const glowGeo = new THREE.CircleGeometry(SUN_RADIUS * 1.6, 64);
const glowMat = new THREE.MeshBasicMaterial({ color: 0xff6644, transparent: true, opacity: 0.18 });
const glow = new THREE.Mesh(glowGeo, glowMat);
glow.position.set(0, 2200, -16010);
scene.add(glow);
const structures = [];
const structureColors = [0x00ffff, 0xff00ff, 0xff6600, 0xffff00, 0x9933ff];
function makeStructure(x, z) {
const color = structureColors[Math.floor(Math.random() * structureColors.length)];
const g = new THREE.BoxGeometry(
200 + Math.random() * 300,
800 + Math.random() * 2200,
200 + Math.random() * 300
);
const m = new THREE.MeshBasicMaterial({ color, wireframe: true, transparent: true, opacity: 0.55 });
const mesh = new THREE.Mesh(g, m);
mesh.position.set(x, mesh.geometry.parameters.height / 2 - 400, z);
scene.add(mesh);
const glowColor = new THREE.Color(color).offsetHSL(0.5, 0, 0);
const ig = new THREE.BoxGeometry(
mesh.geometry.parameters.width * 0.6,
mesh.geometry.parameters.height * 0.6,
mesh.geometry.parameters.depth * 0.6
);
const im = new THREE.MeshBasicMaterial({ color: glowColor, transparent: true, opacity: 0.12 });
mesh.add(new THREE.Mesh(ig, im));
structures.push({ mesh, baseZ: z, baseColor: new THREE.Color(color) });
}
for (let i = 0; i < 12; i++) {
const side = (i % 2 === 0) ? 1 : -1;
const x = side * (3500 + Math.random() * 5000);
const z = i * (PLANE_SIZE / 12);
makeStructure(x, z);
}
const PARTICLE_COUNT = 800;
const partGeo = new THREE.BufferGeometry();
const partPos = new Float32Array(PARTICLE_COUNT * 3);
const partCol = new Float32Array(PARTICLE_COUNT * 3);
function randParticle(i) {
partPos[i*3+0] = (Math.random()-0.5) * 30000;
partPos[i*3+1] = Math.random() * 8000 + 200;
partPos[i*3+2] = Math.random() * 50000 - 5000;
const r = Math.random();
if (r < 0.25) { partCol[i*3]=1; partCol[i*3+1]=0.2; partCol[i*3+2]=0.8; }
else if (r < 0.5) { partCol[i*3]=0; partCol[i*3+1]=0.9; partCol[i*3+2]=1; }
else if (r < 0.7) { partCol[i*3]=1; partCol[i*3+1]=1; partCol[i*3+2]=0; }
else if (r < 0.85) { partCol[i*3]=1; partCol[i*3+1]=0.4; partCol[i*3+2]=0; }
else { partCol[i*3]=0.6; partCol[i*3+1]=0.2; partCol[i*3+2]=1; }
}
for (let i = 0; i < PARTICLE_COUNT; i++) randParticle(i);
partGeo.setAttribute('position', new THREE.BufferAttribute(partPos, 3));
partGeo.setAttribute('color', new THREE.BufferAttribute(partCol, 3));
const partMat = new THREE.PointsMaterial({ size: 8, vertexColors: true, transparent: true, opacity: 0.9 });
const particles = new THREE.Points(partGeo, partMat);
scene.add(particles);
const flyingObjects = [];
const objectShapes = ['sphere', 'cube', 'pyramid', 'torus'];
function createFlyingObject() {
let mesh;
const shape = objectShapes[Math.floor(Math.random() * objectShapes.length)];
const color = structureColors[Math.floor(Math.random() * structureColors.length)];
switch(shape) {
case 'sphere':
mesh = new THREE.Mesh(
new THREE.SphereGeometry(150 + Math.random() * 200, 16, 16),
new THREE.MeshBasicMaterial({ color, wireframe: true, transparent: true, opacity: 0.6 })
);
break;
case 'cube':
mesh = new THREE.Mesh(
new THREE.BoxGeometry(200 + Math.random() * 300, 200 + Math.random() * 300, 200 + Math.random() * 300),
new THREE.MeshBasicMaterial({ color, wireframe: true, transparent: true, opacity: 0.6 })
);
break;
case 'pyramid':
mesh = new THREE.Mesh(
new THREE.ConeGeometry(150 + Math.random() * 200, 300 + Math.random() * 400, 4),
new THREE.MeshBasicMaterial({ color, wireframe: true, transparent: true, opacity: 0.6 })
);
break;
case 'torus':
mesh = new THREE.Mesh(
new THREE.TorusGeometry(150 + Math.random() * 100, 40 + Math.random() * 60, 16, 32),
new THREE.MeshBasicMaterial({ color, wireframe: true, transparent: true, opacity: 0.6 })
);
break;
}
const side = Math.random() > 0.5 ? 1 : -1;
mesh.position.set(
side * (8000 + Math.random() * 6000),
1000 + Math.random() * 4000,
-5000 + Math.random() * 30000
);
scene.add(mesh);
flyingObjects.push({
mesh,
baseColor: new THREE.Color(color),
speed: 0.3 + Math.random() * 0.8,
rotSpeed: { x: (Math.random() - 0.5) * 0.02, y: (Math.random() - 0.5) * 0.02, z: (Math.random() - 0.5) * 0.02 }
});
}
for (let i = 0; i < 8; i++) createFlyingObject();
const laserBeams = [];
function createLaserBeam(x, z) {
const colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff6600];
const color = colors[Math.floor(Math.random() * colors.length)];
const geo = new THREE.CylinderGeometry(20, 20, 8000, 8);
const mat = new THREE.MeshBasicMaterial({
color,
transparent: true,
opacity: 0.15,
side: THREE.DoubleSide
});
const beam = new THREE.Mesh(geo, mat);
beam.position.set(x, 4000, z);
scene.add(beam);
const coreGeo = new THREE.CylinderGeometry(8, 8, 8000, 8);
const coreMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.4 });
const core = new THREE.Mesh(coreGeo, coreMat);
beam.add(core);
laserBeams.push({
mesh: beam,
baseColor: new THREE.Color(color),
baseZ: z,
phase: Math.random() * Math.PI * 2
});
}
for (let i = 0; i < 6; i++) {
const side = i % 2 === 0 ? 1 : -1;
const x = side * (4000 + Math.random() * 8000);
const z = i * (PLANE_SIZE / 6);
createLaserBeam(x, z);
}
const energyOrbs = [];
for (let i = 0; i < 10; i++) {
const colors = [0xff00ff, 0x00ffff, 0xffff00, 0xff6600, 0x9933ff];
const color = colors[Math.floor(Math.random() * colors.length)];
const geo = new THREE.SphereGeometry(80 + Math.random() * 120, 16, 16);
const mat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.5 });
const orb = new THREE.Mesh(geo, mat);
orb.position.set(
(Math.random() - 0.5) * 15000,
300 + Math.random() * 600,
i * (PLANE_SIZE / 10)
);
scene.add(orb);
energyOrbs.push({
mesh: orb,
baseColor: new THREE.Color(color),
baseY: orb.position.y,
baseZ: orb.position.z,
floatPhase: Math.random() * Math.PI * 2
});
}
const rings = [];
const RING_COUNT = 4;
const ringColors = [0xff00ff, 0x00ffff, 0xffff00, 0xff6600];
for (let i = 0; i < RING_COUNT; i++) {
const rGeo = new THREE.RingGeometry(200, 250, 64);
const color = ringColors[i % ringColors.length];
const rMat = new THREE.MeshBasicMaterial({ color, transparent: true, opacity: 0.0, side: THREE.DoubleSide });
const rMesh = new THREE.Mesh(rGeo, rMat);
rMesh.rotation.x = -Math.PI / 2;
rMesh.position.set(0, 5, 0);
scene.add(rMesh);
rings.push({ mesh: rMesh, mat: rMat, phase: i / RING_COUNT, baseColor: new THREE.Color(color) });
}
let lastTime = performance.now();
function animate() {
requestAnimationFrame(animate);
const now = performance.now();
const dt = Math.min((now - lastTime) / 1000, 0.05);
lastTime = now;
clock.t += dt;
groundPlanes.forEach(p => {
p.position.z -= SPEED * (p.userData.waveIntensity * 0.5 + 0.5);
if (p.position.z < -PLANE_SIZE) p.position.z += PLANE_SIZE * 3;
const waveInt = p.userData.waveIntensity;
const hexagons = p.userData.hexagons;
hexagons.forEach((hex) => {
const x = hex.userData.baseX;
const y = hex.userData.baseY;
const wave = Math.sin(x * 0.0004 + clock.t * waveInt) * WAVE_AMP * waveInt;
hex.position.z = wave;
});
const hueShift = 0.02 * Math.sin(clock.t * 0.3);
const newColor = p.userData.baseColor.clone();
newColor.offsetHSL(hueShift, 0, 0);
hexagons.forEach(hex => {
hex.material.color.copy(newColor);
if (hex.children[0]) {
hex.children[0].material.color.copy(newColor);
}
});
});
structures.forEach(s => {
s.mesh.position.z -= SPEED;
if (s.mesh.position.z < -PLANE_SIZE * 0.5) {
s.mesh.position.z += PLANE_SIZE;
}
s.mesh.rotation.y += dt * 0.15;
const pulse = 0.9 + Math.sin(clock.t * 2 + s.mesh.position.x * 0.001) * 0.1;
const pulsedColor = s.baseColor.clone().multiplyScalar(pulse);
s.mesh.material.color.copy(pulsedColor);
});
const pPos = particles.geometry.attributes.position;
for (let i = 0; i < PARTICLE_COUNT; i++) {
let z = pPos.getZ(i);
z -= SPEED * 0.6;
if (z < -8000) {
z = 42000;
pPos.setX(i, (Math.random()-0.5)*30000);
pPos.setY(i, Math.random()*8000+200);
}
pPos.setY(i, pPos.getY(i) + Math.sin(clock.t * 2 + i) * 0.4);
pPos.setZ(i, z);
}
pPos.needsUpdate = true;
flyingObjects.forEach(obj => {
obj.mesh.position.z -= SPEED * obj.speed;
if (obj.mesh.position.z < -10000) {
obj.mesh.position.z = 35000;
const side = Math.random() > 0.5 ? 1 : -1;
obj.mesh.position.x = side * (8000 + Math.random() * 6000);
obj.mesh.position.y = 1000 + Math.random() * 4000;
}
obj.mesh.rotation.x += obj.rotSpeed.x;
obj.mesh.rotation.y += obj.rotSpeed.y;
obj.mesh.rotation.z += obj.rotSpeed.z;
const pulse = 0.85 + Math.sin(clock.t * 3 + obj.mesh.position.x * 0.001) * 0.15;
const pulsedColor = obj.baseColor.clone().multiplyScalar(pulse);
obj.mesh.material.color.copy(pulsedColor);
});
laserBeams.forEach(laser => {
laser.mesh.position.z -= SPEED * 0.3;
if (laser.mesh.position.z < -PLANE_SIZE * 0.5) {
laser.mesh.position.z += PLANE_SIZE;
}
const pulseIntensity = 0.15 + Math.sin(clock.t * 4 + laser.phase) * 0.1;
laser.mesh.material.opacity = pulseIntensity;
laser.mesh.children[0].material.opacity = pulseIntensity * 2;
const scale = 1 + Math.sin(clock.t * 2 + laser.phase) * 0.2;
laser.mesh.scale.set(scale, 1, scale);
});
energyOrbs.forEach(orb => {
orb.mesh.position.z -= SPEED * 0.4;
if (orb.mesh.position.z < -PLANE_SIZE * 0.5) {
orb.mesh.position.z += PLANE_SIZE;
orb.mesh.position.x = (Math.random() - 0.5) * 15000;
}
orb.mesh.position.y = orb.baseY + Math.sin(clock.t * 1.5 + orb.floatPhase) * 200;
orb.mesh.rotation.y += dt * 0.5;
const glowPulse = 0.5 + Math.sin(clock.t * 5 + orb.floatPhase) * 0.3;
orb.mesh.material.opacity = glowPulse;
const colorShift = orb.baseColor.clone();
colorShift.offsetHSL(Math.sin(clock.t * 0.5) * 0.05, 0, 0);
orb.mesh.material.color.copy(colorShift);
});
rings.forEach((r, idx) => {
let progress = ((clock.t * 0.35 + r.phase) % 1);
const scale = 10 + progress * 3000;
r.mesh.scale.set(scale, scale, scale);
const alpha = 1 - Math.abs(progress - 0.5) * 2;
r.mat.opacity = Math.max(0, alpha * 0.5);
const shiftedColor = r.baseColor.clone();
shiftedColor.offsetHSL(Math.sin(clock.t * 0.5) * 0.1, 0, 0);
r.mat.color.copy(shiftedColor);
});
const pulse = 0.96 + Math.sin(clock.t * 3.7) * 0.04;
sunMat.color.setHex(0xffddaa);
sunMat.color.multiplyScalar(pulse);
glowMat.opacity = 0.14 + Math.sin(clock.t * 2.1) * 0.06;
const glowHue = 0.05 + Math.sin(clock.t * 0.3) * 0.03;
glowMat.color.setHSL(glowHue, 0.8, 0.6);
camera.position.y = 4800 + Math.sin(clock.t * 0.55) * 120;
camera.rotation.z = Math.sin(clock.t * 0.7) * 0.015;
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
})();
</script>
</body>
</html>