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,
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 }),
],
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) |