Custom Code Experiment •
ASCII: Animated Shapes
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>ASCII Animations 1-5</title>
<style>
body {
margin: 0;
padding: 0;
background-color: #000;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-height: 100vh;
overflow: hidden;
}
pre {
font-family: 'Courier New', monospace;
font-size: 18px;
line-height: 1.1;
color: #fff;
text-align: center;
margin: 0;
padding: 0;
filter: brightness(1.1);
text-shadow: 0 0 2px rgba(255, 255, 255, 0.8);
white-space: pre;
width: 100%;
height: 484px;
display: flex;
align-items: center;
justify-content: center;
}
.nav-container {
position: fixed;
top: 30px;
display: flex;
align-items: center;
gap: 20px;
z-index: 10;
background-color: rgba(0, 0, 0, 0.6);
padding: 8px 16px;
border-radius: 10px;
border: 1px solid rgba(255, 255, 255, 0.2);
backdrop-filter: blur(10px);
}
.nav-button {
padding: 8px 16px;
font-size: 16px;
font-family: 'Courier New', monospace;
background-color: rgba(34, 34, 34, 0.5);
color: #fff;
border: 1px solid rgba(255, 255, 255, 0.3);
cursor: pointer;
border-radius: 6px;
transition: all 0.3s ease;
min-width: 40px;
text-align: center;
user-select: none;
}
.nav-button:hover {
background-color: rgba(68, 68, 68, 0.7);
border-color: rgba(255, 255, 255, 0.6);
box-shadow: 0 0 10px rgba(255, 255, 255, 0.4);
transform: translateY(-1px);
}
.nav-button:active {
transform: translateY(0);
box-shadow: 0 0 5px rgba(255, 255, 255, 0.3);
}
.animation-number {
color: #fff;
font-family: 'Courier New', monospace;
font-size: 20px;
font-weight: bold;
min-width: 40px;
text-align: center;
padding: 6px 12px;
border: 1px solid rgba(255, 255, 255, 0.3);
border-radius: 6px;
background-color: rgba(255, 255, 255, 0.05);
}
.arrow {
font-size: 18px;
font-weight: bold;
}
</style>
</head>
<body>
<div class="nav-container">
<button class="nav-button arrow" id="prevBtn">←</button>
<div class="animation-number" id="animationNumber">1</div>
<button class="nav-button arrow" id="nextBtn">→</button>
</div>
<pre id="display"></pre>
<script>
const pre = document.getElementById('display');
const prevBtn = document.getElementById('prevBtn');
const nextBtn = document.getElementById('nextBtn');
const animationNumber = document.getElementById('animationNumber');
const animations = ['donut', 'cube', 'octahedron', 'dodecahedron', 'tetrahedron'];
let currentIndex = 0;
let animationId = null;
// Donut variables
let x = 1760, z = 0, y = 0;
// Shared variables for 3D shapes
let A = 0, B = 0, C = 0;
const width = 160;
const height = 44;
const zBuffer = new Array(width * height);
const buffer = new Array(width * height);
const backgroundASCIICode = ' ';
const distanceFromCam = 100;
const K1 = 40;
// 3D rotation and projection functions
const calculateX = (i, j, k) => {
return j * Math.sin(A) * Math.sin(B) * Math.cos(C) - k * Math.cos(A) * Math.sin(B) * Math.cos(C) +
j * Math.cos(A) * Math.sin(C) + k * Math.sin(A) * Math.sin(C) + i * Math.cos(B) * Math.cos(C);
};
const calculateY = (i, j, k) => {
return j * Math.cos(A) * Math.cos(C) + k * Math.sin(A) * Math.cos(C) -
j * Math.sin(A) * Math.sin(B) * Math.sin(C) + k * Math.cos(A) * Math.sin(B) * Math.sin(C) -
i * Math.cos(B) * Math.sin(C);
};
const calculateZ = (i, j, k) => {
return k * Math.cos(A) * Math.cos(B) - j * Math.sin(A) * Math.cos(B) + i * Math.sin(B);
};
const renderPoint = (px, py, pz, ch) => {
const x = calculateX(px, py, pz);
const y = calculateY(px, py, pz);
const z = calculateZ(px, py, pz) + distanceFromCam;
const ooz = 1 / z;
const xp = Math.floor(width / 2 + K1 * ooz * x * 2);
const yp = Math.floor(height / 2 + K1 * ooz * y);
const idx = xp + yp * width;
if (idx >= 0 && idx < width * height) {
if (ooz > zBuffer[idx]) {
zBuffer[idx] = ooz;
buffer[idx] = ch;
}
}
};
// Donut animation
const animateDonut = () => {
z += 0.02;
y += 0.01;
const a = [...new Array(x)].map((a, r) => r % 80 === 79 ? "\n" : " ");
const r = new Array(x).fill(0);
const t = Math.cos(z);
const e = Math.sin(z);
const n = Math.cos(y);
const o = Math.sin(y);
for (let s = 0; s < 6.28; s += 0.07) {
const c = Math.cos(s);
const h = Math.sin(s);
for (let s = 0; s < 6.28; s += 0.02) {
const v = Math.sin(s);
const M = Math.cos(s);
const i = c + 2;
const l = 1 / (v * i * e + h * t + 5);
const p = v * i * t - h * e;
const d = 0 | (40 + 30 * l * (M * i * n - p * o));
const m = 0 | (12 + 15 * l * (M * i * o + p * n));
const f = 0 | (8 * ((h * e - v * c * t) * n - v * c * e - h * t - M * c * o));
const yIndex = d + 80 * m;
if (m < 22 && m >= 0 && d >= 0 && d < 79 && l > r[yIndex]) {
r[yIndex] = l;
a[yIndex] = ".,-~:;=!*#$@"[f > 0 ? f : 0];
}
}
}
pre.textContent = a.join("");
if (animations[currentIndex] === 'donut') {
setTimeout(() => {
animationId = requestAnimationFrame(animateDonut);
}, 100);
}
};
// Cube animation
const animateCube = () => {
buffer.fill(backgroundASCIICode);
zBuffer.fill(0);
const cubeSize = 20;
for (let cubeX = -cubeSize; cubeX < cubeSize; cubeX += 0.5) {
for (let cubeY = -cubeSize; cubeY < cubeSize; cubeY += 0.5) {
renderPoint(cubeX, cubeY, -cubeSize, '@');
renderPoint(cubeX, cubeY, cubeSize, '#');
renderPoint(-cubeSize, cubeY, cubeX, '$');
renderPoint(cubeSize, cubeY, cubeX, '~');
renderPoint(cubeX, cubeSize, cubeY, ';');
renderPoint(cubeX, -cubeSize, cubeY, '+');
}
}
let output = '';
for (let k = 0; k < width * height; k++) {
output += k % width === 0 && k !== 0 ? '\n' : buffer[k];
}
pre.textContent = output;
A += 0.05;
B += 0.05;
C += 0.01;
if (animations[currentIndex] === 'cube') {
setTimeout(() => {
animationId = requestAnimationFrame(animateCube);
}, 50);
}
};
// Octahedron animation
const animateOctahedron = () => {
buffer.fill(backgroundASCIICode);
zBuffer.fill(0);
const size = 20;
for (let u = 0; u <= 1; u += 0.02) {
for (let v = 0; v <= 1 - u; v += 0.02) {
const x1 = (u - 0.5) * size * 2;
const y1 = size - (u + v) * size;
const z1 = (v - 0.5) * size * 2;
renderPoint(x1, y1, z1, '*');
const x2 = (v - 0.5) * size * 2;
const y2 = size - (u + v) * size;
const z2 = -(u - 0.5) * size * 2;
renderPoint(x2, y2, z2, '@');
const x3 = -(u - 0.5) * size * 2;
const y3 = size - (u + v) * size;
const z3 = -(v - 0.5) * size * 2;
renderPoint(x3, y3, z3, '#');
const x4 = -(v - 0.5) * size * 2;
const y4 = size - (u + v) * size;
const z4 = (u - 0.5) * size * 2;
renderPoint(x4, y4, z4, '$');
}
}
for (let u = 0; u <= 1; u += 0.02) {
for (let v = 0; v <= 1 - u; v += 0.02) {
const x1 = (u - 0.5) * size * 2;
const y1 = -size + (u + v) * size;
const z1 = (v - 0.5) * size * 2;
renderPoint(x1, y1, z1, '+');
const x2 = (v - 0.5) * size * 2;
const y2 = -size + (u + v) * size;
const z2 = -(u - 0.5) * size * 2;
renderPoint(x2, y2, z2, '~');
const x3 = -(u - 0.5) * size * 2;
const y3 = -size + (u + v) * size;
const z3 = -(v - 0.5) * size * 2;
renderPoint(x3, y3, z3, '=');
const x4 = -(v - 0.5) * size * 2;
const y4 = -size + (u + v) * size;
const z4 = (u - 0.5) * size * 2;
renderPoint(x4, y4, z4, ':');
}
}
let output = '';
for (let k = 0; k < width * height; k++) {
output += k % width === 0 && k !== 0 ? '\n' : buffer[k];
}
pre.textContent = output;
A += 0.05;
B += 0.05;
C += 0.01;
if (animations[currentIndex] === 'octahedron') {
setTimeout(() => {
animationId = requestAnimationFrame(animateOctahedron);
}, 50);
}
};
// Dodecahedron animation
const animateDodecahedron = () => {
buffer.fill(backgroundASCIICode);
zBuffer.fill(0);
const size = 60;
const phi = (1 + Math.sqrt(5)) / 2;
const a = size / 2;
const b = size / (2 * phi);
const verts = [
[0, b, -a], [b, a, 0], [-b, a, 0], [0, b, a],
[0, -b, a], [-a, 0, b], [a, 0, b], [0, -b, -a],
[a, 0, -b], [-a, 0, -b], [b, -a, 0], [-b, -a, 0]
];
const faces = [
[0,1,2],[3,2,1],[3,4,5],[3,1,6],[0,2,9],[0,8,1],[0,7,8],
[0,9,7],[4,3,6],[4,10,5],[6,1,10],[7,11,8],[5,11,4],[10,11,5],
[8,11,10],[8,10,1],[9,2,11],[9,11,7],[2,3,5],[2,5,11]
];
const chars = '@#$~*+=:;.-%^&!?';
faces.forEach((face, idx) => {
for (let u = 0; u <= 1; u += 0.015) {
for (let v = 0; v <= 1 - u; v += 0.015) {
const v0 = verts[face[0]];
const v1 = verts[face[1]];
const v2 = verts[face[2]];
const px = v0[0] * (1 - u - v) + v1[0] * u + v2[0] * v;
const py = v0[1] * (1 - u - v) + v1[1] * u + v2[1] * v;
const pz = v0[2] * (1 - u - v) + v1[2] * u + v2[2] * v;
renderPoint(px, py, pz, chars[idx % chars.length]);
}
}
});
let output = '';
for (let k = 0; k < width * height; k++) {
output += k % width === 0 && k !== 0 ? '\n' : buffer[k];
}
pre.textContent = output;
A += 0.05;
B += 0.05;
C += 0.01;
if (animations[currentIndex] === 'dodecahedron') {
setTimeout(() => {
animationId = requestAnimationFrame(animateDodecahedron);
}, 50);
}
};
// Tetrahedron animation
const animateTetrahedron = () => {
buffer.fill(backgroundASCIICode);
zBuffer.fill(0);
const size = 35;
const vertices = [
[size, size, size],
[-size, -size, size],
[-size, size, -size],
[size, -size, -size]
];
const faces = [
[0, 1, 2],
[0, 1, 3],
[0, 2, 3],
[1, 2, 3]
];
const chars = ['@', '#', '$', '*'];
faces.forEach((face, faceIdx) => {
for (let u = 0; u <= 1; u += 0.015) {
for (let v = 0; v <= 1 - u; v += 0.015) {
const v0 = vertices[face[0]];
const v1 = vertices[face[1]];
const v2 = vertices[face[2]];
const px = v0[0] * (1 - u - v) + v1[0] * u + v2[0] * v;
const py = v0[1] * (1 - u - v) + v1[1] * u + v2[1] * v;
const pz = v0[2] * (1 - u - v) + v1[2] * u + v2[2] * v;
renderPoint(px, py, pz, chars[faceIdx]);
}
}
});
let output = '';
for (let k = 0; k < width * height; k++) {
output += k % width === 0 && k !== 0 ? '\n' : buffer[k];
}
pre.textContent = output;
A += 0.05;
B += 0.05;
C += 0.01;
if (animations[currentIndex] === 'tetrahedron') {
setTimeout(() => {
animationId = requestAnimationFrame(animateTetrahedron);
}, 50);
}
};
// Stop current animation and start new one
const switchAnimation = () => {
if (animationId) {
cancelAnimationFrame(animationId);
}
// Reset animation variables
x = 1760; z = 0; y = 0;
A = 0; B = 0; C = 0;
// Update display
animationNumber.textContent = (currentIndex + 1).toString();
// Start new animation
if (animations[currentIndex] === 'donut') {
animateDonut();
} else if (animations[currentIndex] === 'cube') {
animateCube();
} else if (animations[currentIndex] === 'octahedron') {
animateOctahedron();
} else if (animations[currentIndex] === 'dodecahedron') {
animateDodecahedron();
} else if (animations[currentIndex] === 'tetrahedron') {
animateTetrahedron();
}
};
// Navigation functions
prevBtn.addEventListener('click', () => {
currentIndex = (currentIndex - 1 + animations.length) % animations.length;
switchAnimation();
});
nextBtn.addEventListener('click', () => {
currentIndex = (currentIndex + 1) % animations.length;
switchAnimation();
});
// Keyboard navigation
document.addEventListener('keydown', (e) => {
if (e.key === 'ArrowLeft') {
currentIndex = (currentIndex - 1 + animations.length) % animations.length;
switchAnimation();
} else if (e.key === 'ArrowRight') {
currentIndex = (currentIndex + 1) % animations.length;
switchAnimation();
}
});
// Start with first animation
switchAnimation();
</script>
</body>
</html>