Here’s the version I wrote in Processing a bit over a year ago. Use the arrows to turn, A and D to roll, and W and S to control speed. I’m not adjusting time to the frame rate because Processing is pretty steady with its frame rate. The `stuff`

contains 8 copies of the geometry and the ship position wraps within it so appears to be an infinite space.

```
int nShapes = 12000;
int[] keys;
PVector xAxis, yAxis, zAxis;
Ship ship;
PShape stuff;
int numFrames = 180;
int saveFrame = 0;
boolean bSaveAnim = false;
void setup() {
fullScreen( P3D, SPAN );
//size( 720, 480, P3D );
keys = new int[256];
xAxis = new PVector( 1, 0, 0 );
yAxis = new PVector( 0, 1, 0 );
zAxis = new PVector( 0, 0, 1 );
noStroke();
makeStuff();
ship = new Ship();
frameRate( 30 );
}
void draw() {
background( 0 );
ship.move();
ship.setCamera();
shape( stuff );
if( bSaveAnim ) {
save( "frames/fr" + nf( saveFrame, 4 ) + ".png" );
if( ++saveFrame == numFrames ) {
println( "done saving "+saveFrame+" frames" );
bSaveAnim = false;
}
}
}
class Quat {
PVector v;
float w;
Quat( ) { v = new PVector( 0., 0., 0. ); w = 1.; }
Quat( PVector axis, float ang ) {
v = PVector.mult( axis, sin( ang*0.5 )/axis.mag() );
w = cos( ang*0.5 );
}
Quat normalize() {
float t = 1./sqrt( v.magSq() + w*w );
v.mult( t );
w *= t;
return this;
}
PVector rotate( PVector p ) {
// ( w*w - dot( v, v )) * p + 2 * dot( p, v ) * v + 2 * w * cross( v, p )
return PVector.mult( p, w*w - v.dot(v) )
.add( PVector.mult( v, 2 * p.dot(v) ) )
.add( PVector.mult( v, 2*w ).cross( p ) );
}
PVector unrotate( PVector p ) {
// ( w*w - dot( v, v )) * p + 2 * dot( p, v ) * v - 2 * w * cross( v, p )
return PVector.mult( p, w*w - v.dot(v) )
.add( PVector.mult( v, 2 * p.dot(v) ) )
.sub( PVector.mult( v, 2*w ).cross( p ) );
}
Quat rotateBy( Quat q ) {
float t = w*q.w - v.dot( q.v );
v = q.v.cross( v )
.add( PVector.mult( v, q.w ) )
.add( PVector.mult( q.v, w ) );
w = t;
normalize();
return this;
}
}
float SIDE_THRUST = 0.02;
float BACK_THRUST = 0.05;
float FORE_THRUST = 0.02;
class Ship {
PVector pos;
PVector vel;
Quat rot;
float speed;
Ship() {
pos = new PVector();
speed = 1000.0/numFrames;
vel = new PVector( 0, 0, -speed );
rot = new Quat();
}
void move() {
// UP, DOWN, LEFT, RIGHT, 'd', 'a'
PVector turn = new PVector( keys[38]-keys[40], keys[37]-keys[39], keys[68]-keys[65] );
if( turn.magSq() > 0 )
rot = new Quat( turn, 0.02 ).rotateBy(rot);
speed += (keys[87]-keys[83]) * 0.025; // 'w', 's'
PVector lclVel = rot.unrotate( vel );
if( lclVel.x < -SIDE_THRUST ) lclVel.x += SIDE_THRUST;
else if( lclVel.x > SIDE_THRUST ) lclVel.x -= SIDE_THRUST;
else lclVel.x = 0;
if( lclVel.y < -SIDE_THRUST ) lclVel.y += SIDE_THRUST;
else if( lclVel.y > SIDE_THRUST ) lclVel.y -= SIDE_THRUST;
else lclVel.y = 0;
if( lclVel.z > -speed+BACK_THRUST ) lclVel.z -= BACK_THRUST;
else if( lclVel.z < -speed-FORE_THRUST ) lclVel.z += FORE_THRUST;
else lclVel.z = -speed;
vel = rot.rotate( lclVel );
//pos.add( rot.rotate( zAxis ).mult(-speed) );
pos.add( vel );
pos.x = (pos.x+1000) % 1000;
pos.y = (pos.y+1000) % 1000;
pos.z = (pos.z+1000) % 1000;
}
void setCamera() {
PVector back = rot.rotate( zAxis );
PVector up = rot.rotate( yAxis );
camera( pos.x, pos.y, pos.z,
pos.x-back.x, pos.y-back.y, pos.z-back.z,
up.x, up.y, up.z );
perspective( TAU/4, 1.*width/height, 1, 400 );
//frustum( -1.*width/height, 1.*width/height, -1, 1, 1, 400 );
//lightFalloff( 1, 0, 1./80000 );
pointLight( 255, 255, 255, pos.x, pos.y, pos.z );
}
}
PShape makeStuff() {
colorMode( HSB, 1, 1, 1 );
stuff = createShape( GROUP );
for( int is=0; is<nShapes; is++ ) {
float size = pow(random(1),2) * 5.5 + 0.5;
float px, py, pz;
do {
px = random(-500, 500);
py = random(-500, 500);
} while( px*px+py*py < (size+2)*(size+2) );
pz = random(-500, 500);
PVector axis = PVector.random3D();
float ang = random(TAU);
color c = color(random(1), 0.7, 1);
for( int i=0; i<2000; i+=1000 )
for( int j=0; j<2000; j+=1000 )
for( int k=0; k<2000; k+=1000 ) {
PShape thing = createShape( BOX, size, size, size );
thing.setFill( c );
thing.rotate( ang, axis.x, axis.y, axis.z );
thing.translate( px+i, py+j, pz+k );
stuff.addChild( thing );
}
}
colorMode( RGB, 255, 255, 255 );
return stuff;
}
void keyPressed() {
if( keyCode < 256 ) keys[keyCode] = 1;
if( key == '*' ) {
saveFrame = 0;
bSaveAnim = true;
}
}
void keyReleased() {
if( keyCode < 256 ) keys[keyCode] = 0;
}
```