Help using rotateX() rotateY() and rotateZ() to make an object pitch yaw and roll

I have a program where I have a spaceship with pitch, yaw, and roll values and I want to rotate everything else to make it seem like the camera is attached to the ship. Currently, this is my code:

rotateX(pitch);
rotateY(yaw);
rotateZ(roll);

This works fine for any rotation as long as the rotate functions above it have not had any effect(so pitch always works fine), but if they have modified the orientation in any way, the object does not rotate correctly. I don’t know how to explain it well, but hopefully, someone will understand.

I’ve tried many things already and figured out that what I’m trying to do is go from Euler coordinates to Cartesian coordinates, but I don’t know how to do this and all the explanations I’ve found I don’t know how to get working in Processing.

This is the one thing that’s holding me back from completing this project, I feel like everything else I can figure out on my own but this is just too confusing for me. I’ve done something similar in the past but can’t quite remember how to do it.

If anyone knows how to fix this, that would be greatly appreciated.

1 Like

Hi @RomketBoi,

Welcome to the forum! :wink:

If I understand correctly, what you need is: pushMatrix() and popMatrix()

It allows you to save the state of the current transform and restore it later. That way the transformations don’t accumulate over time.

// Save the current transform
pushMatrix();

// Transform
rotateX(pitch);
rotateY(yaw);
rotateZ(roll);

// Display the thing

popMatrix();
// Now we are back to the previous transform

I think Johnny is not on the right track.

The sentence indicates that the issue is elsewhere.
It’s that the rotation accumulated has not the desired effect.

It’s something along the lines that after the first rotation when you rotate again the second axis is not correct anymore. This is because the axis has been rotated and is now globally and not locally to the plane or something.

So the rotation is not as you expect a plane to rotate, the tilt is wrong.

There is no easy solution because that’s how rotation goes.

It’s been discussed and solved with mathematical magic but I can’t find it.

2 Likes

Yeah this is what I’m looking for. I kept looking and what I’m trying to do is convert a rotation from an intrinsic system (pitch, yaw, roll) to extrinsic (rotationX, rotationY, rotationZ). I just need the formula to do this,

1 Like

Hello @RomketBoi,

The order of the rotations is important.

Match the yaw, pitch and roll to the correct axis (see Processing tutorials) and think about it.

You can go through all the combinations or think through it intuitively (this may come with experience).

image

Try something simple and rotate with a mouse movement to visualize and understand this.

You can map() the mouse movement to an angle:
https://processing.org/reference/map_.html

Reference:
https://www.euclideanspace.com/maths/geometry/rotations/euler/

The above was an excellent reference for me in my exploration of this in the past.
The important thing here is order is important.

:)

1 Like

There are two matrices to consider

  1. Model view matrix (mvMatrix)
  2. Projection matrix (pMatrix)

The pitch, yaw and roll rotations have to be applied to the mvMatrix but (AFAIK) Processing’s transformation methods including rotation are applied to pMatrix.

I have tried many times to find the pMatrix rotations for given values of pitch, yaw and roll without success.

3 Likes

It’s been a while since I dealt with this sorta problem, but it sounds like gimbal lock. Maybe try keeping a matrix around to store the rotation. Rotate by an angular speed around an axis. Then apply the matrix.

boolean[] buttons = new boolean[256];
PMatrix3D m = new PMatrix3D();
float rotSpeed = 0.05;

void setup() {
  size(640, 320, P3D);
}

