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>