College of Science, Engineering and Technology
⋄
INTERACTIVE 3D SPACE STATION
Project Report: Assessment 2 & 4
Semester 1 — 2026
⋄
Module Code: COS3712
Module Name: Computer Graphics
Assignment No.: Assessment 2 & 4
Due Date: 2026
Submitted in partial fulfilment of the requirements for COS3712: Computer Graphics
at the University of South Africa.
,UNISA | COS3712 Interactive 3D Space Station Project
Project Overview
This report documents the design and implementation of a real-time interactive 3D space
station orbiting a planet, developed for COS3712: Computer Graphics. The project spans two
assessment parts: Assessment 2 covers space station construction, object animation, camera
navigation, and perspective projection; Assessment 4 extends this with advanced lighting,
three shading models, and surface mapping techniques. The implementation uses JavaScript
with the Three.js library, rendered via WebGL in a standard browser environment.
Technology Stack
Table 1: Tools and Technologies Used
Component Choice and Justification
Programming Lan- JavaScript (ES6+) – runs natively in any browser, no compilation
guage step
Graphics Library Three.js (r158) – abstracts WebGL while giving full control over
shaders
Shader Language GLSL (ES 300) – used for custom Flat, Gouraud, and Phong
shaders
IDE Visual Studio Code with Live Server extension
Version Control Git / GitHub (private repository)
¥ Implementation Insight
Three.js was selected because it is the most widely adopted WebGL abstraction library
in the academic COS3712 cohort, and the department has made a Three.js reference
book available under myModules additional resources. The library ships with built-in
support for perspective cameras, geometry primitives, and shader materials, which
maps directly onto every assessment requirement.
Page 1 of 15
,UNISA | COS3712 Interactive 3D Space Station Project
Assessment 2 – Part 1: Space Station Construction & Animation
2.1 Space Station Structure
The station is built from Three.js geometry primitives arranged in a hierarchical scene graph.
Every major assembly is parented to a root THREE.Group called stationGroup, which rotates
as a single unit. This mirrors real hierarchical transformation: child objects inherit the par-
ent’s world transform, so rotating stationGroup rotates the entire station without touching
individual component matrices.
Central Core
The core uses a CylinderGeometry (radius 2, height 6, 32 radial segments) oriented along the
Y-axis. A secondary oblate SphereGeometry (radius 2.5, scaleY 0.6) caps each end, giving the
classic ISS-style hub appearance.
const coreGeo = new THREE.CylinderGeometry(2, 2, 6, 32); const coreMat
= new THREE.MeshPhongMaterial( color: 0xaaaaaa ); const core = new
THREE.Mesh(coreGeo, coreMat); stationGroup.add(core);
Docking Modules (6 required)
Six CylinderGeometry arms extend radially from the core at 60-degree intervals on the XZ-
plane. Each arm carries a spherical docking collar at its outer end:
for (let i = 0; i < 6; i++) const angle = (i / 6) * Math.PI * 2; const
arm = new THREE.Mesh( new THREE.CylinderGeometry(0.4, 0.4, 4, 16), dockMat
); arm.rotation.z = Math.PI / 2; arm.position.set(Math.cos(angle)*4, 0,
Math.sin(angle)*4); arm.rotation.y = angle; stationGroup.add(arm);
const collar = new THREE.Mesh( new THREE.SphereGeometry(0.6, 16, 16),
collarMat ); collar.position.set(Math.cos(angle)*6.2, 0, Math.sin(angle)*6.2);
stationGroup.add(collar);
Page 2 of 15
,UNISA | COS3712 Interactive 3D Space Station Project
Solar Panel Arrays (4 required)
Four solar arrays are attached at the top and bottom of the core, two per side. Each array is a
flat BoxGeometry (width 5, height 0.05, depth 2) with a dark-blue diffuse material simulating
photovoltaic cells. The arrays are parented to a solarGroup that spins independently of the
station core, allowing the panels to track the directional sun light.
const solarGroup = new THREE.Group(); stationGroup.add(solarGroup);
const positions = [ [ 0, 4, 2.5], [ 0, 4, -2.5], [ 0, -4, 2.5], [ 0, -4,
-2.5] ]; positions.forEach(([x, y, z]) => const panel = new THREE.Mesh(
new THREE.BoxGeometry(5, 0.05, 2), solarMat ); panel.position.set(x, y, z);
solarGroup.add(panel); );
Communication Towers (2 required)
Two communication towers stand on the north and south poles of the core. Each tower is a
thin CylinderGeometry mast topped by a TorusGeometry dish:
.forEach(sign => const mast = new THREE.Mesh( new THREE.CylinderGeometry(0.08,
0.08, 2.5, 8), mastMat ); mast.position.set(0, sign * 4.25, 0);
const dish = new THREE.Mesh( new THREE.TorusGeometry(0.5, 0.08, 8, 24), dishMat
); dish.rotation.x = Math.PI / 2; dish.position.set(0, sign * 5.5, 0);
stationGroup.add(mast, dish); );
2.2 Transformations Applied
Table 2: Transformation Summary Across Components
Component Scaling Rotation Translation
Station core Default (1,1,1) Y-axis slow spin Origin (0,0,0)
Docking modules Uniform 1x Z-axis 90 deg tilt Radial XZ offset
Solar panels Scaled flat (5, 0.05, None (in so- Y and Z offset
2) larGroup)
Comm towers Default X-axis 90 deg (dish) Y pole offset
Spacecraft Default Y-axis per orbit Circular orbit path
Planet Scale (8,8,8) Y-axis slow Fixed offset (30,0,0)
Page 3 of 15
,UNISA | COS3712 Interactive 3D Space Station Project
Key Distinction
Hierarchical vs. Local Transformations. A local transformation changes an object
relative to its own axis. A hierarchical transformation propagates down the scene graph:
rotating stationGroup moves all children without touching their local matrices. The
solar panels spin via solarGroup.rotation.y += 0.002 each frame, but their position
relative to the core stays fixed because they are children of solarGroup, which is itself
a child of stationGroup.
2.3 Station Rotation
In the main animation loop (requestAnimationFrame), the station rotates slowly on its Y-
axis:
function animate() requestAnimationFrame(animate); stationGroup.rotation.y +=
0.001; // slow station spin solarGroup.rotation.y += 0.003; // solar panels
track faster renderer.render(scene, camera);
2.4 Spacecraft and Orbital Animation (4 ships required)
Four spacecraft orbit the station along four distinct elliptical paths. Each ship is a simple
composite mesh (cone body + box wings) grouped into a THREE.Group. Orbital motion uses
parametric equations applied each frame:
ships.forEach((ship, i) => ship.userData.angle += ship.userData.speed; const a
= ship.userData.a; // semi-major axis const b = ship.userData.b; // semi-minor
axis const t = ship.userData.angle;
ship.position.set( a * Math.cos(t), ship.userData.yOffset, b * Math.sin(t) ); //
face direction of travel ship.rotation.y = -t + Math.PI / 2; );
Each ship carries different a, b, speed, and yOffset values, so the orbits are visually distinct.
A pause/resume toggle updates a paused boolean checked at the top of animate():
document.getElementById(’pauseBtn’) .addEventListener(’click’, () => paused =
!paused; );
// Inside animate(): if (!paused) stationGroup.rotation.y += 0.001;
ships.forEach(ship => ship.userData.angle += ship.userData.speed; );
Page 4 of 15
, UNISA | COS3712 Interactive 3D Space Station Project
2.5 Perspective Projection
Three.js uses a PerspectiveCamera that internally constructs a standard perspective projec-
tion matrix. The parameters control the depth simulation:
const camera = new THREE.PerspectiveCamera( 60, // field of view (degrees)
window.innerWidth / window.innerHeight, // aspect ratio 0.1, // near
clipping plane 2000 // far clipping plane ); camera.position.set(0, 15, 40);
camera.lookAt(0, 0, 0);
The projection matrix P maps 3D eye-space coordinates to normalised device coordinates. For
a symmetric frustum:
P = ( ) f aspect000
0 f 0 0
0 0 far+nearnear-far 2·f ar · nearnear − f ar
0 0 -1 0
where f = cot(f ov2).
√
With f ov = 60◦ : f = cot(30◦ ) = 3 ≈ 1.732.
Objects far from the camera appear smaller because the w-component grows with depth be-
fore perspective division, dividing x and y by a larger value.
2.6 Camera Navigation and User Controls
Camera smoothing is achieved via linear interpolation (lerp) each frame so the camera glides
rather than snapping:
// In animate(): camera.position.lerp(targetCameraPos, 0.05);
camera.quaternion.slerp(targetCameraQuat, 0.05);
Page 5 of 15