在3D空间中找到旋转点的角度,使其面向另一点。

5

假设我有两个向量:

V1 = { x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968 }

V2 = { x: 2.3652551657790695, y: -16.732085083053185, z: 8.945905454164146 }

如何计算将V1旋转多少角度才能直接朝向V2?

换言之,如果我知道自己在空间中的确切位置以及另一个人在空间中的确切位置……那么数学上,我如何计算出需要调整手指的角度才能指向他们?

这是我的轴线图示

我当前(不正确)的公式

v2.x -= v1.x; // move v2 (vector 2) relative to the new origin
v2.y -= v1.y;
v2.z -= v1.z;

v1.x = 0; // set v1 (vector 1) as the origin
v1.y = 0;
v1.z = 0;

var r = Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.y,2) + Math.pow(v2.z,2));
var θ = Math.acos((Math.pow(v2.x,2) + Math.pow(v2.z,2))/(r*Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.z,2))));
var ϕ = Math.acos(v2.x/Math.sqrt(Math.pow(v2.x,2) + Math.pow(v2.z,2)));

然后我用theta和phi旋转v1。

v1.rotation.y = θ;
v2.rotation.x = ϕ;

但是这给了我错误的旋转。

θ = 0.6099683401012933

ϕ = 1.8663452274936656

但是如果我使用THREE.js并使用lookAt函数,它会输出这些旋转值,这些旋转值可以完美地工作:

y/θ: -0.24106818240525682

x/ϕ: 2.5106584861123644

提前感谢您的所有帮助!我不能在我的最终结果中使用THREE.js,我正在尝试使用纯原生JS,以便我可以将其移植到另一种语言。


你可以查看THREE.js中lookAt的实现方式,以获取一些想法。 - Ayush Seth
我已经看过了,但实际上没有任何关于它如何工作的文档,并且它依赖于与库深度绑定的函数... - Darryl Huffman
你能具体说明一下后面如何使用这些 angels 吗?可能是我和 @Zich 没有正确理解你需要哪些具体的 angels。 - SergGr
THREE.js 的结果不正确,因为当你在 V1 观察 V2 时,角度应该在 1.57 到 2.35 之间,因为向量将位于第三象限。我将在回家后通过图示进行解释。 - Iman Nia
我假设你使用的是THREE.js代码来计算这些值,但这可能并不是你实际所做的。 - K Scandrett
5个回答

3

使用矩阵怎么样?我认为v1是你的视点,朝向v2。矩阵是展示方向的好方法。欧拉角是方向的另一种解释。

我的想法是从对象空间到世界空间构建一个转换矩阵,你想做的可以分为三个步骤:

  1. 一开始,相机位于世界空间原点,相机旋转为(0,0,0),世界空间与对象空间相同。v1'(0,0,0)
  2. 我们将相机移动到v1(3.296372727813439,-14.497928014719344,12.004105246875968),对象空间与世界空间有一个偏移,但对象空间轴与世界空间轴平行,相机旋转仍为(0,0,0)。
  3. 我们让相机看向v2,如你所见,相机旋转会改变。

如果我能构建一个代表上述所有操作的转换矩阵,我就可以得到方向。

