• 01 - Critter Models 02
  • 02 - VantaJS Shaders
  • 03 - App Bundle 02
  • 04 - Cathedrals
  • 05 - App Bundle 01
  • 06 - Retroscopic
  • 07 - Games
  • 08 - Voronoi
  • 09 - Terrain
  • 10 - Critter Models 01
  • 11 - Chromatophores
  • 12 - Conformal Maps
  • 13 - Echo Logix
  • 14 - Music
  • 15 - Colour Theory
MIKE'S NOTEBOOK
Notes
Number:
Project:
07Games
└ Rockfall└ Speak & Math└ Space Invaders└ Apache└ Asteroids└ Molly Roger└ Carrom└ Chess└ Connect 4└ Anagrams
08Voronoi
└ 001└ 002└ 003└ 004└ 005
09Terrain
└ Simple Polygon└ Simple Flyover└ Infinite Flyover 01└ Infinite Flyover 02└ Hexagonal Plane└ Mountain Ridges
10Critter Models 01
└ Wireframe Spider└ Wireframe Fly└ Wireframe Scorpion└ Wireframe Mosquito└ Wireframe Bee└ Wireframe Butterfly└ Wireframe Locust
11Chromatophores
└ 01└ 02└ 03└ 04
12Conformal Maps
└ 01└ 02└ 03└ 04└ 05
02 / 03

SIMPLE FLYOVER - 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>Retro Wireframe Flyover</title>
    <style>
        html, body {
            border: 0;
            padding: 0;
            margin: 0;
            overflow: hidden;
            height: 100%;
            width: 100%;
            background: #000;
        }
        #container {
            height: 100vh;
            width: 100%;
            overflow: hidden;
        }
    </style>
</head>
<body>
    <div id="container"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
    <script>
        try {
            var container = document.getElementById('container');

            var camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 1, 100000);
            camera.position.set(0, 5000, 0);
            camera.rotation.x = -Math.PI / 3;

            var scene = new THREE.Scene();
            scene.fog = new THREE.Fog(0x1a0033, 10000, 40000);

            // Create foreground plane
            var planeSize = 30000;
            var segments = 20;
            var planeGeo = new THREE.PlaneGeometry(planeSize, planeSize, segments, segments);

            var planeFront = new THREE.Mesh(planeGeo, new THREE.MeshBasicMaterial({
                color: 0xff00ff,
                wireframe: true
            }));
            planeFront.rotation.x = -Math.PI / 2;
            planeFront.position.z = 0;
            scene.add(planeFront);

            // Create background plane
            var planeGeo2 = new THREE.PlaneGeometry(planeSize, planeSize, segments, segments);
            var planeBack = new THREE.Mesh(planeGeo2, new THREE.MeshBasicMaterial({
                color: 0x00ffff,
                wireframe: true,
                transparent: true,
                opacity: 0.5
            }));
            planeBack.rotation.x = -Math.PI / 2;
            planeBack.position.z = planeSize;
            scene.add(planeBack);

            var renderer = new THREE.WebGLRenderer({alpha: false, antialias: false});
            renderer.setSize(window.innerWidth, window.innerHeight);
            renderer.setClearColor(0x0a0015, 1);
            container.appendChild(renderer.domElement);

            // ---------- 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 = '#0a0015';
                        if (typeof renderer !== 'undefined' && typeof scene !== 'undefined') {
                            renderer.setClearColor(0x0a0015, 1);
                            scene.fog.color.setHex(0x1a0033);
                        }
                    }
                } 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);

            var count = 0;
            var speed = 50;

            function render() {
                requestAnimationFrame(render);

                // Move planes forward
                planeFront.position.z -= speed;
                planeBack.position.z -= speed;

                // Loop planes seamlessly
                if (planeFront.position.z < -planeSize) {
                    planeFront.position.z = planeSize;
                }
                if (planeBack.position.z < -planeSize) {
                    planeBack.position.z = planeSize;
                }

                // Animate vertices
                var positions = planeFront.geometry.attributes.position;
                for (var i = 0; i < positions.count; i++) {
                    var x = positions.getX(i);
                    var y = positions.getY(i);
                    var wave = Math.sin(x * 0.0005 + y * 0.0005 + count * 0.05) * 2000;
                    positions.setZ(i, wave);
                }
                positions.needsUpdate = true;

                var positions2 = planeBack.geometry.attributes.position;
                for (var i = 0; i < positions2.count; i++) {
                    var x = positions2.getX(i);
                    var y = positions2.getY(i);
                    var wave = Math.sin(x * 0.0005 + y * 0.0005 + count * 0.05) * 2000;
                    positions2.setZ(i, wave);
                }
                positions2.needsUpdate = true;

                count += 0.1;
                renderer.render(scene, camera);
            }

            render();

            window.addEventListener('resize', onWindowResize, false);

            function onWindowResize() {
                camera.aspect = window.innerWidth / window.innerHeight;
                camera.updateProjectionMatrix();
                renderer.setSize(window.innerWidth, window.innerHeight);
            }
        } catch(e) {
            console.error('Error:', e);
            document.body.innerHTML = '<div style="color:white;padding:20px;">Error loading animation: ' + e.message + '</div>';
        }
    </script>
</body>
</html>
MikeHiggins.me.uk LinkedIn GitHub Bluesky Contra Upwork
© 2026 Mike Higgins. All rights reserved.