Tutorial: A Solar System
A glowing sun, planets going round it at different speeds, a moon going round one of the planets, and a sky full of stars. The trick that makes orbits easy is the group: put a planet in a group at the sun, move the planet out along the group's arm, and turning the group swings the planet round the sun.
How an orbit works
sun (in the middle)
└── orbit group ← turn this
└── planet ← placed 4 units out along x
The planet never moves by itself. Its group turns, and it is carried round, like a ball on the end of a stick. A moon is one more level: a group inside the planet's group, at the planet's position, with the moon out on its own arm.
The program
import math, random
renderer = THREE.WebGLRenderer(canvas=canvas, antialias=True)
renderer.setPixelRatio(pixel_ratio)
renderer.setSize(width, height, False)
scene = THREE.Scene()
scene.background = THREE.Color("black")
camera = THREE.PerspectiveCamera(55, width / height, 0.1, 500)
camera.position.set(0, 14, 24)
controls = addons.OrbitControls(camera, canvas)
controls.enableDamping = True
# --- the sun: glows by itself, and lights everything ---------------------
sun = THREE.Mesh(THREE.SphereGeometry(1.5, 48, 24), THREE.MeshBasicMaterial(color="gold"))
scene.add(sun)
scene.add(THREE.PointLight("white", 400, 0)) # a bulb at the sun's centre
scene.add(THREE.AmbientLight("white", 0.08)) # so night sides are not pure black
# --- planets ---------------------------------------------------------------
# name colour size distance speed (turns a second)
planets_data = [
("Mercury", "darkgray", 0.25, 3.0, 0.40),
("Venus", "navajowhite", 0.45, 4.5, 0.25),
("Earth", "royalblue", 0.5, 6.5, 0.16),
("Mars", "orangered", 0.35, 8.5, 0.11),
("Jupiter", "sandybrown", 1.1, 12.0, 0.05),
]
orbits = [] # (group, speed) pairs to turn each frame
earth = None
for name, colour, size, distance, speed in planets_data:
orbit = THREE.Group()
orbit.rotation.y = random.uniform(0, math.tau) # start somewhere along the orbit
scene.add(orbit)
planet = THREE.Mesh(THREE.SphereGeometry(size, 32, 16), THREE.MeshStandardMaterial(color=colour, roughness=0.8))
planet.position.x = distance
orbit.add(planet)
orbits.append((orbit, speed))
# a faint ring to show the path
path = THREE.Mesh(
THREE.RingGeometry(distance - 0.02, distance + 0.02, 128),
THREE.MeshBasicMaterial(color="#333", side=THREE.DoubleSide),
)
path.rotation.x = -math.pi / 2
scene.add(path)
if name == "Earth":
earth = planet
# --- a moon going round the Earth ---------------------------------------
moon_orbit = THREE.Group()
earth.add(moon_orbit) # the moon's group rides along with the Earth
moon = THREE.Mesh(THREE.SphereGeometry(0.14, 16, 8), THREE.MeshStandardMaterial(color="lightgray"))
moon.position.x = 0.9
moon_orbit.add(moon)
# --- stars: a few thousand points far away ---------------------------------
positions = []
for _ in range(2000):
# a random direction, 80 to 150 units away
theta = random.uniform(0, math.tau)
phi = math.acos(random.uniform(-1, 1))
r = random.uniform(80, 150)
positions += [r * math.sin(phi) * math.cos(theta), r * math.cos(phi), r * math.sin(phi) * math.sin(theta)]
stars_geometry = THREE.BufferGeometry()
stars_geometry.setAttribute("position", THREE.Float32BufferAttribute(positions, 3))
scene.add(THREE.Points(stars_geometry, THREE.PointsMaterial(color="white", size=0.4)))
# --- every frame ---------------------------------------------------------
speed_up = 1.0
def on_key_down(key):
global speed_up
if key == "ArrowUp":
speed_up *= 2
elif key == "ArrowDown":
speed_up /= 2
def update(dt):
for orbit, speed in orbits:
orbit.rotation.y += speed * math.tau * dt * speed_up / 4
moon_orbit.rotation.y += 2 * dt * speed_up
earth.rotation.y += 3 * dt * speed_up # the Earth spins on its own axis too
sun.rotation.y += 0.1 * dt
def draw():
controls.update()
renderer.render(scene, camera)
Drag to look around, pinch or scroll to zoom in, and press the up and down arrows (after clicking the preview) to speed time up or slow it down.
What each part does
- The sun uses
MeshBasicMaterial, which ignores light, so it always looks lit. ThePointLightat the centre is what lights the planets, so each planet's sun-facing side is bright and its far side is dark. See lighting a scene. - Each planet sits
distanceunits out along its own group's x axis. Turning the group (orbit.rotation.y) carries it round. Different speeds give different years. - The moon's group is added to the Earth, so it goes wherever the Earth goes, and turns on its own as well.
- The paths are very thin flat rings (
RingGeometry), laid flat by turning them a quarter turn about x. - The stars are one
Pointsobject with 2,000 positions, placed on a big shell around everything.
The sizes and distances are chosen to fit on screen, not to scale: at true scale the planets would be too small to see.
Try this
- Add Saturn with a ring: a
RingGeometry(1.4, 2.2, 64)added to the planet, tilted a little withrotation.x. - Tilt an orbit: set
orbit.rotation.z = 0.1so the planet goes slightly above and below the others. - Give Mars two small moons, each in its own group with its own speed.