首先,计算翻译矩阵:由于翻译是一种仿射变换,我们需要使用一个4x4矩阵来表示翻译。我们可以轻松获取矩阵: translation matrix 我们使用基础轴来获取旋转矩阵。
您可能需要设置摄像机上向量。默认值为(0,1,0)。在物体空间中,可以通过v1-v2计算基础<强> z 轴。 z =(v1.x-v2.x,v1.y-v2.y,v1.z-v2.z).normalize() 基础<强> x 向量:我们知道基础向量是与<强> z-up 平面垂直的向量,我们通过叉积获得<强> x 向量和上向量。 x = up.crossproduct(z) 基础<强> y 向量,<强> y 与<强> z-x 平面垂直。 y = z.product(x) 我们可以将旋转矩阵构建为3 x 3矩阵: rotation matrix 然后,我们最终得到转换矩阵: transformation matrix 我们可以使用该矩阵表示相机方向。如果您需要欧拉角或四元数,则有一些方法可在本书中找到:3D Math Primer for Graphics and Game Development Three.js实现了与我的方式相同的 LookAt()函数。
以下是three.js源代码,我添加了一些注释:
function lookAt(eye,target,up)//眼睛:您的相机位置;目标:你想看的点;上:相机向上的向量
{  

    if(x === undefined){

       x = new Vector3();
       y = new Vector3();
       z = new Vector3();

    }

    var te = this.elements;//这个元素是存储在列表中的4 x 4矩阵。

    z.subVectors(eye,target)。normalize(); //使用从您的相机到目标点的方向设置z向量。

    if(z.lengthSq()=== 0){

        z.z = 1;

    }

    x.crossVectors(up,z)。normalize(); //通过叉乘上和z矢量设置x向量,您知道叉积会得到与这两个向量垂直的//向量。

    if(x.lengthSq()=== 0){

        z.z + = 0.0001; //如果z是ZERO向量,则对z.z进行一些小的加法
        x.crossVectors(up,z)。normalize();

    }

    y.crossVectors(z,x); //通过叉积z和x设置y。

    //使用基本的轴来设置矩阵。
    te [0] = x.x; te [4] = y.x; te [8] = z.x;
    te [1] = x.y; te [5] = y.y; te [9] = z.y;
    te [2] = x.z; te [6] = y.z; te [10] = z.z;

    return this;

};
//现在您已经获得了转换矩阵,可以使用该矩阵设置旋转或方向。

<p>你可以像three.js一样使用列表来实现矩阵。</p>

<p>我还有另一个想法——球面极坐标系。球面坐标通常被表示为(r,Θ,Φ),其中Θ是方位角,Φ是俯仰角。你需要做的就是将v1和v2的笛卡尔坐标转换为球面坐标。因为球面坐标的第二个和第三个元素是角度,所以我们可以计算出v1和v2之间的角位移。然后,将这个位移添加到相机旋转中。</p>

<p>这是我的代码(假设你的相机在世界原点(0,0,0)):</p>

<pre><code>//convert v1 and v2 Cartesian coordinates to Spherical coordinates;
var radiusV1 = Math.sqrt( Math.pow(v1.x) + Math.pow(v1.y) + Math.pow(v1.z));
var headingV1 = Math.atan2(v1.x , v1.z);
var pitchV1 = Math.asin(-(v1.y) / radiusV1);

var radiusV2 = Math.sqrt( Math.pow(v2.x) + Math.pow(v2.y) + Math.pow(v2.z));
var headingV2 = Math.atan2(v2.x , v2.z);
var pitchV2 = Math.asin(-(v2.y) / radiusV2);

//calculate angular displacement.
var displacementHeading = headingV2 - headingV1;
var displacementPitch = pitchV2 - pitchV1;

//make this displacement as an addition to camera rotation.
camera.rotation.x += displacementPitch;
camera.rotation.y += displacementHeading;

顺便说一下,3D数学非常有帮助,值得学习,我参考的所有公式和概念都可以在这本书中找到。
希望对你有所帮助。

此外,up向量可以是类似于[0,0,1]的东西,它指向z方向上方。 - himty

2
正确的公式如下所示:
 var dx = v2.x-v1.x; //-0.93
 var dy = v2.y-v1.y; //-31.22
 var dz = v2.z-v1.z;
 var rxy = Math.sqrt( Math.pow(dx,2) + Math.pow(dy,2) );
 var lambda = Math.atan(dy/dx);
 var phi = Math.atan(dz/rxy)

以上公式中的 philambda 需要根据您的向量所处的象限进行调整。我为您做到了简易操作:
 //if you do the calculations in degrees, you need to add 180 instead of PI
 if (dx < 0) phi = phi + Math.PI;
 if (dz < 0) lambda = -1 * lambda;

