How to make a spherical polyhedron (soccer or football ball) with three.js
Categories:
Crafting Spherical Polyhedra (Soccer Balls) with Three.js

Learn how to programmatically generate a realistic spherical polyhedron, like a soccer ball, using Three.js. This guide covers the mathematical principles and Three.js implementation.
Creating complex 3D shapes in a web browser often involves libraries like Three.js. While Three.js provides basic primitives, generating more intricate geometries like a spherical polyhedron (often seen as a soccer ball or geodesic dome) requires a deeper understanding of its underlying structure and how to manipulate vertices and faces. This article will guide you through the process of constructing such a shape, starting from a basic icosahedron and subdividing its faces to approximate a sphere.
Understanding the Icosahedron Base
The foundation of a spherical polyhedron is typically an icosahedron. An icosahedron is one of the five Platonic solids, characterized by 20 equilateral triangular faces, 12 vertices, and 30 edges. Its highly symmetrical nature makes it an ideal starting point for approximating a sphere through subdivision. Each vertex of an icosahedron is equidistant from its center, and its faces are all identical equilateral triangles.
graph TD A[Start with Icosahedron] --> B{Subdivide Each Face} B --> C[Find Midpoints of Edges] C --> D[Normalize Midpoints to Sphere Surface] D --> E[Create New Faces from Subdivided Triangles] E --> F[Repeat Subdivision for Desired Smoothness] F --> G[Apply Material and Render]
High-level process for generating a spherical polyhedron.
Subdivision Algorithm: From Icosahedron to Sphere
The core of creating a spherical polyhedron lies in the subdivision algorithm. This process involves taking each triangular face of the base icosahedron and dividing it into smaller triangles. For each edge of a triangle, a new vertex is created at its midpoint. These midpoints, along with the original vertices, are then normalized (projected) onto the surface of a sphere. This creates four new triangles from each original triangle. Repeating this process multiple times increases the number of faces and vertices, making the shape progressively smoother and more spherical.
function subdivide(geometry, iterations) {
const positions = geometry.attributes.position.array;
const newPositions = [];
const newFaces = [];
const getMidpoint = (v1, v2) => {
const x = (positions[v1 * 3] + positions[v2 * 3]) / 2;
const y = (positions[v1 * 3 + 1] + positions[v2 * 3 + 1]) / 2;
const z = (positions[v1 * 3 + 2] + positions[v2 * 3 + 2]) / 2;
const length = Math.sqrt(x*x + y*y + z*z);
return [x / length, y / length, z / length]; // Normalize to sphere
};
// Simplified for brevity, actual implementation involves managing indices and new vertices
// This example focuses on the conceptual normalization step
// ... (full subdivision logic would go here)
return new THREE.BufferGeometry().setFromPoints(newPositions);
}
Implementing in Three.js
Three.js provides BufferGeometry
which is highly efficient for custom geometries. You'll define your initial icosahedron vertices and faces, then apply the subdivision algorithm. After subdivision, you'll update the position
attribute of your BufferGeometry
and define the new faces using index
attributes. Finally, apply a material and add it to your scene.
import * as THREE from 'three';
function createSphericalPolyhedron(radius = 1, detail = 2) {
const geometry = new THREE.IcosahedronGeometry(radius, 0); // Start with basic icosahedron
const positions = geometry.attributes.position.array;
const indices = geometry.index.array;
let currentVertices = [];
for (let i = 0; i < positions.length; i += 3) {
currentVertices.push(new THREE.Vector3(positions[i], positions[i + 1], positions[i + 2]));
}
let currentFaces = [];
for (let i = 0; i < indices.length; i += 3) {
currentFaces.push([indices[i], indices[i + 1], indices[i + 2]]);
}
// Perform subdivision iterations
for (let i = 0; i < detail; i++) {
const nextFaces = [];
const midpoints = new Map(); // Cache midpoints to avoid duplicates
const getMidpointIndex = (v1Index, v2Index) => {
const key = `${Math.min(v1Index, v2Index)}-${Math.max(v1Index, v2Index)}`;
if (midpoints.has(key)) {
return midpoints.get(key);
}
const v1 = currentVertices[v1Index];
const v2 = currentVertices[v2Index];
const mid = new THREE.Vector3().addVectors(v1, v2).normalize().multiplyScalar(radius);
currentVertices.push(mid);
const newIndex = currentVertices.length - 1;
midpoints.set(key, newIndex);
return newIndex;
};
for (const face of currentFaces) {
const [a, b, c] = face;
const ab = getMidpointIndex(a, b);
const bc = getMidpointIndex(b, c);
const ca = getMidpointIndex(c, a);
nextFaces.push([a, ab, ca]);
nextFaces.push([b, bc, ab]);
nextFaces.push([c, ca, bc]);
nextFaces.push([ab, bc, ca]);
}
currentFaces = nextFaces;
}
const finalGeometry = new THREE.BufferGeometry();
finalGeometry.setAttribute('position', new THREE.Float32BufferAttribute(currentVertices.flatMap(v => v.toArray()), 3));
finalGeometry.setIndex(currentFaces.flat());
finalGeometry.computeVertexNormals();
const material = new THREE.MeshPhongMaterial({ color: 0x00ff00, flatShading: true });
return new THREE.Mesh(finalGeometry, material);
}
// Example usage:
// const scene = new THREE.Scene();
// const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
// const renderer = new THREE.WebGLRenderer();
// renderer.setSize(window.innerWidth, window.innerHeight);
// document.body.appendChild(renderer.domElement);
// const soccerBall = createSphericalPolyhedron(1, 3); // Radius 1, 3 subdivisions
// scene.add(soccerBall);
// camera.position.z = 5;
// function animate() {
// requestAnimationFrame(animate);
// soccerBall.rotation.x += 0.01;
// soccerBall.rotation.y += 0.01;
// renderer.render(scene, camera);
// }
// animate();
Three.js code to create a spherical polyhedron with subdivision.
1. Initialize Three.js Scene
Set up your basic Three.js scene, including a camera, renderer, and a light source to illuminate your polyhedron.
2. Create createSphericalPolyhedron
Function
Implement the createSphericalPolyhedron
function as shown in the code example. This function will take a radius
and detail
(number of subdivisions) as arguments.
3. Add to Scene and Animate
Call your createSphericalPolyhedron
function, add the resulting THREE.Mesh
to your scene, and set up an animation loop to render the scene and potentially rotate your polyhedron.
4. Experiment with Detail
Adjust the detail
parameter to see how it affects the smoothness and complexity of the spherical polyhedron. Higher detail values will result in more vertices and faces, leading to a smoother appearance but also increased computational cost.