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.
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)
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);
}
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;