3

I'm really struggling here and I can't get it right, not even knowing why. I'm using p5.js in WEBGL mode, I want to compute the position of on point rotated on the 3 axes around the origin in order to follow the translation and the rotation given to object through p5.js, translation and rotatation on X axis, Y axis and Z axis.

The fact is that drawing a sphere in 3d space, withing p5.js, is obtained by translating and rotating, since the sphere is created at the center in the origin, and there is no internal model giving the 3d-coordinates.

After hours of wandering through some math too high for my knowledge, I understood that the rotation over 3-axis is not as simple as I thought, and I ended up using Quaternion.js. But I'm still not able to match the visual position of the sphere in the 3d world with the coordinates I have computed out of the original point on the 2d-plane (150, 0, [0]).

For example, here the sphere is rotated on 3 axis. At the beginning the coordinates are good (if I ignore the fact that Z is negated) but at certain point it gets completely out of sync. The computed position of the sphere seems to be completely unrelated:

wrong coordinates

It's really hours that I'm trying to solve this issue, with no result, what did I miss?

Here it follows my code:

//font for WEBGL
var robotoFont;
var dotId = 0;

var rotating = true;

var orbits = [];
var dotsData = [];

function preload() {
    robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}

function setup() {
    createCanvas(windowWidth, windowHeight, WEBGL);
    textFont(robotoFont);
    background(0);

    let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
    orbit1.obj.push(new Dot(0, 0));
    orbits.push(orbit1);
    // let orbit2 = new Orbit(90, 45, 0);
    // orbit2.obj.push(new Dot(0, 0));
    // orbits.push(orbit2);
}

function draw() {
    angleMode(DEGREES);
    background(0);
    orbitControl();

    let len = 200;
    fill('white');
    stroke('white');
    sphere(2);
    stroke('red');
    line(0, 0, 0, len, 0, 0);
    text('x', len, 0)
    stroke('green');
    line(0, 0, 0, 0, len, 0);
    text('y', 0, len)
    push();
    rotateX(90);
    stroke('yellow');
    line(0, 0, 0, 0, len, 0);
    text('z', 0, len)
    pop();

    dotsData = [];

    orbits.forEach(o => o.draw());

    textSize(14);
    push();
    for (let i = 0; i < 2; i++) {
        let yPos = -(windowHeight / 2) + 15;
        for (let i = 0; i < dotsData.length; i++) {
            let [id, pos, pos3d] = dotsData[i];
            let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
            let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
            text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
            yPos += 18;
        }

        rotateX(-90);
    }
    pop();

}

function mouseClicked() {
    // controls.mousePressed();
}

function keyPressed() {
    // controls.keyPressed(keyCode);
    if (keyCode === 32) {
        rotating = !rotating;
    }
}

class Orbit {
    constructor(x, y, z, xr, yr, zr) {
        this.obj = [];
        this.currentRot = [
            x ? x : 0,
            y ? y : 0,
            z ? z : 0
        ]
        this.rot = [
            xr ? xr : 0,
            yr ? yr : 0,
            zr ? zr : 0
        ]
    }

    draw() {
        push();

        if (rotating) {
            this.currentRot[0] += this.rot[0];
            this.currentRot[1] += this.rot[1];
            this.currentRot[2] += this.rot[2];
        }

        rotateY(this.currentRot[1]);
        rotateX(this.currentRot[0]);
        rotateZ(this.currentRot[2]);

        noFill();
        stroke('white');
        ellipse(0, 0, 300, 300);

        for (let i = 0; i < this.obj.length; i++) {
            let o = this.obj[i];
            o.draw();
            dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
        }

        pop();
    }

    #get3DPos(o) {
        let [x, y, z] = o.getPosition();
        let w = 0;
        let rotX = this.currentRot[0] * PI / 180;
        let rotY = this.currentRot[1] * PI / 180;
        let rotZ = this.currentRot[2] * PI / 180;

        let rotation = Quaternion.fromEuler(rotZ, rotX, rotY, 'ZXY').conjugate();
        [x, y, z] = rotation.rotateVector([x, y, z]);

        return createVector(x, y, z);
    }
}


class Dot {

    constructor(angle) {
        this.id = ++dotId;
        this.x = cos(angle) * 150;
        this.y = sin(angle) * 150;
    }

    draw() {
        push();
        fill('gray');
        translate(this.x, this.y);
        noStroke();
        sphere(15);
        pop();
    }

    getPosition() {
        return [this.x, this.y, 0];
    }
}

It doesn't work in stackoverflow because I need local asset like the font.

Here the working code: https://editor.p5js.org/cigno5/sketches/_ZVq0kjJL

