Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

C++ OpenGL/GLUT Solar System Simulation with Lighting and Camera Controls

Tech 4

C++ OpenGL/GLUT programs render to a window created and driven by GLUT’s event loop. OpenGL performs drawing; GLUT supplies cross‑platform windowing, input, and callbacks.

  • Install GLUT (Ubuntu/Debian): sudo apt-get update && sudo apt-get install freeglut3 freeglut3-dev

A minimal GLUT program wires display, idle, and keyboard callbacks and starts the loop:

// main.cpp
#include <GL/glut.h>
#include "scene.hpp"

static const int kWinW = 700;
static const int kWinH = 700;
static const int kWinX = 60;
static const int kWinY = 40;

SolarScene gScene;

static void displayCb() { gScene.render(); }
static void idleCb()    { gScene.tick(); }
static void keyCb(unsigned char key, int x, int y) { gScene.onKey(key, x, y); }

int main(int argc, char** argv) {
    glutInit(&argc, argv);
    glutInitDisplayMode(GLUT_RGBA | GLUT_DOUBLE | GLUT_DEPTH);
    glutInitWindowSize(kWinW, kWinH);
    glutInitWindowPosition(kWinX, kWinY);
    glutCreateWindow("Solar System (OpenGL/GLUT)");

    glutDisplayFunc(displayCb);
    glutIdleFunc(idleCb);
    glutKeyboardFunc(keyCb);

    glutMainLoop();
    return 0;
}

Double buffering draws into a hidden back buffer and swaps to the screen in one operation, avoiding flicker. GLUT_DOUBLE enables this; glutSwapBuffers() performs the swap.

OpenGL core matrix usage

  • glMatrixMode selects the target matrix stack: GL_PROJECTION (projection), GL_MODELVIEW (model and view), GL_TEXTURE (textures).
  • Always glLoadIdentity() before setting up a new transform block.
  • Use gluPerspective for perspective frustum and gluLookAt for camera transform.

Common immediate-mode calls

  • glEnable(GLenum cap): toggle features such as GL_LIGHTING, GL_LIGHT0, GL_DEPTH_TEST.
  • glPushMatrix / glPopMatrix: save/restore the current transform.
  • glTranslatef, glRotatef: compose transforms.
  • glBegin/glEnd with glVertex*: submit primitives. Example: circle via line loop:
// Draw a 2D circle of radius r
static void drawCircle(float r, int segments) {
    glBegin(GL_LINE_LOOP);
    for (int i = 0; i < segments; ++i) {
        float t = 2.0f * 3.1415926535f * i / segments;
        glVertex2f(r * cosf(t), r * sinf(t));
    }
    glEnd();
}

Object model

  • Every body has: radius, orbit radius, orbital period, spin speed, RGBA color, an optional parent body, and two angles (orbit, spin).
  • Assumptions: circular orbits, uniform angular velocities, one simulation tick equals one day.
// body.hpp
#ifndef BODY_HPP
#define BODY_HPP
#include <GL/glut.h>

class Body {
public:
    Body(GLfloat radius, GLfloat orbitRadius, GLfloat orbitDays, GLfloat spinDegPerTick, Body* parent);
    virtual ~Body() {}

    virtual void step(GLfloat dt);
    virtual void render();

    GLfloat color[4];

protected:
    GLfloat r_;
    GLfloat orbitR_;
    GLfloat orbitDegPerDay_;  // degrees progressed per day for revolution
    GLfloat spinDegPerTick_;  // degrees per tick for self-rotation
    GLfloat orbitAngle_;
    GLfloat spinAngle_;
    Body* parent_;

    void applyParentTransform() const;
    void drawOrbit() const;
    void drawSphere() const;
};

class PlanetBody : public Body {
public:
    PlanetBody(GLfloat radius, GLfloat orbitRadius, GLfloat orbitDays, GLfloat spinDegPerTick,
               Body* parent, const GLfloat rgb[3]);
    void render() override; // sets material, then draws
};

class EmissiveBody : public PlanetBody { // e.g., the Sun (light emitter)
public:
    EmissiveBody(GLfloat radius, GLfloat spinDegPerTick, const GLfloat rgb[3]);
    void render() override; // configures GL_LIGHT0 + emissive material
};

