b3d-planet
Procedural planet mesh using a subdivided cube projected onto a sphere.
Height displacement from 3D Perlin noise (gross + detail layers) with
gradient filters for terrain shaping. Same noise system as b3d-terrain
so ground-level and orbital views are consistent.
Optional atmosphere (glow shell) and ocean (water sphere at sea level).
Demo
import { b3d, b3dSun, b3dSkybox, b3dLight, b3dPlanet, label3d, slider3d, toggle3d } from 'tosijs-3d'
import { tosi, elements } from 'tosijs'
const { div, p } = elements
const { demo } = tosi({
demo: {
grossScale: 0.005,
detailScale: 0.02,
grossAmplitude: 5,
detailAmplitude: 1,
atmosphere: 0.08,
ocean: 0.6,
wireframe: false,
rotationSpeed: 0.05,
},
})
const planet = b3dPlanet({
seed: 42,
radius: 50,
subdivisions: 64,
biome: 'on', // the biome shader's planetary front-end — radial altitude, insolation
grossScale: demo.grossScale,
detailScale: demo.detailScale,
grossAmplitude: demo.grossAmplitude,
detailAmplitude: demo.detailAmplitude,
atmosphere: demo.atmosphere,
ocean: demo.ocean,
wireframe: demo.wireframe,
rotationSpeed: demo.rotationSpeed,
})
const scene = b3d(
{
frameRate: 60,
scenePanel: () => [
label3d({ text: 'Planet' }),
slider3d({ label: 'gross scale', value: demo.grossScale, min: 0.001, max: 0.05, step: 0.001 }),
slider3d({ label: 'detail scale', value: demo.detailScale, min: 0.005, max: 0.1, step: 0.005 }),
slider3d({ label: 'gross amp', value: demo.grossAmplitude, min: 0, max: 20, step: 0.5 }),
slider3d({ label: 'detail amp', value: demo.detailAmplitude, min: 0, max: 5, step: 0.1 }),
slider3d({ label: 'rotation', value: demo.rotationSpeed, min: 0, max: 0.5, step: 0.01 }),
slider3d({ label: 'atmosphere', value: demo.atmosphere, min: 0, max: 0.2, step: 0.01 }),
slider3d({ label: 'ocean', value: demo.ocean, min: 0, max: 1, step: 0.05 }),
toggle3d({ label: 'wireframe', value: demo.wireframe }),
// The biome climate, in the terms you think in: temperature at the
// equator, the temperate zone, and the pole — plus moisture and the
// rain-shadow strength. All live (bindForSubMesh reads each frame).
label3d({ text: 'Climate' }),
...(planet.biomePlugin
? (() => {
const p = planet.biomePlugin.params
const bind = (label, key, min, max, step) =>
slider3d({ label, value: p[key], min, max, step, onChange: (v) => { p[key] = v } })
return [
bind('equator temp', 'equatorTemp', 0, 1, 0.01),
bind('temperate temp', 'temperateTemp', 0, 1, 0.01),
bind('pole temp', 'poleTemp', 0, 1, 0.01),
bind('moisture', 'mapMoisture', 0, 1, 0.01),
bind('rain shadow', 'rainShadow', 0, 0.6, 0.01),
bind('volcanism', 'volcanism', 0, 1, 0.01),
]
})()
: []),
],
sceneCreated(el, BABYLON) {
const camera = new BABYLON.ArcRotateCamera(
'orbit-cam',
-Math.PI / 2,
Math.PI / 3,
150,
BABYLON.Vector3.Zero(),
el.scene
)
camera.lowerRadiusLimit = 60
camera.upperRadiusLimit = 500
camera.minZ = 0.5
camera.maxZ = 2000
camera.attachControl(el.querySelector('canvas'), true)
el.setActiveCamera(camera)
},
},
b3dSun(),
b3dSkybox({ timeOfDay: 10, realtimeScale: 0 }),
b3dLight({ intensity: 0.3 }),
planet,
)
preview.append(
scene,
div({ class: 'debug-panel' }, p('Scroll to zoom, drag to orbit'))
)
for (const key of ['grossScale', 'detailScale', 'grossAmplitude', 'detailAmplitude']) {
demo[key].observe(() => {
planet.regenerate()
})
}
for (const key of ['atmosphere', 'ocean', 'wireframe', 'rotationSpeed']) {
demo[key].observe(() => {
planet.updateOptions()
})
}
tosi-b3d {
width: 100%;
height: 100%;
}
.debug-panel {
position: absolute;
top: 8px;
right: 8px;
background: rgba(0,0,0,0.6);
color: white;
padding: 8px 12px;
border-radius: 6px;
font: 12px monospace;
display: flex;
flex-direction: column;
gap: 2px;
}
Attributes
| Attribute | Default | Description |
|---|---|---|
seed |
12345 |
Noise seed |
radius |
50 |
Base sphere radius |
subdivisions |
64 |
Grid subdivisions per cube face |
grossScale |
0.005 |
Gross noise frequency |
detailScale |
0.02 |
Detail noise frequency |
grossAmplitude |
5 |
Gross height multiplier |
detailAmplitude |
1 |
Detail height multiplier |
atmosphere |
0.08 |
Atmosphere thickness (fraction of radius, 0=none) |
ocean |
0.6 |
Ocean coverage (0=none, 0.6=60% of surface underwater) |
wireframe |
false |
Debug: render as wireframe |
rotationSpeed |
0 |
Auto-rotation speed (radians/sec) |