void draw() {

  float xAxis = 0.0;
  float yAxis = 0.0;
  float zAxis = 0.0;

  // pitch
  if (buttons[87]) // w
    --xAxis;
  if (buttons[83]) // s
    ++xAxis;

  // yaw
  if (buttons[65]) // a
    ++yAxis;
  if (buttons[68]) // d
    --yAxis;

  // roll
  if (buttons[81]) // q
    --zAxis;
  if (buttons[69]) // e
    ++zAxis;

  // If the axis is nonzero, then normalize it.
  float axisMagSq = xAxis * xAxis
    + yAxis * yAxis
    + zAxis * zAxis;
  if (axisMagSq > 0.0) {
    float axisMag = sqrt(axisMagSq);
    xAxis /= axisMag;
    yAxis /= axisMag;
    zAxis /= axisMag;
    m.rotate(rotSpeed, xAxis, yAxis, zAxis);
  }

  background(#fff7d5);
  lights();
  translate(width * 0.5, height * 0.5);

  pushMatrix();
  applyMatrix(m);
  fill(#ff0000);
  box(150, 20, 20);
  fill(#0000ff);
  box(20, 20, 150);
  fill(#00ff00);
  box(20, 150, 20);
  popMatrix();
}

void keyPressed() {
  buttons[keyCode] = true;
}

void keyReleased() {
  buttons[keyCode] = false;
}

I read from your description that you’re trying to rotate the world, not an object in the world. This will not directly apply to that larger goal, but I hope it helps with the smaller, immediate issue.

3 Likes

Here as an excellent tutorial for rotation, which might be helpful:

1 Like

And these as well:

:)

3 Likes

@glv’s second link gives the right answer to your question, but it’s buried pretty deep.

You need to track your ship’s orientation either as a 3x3 matrix or as a quaternion. Processing has a PMatrix3D which is a 4x4 matrix that could do the trick, but it’s not in the Processing reference. You can find it here https://github.com/processing/processing4/blob/4eb0939a9ecee88cf00a509fc1ed612b3c007270/core/src/processing/core/PMatrix3D.java.

The key is that when you rotate your ship, you want to do so in ship coordinates, not in world space coordinates. That means you want to pre-multiply the X-, Y-, or Z-rotation matrix on the left side of the ship’s orientation matrix or quaternion.

Another thing to keep in mind is that your orientation matrix or quaternion with accumulate floating-point errors over time, so you will want to normalize it. For a matrix, that means making sure that the rows are unit vectors that are perpendicular to each other. For a quaternion, you just have to make it unit length. I prefer quaternions.

4 Likes

I agree apart from the notion that op wants to rotate not the plane / spaceship but the world. Probably camera is following the spaceship closely like in a TPS (third-person shooter).
(Maybe it’s like a flight simulator and it’s the look out of the plane’s windows.)

1 Like

Rotating the world is identical to rotating the ship. One is just the inverse of the other. When geometry moves through the rendering pipeline, vertices are transformed from object space to world space and then transformed into camera space. It’s more efficient to store the geometry in world space coordinates and let opengl transform to camera space for the rendering.

The important point is that Processing doesn’t retain any information about camera position from frame to frame, so you have to track that yourself. Processing’s transformations (rotateX(), etc) all apply on the world-space side of the matrix stack (post-multiplying). To move the camera using local transformations, you need to pre-multiply which means you can’t use the convenient, documented, Processing functions do move your camera around. PMatrix3D, on the other hand, has a preApply() function that multiplies on the left.

4 Likes

Here’s a flyer, using some bits from @behreajj, that uses quaternions for the rotation. Use the arrows for pitch and yaw, and a and d for roll. w and s control the speed.

Each box is included 8 times (twice in each dimension) to wrap the space. I can go up to nShapes = 40000 and still get a comfortable frame rate. At 50000, Processing runs out of memory somewhere.

int nShapes = 8000;
int[] keys;
PVector xAxis, yAxis, zAxis;
Ship ship;
PShape stuff;

void setup() {
  fullScreen( P3D );
  //size( 900, 600, 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();
}

void draw() {
  background( 0 );
  ship.move();
  ship.setCamera();
  shape( stuff );
}

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

class Ship {
  PVector pos;
  Quat rot;
  float speed;
  
  Ship() {
    pos = new PVector();
    speed = 2;
    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.01 ).rotateBy(rot);
    speed += (keys[87]-keys[83]) * 0.015;   // 'w', 's'
    pos.add( rot.rotate( zAxis ).mult(-speed) );
    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 );
    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 = random(-500, 500);
    float py = random(-500, 500);
    float 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;
}

void keyReleased() {
  if( keyCode < 256 ) keys[keyCode] = 0;
}

Thank you, this is exactly what I needed.

2 Likes