p5.js 如何正确计算点相对于原点的 3D 旋转

p5.js how to correctly compute the 3D rotation of a point in respect of the origin

我真的在这里很挣扎,我做不对,甚至不知道为什么。 我在 WEBGL 模式下使用 p5.js,我想计算围绕原点在 3 个轴上旋转的点的位置,以便跟随通过 p5.js, translation and rotatation on X axis, Y axis and Z axis 赋予对象的平移和旋转。

事实是,在3d中绘制一个球体space,p5.js,是通过平移和旋转得到的,因为球体是在原点的中心创建的,并且有没有内部模型 给出 3d 坐标。

经过几个小时的数学学习,我的知识太高了,我明白了 3 轴上的旋转并不像我想象的那么简单,我最终使用了 Quaternion.js。但是我仍然无法将球体在 3d 世界中的视觉位置与我从 2d 平面上的原始点 (150, 0, [0]) 计算出的坐标相匹配。

例如,这里的球体在 3 轴上旋转。一开始坐标很好(如果我忽略 Z 被否定的事实)但在某些时候它完全不同步。球体的计算位置似乎完全无关:

我真的花了几个小时来解决这个问题,没有结果,我错过了什么?

下面是我的代码:

//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];
    }
}

它在 Whosebug 中不起作用,因为我需要像字体这样的本地资源。

这里是工作代码:https://editor.p5js.org/cigno5/sketches/_ZVq0kjJL

我终于整理好了。我真的不明白为什么要这样工作,但我根本不需要四元数,而且我使用矩阵乘法在 3 轴上应用旋转的第一个直觉是正确的。

我首先错过的(并让我的生活变得悲惨)是矩阵乘法 不是可交换的 。这意味着在 x、y 和 z-axis 上应用旋转 不等同于 在 z、y 和 x 上应用相同的旋转角度。

工作解决方案已通过 3 个简单步骤实现:

  1. 使用向量将四元数替换为矩阵乘法(方法#resize2
  2. 按Z-Y-X顺序旋转绘图平面
  3. 按 X-Y-Z 顺序计算旋转
//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;
    }

    //
    #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];
    }
}

现在它就像一个魅力:

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