2

I have a cube that rotates on its 3 axis, when key[a] == true it will spin to the left as if it was rolling that way. rotating the cube 45 degrees in any direction sets it back 90 degrees for the illusion of continuing. this maintains 3 axis that are < 45 degrees off from the environment

I believe this is correct but the x axis for the cube seems to be relative to the environment while y and z are relative to the cubes orientation, I can not find reference to this in the documentation is it a bug? https://processing.org/reference/rotateY_.html https://processing.org/reference/rotateX_.html

  if(keys[w]) { 
    if (x >= 359) x = 0;
    x = x + 1;
  }  
  if(keys[a]) { 
    if (z >= 359) z = 0;
    z = z + 1;
  }  
  if(keys[s]) { 
    if (x <= 0) x = 359;
    x = x - 1;
  }  
  if(keys[d]) { 
    if (z <= 0) z = 359;     
    z = z - 1;
  }
   
  // return 90 deg for magic trick       
  if (x > 45 && x < 180) x = 270 + x;
  if (x < 316 && x > 180) x = 360 - x;
 
  if (y > 45 && y < 180) y = 270 + y;
  if (y < 316 && y > 180) y = 360 - y;
 
cubesareneat
  • 302
  • 2
  • 14
  • 1
    It's certainly not a library bug (for sure). You have to show how you use the variables `x`, `y` and `z`. Note matrix transformations are not [commutative](https://en.wikipedia.org/wiki/Commutative_property). It all depends on the order of the [`rotate()`](https://processing.org/reference/rotate_.html) calls – Rabbid76 Dec 31 '20 at 14:06

2 Answers2

2

Matrix transformations are not commutative. The order matters. The matrix operations like rotate() specify a new matrix and multiply the current matrix by the new matrix.

Hence, there is a difference between doing this

rotateX(x);
rotateY(y);
rotateZ(z);

and doing that

rotateZ(z);
rotateY(y);
rotateX(x);

And

rotateX(x1 + x2);
rotateY(y1 + y2);
rotateZ(z1 + z2);

is not the same as

rotateX(x1);
rotateY(y1);
rotateZ(z1);
rotateX(x2);
rotateY(y2);
rotateZ(z2);

One possible solution to your problem would be to use Quaternions. Quaternions behave differently than Euler angles and have also no Gimbal lock issue. Processing use OpenGL under the hood and doesn't support quaternions. However a quaternion can be transformed to a matrix and a matrix can be applied by applyMatrix().

Rabbid76
  • 202,892
  • 27
  • 131
  • 174
  • is quarternions a solution? do you know any others? I think I have heard this referred to as gimbal lock elsewhere – cubesareneat Jan 01 '21 at 02:32
  • 1
    @cubesareneat [Quaternion](https://en.wikipedia.org/wiki/Quaternion) behaves different than [Euler angles](https://en.wikipedia.org/wiki/Euler_angles) and have no [Gimbal lock](https://en.wikipedia.org/wiki/Gimbal_lock) issue. Processing use OpenGL under the hood and doesn't support quaternions. However a quaternion can be transformed to a matrix and a matrix can be applied by [`applyMatrix()`](https://processing.org/reference/applyMatrix_.html). – Rabbid76 Jan 01 '21 at 07:48
0

I found this ArcBall example that dose exactly what I wanted. just added a modification to work with keys instead of mouse drag.
ArcBall with mod

// Ariel and V3ga's arcball class with a couple tiny mods by Robert Hodgin & more by me

class Arcball {
  float center_x, center_y, radius;
  Vec3 v_down, v_drag;
  Quat q_now, q_down, q_drag;
  Vec3[] axisSet;
  int axis;
  float mxv, myv;
  float x, y;
  
  Arcball(float center_x, float center_y, float radius){
    this.center_x = center_x;
    this.center_y = center_y;
    this.radius = radius;

    v_down = new Vec3();
    v_drag = new Vec3();

    q_now = new Quat();
    q_down = new Quat();
    q_drag = new Quat();

    axisSet = new Vec3[] {new Vec3(1.0f, 0.0f, 0.0f), new Vec3(0.0f, 1.0f, 0.0f), new Vec3(0.0f, 0.0f, 1.0f)};
    axis = -1;  // no constraints...    
  }

  void rollforward(){
    q_down.set(q_now);
    v_down = XY_to_sphere(center_x, center_y);
    q_down.set(q_now);
    q_drag.reset();
    
    v_drag = XY_to_sphere(center_x, center_y - 10);
    q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag)); 
  }
  void rolldown(){
    q_down.set(q_now);
    v_down = XY_to_sphere(center_x, center_y);
    q_down.set(q_now);
    q_drag.reset();
    
    v_drag = XY_to_sphere(center_x, center_y + 10);
    q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag)); 
  }
  void rollleft(){
    q_down.set(q_now);
    v_down = XY_to_sphere(center_x + radius, center_y + radius);
    q_down.set(q_now);
    q_drag.reset();
    
    v_drag = XY_to_sphere(center_x + 10 * PI + radius, center_y + radius);
    q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag)); 
  }
  void rollright(){
    q_down.set(q_now);
    v_down = XY_to_sphere(center_x + radius, center_y + radius);
    q_down.set(q_now);
    q_drag.reset();
    
    v_drag = XY_to_sphere(center_x - 10 * PI + radius, center_y + radius);
    q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag)); 
  }

  void mousePressed(){
    v_down = XY_to_sphere(mouseX, mouseY);   // when m pressed 
    q_down.set(q_now);
    q_drag.reset();
  }

  void mouseDragged(){
    v_drag = XY_to_sphere(mouseX, mouseY);
    q_drag.set(Vec3.dot(v_down, v_drag), Vec3.cross(v_down, v_drag));
  }

  void run(){
    q_now = Quat.mul(q_drag, q_down);
    applyQuat2Matrix(q_now);
    
    x += mxv;
    y += myv;
    mxv -= mxv * .01;
    myv -= myv * .01;
  }
  
  Vec3 XY_to_sphere(float x, float y){
    Vec3 v = new Vec3();
    v.x = (x - center_x) / radius;
    v.y = (y - center_y) / radius;

    float mag = v.x * v.x + v.y * v.y;
    if (mag > 1.0f){
      v.normalize();
    } else {
      v.z = sqrt(1.0f - mag);
    }

    return (axis == -1) ? v : constrain_vector(v, axisSet[axis]);
  }

  Vec3 constrain_vector(Vec3 vector, Vec3 axis){
    Vec3 res = new Vec3();
    res.sub(vector, Vec3.mul(axis, Vec3.dot(axis, vector)));
    res.normalize();
    return res;
  }

  void applyQuat2Matrix(Quat q){
    // instead of transforming q into a matrix and applying it...

    float[] aa = q.getValue();
    rotate(aa[0], aa[1], aa[2], aa[3]);
  }
}

