p5.js如何正确计算点关于原点的三维旋转

3
我在这里真的很苦恼,甚至不知道为什么做不对。我正在使用 WEBGL 模式下的 p5.js,想要计算绕原点旋转在三个轴上的一个点的位置,以便通过 p5.js平移, 以及 X轴旋转Y轴Z轴 给出对象的平移和旋转。
事实上,在 3D 空间中绘制球体,使用 p5.js 是通过平移和旋转来完成的,因为球体是在原点中心创建的,并没有内部模型给出 3D 坐标。
经过数小时的漫游,我发现绕3轴旋转并不像我想象的那么简单,最终我使用了Quaternion.js。但是我仍然无法将3D世界中球体的视觉位置与我从2D平面上的原始点(150,0,[0])计算出的坐标相匹配。
例如,在这里,球体在3个轴上旋转。一开始坐标是正确的(如果我忽略Z被取反的事实),但在某个时刻它完全失去同步。球体的计算位置似乎完全不相关:

wrong coordinates

我已经花了好几个小时尝试解决这个问题,但是没有任何结果,我错过了什么?

以下是我的代码:

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

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

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


我认为你的问题与在三个方向上旋转有关。 如果您想在笛卡尔平面上进行旋转,则仅需要在一个方向上旋转即可。 因此,我会假设在3D空间中,您可以使用仅2个旋转轴在所有方向上旋转轨道,但是这样做将限制您的运动自由度。 这实际上取决于您想要实现什么目标。 您的旋转目标是什么? 您需要使球体动画化还是计划将其放置在固定位置? 球体是否像行星一样绕行? - Henhen1227
最初,这个球体的设计是像行星一样绕轨道运转,但这个想法很快就升级到了最高难度(我也被困在其中),我想要旋转所有三个轴,并且让球体沿着二维平面上绘制的轨道运行。 - cigno5.5
但我一直保持思考,我的最新想法是2点:
  1. 我不需要三轴旋转! 两轴旋转加上球体的轨道运动就足够了(这样简化可以解决)
  2. 不对齐可能是因为视口中实现的三轴旋转受制于万向节锁定,并且使用四元数进行的数学计算则不会受到影响!(<--仍然是假设,尚未确认)
- cigno5.5
1个回答

3

我终于解决了。我真的不太明白为什么会这样,但我根本不需要四元数,而我最初的想法是使用矩阵乘法在三个轴上应用旋转。

我在第一次尝试中错过的(并使我的生活变得痛苦)是矩阵乘法不是可交换的。这意味着在x、y和z轴上应用旋转与在z、y和x上应用相同的旋转角度不等价。

通过以下三个简单步骤实现了工作解决方案:

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

    //https://dev59.com/ysDqa4cB1Zd3GeqPhZ3x
    #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


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接