cigno5.5
  • 703
  • 4
  • 13
  • I think your issue has something to do with rotating in all 3 directions. If you want to do a rotation on a cartesian plane you only need to rotate in one orientation. So I would assume that in 3D space you can rotate the orbit in all orientations with only 2 axes of rotation, however, doing this will limit your freedom of motion. It really depends on what you're trying to achieve. What is your goal with the rotation? Do you need the sphere to be animated or is your plan to have it in a fixed spot? Is the sphere going to orbit like a planet? – Henhen1227 Feb 26 '22 at 17:00
  • The sphere was originally meant to orbit like a planet but the idea quickly scaled to the highest degree of difficulty (where I've found myself trapped in) and I wanted to rotate all 3 axes **plus** the sphere running through the orbit drawn on a 2d plane – cigno5.5 Feb 26 '22 at 21:01
  • But I've kept reasoning and my latest thoughts are 2: 1. I don't need 3-axis rotation! 2-axis rotation plus the orbital movement of the sphere is enough (simplify this way I can solve) 2. The misalignament is *probably* due because the 3-axis rotation achieved in the viewport is subject to the [gimbal lock](https://en.wikipedia.org/wiki/Gimbal_lock) and the math calculation using quaternions is not! (<-- still an hypothesis, not confirmed yet) – cigno5.5 Feb 26 '22 at 21:03

1 Answers1

3

I've finally sorted out. I can't really understand why works this way but I didn't need quaternion at all, and my first intuition of using matrix multiplications to apply rotation on 3-axis was correct.

What I did miss in first instance (and made my life miserable) is that matrix multiplication is not commutative. This means that applying rotation on x, y and z-axis is not equivalent to apply same rotation angle on z, y and x.

The working solution has been achieved with 3 simple steps:

  1. Replace quaternion with matrix multiplications using vectors (method #resize2)
  2. Rotating the drawing plane with Z-Y-X order
  3. Doing the math of rotation in X-Y-Z order
//font for WEBGL
var robotoFont;
var dotId = 0;

var rotating = true;

var orbits = [];
var dotsData = [];

function preload() {
    robotoFont = loadFont('./assets/Roboto-Regular.ttf');
}

function setup() {
    createCanvas(windowWidth, windowHeight, WEBGL);
    textFont(robotoFont);
    background(0);

    let orbit1 = new Orbit(0, 0, 0, 0.5, 0.5, 0.5);
    orbit1.obj.push(new Dot(0, 0.5));
    orbits.push(orbit1);
    // let orbit2 = new Orbit(90, 45, 0);
    // orbit2.obj.push(new Dot(0, 0));
    // orbits.push(orbit2);
}

function draw() {
    angleMode(DEGREES);
    background(0);
    orbitControl();

    let len = 200;
    fill('white');
    stroke('white');
    sphere(2);
    stroke('red');
    line(0, 0, 0, len, 0, 0);
    text('x', len, 0)
    stroke('green');
    line(0, 0, 0, 0, len, 0);
    text('y', 0, len)
    push();
    rotateX(90);
    stroke('yellow');
    line(0, 0, 0, 0, len, 0);
    text('z', 0, len)
    pop();

    dotsData = [];

    orbits.forEach(o => o.draw());

    textSize(14);
    push();
    for (let i = 0; i < 2; i++) {
        let yPos = -(windowHeight / 2) + 15;
        for (let i = 0; i < dotsData.length; i++) {
            let [id, pos, pos3d] = dotsData[i];
            let [x1, y1, z1] = [pos[0].toFixed(0), pos[1].toFixed(0), pos[2].toFixed(0)];
            let [x2, y2, z2] = [pos3d.x.toFixed(0), pos3d.y.toFixed(0), pos3d.z.toFixed(0)];
            text(`${id}: (${x1}, ${y1}, ${z1}) -> (${x2}, ${y2}, ${z2})`, -windowWidth / 2 + 5, yPos);
            yPos += 18;
        }

        rotateX(-90);
    }
    pop();

}

function mouseClicked() {
    // controls.mousePressed();
}

function keyPressed() {
    // controls.keyPressed(keyCode);
    if (keyCode === 32) {
        rotating = !rotating;
    }
}

class Orbit {
    constructor(x, y, z, xr, yr, zr) {
        this.obj = [];
        this.currentRot = [
            x ? x : 0,
            y ? y : 0,
            z ? z : 0
        ]
        this.rot = [
            xr ? xr : 0,
            yr ? yr : 0,
            zr ? zr : 0
        ]
    }

    draw() {
        push();

        if (rotating) {
            this.currentRot[0] += this.rot[0];
            this.currentRot[1] += this.rot[1];
            this.currentRot[2] += this.rot[2];
        }

        rotateZ(this.currentRot[2]);
        rotateY(this.currentRot[1]);
        rotateX(this.currentRot[0]);

        noFill();
        stroke('white');
        ellipse(0, 0, 300, 300);

        for (let i = 0; i < this.obj.length; i++) {
            let o = this.obj[i];
            o.draw();
            dotsData.push([o.id, o.getPosition(), this.#get3DPos(o)]);
        }

        pop();
    }

    #get3DPos(o) {
        let [x, y, z] = o.getPosition();
        let pos = createVector(x, y, z);
        pos = this.#rotate2(pos, createVector(1, 0, 0), this.currentRot[0]);
        pos = this.#rotate2(pos, createVector(0, 1, 0), this.currentRot[1]);
        pos = this.#rotate2(pos, createVector(0, 0, 1), this.currentRot[2]);
        return pos;
    }

    //https://stackoverflow.com/questions/67458592/how-would-i-rotate-a-vector-in-3d-space-p5-js
    #rotate2(vect, axis, angle) {
        // Make sure our axis is a unit vector
        axis = p5.Vector.normalize(axis);

        return p5.Vector.add(
            p5.Vector.mult(vect, cos(angle)),
            p5.Vector.add(
                p5.Vector.mult(
                    p5.Vector.cross(axis, vect),
                    sin(angle)
                ),
                p5.Vector.mult(
                    p5.Vector.mult(
                        axis,
                        p5.Vector.dot(axis, vect)
                    ),
                    (1 - cos(angle))
                )
            )
        );
    }

}


class Dot {

    constructor(angle, speed) {
        this.id = ++dotId;
        this.angle = angle;
        this.speed = speed
    }

    draw() {
        this.angle += this.speed;
        this.x = cos(this.angle) * 150;
        this.y = sin(this.angle) * 150;

        push();
        fill('gray');
        translate(this.x, this.y);
        noStroke();
        sphere(15);
        pop();
    }

    getPosition() {
        return [this.x, this.y, 0];
    }
}

And now it works like a charm:

https://editor.p5js.org/cigno5/sketches/PqB9CEnBp

cigno5.5
  • 703
  • 4
  • 13