Three.js Exploration

This visualisation was one of my exploration of three.js. I decided to use it for my portfolio’s landing page.

For this visualisation I had wanted to test an animation with some simple geometry, represents a moon orbiting a planet, due to my connection to the Space Industry, and to render it as ASCII to fit with the Console Like theme.


The code for it can be found below:


<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Moon Orbiting Planet with ASCII Effect</title>
  <link rel="stylesheet" href="/assets/css/custom.css">
   <style>
    body { margin: 0; overflow: hidden; }
    canvas { display: block; }
    #cameraControls {
      position: absolute;
      top: 10px;
      left: 10px;
      z-index: 100;
      background: rgba(255, 255, 255, 0.8);
      padding: 10px;
      border-radius: 8px;
    }
  </style>
</head>
<body>
    <div id="cameraControls">
        <button id="toggleCamera">Switch Camera</button>
      </div>

  <script type="module">
  import * as THREE from '../js/libs/three.module.js';
  import { OrbitControls } from '../js/libs/OrbitControls.js';
  import { AsciiEffect } from '../js/libs/AsciiEffect.js';

 // Scene, Renderer, and Initial Camera
 const scene = new THREE.Scene();
    const renderer = new THREE.WebGLRenderer();
    renderer.setSize(window.innerWidth, window.innerHeight);
    renderer.setPixelRatio(window.devicePixelRatio);

    // ASCII Effect Renderer
    const effect = new AsciiEffect(renderer, ' .:-=+*#%@', { invert: true, foreground: '#A5FBFF' });
    effect.setSize(window.innerWidth, window.innerHeight);
    effect.domElement.classList.add('ascii-effect');
    document.body.appendChild(effect.domElement);  // Replace renderer.domElement with effect.domElement

    // Perspective Camera
    const perspectiveCamera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
    perspectiveCamera.position.z = 20;

    // Orthographic Camera
    const aspect = window.innerWidth / window.innerHeight;
    const orthoCamera = new THREE.OrthographicCamera(-10 * aspect, 10 * aspect, 10, -10, 0.1, 1000);
    orthoCamera.position.z = 20;

    // Set current camera to perspective camera
    let currentCamera = perspectiveCamera;

    // Controls for the current camera
    const controls = new OrbitControls(currentCamera, effect.domElement);

    // Planet (Green)
    const planetGeometry = new THREE.SphereGeometry(5, 32, 32);
    const planetMaterial = new THREE.MeshPhongMaterial({ color: 0x00ff00 });
    const planet = new THREE.Mesh(planetGeometry, planetMaterial);
    scene.add(planet);

    // Moon (Gray)
    const moonGeometry = new THREE.SphereGeometry(1, 32, 32);
    const moonMaterial = new THREE.MeshPhongMaterial({ color: 0xaaaaaa });
    const moon = new THREE.Mesh(moonGeometry, moonMaterial);
    moon.position.set(15, 0, 0);  // Place the moon to the side of the planet
    scene.add(moon);

    // Lighting
    const sunLight = new THREE.DirectionalLight(0xffffff, 2);
    sunLight.position.set(100, 100, 100);
    scene.add(sunLight);

    const ambientLight = new THREE.AmbientLight(0x404040, 1); 
    scene.add(ambientLight);

    // Create a Sprite with Text
    const createTextSprite = (text) => {
      const canvas = document.createElement('canvas');
      const context = canvas.getContext('2d');
      const textureSize = 512;

      // Set canvas size
      canvas.width = textureSize;
      canvas.height = textureSize;

      // Draw text on the canvas
      context.font = '96px Impact';
      context.fillStyle = 'white';
      context.textAlign = 'center';
      context.textBaseline = 'middle';
      const padding = 50;
      context.fillText(text, textureSize / 2, textureSize / 2);

      // Create a texture from the canvas
      const texture = new THREE.CanvasTexture(canvas);

      // Create a SpriteMaterial with the texture
      const spriteMaterial = new THREE.SpriteMaterial({ map: texture });

      // Create a Sprite
      const sprite = new THREE.Sprite(spriteMaterial);
      sprite.scale.set(10, 10, 1); 
      sprite.position.set(0, 0, 10); 

      return sprite;
    };

    // Add the text sprite to the scene
    const textSprite = createTextSprite('PORTFOLIO');
    scene.add(textSprite);

    // Animation Loop
    function animate() {
      requestAnimationFrame(animate);

      // Moon Orbit
      const time = Date.now() * 0.001;
      moon.position.x = Math.cos(time) * 15; // Horizontal motion
      moon.position.y = Math.sin(time * 1.5) * 5; // Vertical motion (adjust multiplier for more/less tilt)
      moon.position.z = Math.sin(time) * 15; // Depth motion

      // Render with ASCII Effect
      effect.render(scene, currentCamera);
    }

    // Toggle between Perspective and Orthographic Cameras
    document.getElementById('toggleCamera').addEventListener('click', () => {
      currentCamera = (currentCamera === perspectiveCamera) ? orthoCamera : perspectiveCamera;
      controls.object = currentCamera;
      controls.update();
    });

    // Handle Window Resize
    window.addEventListener('resize', () => {
      const aspect = window.innerWidth / window.innerHeight;

      // Update Perspective Camera
      perspectiveCamera.aspect = aspect;
      perspectiveCamera.updateProjectionMatrix();

      // Update Orthographic Camera
      orthoCamera.left = -10 * aspect;
      orthoCamera.right = 10 * aspect;
      orthoCamera.top = 10;
      orthoCamera.bottom = -10;
      orthoCamera.updateProjectionMatrix();

      // Update Renderer and ASCII Effect
      renderer.setSize(window.innerWidth, window.innerHeight);
      effect.setSize(window.innerWidth, window.innerHeight);
    });

    animate();
  </script>
</body>
</html>