static class Vec3{
  float x, y, z;

  Vec3(){
  }

  Vec3(float x, float y, float z){
    this.x = x;
    this.y = y;
    this.z = z;
  }

  void normalize(){
    float length = length();
    x /= length;
    y /= length;
    z /= length;
  }

  float length(){
    return (float) Math.sqrt(x * x + y * y + z * z);
  }

  static Vec3 cross(Vec3 v1, Vec3 v2){
    Vec3 res = new Vec3();
    res.x = v1.y * v2.z - v1.z * v2.y;
    res.y = v1.z * v2.x - v1.x * v2.z;
    res.z = v1.x * v2.y - v1.y * v2.x;
    return res;
  }

  static float dot(Vec3 v1, Vec3 v2){
    return v1.x * v2.x + v1.y * v2.y + v1.z * v2.z;
  }
  
  static Vec3 mul(Vec3 v, float d){
    Vec3 res = new Vec3();
    res.x = v.x * d;
    res.y = v.y * d;
    res.z = v.z * d;
    return res;
  }

  void sub(Vec3 v1, Vec3 v2){
    x = v1.x - v2.x;
    y = v1.y - v2.y;
    z = v1.z - v2.z;
  }
}

static class Quat{
  float w, x, y, z;

  Quat(){
    reset();
  }

  Quat(float w, float x, float y, float z){
    this.w = w;
    this.x = x;
    this.y = y;
    this.z = z;
  }

  void reset(){
    w = 1.0f;
    x = 0.0f;
    y = 0.0f;
    z = 0.0f;
  }

  void set(float w, Vec3 v){
    this.w = w;
    x = v.x;
    y = v.y;
    z = v.z;
  }

  void set(Quat q){
    w = q.w;
    x = q.x;
    y = q.y;
    z = q.z;
  }

  static Quat mul(Quat q1, Quat q2){
    Quat res = new Quat();
    res.w = q1.w * q2.w - q1.x * q2.x - q1.y * q2.y - q1.z * q2.z;
    res.x = q1.w * q2.x + q1.x * q2.w + q1.y * q2.z - q1.z * q2.y;
    res.y = q1.w * q2.y + q1.y * q2.w + q1.z * q2.x - q1.x * q2.z;
    res.z = q1.w * q2.z + q1.z * q2.w + q1.x * q2.y - q1.y * q2.x;
    return res;
  }
  
  float[] getValue(){
    // transforming this quat into an angle and an axis vector...

    float[] res = new float[4];

    float sa = (float) Math.sqrt(1.0f - w * w);
    if (sa < EPSILON){
      sa = 1.0f;
    }

    res[0] = (float) Math.acos(w) * 2.0f;
    res[1] = x / sa;
    res[2] = y / sa;
    res[3] = z / sa;

    return res;
  }
}

main

Arcball arcball;

//framecount
int fcount, lastm;
float frate;
int fint = 3;

boolean[] keys = new boolean[4];
    final int w = 0;
    final int s = 1;
    final int a = 2;
    final int d = 3;

void setup() {
  size(900, 700, P3D); 
  frameRate(60);
  noStroke();
  
  arcball = new Arcball(width/2, height/2+100, 360);  
}

void draw() {
  lights();
  background(255,160,122);
  
  if(keys[w]) { arcball.rollforward(); }
  if(keys[a]) { arcball.rollleft(); }
  if(keys[s]) { arcball.rolldown(); }
  if(keys[d]) { arcball.rollright(); }
   
  ambient(80);   
  lights();
  translate(width/2, height/2-100, 0);
  box(50);
   
  translate(0, 200, 0);
  arcball.run();
  box(50);  
  
}

void keyPressed() {
  switch(key) {
    case 97: 
        keys[a] = true;
        break;
    case 100: 
        keys[d] = true;
        break;      
    case 115: 
        keys[s] = true;
        break;
    case 119: 
        keys[w] = true;
        break;
    } 
}

void keyReleased() {
  switch(key) {
    case 97: 
        keys[a] = false;
        break;
    case 100: 
        keys[d] = false;
        break;      
    case 115: 
        keys[s] = false;
        break;
    case 119: 
        keys[w] = false;
        break;
    } 
}

will add support for multiple keys at once later with an edit.... stay tuned

cubesareneat
  • 302
  • 2
  • 14