This tutorial is continuation of "Matrix 2d" where i will show simple 2d solar system simulations using matrices
1. Astral objects
We will define 4 Astral objects:
- Star
- Planet
- Satellite
- Orbit
All astral object will have only one point that define their position in their local space. For this simple example all astral object will be located
in their own center of local space, this mean that all astral object will hold only one point (0.0, 0.0, 1.0) except star that will be defined by 6 points.
Orbit will be wrapper for one Astral object, and will move child local space inside orbit space, this will actually move
it's child without them knowing about this operation. This works just like in real live, planet is rotating around sun but people on earth do not know about this movement.
public abstract class AstralObject {
protectedfinalfloat size;
protectedfinalint color;
protected Matrix modelMatrix;
protected Matrix modelViewMatrix;
protectedfloat[] center = newfloat[] { 0.0f, 0.0f, 1.0f };
protectedfloat[] out = newfloat[] {0.0f, 0.0f, 0.0f };
public AstralObject(float size, int color) {
this.size = size;
this.color = color;
this.modelMatrix = new Matrix();
this.modelViewMatrix = new Matrix();
}
public abstract void drawImpl(Graphics2D g2, Matrix modelViewMatrix);
public abstract void update(float dt);
public void draw(Graphics2D g2, Matrix parentMatrix) {
calculateModelViewMatrix(parentMatrix);
drawImpl(g2, modelViewMatrix);
}
public Matrix calculateModelViewMatrix(Matrix viewMatrix) {
Matrix.mul(viewMatrix, modelMatrix, modelViewMatrix);
return modelViewMatrix;
}
}
Abstract object holds information about :
- size and object color
- model matrix - this is local matrix it will translate/rotate current astral abject and all child with it.
- modelViewModel - this is buffer for merged model matrix and parent matrix
Astral object will have 4 methods:
- draw - this method will merge model matrix with it parent matrix, after that it will invoke abstract drawImpl with merged matrix.
- update - this method will be executed by timer with time difference between current call and previous call
- calculateModelViewMatrix this method do actual matrix merging
1.1 Natural Satellite
First we will look at Natural satellite model, this model will rotate little of center using modelMatrix.
We will rotate two point, center and shifted. First point will only be used to draw expected rotation, second point will be our actual satellite local position.
Update method will modify total rotation angle, and setup our local model matrix with this rotation.
Our abstract draw method will merge this local matrix to global vie model matrix
Drawing method will calculate center point in global position and draw magenta circle that will represent expected shifted rotation.
Radius of this circle will br equal to our shift vector length.
// apply view matrix to center point and store it in out arraymodelViewMatrix.mulVector(center, 0, out , 0, 1);
// extract x and y, and cast size to int.int x = (int)out[0];
int y = (int)out[1];
int hs = (int)size;
// draw arc, radius for local shift is equal to hals object sizeg2.setColor(Color.MAGENTA);
g2.drawArc(x - hs/2, y - hs/2, hs, hs, 0, 360);
Next step draw actual satellite :
// apply view matrix to shifted point,modelViewMatrix.mulVector(localShift, 0, out , 0, 1);
// extract and cast x,y to intx = (int)out[0];
y = (int)out[1];
// draw rectangle using (x,y) as cater pointg2.setColor(new Color(color));
g2.fillRect(x - hs /2, y - hs / 2, hs, hs);
1.2 Orbit
This object will wrap any AstralObject and append view model matrix with it "orbit matrix" before passing it to wrapped astral object.
This is additional data used by Orbit object:
- object - wrapped astral object
- angle / angleSpeed - this is current orbit angle and its angular speed
- shift matrix - this is orbit radius vector (object position for angle 0)
- rotation matrix - matrix created from "angle" it will represent orbit rotation
Shift and Rotation matrix merged together will produce Model Matrix, shift matrix is constant and equal to initial object shift( 0, radius)
First we update angle and then refresh rotation matrix, then we merge rotation with shift and create modelMatrix.
Next we will draw orbit, to do this we have to use only parent matrix, to do this we override "draw" method and before executing old
implementation that will merge modelMatrix with parentView Matrix we will draw orbit.
@Override
public void draw(Graphics2D g2, Matrix parentMatrix) {
g2.setColor(new Color(color));
int radius = (int)size/2;
// parentMatrix - matrix before added modelMatrix (orbit matrix) parentMatrix.mulVector(center, 0, out , 0, 1);
int x = (int)out[0];
int y = (int)out[1];
g2.drawArc(x - radius, y - radius, (int)radius * 2, (int)radius * 2, 0, 360);
// this method will merge orbit matrix and pass result to drawImpl super.draw(g2, parentMatrix);
}
Now we will merge matrix and pass it to wrapped astral object.
DrawImpl have merged model and view matrix as a second parameter, we have to only pass this value to wrapped object.
1.3 Plane.
This object have list of Orbits, all it doest is draw itself and pass method drawImpl and update method to orbits.
@Override
public void drawImpl(Graphics2D g2, Matrix modelViewMatrix) {
modelViewMatrix.mulVector(center, 0, out , 0, 1);
int x = (int)out[0];
int y = (int)out[1];
g2.setColor(new Color(color));
int hs = (int)size;
g2.fillArc(x - hs /2, y - hs / 2, hs, hs, 0, 360);
// pass drawing to orbitsfor(Orbit orbit : satellite) {
orbit.draw(g2, modelViewMatrix);
}
}
@Override
public void update(float dt) {
// pass update to orbitsfor(Orbit orbit : satellite) {
orbit.update(dt);
}
}
1.4 Star
This object works like planet it holds list of orbits and pass methods to them but it also show "complex" model rotation.
We will draw and rotate whole solar system and star model.
First we define our model when rotation angle is 0.
// this angle is only used to generate model, star will have every point 60 degree from each otherfloat angle = 60.0f / 360.0f;
angle = 2.0f * 3.14f * angle;
// 6 points (x,y w) starPoints = newfloat[] {
0.0f, -size / 2f, 1.0f,
size * (float)Math.sin(angle) / 2.0f, size * (float)Math.cos(angle) / 2.0f, 1.0f,
size * (float)Math.sin(-angle) / 2.0f, size * (float)Math.cos(-angle) / 2.0f, 1.0f,
0.0f, size / 2f, 1.0f,
size * (float)Math.sin(angle) / 2.0f, -size * (float)Math.cos(angle) / 2.0f, 1.0f,
size * (float)Math.sin(-angle) / 2.0f, -size * (float)Math.cos(-angle) / 2.0f, 1.0f,
};
// output buffer (model after applying matrix) starPointsOut = newfloat[3 * 6];
first we apply modelViewMatrix to star model, output is in startPointOut, after that we draw two overlapped triangles using fillPolygon method.
This method use two separate array, firs have only "x" values, second only "y". Next we pass drawing to our orbits with modelViewMatrix so orbits will rotate with solar system.
If orbit drawing was moved to draw(...) only star would rotate.
// create first green planet with size of 15 and add two natural satellites// first satellite have size of 12, starting rotation 45 degree, full rotation in 5 seconds, radius 50, rotating clockwise// Second satellite have size of 8, starting rotation 30, full rotation in 2 second radius 20, rotating counter clockwise Planet planet1 = new Planet(15.0f, 0x009900);
planet1.addNaturalSatellite(45.f, 360.0f/5f, 50.0f, 12.0f);
planet1.addNaturalSatellite(30.f, -360.0f/2f, 20.0f, 8.0f);
// create second blue planet size 60 with one natural satellite// Natural satellite start at -30 degree, full rotation in 10 seconds, radius 60, size 15 rotating clockwise Planet planet2 = new Planet(60.0f, 0x000099);
planet2.addNaturalSatellite(-30.f, 360.0f/10f, 60.0f, 15.0f);
// create third red planet size 30 with one natural satellite// Natural satellite start at -30 degree, full rotation in 10 seconds, radius 50, size 15 rotating clockwise Planet planet3 = new Planet(30.0f, 0x990000);
planet3.addNaturalSatellite(-30.f, 360.0f/10f, 50.0f, 15.0f);
// create star and add three planets// first(green) start at 0 degree, rotate in 15 seconds, radius 100, rotate clockwise// second(blue) start at 220 degree, rotate in 30 seconds, radius 220, rotate counter clockwise// third(red) start at 120 degree, rotate in 20 seconds, radius 350, rotate clockwise star = new Star(80.0f);
star.addPlanet(0.0f, 360/15.0f, 100.0f, planet1);
star.addPlanet(220.0f, -360/30.0f, 220.0f , planet2);
star.addPlanet(120.0f, 360/20.0f, 350.0f, planet3);