Procedural planet generation in JavaScript is the process of algorithmically creating round, explorable game worlds directly in the browser using mathematical functions instead of hand-crafted sprites. By combining coherent noise algorithms with Voronoi cell partitioning, browser game developers can generate infinite variations of terrain, biomes, and destructible 2D level meshes in real time.
If you have spent any time playing 2D orbital platformers, gravity-based roguelikes, or space exploration web games recently, you have likely marvelled at how smooth these miniature worlds look. Behind that polished finish lies a clever mix of trigonometry, noise, and computational geometry.
[ Seed Value ]
β
βββββββββββ΄ββββββββββ
βΌ βΌ
[ Simplex Noise ] [ Voronoi Cells ]
(Elevation/Hills) (Biomes/Tectonic Plates)
β β
βββββββββββ¬ββββββββββ
βΌ
[ Radial Vertex Mesh ] βββΊ [ Canvas / WebGL Render ]
The Core Trio: Trigonometry, Noise, and Voronoi Cells
Building a round 2D planet from scratch requires three distinct computational layers working in unison:
1. Radial Mapping: Translating a one-dimensional array of height values into a closed circular polygon using polar coordinates (angle and radius).
2. Coherent Noise (Simplex / Perlin): Providing organic, continuous height variations so your planet has rolling hills, jagged peaks, and deep oceans rather than pure static fuzz.
3. Voronoi Tiling: Splitting the circular surface into discrete regional cells for distinct biomes (such as volcanic crags, crystal forests, or subterranean caverns).
Why Pure Randomness Fails
Using standard Math.random() produces disconnected, jagged spikes resembling an angry audio waveform. Coherent noise ensures that adjacent vertices on your planet mesh share mathematically related elevations, yielding natural hills and valleys.
Step 1: Generating the Radial Mesh with Noise
To construct a basic 2D planetary crust, we divide a full circle ($2\pi$ radians) into discrete slices (steps), sample a noise value at each step, and convert the resulting polar coordinates back into Cartesian coordinates $(x, y)$.
Here is a lean implementation demonstrating this in vanilla JavaScript:
// Procedural 2D Planet Mesh Generator
function createPlanetMesh(centerX, centerY, baseRadius, segments, noiseScale, seed) {
const vertices = [];
const angleStep = (Math.PI * 2) / segments;
for (let i = 0; i < segments; i++) {
const angle = i * angleStep;
// Convert polar angle to a 2D sample point for looping noise
const sampleX = Math.cos(angle) * noiseScale + seed;
const sampleY = Math.sin(angle) * noiseScale + seed;
// Basic pseudo-noise sampling (replace with Simplex / FastNoise)
const elevation = Math.sin(sampleX * 4) * Math.cos(sampleY * 4) * 25;
const totalRadius = baseRadius + elevation;
const x = centerX + Math.cos(angle) * totalRadius;
const y = centerY + Math.sin(angle) * totalRadius;
vertices.push({ x, y, angle, radius: totalRadius });
}
return vertices;
}
Because the noise coordinate space loops smoothly as the angle sweeps from $0$ to $2\pi$, the resulting polygon closes seamlessly without an awkward visual seam at the junction.
Step 2: Adding Biomes with Voronoi Cells
While noise controls height, Voronoi diagrams dictate character. Voronoi partitioning places a scatter of seed points across the planet's circumference. Every point on the surface belongs to whichever seed point is nearest.
In browser development, Voronoi diagrams provide two massive benefits:
- Tectonic & Biome Distribution: They naturally group sectors of the circular crust into deserts, tundra, or toxic sludge without manual painting.
- Destructible Mining Nodes: If your game allows players to blast chunks out of the planet, removing entire Voronoi polygons produces believable, angular fracture lines rather than artificial square cutouts.
Comparing Procedural Techniques for Browser Engines
Choosing the right procedural algorithm depends directly on your performance budget and target frame rate on mobile web browsers:
| Algorithm | Computational Cost | Visual Quality | Best Use Case in 2D Web Games |
|---|---|---|---|
| Value Noise | Very Low | Low (blocky transitions) | Retro pixel-art celestial bodies |
| Perlin Noise | Moderate | Smooth, organic curves | Base terrain contours and sea-level depth |
| Simplex Noise | LowβModerate | High (fewer directional artefacts) | High-speed, real-time infinite world generation |
| Voronoi / Worley | ModerateβHigh | Sharp, cellular segments | Biomes, destructible mineral veins, caves |
| Marching Squares | High | Dynamic iso-surfaces | Fully deformable, subterranean 2D caverns |
Handling 2D Radial Gravity and Physics
Once your procedural planet exists as a dynamic vertex loop, standard downward physics (velocity.y += gravity) stops working.
To implement radial physics:
- Attraction Vector: For any player or projectile at position $(p_x, p_y)$, calculate the angle to the planet centre $(c_x, c_y)$ using
Math.atan2(c_y - p_y, c_x - p_x). - Surface Alignment: Rotate the player sprite's transform matrix to match the normal angle of the nearest planet polygon edge.
- Raycast Snapping: Check the distance from the player to the planet centre against the interpolated radius of the nearest two mesh vertices to prevent clipping through procedural crusts.
Practical Takeaways for Web Game Developers
- Pre-generate Collision Buffers: Avoid calculating noise on every animation frame. Generate the vertex array once, store it in an
ArrayBufferorFloat32Array, and only recalculate local segments when cratering terrain. - Keep Mesh Resolutions Sensible: Between 120 and 250 vertices are typically plenty for smooth circular contours on standard mobile displays without bogging down canvas redraw routines.
- Layer Multiple Noise Octaves: Combine low-frequency noise (for broad continental shelves) with high-frequency, low-amplitude noise (for surface crags) to give your planets instant visual depth.