#endif
// body.cpp
#include "body.hpp"
#include <cmath>

static const float kPI = 3.14159265358979323846f;

Body::Body(GLfloat radius, GLfloat orbitRadius, GLfloat orbitDays, GLfloat spinDegPerTick, Body* parent)
    : r_(radius), orbitR_(orbitRadius),
      orbitDegPerDay_(orbitDays > 0 ? 360.0f / orbitDays : 0.0f),
      spinDegPerTick_(spinDegPerTick),
      orbitAngle_(0.0f), spinAngle_(0.0f), parent_(parent) {
    color[0] = color[1] = color[2] = color[3] = 1.0f;
}

void Body::step(GLfloat dt) {
    orbitAngle_ += orbitDegPerDay_ * dt;
    spinAngle_  += spinDegPerTick_ * dt;
}

void Body::applyParentTransform() const {
    if (parent_ && parent_->orbitR_ > 0.0f) {
        glRotatef(parent_->orbitAngle_, 0.0f, 0.0f, 1.0f);
        glTranslatef(parent_->orbitR_, 0.0f, 0.0f);
    }
}

void Body::drawOrbit() const {
    if (orbitR_ <= 0.0f) return;
    glColor3f(0.6f, 0.6f, 0.6f);
    glBegin(GL_LINE_LOOP);
    const int segments = 256;
    for (int i = 0; i < segments; ++i) {
        float t = 2.0f * kPI * i / segments;
        glVertex2f(orbitR_ * std::cos(t), orbitR_ * std::sin(t));
    }
    glEnd();
}

void Body::drawSphere() const {
    glutSolidSphere(r_, 36, 28);
}

void Body::render() {
    glPushMatrix();
    {
        applyParentTransform();
        drawOrbit();
        glRotatef(orbitAngle_, 0.0f, 0.0f, 1.0f);
        glTranslatef(orbitR_, 0.0f, 0.0f);
        glRotatef(spinAngle_, 0.0f, 0.0f, 1.0f);
        glColor3f(color[0], color[1], color[2]);
        drawSphere();
    }
    glPopMatrix();
}

PlanetBody::PlanetBody(GLfloat radius, GLfloat orbitRadius, GLfloat orbitDays, GLfloat spinDegPerTick,
                       Body* parent, const GLfloat rgb[3])
    : Body(radius, orbitRadius, orbitDays, spinDegPerTick, parent) {
    color[0] = rgb[0];
    color[1] = rgb[1];
    color[2] = rgb[2];
    color[3] = 1.0f;
}

void PlanetBody::render() {
    // Simple material: subtle ambient/diffuse, modest specular, faint emission
    const GLfloat amb[]  = {0.1f, 0.1f, 0.1f, 1.0f};
    const GLfloat diff[] = {color[0], color[1], color[2], 1.0f};
    const GLfloat spec[] = {0.3f, 0.3f, 0.3f, 1.0f};
    const GLfloat emi[]  = {color[0] * 0.05f, color[1] * 0.05f, color[2] * 0.05f, 1.0f};
    const GLfloat shin   = 60.0f;

    glMaterialfv(GL_FRONT, GL_AMBIENT,  amb);
    glMaterialfv(GL_FRONT, GL_DIFFUSE,  diff);
    glMaterialfv(GL_FRONT, GL_SPECULAR, spec);
    glMaterialfv(GL_FRONT, GL_EMISSION, emi);
    glMaterialf (GL_FRONT, GL_SHININESS, shin);

    Body::render();
}

EmissiveBody::EmissiveBody(GLfloat radius, GLfloat spinDegPerTick, const GLfloat rgb[3])
    : PlanetBody(radius, 0.0f, 0.0f, spinDegPerTick, nullptr, rgb) {}

void EmissiveBody::render() {
    // White point light at the origin (w=1 means positional light)
    const GLfloat pos[] = {0.0f, 0.0f, 0.0f, 1.0f};
    const GLfloat amb[] = {0.0f, 0.0f, 0.0f, 1.0f};
    const GLfloat dif[] = {1.0f, 1.0f, 1.0f, 1.0f};
    const GLfloat spc[] = {1.0f, 1.0f, 1.0f, 1.0f};

    glLightfv(GL_LIGHT0, GL_POSITION, pos);
    glLightfv(GL_LIGHT0, GL_AMBIENT,  amb);
    glLightfv(GL_LIGHT0, GL_DIFFUSE,  dif);
    glLightfv(GL_LIGHT0, GL_SPECULAR, spc);

    // Strongly emissive material for the star
    const GLfloat starEmi[] = {color[0], color[1], color[2], 1.0f};
    glMaterialfv(GL_FRONT, GL_EMISSION, starEmi);

    Body::render();
}