我假设在第二个代码块中,x和z来自V2?如果是这样,在上面提到的位置,它会输出这些角度:landa = -1.1759217695199877和phi = 2.2403038274064055.... 这不是y和x旋转的正确角度。 :/ - Darryl Huffman
@DarrylHuffman 我假设v2是在v2-=v1之后的差分向量,因此我编辑了答案,请再次检查并告诉我结果。 - Iman Nia

0

x/ϕ 是绕 x 轴旋转,因此它等于 y、z 之间的角度,而对于 y/θ,我们需要找到 x、z 之间的角度。

V1 = { x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968 }
V2 = { x: 2.3652551657790695, y: -16.732085083053185, z: 8.945905454164146 }
var v={dx:V2.x-V1.x, dy:V2.y-V1.y, dz:V2.z-V1.z}
testVector(v);
 
function testVector(vec){
   console.log();
   var angles=calcAngles(vec);
   console.log("phi:"+angles.phi+" theta:"+angles.theta);
}
function calcAngles(vec){
   return {
      theta:(Math.PI/2)+Math.atan2(vec.dz, vec.dx),
      phi:(3*Math.PI/2)+Math.atan2(vec.dz, vec.dy)
   };
}


0

我已从最新版本的THREE.js(r84)中提取了相关代码。 我认为这是获得您想要的结果的最佳方式。

    // Unless otherwise noted by comments, all functions originate from the latest version of THREE.js (r84) 
    // https://github.com/mrdoob/three.js/tree/master
    // THREE.js is licensed under MIT (Copyright © 2010-2017 three.js authors)
    // 
    // Some functions have been changed by K Scandrett to work within this setting, 
    // but not the calculations.
    // Any mistakes are considered mine and not the authors of THREE.js. 
    // I provide no guarantees that I haven't created any bugs in reworking the original code
    // so use at your own risk. Enjoy the pizza.
    
    
    var v1 = {x: 3.296372727813439, y: -14.497928014719344, z: 12.004105246875968};
    var v2 = {x: 2.3652551657790695, y: -16.732085083053185,z: 8.945905454164146};
    
    var startVec = {x: v1.x, y: v1.y, z: v1.z, w: 0};
    var endVec = {x: v2.x, y: v2.y, z: v2.z, w: 0};
    
    var upVec = {x: 0, y: 1, z: 0}; // y up
    
    var quat = lookAt(startVec, endVec, upVec);
    var angles = eulerSetFromQuaternion(quat);
    
    console.log(angles.x + " " + angles.y + " " + angles.z);
    
    /* KS function */
    function magnitude(v) {
      return Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
    }
    
    /* KS function */
    function normalize(v) {
      var mag = magnitude(v);
      return {
        x: v.x / mag,
        y: v.y / mag,
        z: v.z / mag
      };
    }
    
    function subVectors(a, b) {
      return {
        x: a.x - b.x,
        y: a.y - b.y,
        z: a.z - b.z
      };
    }
    
    function crossVectors(a, b) {
      var ax = a.x,
        ay = a.y,
        az = a.z;
      var bx = b.x,
        by = b.y,
        bz = b.z;
      return {
        x: ay * bz - az * by,
        y: az * bx - ax * bz,
        z: ax * by - ay * bx
      };
    }
    
    function lengthSq(v) {
      return v.x * v.x + v.y * v.y + v.z * v.z;
    }
    
    
    function makeRotationFromQuaternion(q) {
    
      var matrix = new Float32Array([
    
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    
      ]);
    
      var te = matrix;
    
      var x = q.x,
        y = q.y,
        z = q.z,
        w = q.w;
      var x2 = x + x,
        y2 = y + y,
        z2 = z + z;
      var xx = x * x2,
        xy = x * y2,
        xz = x * z2;
      var yy = y * y2,
        yz = y * z2,
        zz = z * z2;
      var wx = w * x2,
        wy = w * y2,
        wz = w * z2;
    
      te[0] = 1 - (yy + zz);
      te[4] = xy - wz;
      te[8] = xz + wy;
    
      te[1] = xy + wz;
      te[5] = 1 - (xx + zz);
      te[9] = yz - wx;
    
      te[2] = xz - wy;
      te[6] = yz + wx;
      te[10] = 1 - (xx + yy);
    
      // last column
      te[3] = 0;
      te[7] = 0;
      te[11] = 0;
    
      // bottom row
      te[12] = 0;
      te[13] = 0;
      te[14] = 0;
      te[15] = 1;
    
      return te;
    
    }
    
    function RotationMatrix(m) {
    
      // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
    
      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
    
      var _w, _x, _y, _z;
      var te = m,
    
        m11 = te[0],
        m12 = te[4],
        m13 = te[8],
        m21 = te[1],
        m22 = te[5],
        m23 = te[9],
        m31 = te[2],
        m32 = te[6],
        m33 = te[10],
    
        trace = m11 + m22 + m33,
        s;
    
      if (trace > 0) {
    
        s = 0.5 / Math.sqrt(trace + 1.0);
    
        _w = 0.25 / s;
        _x = (m32 - m23) * s;
        _y = (m13 - m31) * s;
        _z = (m21 - m12) * s;
    
      } else if (m11 > m22 && m11 > m33) {
    
        s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
    
        _w = (m32 - m23) / s;
        _x = 0.25 * s;
        _y = (m12 + m21) / s;
        _z = (m13 + m31) / s;
    
      } else if (m22 > m33) {
    
        s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
    
        _w = (m13 - m31) / s;
        _x = (m12 + m21) / s;
        _y = 0.25 * s;
        _z = (m23 + m32) / s;
    
      } else {
    
        s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
    
        _w = (m21 - m12) / s;
        _x = (m13 + m31) / s;
        _y = (m23 + m32) / s;
        _z = 0.25 * s;
    
      }
    
      return {
        w: _w,
        x: _x,
        y: _y,
        z: _z
      };
    }
    
    function eulerSetFromQuaternion(q, order, update) {
    
      var matrix;
    
      matrix = makeRotationFromQuaternion(q);
    
      return eulerSetFromRotationMatrix(matrix, order);
    }
    
    function eulerSetFromRotationMatrix(m, order, update) {
    
      var _x, _y, _z;
      var clamp = function(value, min, max) {
        return Math.max(min, Math.min(max, value));
      };
    
      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
    
      var te = m;
      var m11 = te[0],
        m12 = te[4],
        m13 = te[8];
      var m21 = te[1],
        m22 = te[5],
        m23 = te[9];
      var m31 = te[2],
        m32 = te[6],
        m33 = te[10];
    
      //order = order || this._order;
      order = order || 'XYZ'; // KS added. Other code sets the rotation order default
    
      if (order === 'XYZ') {
    
        _y = Math.asin(clamp(m13, -1, 1));
    
        if (Math.abs(m13) < 0.99999) {
    
          _x = Math.atan2(-m23, m33);
          _z = Math.atan2(-m12, m11);
    
        } else {
    
          _x = Math.atan2(m32, m22);
          _z = 0;
    
        }
    
      } else if (order === 'YXZ') {
    
        _x = Math.asin(-clamp(m23, -1, 1));
    
        if (Math.abs(m23) < 0.99999) {
    
          _y = Math.atan2(m13, m33);
          _z = Math.atan2(m21, m22);
    
        } else {
    
          _y = Math.atan2(-m31, m11);
          _z = 0;
    
        }
    
      } else if (order === 'ZXY') {
    
        _x = Math.asin(clamp(m32, -1, 1));
    
        if (Math.abs(m32) < 0.99999) {
    
          _y = Math.atan2(-m31, m33);
          _z = Math.atan2(-m12, m22);
    
        } else {
    
          _y = 0;
          _z = Math.atan2(m21, m11);
    
        }
    
      } else if (order === 'ZYX') {
    
        _y = Math.asin(-clamp(m31, -1, 1));
    
        if (Math.abs(m31) < 0.99999) {
    
          _x = Math.atan2(m32, m33);
          _z = Math.atan2(m21, m11);
    
        } else {
    
          _x = 0;
          _z = Math.atan2(-m12, m22);
    
        }
    
      } else if (order === 'YZX') {
    
        _z = Math.asin(clamp(m21, -1, 1));
    
        if (Math.abs(m21) < 0.99999) {
    
          _x = Math.atan2(-m23, m22);
          _y = Math.atan2(-m31, m11);
    
        } else {
    
          _x = 0;
          _y = Math.atan2(m13, m33);
    
        }
    
      } else if (order === 'XZY') {
    
        _z = Math.asin(-clamp(m12, -1, 1));
    
        if (Math.abs(m12) < 0.99999) {
    
          _x = Math.atan2(m32, m22);
          _y = Math.atan2(m13, m11);
    
        } else {
    
          _x = Math.atan2(-m23, m33);
          _y = 0;
    
        }
    
      } else {
    
        console.warn('THREE.Euler: .setFromRotationMatrix() given unsupported order: ' + order);
    
      }
    
      //_order = order;
    
      //if ( update !== false ) this.onChangeCallback();
    
      return {
        x: _x,
        y: _y,
        z: _z
      };
    
    }
    
    function setFromQuaternion(q, order, update) {
    
      var matrix = makeRotationFromQuaternion(q);
    
      return setFromRotationMatrix(matrix, order, update);
    }
    
    function setFromRotationMatrix(m) {
    
      // http://www.euclideanspace.com/maths/geometry/rotations/conversions/matrixToQuaternion/index.htm
    
      // assumes the upper 3x3 of m is a pure rotation matrix (i.e, unscaled)
    
      var _w, _x, _y, _z;
      var te = m,
    
        m11 = te[0],
        m12 = te[4],
        m13 = te[8],
        m21 = te[1],
        m22 = te[5],
        m23 = te[9],
        m31 = te[2],
        m32 = te[6],
        m33 = te[10],
    
        trace = m11 + m22 + m33,
        s;
    
      if (trace > 0) {
    
        s = 0.5 / Math.sqrt(trace + 1.0);
    
        _w = 0.25 / s;
        _x = (m32 - m23) * s;
        _y = (m13 - m31) * s;
        _z = (m21 - m12) * s;
    
      } else if (m11 > m22 && m11 > m33) {
    
        s = 2.0 * Math.sqrt(1.0 + m11 - m22 - m33);
    
        _w = (m32 - m23) / s;
        _x = 0.25 * s;
        _y = (m12 + m21) / s;
        _z = (m13 + m31) / s;
    
      } else if (m22 > m33) {
    
        s = 2.0 * Math.sqrt(1.0 + m22 - m11 - m33);
    
        _w = (m13 - m31) / s;
        _x = (m12 + m21) / s;
        _y = 0.25 * s;
        _z = (m23 + m32) / s;
    
      } else {
    
        s = 2.0 * Math.sqrt(1.0 + m33 - m11 - m22);
    
        _w = (m21 - m12) / s;
        _x = (m13 + m31) / s;
        _y = (m23 + m32) / s;
        _z = 0.25 * s;
    
      }
    
      return {
        w: _w,
        x: _x,
        y: _y,
        z: _z
      };
    }
    
    function lookAt(eye, target, up) {
    
      // This routine does not support objects with rotated and/or translated parent(s)
    
      var m1 = lookAt2(target, eye, up);
    
      return setFromRotationMatrix(m1);
    
    }
    
    function lookAt2(eye, target, up) {
    
      var elements = new Float32Array([
    
        1, 0, 0, 0,
        0, 1, 0, 0,
        0, 0, 1, 0,
        0, 0, 0, 1
    
      ]);
    
    
      var x = {
        x: 0,
        y: 0,
        z: 0
      };
      var y = {
        x: 0,
        y: 0,
        z: 0
      };
      var z = {
        x: 0,
        y: 0,
        z: 0
      };
    
      var te = elements;
    
      z = subVectors(eye, target);
      z = normalize(z);
    
      if (lengthSq(z) === 0) {
    
        z.z = 1;
    
      }
    
      x = crossVectors(up, z);
      x = normalize(x);
    
      if (lengthSq(x) === 0) {
    
        z.z += 0.0001;
        x = crossVectors(up, z);
        x = normalize(x);
    
      }
    
      y = crossVectors(z, x);
    
    
      te[0] = x.x;
      te[4] = y.x;
      te[8] = z.x;
      te[1] = x.y;
      te[5] = y.y;
      te[9] = z.y;
      te[2] = x.z;
      te[6] = y.z;
      te[10] = z.z;
    
      return te;
    }
    
    
    function lookatOld(vecstart, vecEnd, vecUp) {
    
      var temp = new THREE.Matrix4();
      temp.lookAt(vecEnd, vecstart, vecUp);
    
      var m00 = temp.elements[0],
        m10 = temp.elements[1],
        m20 = temp.elements[2],
        m01 = temp.elements[4],
        m11 = temp.elements[5],
        m21 = temp.elements[6],
        m02 = temp.elements[8],
        m12 = temp.elements[9],
        m22 = temp.elements[10];
    
      var t = m00 + m11 + m22,
        s, x, y, z, w;
    
      if (t > 0) {
        s = Math.sqrt(t + 1) * 2;
        w = 0.25 * s;
        x = (m21 - m12) / s;
        y = (m02 - m20) / s;
        z = (m10 - m01) / s;
      } else if ((m00 > m11) && (m00 > m22)) {
        s = Math.sqrt(1.0 + m00 - m11 - m22) * 2;
        x = s * 0.25;
        y = (m10 + m01) / s;
        z = (m02 + m20) / s;
        w = (m21 - m12) / s;
      } else if (m11 > m22) {
        s = Math.sqrt(1.0 + m11 - m00 - m22) * 2;
        y = s * 0.25;
        x = (m10 + m01) / s;
        z = (m21 + m12) / s;
        w = (m02 - m20) / s;
      } else {
        s = Math.sqrt(1.0 + m22 - m00 - m11) * 2;
        z = s * 0.25;
        x = (m02 + m20) / s;
        y = (m21 + m12) / s;
        w = (m10 - m01) / s;
      }
    
      var rotation = new THREE.Quaternion(x, y, z, w);
      rotation.normalize();
      return rotation;
    }

这是在 Plunker 中相同的代码:http://plnkr.co/edit/vgNko1fJu9eYYCnJbYVo?p=preview


注意THREE使用右手坐标系。问题中的图像是左手坐标系,因此您可能需要进行相应的调整。 - K Scandrett
我想指出上面的代码没有引用THREE.js库 - 它是独立的JavaScript代码,因此您可以将这些函数翻译成另一种编程语言。 - K Scandrett

0

对于你的问题来说,确切/字面上的答案是一个不好/不道德的答案。不要尝试使用欧拉角。欧拉坐标系是用于协调的,不是进行方向/旋转的好系统。虽然易于人类阅读,但容易出现万向节锁,从而产生错误结果。

有两种常见的方向系统:变换矩阵和四元数。three.js lookAt() 使用四元数,Crag.Li 的 答案 使用变换矩阵。

我觉得有必要强调一下,因为我曾经低估了3D变换,并试图用“简单的方法”解决它,浪费了近一个月的时间做傻事。 3D变换很难。没有快速、肮脏的方法,你只能用正确的方法去做。拿一本书(3D数学入门是一本不错的书)花时间学习数学,如果你真的想做到这一点。


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