找到旋转点的角度,使其面向3d空间中的另一个点

时间:2017-03-01 16:08:27

标签: javascript 3d three.js rotation

说我有两个向量:

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

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

如何确定v1需要旋转的角度才能直接观察v2?

说英语:说我确切地知道我在太空中的确切位置,以及另一个人在太空其他地方的确切位置......数学上,我怎样才能弄清楚用手指指向它们的角度?

Here's what my axis looks like

我当前(不正确)公式

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,以便将其移植到另一种语言。

5 个答案:

答案 0 :(得分: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;

答案 1 :(得分:0)

使用矩阵怎么样?我认为v1是你的观点,并期待v2。矩阵是显示方向的好方法。欧拉角是对方向的另一种解释。

我的想法是构建从对象空间到世界空间的转换矩阵,您想要做的事情可以分三步进行翻译:

  1. 开头,相机处于世界空间原点,相机旋转(0,0,0)世界空间与物体空间相同。 v1'(0,0,0)
  2. 我们将相机转换为v1(3.296372727813439,-14.497928014719344,12.004105246875968),物体空间与世界空间有偏移,但物体空间轴与世界空间轴平行,相机旋转仍为(0,0,0)。
  3. 我们让相机查看v2,如您所见,相机旋转会发生变化。
  4. 如果我可以构建转换矩阵表示上面的所有操作,我可以获得方向。

    1. 首先,计算翻译矩阵:因为翻译是仿射变换,我们需要使用4x4矩阵来表示翻译。我们可以很容易地得到矩阵: translation matrix
    2. 我们使用基轴获得旋转矩阵。

      您可能需要设置相机向上矢量。默认值为(0,1,0)。在对象空间中,基础 z 轴可以由v1-v2计算。

      z = (v1.x-v2.x,v1.y-v2.y,v1.z-v2.z).normalize()

      基础 x 向量:我们知道基向量是垂直于 z-up 平面的向量,我们通过叉积得到 x 向量up和 z

      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 Developmen

      Three.js实现与我的方式相同的LookAt()功能。

      这是three.js源代码,我添加了一些注释:

      function lookAt( eye, target, up ) //eye : your camera position; target :     which point you want to look at; up : camera up vector
      {  
      
          if ( x === undefined ) {
      
              x = new Vector3();
              y = new Vector3();
              z = new Vector3();
      
          }
      
          var te = this.elements; //this.elements is a 4 x 4 matrix stored in a list.
      
          z.subVectors( eye, target ).normalize(); // set z vector with the direction from your camera to target point.
      
          if ( z.lengthSq() === 0 ) {
      
              z.z = 1;
      
          }
      
          x.crossVectors( up, z ).normalize(); // set the x vector by cross product up and z vector, you know cross product would get a //vector which perpendicular with these two vectors.
      
          if ( x.lengthSq() === 0 ) {
      
              z.z += 0.0001; // if z is ZERO vector, then, make a little addition to z.z
              x.crossVectors( up, z ).normalize();
      
          }
      
          y.crossVectors( z, x ); // set y by cross product z and x.
      
          // using basic axises to set the matrix.
          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;
      
      };
      // now you get the transformation matrix, you can set the rotation or orientation with this matrix.
      
    3. 您可以使用像three.js这样的列表实现矩阵。

      我还有另一个想法 - 球面极坐标系。球面坐标总是被标记为(r,θ,Φ),θ是前进角,Φ是俯仰角。你需要做的是将v1和v2笛卡尔坐标转换为球坐标。因为球形的第二和第三个元素是角度,我们可以计算v1和v2之间的角位移。然后,将此位移作为相机旋转的补充。

      这是我的代码(假设您的相机位于世界原点(0,0,0)):

      //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数学非常有用且值得学习,我参考的所有公式或概念都可以在书中找到。

      希望它可以帮到你。

答案 2 :(得分: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)
   };
}

答案 3 :(得分: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

答案 4 :(得分:0)

你的问题的确切/字面答案将是一个糟糕/不道德的答案。不要试图使用欧拉角。欧拉坐标系用于协调。做方向/旋转不是一个好的系统。虽然容易为人类阅读,但很容易Gimbal lock - 会产生不正确的结果。

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

我觉得有必要强调这一点,因为我曾经低估了3D转型,并试图以“简单的方式”解决它,浪费了将近一个月做傻瓜的工作。 3D转换很难。没有快速,肮脏的方式,你只能以正确的方式做到这一点。拿一本书(3D数学入门书是一本很好的书),如果你真的想要这本书,可以花时间学习数学。