
I created a program so that students can visualise relativistic length contraction better. When we teach it in Year 13 to our students, we teach that the proper length is contracted by a factor gamma, which depends on the relative speed of the observer and the body being observed. The problem is, this is all a bit too one-dimensional.
This visualisation was inspired by the MIT game called A Slower Speed of Light (available here). In that game, players must collect magic watermelons or something to slow down the speed of light, making relativistic effects observable. There were a few issues with the game. Firstly, it’s a game, and collecting the magic watermelons is tedious when I just want to show something to students. Secondly, the game is in 3-D, making it a little too resource-intensive for my work laptop. I could bring my nice gaming laptop from home to run it, but I would prefer not to. Since my old pink laptop died, I’ve been looking for alternatives.


I was able to get A Slower Speed of Light to run on my Chromebook, believe it or not, at about 0.5 frames per second. It’s unplayable, and the tedium of collecting magic watermelons is augmented my the poor frame rate and slow response time of the controls. Nevertheless, running it within BOX64, I was able to get it to run.
But, hey, I’m a programmer (of sorts), so surely I could whip something up myself?

A few hours later, and here we have it. A two-dimensional simulation that runs pretty well. It uses Dosbox to run QuickBASIC 4.5, but don’t hold that against me. I started coding in BASIC when I was eight years old and I never really grew out of it (a colleague once remarked that they stopped using BASIC when they ‘grew up’. Well, I haven’t).

