How to get rid of flotation point calculation errors?

Help! I recently started the work of a space game similar to Space Flight Simulator or Kerbal space program but 2D. I ran into a problem where if I zoom into a smaller celestial body like deimos (one of the moons of mars) it shakes on it’s ideal axis. I think this is related to a floating point calculation error. I’ve heard a lot of people say a fix for this is to use integer- rather than floating point values when calculating precise values, I just have no clue how I would implement that.

If you have any idea of how to fix this then please help me out.

here’s a short video about the problem: problem.mp4 - Google Drive

here’s some of the code from the game (note that all the values used in the game are 80 times smaller than in real life, examble: mars’es semi-major axis in real life is 227,939,366km. This is so all the planets would fit in the 32-Bit integer limit) (the code is also a bit simplifed and not very realistic to actual orbital mechanics):

float zoom = 0.0;

float deimosDiameter, deimosSemiMajorAxis, deimosEccentricity, deimosPerihelionAngle, deimosFocusPointX, deimosFocusPointY, deimosOrbitalPositionAngle, deimosOrbitalPosition, deimosX, deimosY;

float marsDiameter, marsSemiMajorAxis, marsEccentricity, marsPerihelionAngle, marsFocusPointX, marsFocusPointY, marsOrbitalPositionAngle, marsOrbitalPosition, marsX, marsY;

void setup() {
 size(1280, 720, P2D);
 deimosDiameter = 0.077;
 deimosSemiMajorAxis = 293.0;
 deimosEccentricity = 0.0;
 deimosPerihelionAngle = 0.0;
 deimosOrbitalPositionAngle = 0.0;

 marsDiameter = 84.0;
 marsSemiMajorAxis = 2849242.0;
 marsEccentricity = 0.0934;
 marsPerihelionAngle = 5.0003686;
 marsOrbitalPositionAngle = 0.0;
}
 
