Search code examples
rotationprocessingrenderer

Bug in my reimplementation of Processing's rotate() method


To develop a better understanding of Processing's drawing techniques (especially for the translate() and rotate() functions), I began the quest to reimplement the translate(), rotate() and point() methods.

While doing so, I soon experienced game over, when I got completely stuck on the rotate() method, whose behavior is just mad. Showing some strange (3D) rotation around multiple axes.

Comparison of my reimplementation (left) to the language's implementation (right)

The code doing the rotation for each point:

x = x * c - y * s; // cos(a) * cos(b) - sin(a) * sin(b)
y = y * c + x * s; // sin(a) * cos(b) + cos(a) * sin(b)

Full Code

class MyRenderer {
  /* Added to every point - set through translation. */
  int xOffset = 0;
  int yOffset = 0;
  /** Prevents unnecessary rotations around an angle of 0. Owed to the architecture. */
  boolean rotated = false;

  /** The values of c(osine) and s(ine) in the unit circle, for the given angle.
   *  This way they don't have to be recalculated for every single point. */
  float c, s;

  private void myTranslate(final int x, final int y) {
    xOffset += x;
    yOffset += y;
  }

  /** Actual rotation, transforming each point's coordinates, done in myPoint() */
  private void myRotate(final int deg) {
    c = cos(radians(deg));
    s = sin(radians(deg));

    rotated = true;
  }

  /** Draws the points to the screen with respect to the set translation and rotation. */
  private void myPoint(float x, float y) {
    // First rotate...
    if (rotated) {  // Only do the transformation if a rotation actually occured.
      x = x * c - y * s; // cos(a) * cos(b) - sin(a) * sin(b)
      y = y * c + x * s; // sin(a) * cos(b) + cos(a) * sin(b)
    }
    // ... then translate.
    x += xOffset; y += yOffset;
    // Finally draw.
    point(x, y);
  }

  // Needed to reset the translation and rotation with every draw call - like the original.
  private int debugAngle = 0;
  public void myDraw() {
    reset();
    background(255);

    myTranslate(50, 50);
    myRotate(debugAngle++);
    // Point cloud in shape of a square.
    myPoint(0, 0);
    myPoint(-10, -10);
    myPoint(  0, -10);
    myPoint( 10, -10);
    myPoint( 10,   0);
    myPoint( 10,  10);
    myPoint( 0,   10);
    myPoint(-10,  10);
    myPoint(-10,   0);
  }

  /** Reset all scene transforms, mirroring the behavior of Processing's draw() method. */
  private void reset() {
    xOffset = 0;
    yOffset = 0;
    c = 1;  // cos for 0 degrees.
    s = 0;  // sin for 0 degrees.
    rotated = false;
  }
}


/* ========================================================================================= */

MyRenderer r;

void setup() {
  size(200, 100, FX2D);
  frameRate(30);
  r = new MyRenderer();
}

int debugAngle = 0;
void draw() {
  r.myDraw();
  debugRender();
}

/** Diplay a second 'rectangle' to compare the results of my renderer reimplementation to. */
void debugRender() {
  translate(100, 50);
  rotate(radians(debugAngle++));
  // Point cloud in shape of a square.
  point(0, 0);
  point(-10, -10);
  point(  0, -10);
  point( 10, -10);
  point( 10,   0);
  point( 10,  10);
  point( 0,   10);
  point(-10,  10);
  point(-10,   0);
}

Solution

  • While concluding my question I found the mistake. Writing it down probably helped. Still it is embarrassing how I repeat that beginners' mistake from time to time again!

    I still upload that question, as I have finished writing it down. May it be of help to someone, yet unknown to me.


    The error was overwriting x too soon, what led to a miscalculation of y, by falsely using the already rotated x value, instead of non-transformed x.

    Instead of

    x = x * c - y * s; <== ERROR
    y = y * c + x * s;
    

    the transformed x has to be buffered until all calculations relying on the original x are complete:

    float tmpX = x * c - y * s;
          y    = y * c + x * s;
          x    = tmpX;