I like BASIC. It’s clear, easy to understand, and fairly forgiving. Sure, I could code this in Java, but I’d have to learn Java first. Or C++, but I’d have to learn C++ first. Or Python with Pygame, but I’d… you get the point. I have coded in C before, and Python lots, but I don’t like them and I’d spend so long looking up syntax that it really isn’t worth it for what I wanted to do. BASIC did what I wanted. BASIC is fine.
Watch the video below!
If you want to play with it yourself, open Dosbox, install QBASIC or QuickBASIC, and save the code below in plain text as a .BAS file. Open and enjoy.
Click here to view the QBASIC code, as bad as it is!
DECLARE FUNCTION DISTANCETOVERTEX! (xposition AS SINGLE, yposition AS SINGLE, xvertex AS SINGLE, yvertex AS SINGLE)
DECLARE FUNCTION ATAN2! (x AS SINGLE, y AS SINGLE)
DECLARE FUNCTION GAMMA! (speedis AS SINGLE)
DECLARE FUNCTION SPEED! (xvelocity AS SINGLE, yvelocity AS SINGLE)
'CONST showoriginalgrid = 0
DIM shapepoint(1 TO 16, 1 TO 5, 1 TO 2) AS SINGLE
DIM oldtransformedshapepoint(1 TO 16, 1 TO 5, 1 TO 2) AS SINGLE
DIM transformedshapepoint(1 TO 16, 1 TO 5, 1 TO 2) AS SINGLE
DIM oldviewshapepoint(1 TO 16, 1 TO 5, 1 TO 2) AS SINGLE
DIM viewshapepoint(1 TO 16, 1 TO 5, 1 TO 2) AS SINGLE
DIM perspective AS INTEGER
DIM vieworiginal AS INTEGER
DIM shapetoview AS INTEGER
DIM KS(255), SC(255), DU(255)
FOR E = 0 TO 127
SC(E) = E
DU(E) = 1
NEXT E
FOR E = 128 TO 255
SC(E) = E - 128
DU(E) = 0
NEXT E
CONST pi = 3.141592653499997#
SCREEN 12
CLS
DIM xvelocity AS SINGLE
DIM yvelocity AS SINGLE
DIM directiontovertex AS SINGLE
DIM directiontovelocity AS SINGLE
DIM resolutionangle AS SINGLE
DIM gammatovertex AS SINGLE
DIM speedtovertex AS SINGLE
DIM contracteddistancetovertex AS SINGLE
DIM speedmove AS SINGLE
DIM xposition AS SINGLE
DIM yposition AS SINGLE
DIM oldxposition AS SINGLE
DIM oldyposition AS SINGLE
DIM angularvelocity AS SINGLE
LET xvelocity = 0
LET yvelocity = 0
LET speedmove = 0
LET xposition = 295
LET yposition = 220
LET perspective = 3
LET vieworiginal = 0
LET shapetoview = 1
' Initialise shapes
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 5
READ shapepoint(shapenumber, vertex, 1), shapepoint(shapenumber, vertex, 2)
NEXT vertex
NEXT shapenumber
DIM Ik AS INTEGER
DO
LOCATE 1, 1: COLOR 15: PRINT "Speed="; speedmove; " "
I$ = INKEY$
Ik = INP(&H60)
KS(SC(Ik)) = DU(Ik)
SELECT CASE I$
CASE "q"
END
CASE "Q"
END
END SELECT
IF KS(72) = 1 THEN
LET speedmove = speedmove + 1E+07
IF speedmove >= 299999999 THEN LET speedmove = 299999999
END IF
IF KS(80) = 1 THEN
LET speedmove = speedmove - 1E+07
IF speedmove <= -299999999 THEN LET speedmove = -299999999
END IF
IF KS(77) = 1 THEN
LET angularvelocity = angularvelocity - pi / 256
END IF
IF KS(75) = 1 THEN
LET angularvelocity = angularvelocity + pi / 256
END IF
IF I$ = "f" THEN LET angularvelocity = 0
LET directiontovelocity = directiontovelocity + angularvelocity
IF directiontovelocity > 2 * pi THEN LET directiontovelocity = directiontovelocity - 2 * pi
IF directiontovelocity < 0 THEN LET directiontovelocity = directiontovelocity + 2 * pi
LET xvelocity = speedmove * COS(directiontovelocity)
LET yvelocity = speedmove * SIN(directiontovelocity)
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 5
LET directiontovertex = ATAN2((shapepoint(shapenumber, vertex, 2) - yposition), (shapepoint(shapenumber, vertex, 1) - xposition))
LET resolutionangle = directiontovertex - directiontovelocity
LET speedtovertex = speedmove * COS(resolutionangle)
LET gammatovertex = GAMMA(speedtovertex)
LET contracteddistancetovertex = DISTANCETOVERTEX(xposition, yposition, shapepoint(shapenumber, vertex, 1), shapepoint(shapenumber, vertex, 2)) / gammatovertex
IF perspective = 3 THEN
LET transformedshapepoint(shapenumber, vertex, 1) = xposition + contracteddistancetovertex * SIN(directiontovertex)
LET transformedshapepoint(shapenumber, vertex, 2) = yposition + contracteddistancetovertex * COS(directiontovertex)
LET viewshapepoint(shapenumber, vertex, 1) = shapepoint(shapenumber, vertex, 1)
LET viewshapepoint(shapenumber, vertex, 2) = shapepoint(shapenumber, vertex, 2)
ELSE
LET transformedshapepoint(shapenumber, vertex, 1) = 295 + contracteddistancetovertex * SIN(resolutionangle)
LET transformedshapepoint(shapenumber, vertex, 2) = 220 + contracteddistancetovertex * COS(resolutionangle)
LET viewshapepoint(shapenumber, vertex, 1) = 295 + DISTANCETOVERTEX(xposition, yposition, shapepoint(shapenumber, vertex, 1), shapepoint(shapenumber, vertex, 2)) * SIN(resolutionangle)
LET viewshapepoint(shapenumber, vertex, 2) = 220 + DISTANCETOVERTEX(xposition, yposition, shapepoint(shapenumber, vertex, 1), shapepoint(shapenumber, vertex, 2)) * COS(resolutionangle)
END IF
NEXT vertex
NEXT shapenumber
IF perspective = 3 THEN
LINE (oldxposition - 23, oldyposition - 23)-(oldxposition + 23, oldyposition + 23), 0, BF
DRAW "BM" + STR$(CINT(xposition)) + "," + STR$(CINT(yposition))
DRAW "C13 NM+0,+0 M+5,+20 M-10,+0 M+5,-20 BM+0,+10 P13,13"
DRAW "TA" + STR$(CINT(directiontovelocity * 180 / pi))
ELSE
LINE (290, 220)-(300, 240), 0, BF
DRAW "BM" + STR$(CINT(295)) + "," + STR$(CINT(220))
DRAW "C13 NM+0,+0 M+5,+20 M-10,+0 M+5,-20 BM+0,+10 P13,13"
END IF
LET oldxposition = xposition
LET oldyposition = yposition
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 4
LINE (oldtransformedshapepoint(shapenumber, vertex, 1), oldtransformedshapepoint(shapenumber, vertex, 2))-(oldtransformedshapepoint(shapenumber, vertex + 1, 1), oldtransformedshapepoint(shapenumber, vertex + 1, 2)), 0
IF vieworiginal = 1 THEN
LINE (oldviewshapepoint(shapenumber, vertex, 1), oldviewshapepoint(shapenumber, vertex, 2))-(oldviewshapepoint(shapenumber, vertex + 1, 1), oldviewshapepoint(shapenumber, vertex + 1, 2)), 0
LINE (viewshapepoint(shapenumber, vertex, 1), viewshapepoint(shapenumber, vertex, 2))-(viewshapepoint(shapenumber, vertex + 1, 1), viewshapepoint(shapenumber, vertex + 1, 2)), 15
END IF
LINE (transformedshapepoint(shapenumber, vertex, 1), transformedshapepoint(shapenumber, vertex, 2))-(transformedshapepoint(shapenumber, vertex + 1, 1), transformedshapepoint(shapenumber, vertex + 1, 2)), 14
NEXT vertex
NEXT shapenumber
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 5
LET oldtransformedshapepoint(shapenumber, vertex, 1) = transformedshapepoint(shapenumber, vertex, 1)
LET oldtransformedshapepoint(shapenumber, vertex, 2) = transformedshapepoint(shapenumber, vertex, 2)
IF perspective = 1 THEN
LET oldviewshapepoint(shapenumber, vertex, 1) = viewshapepoint(shapenumber, vertex, 1)
LET oldviewshapepoint(shapenumber, vertex, 2) = viewshapepoint(shapenumber, vertex, 2)
END IF
NEXT vertex
NEXT shapenumber
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 4
LINE (transformedshapepoint(shapenumber, vertex, 1), transformedshapepoint(shapenumber, vertex, 2))-(transformedshapepoint(shapenumber, vertex + 1, 1), transformedshapepoint(shapenumber, vertex + 1, 2)), 14
NEXT vertex
NEXT shapenumber
LET timenow = TIMER
DO: LOOP UNTIL timenow + .025 <= TIMER
LET xposition = xposition - yvelocity / 5E+07
LET yposition = yposition - xvelocity / 5E+07
IF xposition < 10 THEN LET xposition = 630
IF xposition > 630 THEN LET xposition = 10
IF yposition < 10 THEN LET yposition = 470
IF yposition > 470 THEN LET yposition = 10
IF I$ = "p" THEN WHILE INKEY$ = "": WEND
IF I$ = "b" THEN
'
END IF
IF I$ = "s" THEN
IF shapetoview = 1 THEN
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 5
READ shapepoint(shapenumber, vertex, 1), shapepoint(shapenumber, vertex, 2)
NEXT vertex
NEXT shapenumber
LET shapetoview = 3
CLS
END IF
IF shapetoview = 2 THEN
RESTORE
FOR shapenumber = 1 TO 16
FOR vertex = 1 TO 5
READ shapepoint(shapenumber, vertex, 1), shapepoint(shapenumber, vertex, 2)
NEXT vertex
NEXT shapenumber
LET shapetoview = 1
CLS
END IF
IF shapetoview = 3 THEN LET shapetoview = 2
END IF
IF I$ = "v" THEN
CLS
IF perspective = 1 THEN LET perspective = 4
IF perspective = 3 THEN LET perspective = 1
IF perspective = 4 THEN LET perspective = 3
END IF
IF I$ = "o" THEN
CLS
IF vieworiginal = 0 THEN LET vieworiginal = 2
IF vieworiginal = 1 THEN LET vieworiginal = 0
IF vieworiginal = 2 THEN LET vieworiginal = 1
END IF
LOOP UNTIL I$ = "q"
'Rectangle array
DATA 1,1
DATA 106,1
DATA 106,80
DATA 1,80
DATA 1,1
DATA 161,1
DATA 266,1
DATA 266,80
DATA 161,80
DATA 161,1
DATA 321,1
DATA 426,1
DATA 426,80
DATA 321,80
DATA 321,1
DATA 481,1
DATA 586,1
DATA 586,80
DATA 481,80
DATA 481,1
DATA 1,121
DATA 106,121
DATA 106,200
DATA 1,200
DATA 1,121
DATA 161,121
DATA 266,121
DATA 266,200
DATA 161,200
DATA 161,121
DATA 321,121
DATA 426,121
DATA 426,200
DATA 321,200
DATA 321,121
DATA 481,121
DATA 586,121
DATA 586,200
DATA 481,200
DATA 481,121
DATA 1,241
DATA 106,241
DATA 106,320
DATA 1,320
DATA 1,241
DATA 161,241
DATA 266,241
DATA 266,320
DATA 161,320
DATA 161,241
DATA 321,241
DATA 426,241
DATA 426,320
DATA 321,320
DATA 321,241
DATA 481,241
DATA 586,241
DATA 586,320
DATA 481,320
DATA 481,241
DATA 1,361
DATA 106,361
DATA 106,440
DATA 1,440
DATA 1,361
DATA 161,361
DATA 266,361
DATA 266,440
DATA 161,440
DATA 161,361
DATA 321,361
DATA 426,361
DATA 426,440
DATA 321,440
DATA 321,361
DATA 481,361
DATA 586,361
DATA 586,440
DATA 481,440
DATA 481,361
'Big square
DATA 240,160
DATA 250,160
DATA 260,160
DATA 270,160
DATA 280,160
DATA 280,160
DATA 290,160
DATA 300,160
DATA 310,160
DATA 320,160
DATA 320,160
DATA 330,160
DATA 340,160
DATA 350,160
DATA 360,160
DATA 360,160
DATA 370,160
DATA 380,160
DATA 390,160
DATA 400,160
DATA 400,160
DATA 400,170
DATA 400,180
DATA 400,190
DATA 400,200
DATA 400,200
DATA 400,210
DATA 400,220
DATA 400,230
DATA 400,240
DATA 400,240
DATA 400,250
DATA 400,260
DATA 400,270
DATA 400,280
DATA 400,280
DATA 400,290
DATA 400,300
DATA 400,310
DATA 400,320
DATA 240,320
DATA 250,320
DATA 260,320
DATA 270,320
DATA 280,320
DATA 280,320
DATA 290,320
DATA 300,320
DATA 310,320
DATA 320,320
DATA 320,320
DATA 330,320
DATA 340,320
DATA 350,320
DATA 360,320
DATA 360,320
DATA 370,320
DATA 380,320
DATA 390,320
DATA 400,320
DATA 240,160
DATA 240,170
DATA 240,180
DATA 240,190
DATA 240,200
DATA 240,200
DATA 240,210
DATA 240,220
DATA 240,230
DATA 240,240
DATA 240,240
DATA 240,250
DATA 240,260
DATA 240,270
DATA 240,280
DATA 240,280
DATA 240,290
DATA 240,300
DATA 240,310
DATA 240,320
FUNCTION ATAN2 (x AS SINGLE, y AS SINGLE)
DIM baseangle AS SINGLE
DIM result AS STRING
CONST pi = 3.141592653499998#
LET result$ = "Undetermined"
IF x = 0 AND y > 0 THEN
LET ATAN2 = pi / 2
LET result$ = "Determined"
END IF
IF x = 0 AND y < 0 THEN
LET ATAN2 = 3 * pi / 2
LET result$ = "Determined"
END IF
IF x > 0 AND y = 0 THEN
LET ATAN2 = 0
LET result$ = "Determined"
END IF
IF x < 0 AND y = 0 THEN
LET ATAN2 = pi
LET result$ = "Determined"
END IF
IF result$ = "Determined" THEN EXIT FUNCTION
LET baseangle = ATN(ABS(y) / ABS(x))
IF x > 0 AND y > 0 THEN ATAN2 = baseangle
IF x < 0 AND y > 0 THEN ATAN2 = pi - baseangle
IF x < 0 AND y < 0 THEN ATAN2 = pi + baseangle
IF x > 0 AND y < 0 THEN ATAN2 = 2 * pi - baseangle
END FUNCTION
FUNCTION DISTANCETOVERTEX (xposition AS SINGLE, yposition AS SINGLE, xvertex AS SINGLE, yvertex AS SINGLE)
DISTANCETOVERTEX = SQR((xvertex - xposition) ^ 2 + (yvertex - yposition) ^ 2)
END FUNCTION
FUNCTION GAMMA (speedis AS SINGLE)
CONST c = 3E+08
IF speedis < 3E+08 THEN
LET GAMMA = 1 / SQR(1 - (speedis / c) ^ 2)
ELSE
LET GAMMA = 3E+38
END IF
END FUNCTION
FUNCTION SPEED (xvelocity AS SINGLE, yvelocity AS SINGLE)
LET SPEED = SQR(xvelocity ^ 2 + yvelocity ^ 2)
END FUNCTION
Addendum, 2025-05-23:
Soon after writing this blog post, and as a personal challenge to myself, I decided to translate the code into Python. It isn’t pretty, but it works fine. I forgot to update this post until a blogger eidm wrote a post about Special Relativity and included a link to this very post. So, below is the python code I wrote. Enjoy!
Click here to view the Python code
import math
import numpy
import pygame
import sys
from pygame.locals import * # No idea what this does, but you need it for QUIT
def initialise_shape_arrays(shapepoint, shapeis):
if shapeis == 1:
shapepoint[0, 0, 0] = 1
shapepoint[0, 1, 0] = 106
shapepoint[0, 2, 0] = 106
shapepoint[0, 3, 0] = 1
shapepoint[0, 4, 0] = 1
shapepoint[1, 0, 0] = 161
shapepoint[1, 1, 0] = 266
shapepoint[1, 2, 0] = 266
shapepoint[1, 3, 0] = 161
shapepoint[1, 4, 0] = 161
shapepoint[2, 0, 0] = 321
shapepoint[2, 1, 0] = 426
shapepoint[2, 2, 0] = 426
shapepoint[2, 3, 0] = 321
shapepoint[2, 4, 0] = 321
shapepoint[3, 0, 0] = 481
shapepoint[3, 1, 0] = 586
shapepoint[3, 2, 0] = 586
shapepoint[3, 3, 0] = 481
shapepoint[3, 4, 0] = 481
shapepoint[4, 0, 0] = 1
shapepoint[4, 1, 0] = 106
shapepoint[4, 2, 0] = 106
shapepoint[4, 3, 0] = 1
shapepoint[4, 4, 0] = 1
shapepoint[5, 0, 0] = 161
shapepoint[5, 1, 0] = 266
shapepoint[5, 2, 0] = 266
shapepoint[5, 3, 0] = 161
shapepoint[5, 4, 0] = 161
shapepoint[6, 0, 0] = 321
shapepoint[6, 1, 0] = 426
shapepoint[6, 2, 0] = 426
shapepoint[6, 3, 0] = 321
shapepoint[6, 4, 0] = 321
shapepoint[7, 0, 0] = 481
shapepoint[7, 1, 0] = 586
shapepoint[7, 2, 0] = 586
shapepoint[7, 3, 0] = 481
shapepoint[7, 4, 0] = 481
shapepoint[8, 0, 0] = 1
shapepoint[8, 1, 0] = 106
shapepoint[8, 2, 0] = 106
shapepoint[8, 3, 0] = 1
shapepoint[8, 4, 0] = 1
shapepoint[9, 0, 0] = 161
shapepoint[9, 1, 0] = 266
shapepoint[9, 2, 0] = 266
shapepoint[9, 3, 0] = 161
shapepoint[9, 4, 0] = 161
shapepoint[10, 0, 0] = 321
shapepoint[10, 1, 0] = 426
shapepoint[10, 2, 0] = 426
shapepoint[10, 3, 0] = 321
shapepoint[10, 4, 0] = 321
shapepoint[11, 0, 0] = 481
shapepoint[11, 1, 0] = 586
shapepoint[11, 2, 0] = 586
shapepoint[11, 3, 0] = 481
shapepoint[11, 4, 0] = 481
shapepoint[12, 0, 0] = 1
shapepoint[12, 1, 0] = 106
shapepoint[12, 2, 0] = 106
shapepoint[12, 3, 0] = 1
shapepoint[12, 4, 0] = 1
shapepoint[13, 0, 0] = 161
shapepoint[13, 1, 0] = 266
shapepoint[13, 2, 0] = 266
shapepoint[13, 3, 0] = 161
shapepoint[13, 4, 0] = 161
shapepoint[14, 0, 0] = 321
shapepoint[14, 1, 0] = 426
shapepoint[14, 2, 0] = 426
shapepoint[14, 3, 0] = 321
shapepoint[14, 4, 0] = 321
shapepoint[15, 0, 0] = 481
shapepoint[15, 1, 0] = 586
shapepoint[15, 2, 0] = 586
shapepoint[15, 3, 0] = 481
shapepoint[15, 4, 0] = 481
shapepoint[0, 0, 1] = 1
shapepoint[0, 1, 1] = 1
shapepoint[0, 2, 1] = 80
shapepoint[0, 3, 1] = 80
shapepoint[0, 4, 1] = 1
shapepoint[1, 0, 1] = 1
shapepoint[1, 1, 1] = 1
shapepoint[1, 2, 1] = 80
shapepoint[1, 3, 1] = 80
shapepoint[1, 4, 1] = 1
shapepoint[2, 0, 1] = 1
shapepoint[2, 1, 1] = 1
shapepoint[2, 2, 1] = 80
shapepoint[2, 3, 1] = 80
shapepoint[2, 4, 1] = 1
shapepoint[3, 0, 1] = 1
shapepoint[3, 1, 1] = 1
shapepoint[3, 2, 1] = 80
shapepoint[3, 3, 1] = 80
shapepoint[3, 4, 1] = 1
shapepoint[4, 0, 1] = 121
shapepoint[4, 1, 1] = 121
shapepoint[4, 2, 1] = 200
shapepoint[4, 3, 1] = 200
shapepoint[4, 4, 1] = 121
shapepoint[5, 0, 1] = 121
shapepoint[5, 1, 1] = 121
shapepoint[5, 2, 1] = 200
shapepoint[5, 3, 1] = 200
shapepoint[5, 4, 1] = 121
shapepoint[6, 0, 1] = 121
shapepoint[6, 1, 1] = 121
shapepoint[6, 2, 1] = 200
shapepoint[6, 3, 1] = 200
shapepoint[6, 4, 1] = 121
shapepoint[7, 0, 1] = 121
shapepoint[7, 1, 1] = 121
shapepoint[7, 2, 1] = 200
shapepoint[7, 3, 1] = 200
shapepoint[7, 4, 1] = 121
shapepoint[8, 0, 1] = 241
shapepoint[8, 1, 1] = 241
shapepoint[8, 2, 1] = 320
shapepoint[8, 3, 1] = 320
shapepoint[8, 4, 1] = 241
shapepoint[9, 0, 1] = 241
shapepoint[9, 1, 1] = 241
shapepoint[9, 2, 1] = 320
shapepoint[9, 3, 1] = 320
shapepoint[9, 4, 1] = 241
shapepoint[10, 0, 1] = 241
shapepoint[10, 1, 1] = 241
shapepoint[10, 2, 1] = 320
shapepoint[10, 3, 1] = 320
shapepoint[10, 4, 1] = 241
shapepoint[11, 0, 1] = 241
shapepoint[11, 1, 1] = 241
shapepoint[11, 2, 1] = 320
shapepoint[11, 3, 1] = 320
shapepoint[11, 4, 1] = 241
shapepoint[12, 0, 1] = 361
shapepoint[12, 1, 1] = 361
shapepoint[12, 2, 1] = 440
shapepoint[12, 3, 1] = 440
shapepoint[12, 4, 1] = 361
shapepoint[13, 0, 1] = 361
shapepoint[13, 1, 1] = 361
shapepoint[13, 2, 1] = 440
shapepoint[13, 3, 1] = 440
shapepoint[13, 4, 1] = 361
shapepoint[14, 0, 1] = 361
shapepoint[14, 1, 1] = 361
shapepoint[14, 2, 1] = 440
shapepoint[14, 3, 1] = 440
shapepoint[14, 4, 1] = 361
shapepoint[15, 0, 1] = 361
shapepoint[15, 1, 1] = 361
shapepoint[15, 2, 1] = 440
shapepoint[15, 3, 1] = 440
shapepoint[15, 4, 1] = 361
else:
shapepoint[0, 0, 0] = 240
shapepoint[0, 1, 0] = 250
shapepoint[0, 2, 0] = 260
shapepoint[0, 3, 0] = 270
shapepoint[0, 4, 0] = 280
shapepoint[1, 0, 0] = 280
shapepoint[1, 1, 0] = 290
shapepoint[1, 2, 0] = 300
shapepoint[1, 3, 0] = 310
shapepoint[1, 4, 0] = 320
shapepoint[2, 0, 0] = 320
shapepoint[2, 1, 0] = 330
shapepoint[2, 2, 0] = 340
shapepoint[2, 3, 0] = 350
shapepoint[2, 4, 0] = 360
shapepoint[3, 0, 0] = 360
shapepoint[3, 1, 0] = 370
shapepoint[3, 2, 0] = 380
shapepoint[3, 3, 0] = 390
shapepoint[3, 4, 0] = 400
shapepoint[4, 0, 0] = 400
shapepoint[4, 1, 0] = 400
shapepoint[4, 2, 0] = 400
shapepoint[4, 3, 0] = 400
shapepoint[4, 4, 0] = 400
shapepoint[5, 0, 0] = 400
shapepoint[5, 1, 0] = 400
shapepoint[5, 2, 0] = 400
shapepoint[5, 3, 0] = 400
shapepoint[5, 4, 0] = 400
shapepoint[6, 0, 0] = 400
shapepoint[6, 1, 0] = 400
shapepoint[6, 2, 0] = 400
shapepoint[6, 3, 0] = 400
shapepoint[6, 4, 0] = 400
shapepoint[7, 0, 0] = 400
shapepoint[7, 1, 0] = 400
shapepoint[7, 2, 0] = 400
shapepoint[7, 3, 0] = 400
shapepoint[7, 4, 0] = 400
shapepoint[8, 0, 0] = 240
shapepoint[8, 1, 0] = 250
shapepoint[8, 2, 0] = 260
shapepoint[8, 3, 0] = 270
shapepoint[8, 4, 0] = 280
shapepoint[9, 0, 0] = 280
shapepoint[9, 1, 0] = 290
shapepoint[9, 2, 0] = 300
shapepoint[9, 3, 0] = 310
shapepoint[9, 4, 0] = 320
shapepoint[10, 0, 0] = 320
shapepoint[10, 1, 0] = 330
shapepoint[10, 2, 0] = 340
shapepoint[10, 3, 0] = 350
shapepoint[10, 4, 0] = 360
shapepoint[11, 0, 0] = 360
shapepoint[11, 1, 0] = 370
shapepoint[11, 2, 0] = 380
shapepoint[11, 3, 0] = 390
shapepoint[11, 4, 0] = 400
shapepoint[12, 0, 0] = 240
shapepoint[12, 1, 0] = 240
shapepoint[12, 2, 0] = 240
shapepoint[12, 3, 0] = 240
shapepoint[12, 4, 0] = 240
shapepoint[13, 0, 0] = 240
shapepoint[13, 1, 0] = 240
shapepoint[13, 2, 0] = 240
shapepoint[13, 3, 0] = 240
shapepoint[13, 4, 0] = 240
shapepoint[14, 0, 0] = 240
shapepoint[14, 1, 0] = 240
shapepoint[14, 2, 0] = 240
shapepoint[14, 3, 0] = 240
shapepoint[14, 4, 0] = 240
shapepoint[15, 0, 0] = 240
shapepoint[15, 1, 0] = 240
shapepoint[15, 2, 0] = 240
shapepoint[15, 3, 0] = 240
shapepoint[15, 4, 0] = 240
shapepoint[0, 0, 1] = 160
shapepoint[0, 1, 1] = 160
shapepoint[0, 2, 1] = 160
shapepoint[0, 3, 1] = 160
shapepoint[0, 4, 1] = 160
shapepoint[1, 0, 1] = 160
shapepoint[1, 1, 1] = 160
shapepoint[1, 2, 1] = 160
shapepoint[1, 3, 1] = 160
shapepoint[1, 4, 1] = 160
shapepoint[2, 0, 1] = 160
shapepoint[2, 1, 1] = 160
shapepoint[2, 2, 1] = 160
shapepoint[2, 3, 1] = 160
shapepoint[2, 4, 1] = 160
shapepoint[3, 0, 1] = 160
shapepoint[3, 1, 1] = 160
shapepoint[3, 2, 1] = 160
shapepoint[3, 3, 1] = 160
shapepoint[3, 4, 1] = 160
shapepoint[4, 0, 1] = 160
shapepoint[4, 1, 1] = 170
shapepoint[4, 2, 1] = 180
shapepoint[4, 3, 1] = 190
shapepoint[4, 4, 1] = 200
shapepoint[5, 0, 1] = 200
shapepoint[5, 1, 1] = 210
shapepoint[5, 2, 1] = 220
shapepoint[5, 3, 1] = 230
shapepoint[5, 4, 1] = 240
shapepoint[6, 0, 1] = 240
shapepoint[6, 1, 1] = 250
shapepoint[6, 2, 1] = 260
shapepoint[6, 3, 1] = 270
shapepoint[6, 4, 1] = 280
shapepoint[7, 0, 1] = 280
shapepoint[7, 1, 1] = 290
shapepoint[7, 2, 1] = 300
shapepoint[7, 3, 1] = 310
shapepoint[7, 4, 1] = 320
shapepoint[8, 0, 1] = 320
shapepoint[8, 1, 1] = 320
shapepoint[8, 2, 1] = 320
shapepoint[8, 3, 1] = 320
shapepoint[8, 4, 1] = 320
shapepoint[9, 0, 1] = 320
shapepoint[9, 1, 1] = 320
shapepoint[9, 2, 1] = 320
shapepoint[9, 3, 1] = 320
shapepoint[9, 4, 1] = 320
shapepoint[10, 0, 1] = 320
shapepoint[10, 1, 1] = 320
shapepoint[10, 2, 1] = 320
shapepoint[10, 3, 1] = 320
shapepoint[10, 4, 1] = 320
shapepoint[11, 0, 1] = 320
shapepoint[11, 1, 1] = 320
shapepoint[11, 2, 1] = 320
shapepoint[11, 3, 1] = 320
shapepoint[11, 4, 1] = 320
shapepoint[12, 0, 1] = 160
shapepoint[12, 1, 1] = 170
shapepoint[12, 2, 1] = 180
shapepoint[12, 3, 1] = 190
shapepoint[12, 4, 1] = 200
shapepoint[13, 0, 1] = 200
shapepoint[13, 1, 1] = 210
shapepoint[13, 2, 1] = 220
shapepoint[13, 3, 1] = 230
shapepoint[13, 4, 1] = 240
shapepoint[14, 0, 1] = 240
shapepoint[14, 1, 1] = 250
shapepoint[14, 2, 1] = 260
shapepoint[14, 3, 1] = 270
shapepoint[14, 4, 1] = 280
shapepoint[15, 0, 1] = 280
shapepoint[15, 1, 1] = 290
shapepoint[15, 2, 1] = 300
shapepoint[15, 3, 1] = 310
shapepoint[15, 4, 1] = 320
return shapepoint
def gamma(speedis):
if speedis < 3E8:
return 1 / math.sqrt(1 - (speedis/3E8)**2)
else:
return 3E38
def distancetovertex(xposition, yposition, xvertex, yvertex):
return math.sqrt((xvertex-xposition)**2+(yvertex-yposition)**2)
def speed(xvelocity, yvelocity):
return math.sqrt(xvelocity**2+yvelocity**2)
def printtoscreen(message,x ,y):
font = pygame.font.Font(None, 24)
text = font.render(message, True, (255,255,255))
screen.blit(text, (x,y))
def drawshapes(shapepointer,colour):
for shapenumber in range(0,16):
for vertex in range(0, 4):
pygame.draw.line(screen, colour, (shapepointer[shapenumber, vertex,0],shapepointer[shapenumber, vertex,1]),(shapepointer[shapenumber, vertex+1,0],shapepointer[shapenumber, vertex+1,1]),2)
def cls():
screen.fill((0,0,0)) # clears the screen
def drawship(x, y, angle):
point1x = x
point1y = y
rotpoint2x = 5*math.cos(angle) - 20*math.sin(angle)
rotpoint2y = 5*math.sin(angle) + 20*math.cos(angle)
rotpoint3x = -5*math.cos(angle) - 20*math.sin(angle)
rotpoint3y = -5*math.sin(angle) + 20*math.cos(angle)
point2x = x + rotpoint2x
point2y = y + rotpoint2y
point3x = x + rotpoint3x
point3y = y + rotpoint3y
pygame.draw.line(screen, (255,0,255), (point1x,point1y),(point2x,point2y),2)
pygame.draw.line(screen, (255,0,255), (point1x,point1y),(point3x,point3y),2)
pygame.draw.line(screen, (255,0,255), (point2x,point2y),(point3x,point3y),2)
# Main part
pygame.init()
screen = pygame.display.set_mode((640, 480),pygame.FULLSCREEN | pygame.SCALED)
pygame.display.set_caption("Length Contraction Simulator") # Doesn't do anything on Crostini
#shapepoint = numpy.zeros((16,5,2))
#shapepoint = initialise_shape_arrays(shapepoint)
#pygame.display.flip()
# Dimension variables
xvelocity = 0
yvelocity = 0
speedmove = 0
xposition = 295
yposition = 220
angularvelocity = 0
perspective = 1
shapetoview = 1
vieworiginal = 1
directiontovelocity = 0
delayvalue = 30
shapepoint = numpy.zeros((16,5,2))
shapepoint = initialise_shape_arrays(shapepoint,shapetoview)
transformedshapepoint = numpy.zeros((16,5,2))
transformedshapepoint = initialise_shape_arrays(transformedshapepoint,shapetoview)
vieworiginalshapepoint = numpy.zeros((16,5,2))
down = 0
up = 0
left = 0
right = 0
looping = 1
while looping == 1: # This is an infinite do loop
#shapepoint = initialise_shape_arrays(shapepoint)
for event in pygame.event.get():
if event.type == QUIT:
pygame.quit()
elif event.type == KEYDOWN:
if event.key == K_s:
if shapetoview == 1:
shapetoview = 3
if shapetoview == 2:
shapetoview = 1
if shapetoview == 3:
shapetoview = 2
shapepoint = initialise_shape_arrays(shapepoint,shapetoview)
if event.key == K_p:
pausingloop = 0
printtoscreen("PAUSED",570,0)
pygame.display.flip()
while pausingloop == 0:
for event2 in pygame.event.get():
if event2.type == KEYDOWN:
if event2.key == K_p:
pausingloop = 1
if event2.key == K_q:
pygame.quit()
sys.exit()
if event.key == K_PLUS:
delayvalue = delayvalue - 10
if delayvalue < 0:
delayvalue = 0
if event.key == K_EQUALS:
delayvalue = delayvalue - 10
if delayvalue < 0:
delayvalue = 0
if event.key == K_MINUS:
delayvalue = delayvalue + 10
if event.key == K_LEFT:
left = 1
if event.key == K_RIGHT:
right = 1
if event.key == K_UP:
up = 1
if event.key == K_DOWN:
down = 1
if event.key == K_q:
pygame.quit()
sys.exit()
if event.key == K_f:
left = 0
right = 0
angularvelocity = 0
if event.key == K_h:
speedmove = 0
up = 0
down = 0
if event.key == K_o:
if vieworiginal == 1:
vieworiginal = 2
if vieworiginal == 0:
vieworiginal = 1
if vieworiginal == 2:
vieworiginal = 0
if event.key == K_v:
if perspective == 1:
perspective = 2
if perspective == 3:
perspective = 1
if perspective == 2:
perspective = 3
elif event.type == KEYUP:
if event.key == K_LEFT:
left = 0
#angularvelocity = angularvelocity - math.pi / 256
if event.key == K_RIGHT:
right = 0
#angularvelocity = angularvelocity + math.pi / 256
if event.key == K_UP:
up = 0
#speedmove = speedmove + 1E7
#if speedmove >= 299999999:
# speedmove = 299999999
if event.key == K_DOWN:
down = 0
#speedmove = speedmove - 1E7
#if speedmove <= -299999999:
# speedmove = -299999999
if up == 1:
speedmove = speedmove + 5E6
if speedmove >= 299999999:
speedmove = 299999999
if down == 1:
speedmove = speedmove - 5E6
if speedmove <= -299999999:
speedmove = -299999999
if left == 1:
angularvelocity = angularvelocity - math.pi / 4096
if right == 1:
angularvelocity = angularvelocity + math.pi / 4096
directiontovelocity = directiontovelocity + angularvelocity
if directiontovelocity > 2 * math.pi:
directiontovelocity = directiontovelocity - 2*math.pi
if directiontovelocity < 0:
directiontovelocity = directiontovelocity + 2*math.pi
xvelocity = speedmove * math.sin(directiontovelocity+math.pi/2)
yvelocity = speedmove * math.cos(directiontovelocity+math.pi/2)
xposition = xposition - yvelocity / 5E7
yposition = yposition - xvelocity / 5E7
if xposition < 0:
xposition = 639
if xposition > 640:
xposition = 1
if yposition < 0:
yposition = 479
if yposition > 480:
yposition = 1
cls()
for biggerworldx in [-640,0,640]:
for biggerworldy in [-480,0,480]:
for shapenumber in range(16):
for vertex in range(5):
directiontovertex = math.atan2((shapepoint[shapenumber,vertex,1]+biggerworldy)-yposition,(shapepoint[shapenumber,vertex,0]+biggerworldx)-xposition)
resolutionangle = directiontovertex - directiontovelocity
speedtovertex = speedmove * math.sin(resolutionangle)
gammatovertex = gamma(speedtovertex)
contracteddistancetovertex = distancetovertex(xposition, yposition, shapepoint[shapenumber,vertex,0]+biggerworldx, shapepoint[shapenumber,vertex,1]+biggerworldy) / gammatovertex
if perspective == 3:
transformedshapepoint[shapenumber, vertex, 0] = xposition + contracteddistancetovertex * math.cos(directiontovertex)
transformedshapepoint[shapenumber, vertex, 1] = yposition + contracteddistancetovertex * math.sin(directiontovertex)
vieworiginalshapepoint[shapenumber, vertex, 0] = shapepoint[shapenumber, vertex, 0]+biggerworldx
vieworiginalshapepoint[shapenumber, vertex, 1] = shapepoint[shapenumber, vertex, 1]+biggerworldy
else:
transformedshapepoint[shapenumber, vertex, 0] = 295 + contracteddistancetovertex * math.cos(resolutionangle)
transformedshapepoint[shapenumber, vertex, 1] = 220 + contracteddistancetovertex * math.sin(resolutionangle)
vieworiginalshapepoint[shapenumber, vertex, 0] = 295 + distancetovertex(xposition, yposition, shapepoint[shapenumber,vertex,0]+biggerworldx,shapepoint[shapenumber,vertex,1]+biggerworldy) * math.cos(resolutionangle)
vieworiginalshapepoint[shapenumber, vertex, 1] = 220 + distancetovertex(xposition, yposition, shapepoint[shapenumber,vertex,0]+biggerworldx,shapepoint[shapenumber,vertex,1]+biggerworldy) * math.sin(resolutionangle)
if vieworiginal==1:
drawshapes(vieworiginalshapepoint,(128,128,128))
drawshapes(transformedshapepoint,(255,255,0))
if perspective == 3:
drawship(xposition, yposition, directiontovelocity)
else:
drawship(295,220,0)
printtoscreen("Speed = "+str("{:.2E}".format(speedmove)),0,0)
pygame.display.flip()
pygame.time.delay(delayvalue) # 0.01s pause
If you want to read about the process of translating the code from QBASIC to Python, I wrote a blog post about it here.
Reminder: Weekly Live Tuition Sessions!
SUNDAY 11th January
GCSE Physics 9:30am – 10:20am Infrared radiation A-Level Physics 10:30am – 11:20am Measurement of specific charge GCSE Astronomy 11:30am – 12:20pm Exploring the moon If you wish to enrol on the tuition sessions and haven’t yet, then click enrol below

One thought on “Visualising relativistic length contraction in 2D”