void draw() {
 background(0.0);
 if (deimosOrbitalPositionAngle + 0.005 > TWO_PI) {
  deimosOrbitalPositionAngle = 0.0 + (TWO_PI - deimosOrbitalPositionAngle);
 }
 else {
  deimosOrbitalPositionAngle += 0.005;
 }

 if (marsOrbitalPositionAngle - 0.005 < 0.0) {
  marsOrbitalPositionAngle = TWO_PI - (0.005 - marsOrbitalPositionAngle);
 }
 else {
  marsOrbitalPositionAngle -= 0.005;
 }

 marsFocusPointX = 0.0; // -------------------- mars
 marsFocusPointY = 0.0;
 PMatrix marsLocationCalculation = new PMatrix2D();
 PVector mars = new PVector();
 marsLocationCalculation.translate(marsFocusPointX, marsFocusPointY);
 float marsPerihelion = marsSemiMajorAxis * (1.0 - marsEccentricity);
 float marsAphelion = marsSemiMajorAxis * (1.0 + marsEccentricity);
 marsLocationCalculation.translate(-(marsAphelion * zoom - marsPerihelion * zoom), 0.0);
 marsLocationCalculation.rotate(marsPerihelionAngle + marsOrbitalPositionAngle + PI);
 marsLocationCalculation.translate(((marsSemiMajorAxis * zoom) * (1.0 - marsEccentricity * marsEccentricity)) / (1.0 + marsEccentricity * cos(marsPerihelionAngle + marsOrbitalPositionAngle)), 0.0);
 marsLocationCalculation.mult(new PVector(0.0, 0.0), mars);
 marsX = mars.x;
 marsY = mars.y;

 deimosFocusPointX = marsX; // -------------------- deimos
 deimosFocusPointY = marsY;
 PMatrix deimosLocationCalculation = new PMatrix2D();
 PVector deimos = new PVector();
 deimosLocationCalculation.translate(deimosFocusPointX, deimosFocusPointY);
 float deimosPerihelion = deimosSemiMajorAxis * (1.0 - deimosEccentricity);
 float deimosAphelion = deimosSemiMajorAxis * (1.0 + deimosEccentricity);
 // -------------------------------------------------- the zoom variable is currently set to 0.18814
 deimosLocationCalculation.translate(-(deimosAphelion * zoom - deimosPerihelion * zoom), 0.0);
 deimosLocationCalculation.rotate(deimosPerihelionAngle + deimosOrbitalPositionAngle + PI);
 deimosLocationCalculation.translate(((deimosSemiMajorAxis * zoom) * (1.0 - deimosEccentricity * deimosEccentricity)) / (1.0 + deimosEccentricity * cos(deimosPerihelionAngle + deimosOrbitalPositionAngle)), 0.0);
 deimosLocationCalculation.mult(new PVector(0.0, 0.0), deimos);
 deimosX = deimos.x;
 deimosY = deimos.y;
 
 // -------------------- camera

 zoom = 30.0 / deimosDiameter;
 zoom += (30.0 / deimosDiameter - zoom) / 3.0;
 translate(-deimosX + width / 2.0, -deimosY + height / 2.0);

 // ------------------- drawing the orbital paths and celestial bodys

 float resolution = 5000; // mars orbit
 float marsLastPointX = 0.0;
 float marsLastPointY = 0.0;
 noFill();
 stroke(100.0);
 strokeWeight(1); 
 for (int index = 0; index < resolution; index++) {
  pushMatrix();
  PMatrix pointInSpaceLocationCalculation = new PMatrix2D();
  PVector pointInSpace = new PVector();
  pointInSpaceLocationCalculation.translate(marsFocusPointX, marsFocusPointY);
  float angle = marsPerihelionAngle + map(index, 0, resolution - 1, 0.0, TWO_PI); 
  float radius = (marsSemiMajorAxis * (1.0 - marsEccentricity * marsEccentricity)) / (1.0 + marsEccentricity * cos(angle));
  pointInSpaceLocationCalculation.rotate(angle);
  pointInSpaceLocationCalculation.translate(radius * zoom, 0.0);
  pointInSpaceLocationCalculation.mult(new PVector(0.0, 0.0), pointInSpace);
  translate(marsFocusPointX, marsFocusPointY);
  if (index != 0) {
   if (screenY(pointInSpace.x, pointInSpace.y) > 0.0 && screenY(pointInSpace.x, pointInSpace.y) < height) {
    if (screenX(pointInSpace.x, pointInSpace.y) > 0.0 && screenX(pointInSpace.x, pointInSpace.y) < width) {
     line(marsLastPointX, marsLastPointY, pointInSpace.x, pointInSpace.y);
    }
    else if (screenY(marsLastPointX, marsLastPointY) > 0.0 && screenY(marsLastPointX, marsLastPointY) < height) {
     if (screenX(marsLastPointX, marsLastPointY) > 0.0 && screenX(marsLastPointX, marsLastPointY) < width) {
      line(marsLastPointX, marsLastPointY, pointInSpace.x, pointInSpace.y);   
     }
    }
   } 
   else if (screenY(marsLastPointX, marsLastPointY) > 0.0 && screenY(marsLastPointX, marsLastPointY) < height) {
    if (screenX(marsLastPointX, marsLastPointY) > 0.0 && screenX(marsLastPointX, marsLastPointY) < width) {
     line(marsLastPointX, marsLastPointY, pointInSpace.x, pointInSpace.y);   
    }
   }
  }
  marsLastPointX = pointInSpace.x;
  marsLastPointY = pointInSpace.y;
  popMatrix();
 }
 if (marsDiameter * zoom > 3.0) {
  pushMatrix();
  fill(#D8875E); // mars
  noStroke();
  translate(marsX, marsY);
  rotate(marsOrbitalPositionAngle);
  circle(0.0, 0.0, marsDiameter * zoom);
  popMatrix();   
 }
 else {
  pushMatrix();
  noFill(); // mars
  stroke(100.0);
  translate(marsX, marsY);
  circle(0.0, 0.0, 10.0);
  fill(255.0);
  text("Mars", 12.0, 0.0);
  popMatrix();   
 }

 resolution = 500; // deimos orbit
 float deimosLastPointX = 0.0;
 float deimosLastPointY = 0.0;
 noFill();
 stroke(100.0);
 strokeWeight(1); 
 for (int index = 0; index < resolution; index++) {
  pushMatrix();
  PMatrix pointInSpaceLocationCalculation = new PMatrix2D();
  PVector pointInSpace = new PVector();
  float angle = deimosPerihelionAngle + map(index, 0, resolution - 1, 0.0, TWO_PI); 
  float radius = (deimosSemiMajorAxis * (1.0 - deimosEccentricity * deimosEccentricity)) / (1.0 + deimosEccentricity * cos(angle));
  pointInSpaceLocationCalculation.rotate(angle);
  pointInSpaceLocationCalculation.translate(radius * zoom, 0.0);
  pointInSpaceLocationCalculation.mult(new PVector(0.0, 0.0), pointInSpace);
  translate(deimosFocusPointX, deimosFocusPointY);
  if (index != 0) {
   if (screenY(pointInSpace.x, pointInSpace.y) > 0.0 && screenY(pointInSpace.x, pointInSpace.y) < height) {
    if (screenX(pointInSpace.x, pointInSpace.y) > 0.0 && screenX(pointInSpace.x, pointInSpace.y) < width) {
     line(deimosLastPointX, deimosLastPointY, pointInSpace.x, pointInSpace.y);
    }
    else if (screenY(deimosLastPointX, deimosLastPointY) > 0.0 && screenY(deimosLastPointX, deimosLastPointY) < height) {
     if (screenX(deimosLastPointX, deimosLastPointY) > 0.0 && screenX(deimosLastPointX, deimosLastPointY) < width) {
      line(deimosLastPointX, deimosLastPointY, pointInSpace.x, pointInSpace.y);   
     }
    }
   } 
   else if (screenY(deimosLastPointX, deimosLastPointY) > 0.0 && screenY(deimosLastPointX, deimosLastPointY) < height) {
    if (screenX(deimosLastPointX, deimosLastPointY) > 0.0 && screenX(deimosLastPointX, deimosLastPointY) < width) {
     line(deimosLastPointX, deimosLastPointY, pointInSpace.x, pointInSpace.y);   
    }
   }
  }
  deimosLastPointX = pointInSpace.x;
  deimosLastPointY = pointInSpace.y;

  popMatrix();
 }
 if (deimosDiameter * zoom > 3.0) {
  pushMatrix();
  fill(#D8875E); // deimos
  noStroke();
  translate(round(deimosX), round(deimosY));
  rotate(deimosOrbitalPositionAngle); 
  circle(0.0, 0.0, deimosDiameter * zoom);
  //println(deimosX, " ", deimosY);
  popMatrix();   
 }
 else {
  pushMatrix();
  noFill(); // deimos
  stroke(100.0);
  translate(deimosX, deimosY);
  circle(0.0, 0.0, 10.0);
  fill(255.0);
  text("Deimos", 12.0, 0.0);
  popMatrix();   
 }
}

illustration

If you’re having a hard time figuring out whats shown in the sketch then check the attached image.

How to increase precision by using integers. Now you have presented everything as 1/80th km. Instead present everything in meters. deimosDiameter = 12400;. You need to change your zoom factor to counter the change in scale.
If errors come from angles that’s going to be harder to fix.

That would be a pretty simple fix, but the reason why the sketch is in that scale is so all planets would fit in the 32-Bit integer limit. So if I’d use this then the sketch wouldn’t even start.

For simple arithmetic problems then it can be beneficial to use integers instead of fpn’s for example in finance instead of using $ as the unit of money use cents so £1.23 is stored an manipulated as 123 cents.

In this project I don’t think this will work well, especially as you are using a lot of trig functions. The float data type supports ~6-7 significant figures so performing maths with very large and very small numbers can create Infinity and NaN values. If the problem is caused by this then I suggest you use the double (supports ~15-16 significant figures) data type throughout and cast the final result e.g. screen position etc. to float

Unfortunately Processing uses the float data type throughout so it would involve a lot of work on your part including creating your own Vector class with double attributes and using the Java trig functions provided in the Math class rather than the functions provided by Processing.

2 Likes

Hi

Here is some additional stuff

1 Like
1 Like

That seems like a pretty hard thing to accomplish, do you think I should just reprogram the game in a different language whlie it’s still in it’s early state? I’m somewhat decent at lua so I could just use the Löve game engine which already has cool stuff like built-in box2D functions.

Personally I have never used Lua or the Löve game engine so that would not be my first choice and as an experienced Java programmer I would take the route I suggested in my last post. NOTE: if the jitter is not caused by fpn calculation errors then this option won’t solve the problem.

You will have to decide based on your own previous programming experience but at first glance Lua seems a viable option and probably the easier way to go. :slight_smile:

2 Likes