Scene management and camera

// scene.hpp
#ifndef SCENE_HPP
#define SCENE_HPP
#include <GL/glut.h>
#include "body.hpp"

class SolarScene {
public:
    SolarScene();
    ~SolarScene();

    void render();
    void tick();
    void onKey(unsigned char key, int x, int y);

private:
    static const int kCount = 10;
    Body* bodies_[kCount];

    GLdouble eye_[3];
    GLdouble target_[3];
    GLdouble up_[3];
};

#endif
// scene.cpp
#include "scene.hpp"

// Camera defaults
static const GLdouble kCamR   = 700.0;
static const GLdouble kEye[3] = {0.0, -kCamR,  kCamR};
static const GLdouble kAt [3] = {0.0,    0.0,   0.0};
static const GLdouble kUp [3] = {0.0,    0.0,   1.0};

// One tick is one day; spins advance by this many degrees per tick
static const float kTickDays = 1.0f;
static const float kSpinStep = 3.0f;

// Body indices
enum Id { Sun, Mercury, Venus, Earth, Moon, Mars, Jupiter, Saturn, Uranus, Neptune };

// Radii (visual scale units)
#define SUN_R  48.74f
#define MER_R   7.32f
#define VEN_R  18.15f
#define EAR_R  19.13f
#define MOO_R   6.15f
#define MAR_R  10.19f
#define JUP_R  42.90f
#define SAT_R  36.16f
#define URA_R  25.56f
#define NEP_R  24.78f

// Orbit radii
#define MER_ORB   62.06f
#define VEN_ORB  115.56f
#define EAR_ORB  168.00f
#define MOO_ORB   26.01f
#define MAR_ORB  228.00f
#define JUP_ORB  333.40f
#define SAT_ORB  428.10f
#define URA_ORB  848.00f
#define NEP_ORB  949.10f

// Orbital periods (days)
#define MER_DAYS   87.0f
#define VEN_DAYS  225.0f
#define EAR_DAYS  365.0f
#define MOO_DAYS   30.0f
#define MAR_DAYS  687.0f
#define JUP_DAYS 1298.4f
#define SAT_DAYS 3225.6f
#define URA_DAYS 3066.4f
#define NEP_DAYS 6014.8f

#define SET3(a, x, y, z) do { (a)[0]=(x); (a)[1]=(y); (a)[2]=(z); } while(0)

SolarScene::SolarScene() {
    eye_[0]=kEye[0]; eye_[1]=kEye[1]; eye_[2]=kEye[2];
    target_[0]=kAt[0]; target_[1]=kAt[1]; target_[2]=kAt[2];
    up_[0]=kUp[0]; up_[1]=kUp[1]; up_[2]=kUp[2];

    GLfloat c[3];

    // Sun
    SET3(c, 1.0f, 0.9f, 0.3f);
    bodies_[Sun] = new EmissiveBody(SUN_R, kSpinStep, c);

    // Inner planets
    SET3(c, 0.3f, 0.3f, 0.7f);
    bodies_[Mercury] = new PlanetBody(MER_R, MER_ORB, MER_DAYS, kSpinStep, bodies_[Sun], c);

    SET3(c, 1.0f, 0.7f, 0.1f);
    bodies_[Venus]   = new PlanetBody(VEN_R, VEN_ORB, VEN_DAYS, kSpinStep, bodies_[Sun], c);

    SET3(c, 0.2f, 0.8f, 0.3f);
    bodies_[Earth]   = new PlanetBody(EAR_R, EAR_ORB, EAR_DAYS, kSpinStep, bodies_[Sun], c);

    // Moon orbits Earth
    SET3(c, 0.9f, 0.9f, 0.8f);
    bodies_[Moon]    = new PlanetBody(MOO_R, MOO_ORB, MOO_DAYS, kSpinStep, bodies_[Earth], c);

    // Outer planets
    SET3(c, 1.0f, 0.5f, 0.5f);
    bodies_[Mars]    = new PlanetBody(MAR_R, MAR_ORB, MAR_DAYS, kSpinStep, bodies_[Sun], c);

    SET3(c, 1.0f, 1.0f, 0.6f);
    bodies_[Jupiter] = new PlanetBody(JUP_R, JUP_ORB, JUP_DAYS, kSpinStep, bodies_[Sun], c);

    SET3(c, 0.6f, 0.9f, 0.6f);
    bodies_[Saturn]  = new PlanetBody(SAT_R, SAT_ORB, SAT_DAYS, kSpinStep, bodies_[Sun], c);

    SET3(c, 0.7f, 0.7f, 0.8f);
    bodies_[Uranus]  = new PlanetBody(URA_R, URA_ORB, URA_DAYS, kSpinStep, bodies_[Sun], c);

    SET3(c, 0.6f, 0.7f, 1.0f);
    bodies_[Neptune] = new PlanetBody(NEP_R, NEP_ORB, NEP_DAYS, kSpinStep, bodies_[Sun], c);
}

SolarScene::~SolarScene() {
    for (int i = 0; i < kCount; ++i) delete bodies_[i];
}

void SolarScene::render() {
    glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
    glClearColor(0.72f, 0.72f, 0.74f, 1.0f);

    glMatrixMode(GL_PROJECTION);
    glLoadIdentity();
    gluPerspective(70.0, 1.0, 1.0, 4.0e7);

    glMatrixMode(GL_MODELVIEW);
    glLoadIdentity();
    gluLookAt(eye_[0], eye_[1], eye_[2], target_[0], target_[1], target_[2], up_[0], up_[1], up_[2]);

    glEnable(GL_DEPTH_TEST);
    glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

    for (int i = 0; i < kCount; ++i) bodies_[i]->render();

    glutSwapBuffers();
}

void SolarScene::tick() {
    for (int i = 0; i < kCount; ++i) bodies_[i]->step(kTickDays);
    glutPostRedisplay();
}

void SolarScene::onKey(unsigned char key, int, int) {
    const GLdouble delta = 20.0;
    switch (key) {
        case 'w': eye_[1] += delta; break;  // move camera +Y
        case 'x': eye_[1] -= delta; break;  // move camera -Y
        case 'a': eye_[0] -= delta; break;  // move camera -X
        case 'd': eye_[0] += delta; break;  // move camera +X
        case 's': eye_[2] += delta; break;  // move camera +Z
        case 'S': eye_[2] -= delta; break;  // move camera -Z
        case 'r':
            eye_[0]=kEye[0]; eye_[1]=kEye[1]; eye_[2]=kEye[2];
            target_[0]=kAt[0]; target_[1]=kAt[1]; target_[2]=kAt[2];
            up_[0]=kUp[0]; up_[1]=kUp[1]; up_[2]=kUp[2];
            break;
        case 27: // ESC
            std::exit(0);
            break;
        default: break;
    }
}

Build system

# Makefile
CXX     := g++
CXXFLAGS:= -O2 -std=c++11
LDFLAGS := -lglut -lGLU -lGL
SRC     := main.cpp body.cpp scene.cpp
BIN     := solarsystem

all: $(BIN)

$(BIN): $(SRC)
	$(CXX) $(CXXFLAGS) $(SRC) $(LDFLAGS) -o $@

clean:
	rm -f $(BIN) *.o

Run:

make && ./solarsystem

Perspective and lighting recap

  • Camera: gluLookAt(eye, target, up) uses nine parameters to position and orient the view.
  • Projection: set GL_PROJECTION, identity, gluPerspective(fovY, aspect, zNear, zFar).
  • Lighting: enable GL_LIGHTING and at least one light (GL_LIGHT0). Position/diffuse/specular are set with glLightfv; matreials with glMaterialf/glMaterialfv.
  • Posiitonal vs directional light: when the w component of position is 1→positional (x,y,z), when 0→directional (infinite) with direction (x,y,z).

Floating-point types in OpenGL immediate mode are commonly expressed with GLfloat.

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.