【问题标题】:A good way to find a vector perpendicular to another vector?找到垂直于另一个向量的向量的好方法?
【发布时间】:2016-12-23 16:20:00
【问题描述】:

生成垂直于向量的任意向量的好方法是什么?假设生成的向量应该是有效的(即除非给定的向量是(0.0, 0.0, 0.0) - 这种情况下的输出应该是error)?

我知道有无限的可能向量集(例如参见here)。我想要的是任何输入向量都严格且没有错误的东西。

我尝试设置,这里 pn 表示垂直向量:p[0] = 0.5*(n[0] + n[1]; p[1] = 0.5*(n[0] - n[1]) 然后求解点积方程以找到 p[2] 但是当 n[2]0 时这是有缺陷的.目前我只能想到繁琐地创建所有案例检查 - 但必须有更好更优雅的解决方案?

【问题讨论】:

  • 请原谅我的无知,但我不明白这与 C++ 语言有什么关系。我的理解是检测垂直线是数学问题,而不是编程问题。
  • 抱歉,我添加了标签,因为我试图在 C++ 中解决这个问题 - 现在将删除它...
  • 您可以在本 QA 的底部找到交叉积的 C++ 实现:Understanding 4x4 homogenous transform matrices

标签: algorithm math


【解决方案1】:

选择一个与给定向量不共线的任意向量。对于“刚性的东西”,你可以有一个固定的规则。例如,选择 (1, 0, 0),除非对于某些 x,向量是 (x, 0, 0);否则选择 (0, 1, 0)。1 然后取任意向量与输入向量的叉积。这将产生一个垂直向量。 (当然,这只适用于 3D,但这似乎是您正在使用的。)

或者(这适用于任何维度),选择沿坐标轴的单位向量,该单位向量与输入向量产生最小(在大小上)点积。将此单位向量称为 e 和输入向量 x。那么e - (ex) x 将垂直于x。 (很容易检查点积是否为零:不失一般性,假设 x 是单位向量。那么 (e - (e • x) x) • x = (ex ) - (ex)(xx) = (ex) - (ex)(1) = 0.)

1更好的规则是选择 (1, 0, 0)、(0, 1, 0) 或 (0, 0, 1) 中的任何一个形成最小的点积(在大小上)输入向量。这会给你更好的数值稳定性。

【讨论】:

  • 或者从某个坐标在[-1,1]的随机向量e开始,归一化,测试点积小1/2。如果是,则应用上述公式 e - (e • x) x,否则尝试下一个随机向量。
  • @LutzL - 是的,它几乎可以是与输入向量不太共线的任何向量。随机选择一个向量的唯一问题是,在找到一个足够大的角度之前,你必须尝试多少个随机向量是没有限制的。还值得指出的是,归一化的唯一原因是为了数值稳定性。这个概念适用于任何(非零)大小的向量。
  • 你忘了说 x 需要归一化。
  • @YvesDaoust - 是的,对于最后一步(正交性验证),x 需要是单位向量。但这可以在不失一般性的情况下假设(因为我们只检查角度)。
【解决方案2】:

取给定向量与标准基向量的叉积。为了获得最佳精度,请采用平行度较低的基向量,即形成最小(绝对)点积的基向量。不需要大锤子:

if |Nx| = min(|Nx|, |Ny|, |Nz|) => (0, -Nz, Ny)
if |Ny| = min(|Nx|, |Ny|, |Nz|) => (Nz, 0, -Nx)
if |Nz| = min(|Nx|, |Ny|, |Nz|) => (-Ny, Nx, 0)

代码:

double Ax= abs(N.x), Ay= abs(N.y), Az= abs(N.z); 
if (Ax < Ay)
    P= Ax < Az ? Point(0, -N.z, N.y) : Point(-N.y, N.x, 0);
else
    P= Ay < Az ? Point(N.z, 0, -N.x) : Point(-N.y, N.x, 0);

【讨论】:

  • 感谢您的代码 - 我认为您的条件 P = Ay 有错字 - 点应该是 Nz, 0, -Nx :) 这与我选择的答案相似 - 希望我能选择这个不过!
【解决方案3】:

您可以使用线性代数或不同的三角函数来解决这个问题。

给定一个向量A(xi,yj,zk),其中i,j,k 是该向量的unit axis vectorsx,y,z 是该向量的magnitudes,您可以通过选择存在于@987654331 上的两个单独的points 来构造一个line @ 中的vector 使得点P1(xi1,yj1,zk1)P2(xi2,yj2,zk2) 存在于由向量A 生成的线L 上。我们知道L = P2 - P1 的线段存在或平行于A,因此给你一个dot product of 1,如果dot product is 0 它们是OrthogonalPerpendicular。然后,您可以从这两点中找到该线的 3D 斜率,使用该帖子StackExchange:Mathematics 的已接受答案中的数学计算,并且一旦您获得了该线的斜率和基础数学; orthogonal 到另一个他们的产品将 = -1。在二维欧几里得几何中,如果斜率为 1/2,则其垂线的斜率为 -2;因此,一旦您找到由原始 3D 矢量生成的那条线的斜率,您需要做的就是找到它的负倒数,但这仅适用于 2D,并且使用该值,您可以使用该值以及原始 @987654344 上的任意点@ 生成一个有效的任意垂直向量。

如果您知道三角函数及其性质;特定函数将具有值 1、-1、0、1/2 以及其他特定值作为输出,并具有相应的特定值作为输入,例如:

sin A { 0PI, PI, 2PI, 0deg, 180deg, 360deg } = 0
sin A { PI/2, 90deg } = 1 
sin A { 3PI/2, 270deg } = -1 
cos A { 0PI, 2PI, 0deg, 360deg } = 1 
cos A { PI/2, 3PI/2, 90deg, 270deg } = 0 
cos A { PI, 180deg } = -1 
tan A { 0PI, PI, 2PI, 0deg, 180deg, 360deg } = 0 
tan A { PI/4, 45deg } = 1
tan A { PI/2, 3PI/2, 90deg, 270deg } = undefined.

有了这些函数,知道它们之间的关系,知道两个斜率的乘积 = -1。我们可以看到,这些函数中最容易使用的是cos A,其中A = PI or 180 degrees 并知道三角函数的恒等函数,例如倒数、商、毕达哥拉斯、协函数、(偶/奇)恒等式以及各种公式例如 Sum & Difference、Double Angle、Power Reducing、Sum to Product 和 Product to Sum 公式以及三角函数的弧函数和反函数,您应该能够轻松找到与余弦版本的垂直线通过Law of Cosines 进行点积和叉积。这里有更多关于这个的数学:dot:cross 和这里wiki

现在我们知道了一些涉及的数学及其属性,并且我们有了一个向量 A(x,y,z),我们可以使用以下内容:

// Values of Cosine At Specific Angles
cos (PI) = -1, cos (PI/2) = 0, cos (PI) = 1

// Orthogonal Vectors
A dot B = 0
// Parallel Vectors - Multiples Of Each Other
A(1,2)
B(2,4)
C(4,8)
etc.
// Angle Between To Vectors where neither vector is a 0 vector
cos (theta) = (A dot B) / (magnitude(A) * magnitude(B))

这些为我们构建称为单位向量的基向量奠定了基础 或原始向量的归一化向量,并且从该归一化向量中,知道上面列出的几个属性,很容易从中计算出正交向量。要对向量进行归一化以获得其单位向量,可以使用以下公式或函数:

C++ 版本

// -----------------------------------------------------------------------
// normalize()
// Make The Length Of This Vector Equal To One
inline void Vector3::normalize() {

    float magnitude;
    magnitude = sqrt( x*x + y*y + z*z );
    if ( magnitude <= Math::ZERO ) {  // Math::ZERO = (float)1e-7;
        x = 0.0f;
        x = 0.0f;
        x = 0.0f;

        return;
    }

    magnitude = 1 / magnitude;
    x *= magnitude;
    y *= magnitude;
    z *= magnitude;

} // Normalize

// -----------------------------------------------------------------------
// getCosAngle()
// Returns The cos(Angle) Value Between This Vector And Vector V. This
// Is Less Expensive Than Using GetAngle
inline float Vector3::getCosAngle( const Vector3 &v3, const bool normalized ) {
    // a . b = |a||b|cos(angle)
    // -> cos-1((a.b)/(|a||b|))

    // Make Sure We Do Not Divide By Zero
    float magnitudeA = length();
    if ( magnitudeA <= Math::ZERO ) {
        // This (A) Is An Invalid Vector
        return 0;
    }

    float value = 0;

    if ( normalized ) {
        // v3 Is Already Normalized
        value = dot(v3)/magnitudeA;
    } else {
        float magnitudeB = v3.length();
        if ( magnitudeB <= Math::ZERO) {
            // B Is An Invalid Vector
            return 0;
        }

        value = dot(v3)/(magnitudeA * magnitudeB);
    }

    // Correct Value Due To Rounding Problem
    Math::constrain( -1.0f, 1.0f, value );

    return value;

} // getCosAngle

// -----------------------------------------------------------------------
// length()
// Return The Length Of This Vector
inline float Vector3::length() const {

    return sqrtf( x*x + y*y + z*z );

} // length

// -----------------------------------------------------------------------
// Constrain()
// Prevent Value From Going Outside The Min, Max Range.
template<class T>
inline void Math::constrain( T min, T max, T &value ) {

    if ( value < min ) {
        value = min;
        return;
    }

    if ( value > max ) {
        value = max;
    }

} // constrain

一旦你有一个向量的总长度 = 1 的归一化向量。针对这个向量计算叉积和点积是非常便宜的,并且找到一个与之正交的向量很容易并且计算如下

p>
Vector3 original( 2,3,5 );
Vector3 arbitrary( 7,8,6 ); // Any vector that intersects original.

Vector3 findOrthogonalVector( const Vector3& original, const Vector3& arbitrary ) {
    // Test Both Vectors To See If Either Are the 0 Vector If So Just Return the Zero Vector And Be Done
    if ( original.isZero() || arbitrary.isZero() ) {
        return Vector3( 0.0f, 0.0f, 0.0f );
    }

    Vector3 orthogonal(); // default constructor set to (0,0,0);
    original.normalize(); // Total Length or Magnitude of 1 but Gives Direction
    arbitrary.normalize();

    // This will not give you values in degrees; it will give you the value of the cosine at a given angle but not the angle itself for example cos(90) = -1
    // This will give a value of -1 and not 90.
    float cosAngle = original.getCosAngle( arbitrary, true ); // True since arbitrary is normalized       

    // Calculate Actual Angle From Trig Functions
    float angle = someTrigFuncToGiveAngle( cosAngle ); 
    // Clamp Angle between [0,180]
    angle = clampAngle( angle, 0.0f, 180.0f ); 

    float desiredAngle = 0;
    // If a dot b = 0; where dot product = (ax*bx + ay*by + az*bz) then 
    if ( angle == Math::ZERO || angle == 180.0f ) {
        // original & arbitrary are parallel (oops)
        // Quick Fix or Quick Hack - Change X component by 1 and recursively call this function only in this case
        arbitrary.x += 1;
        return findOrthogonalVector( original, arbitrary );

    } else if ( angle == 90.0f ) { // (if in degrees otherwise in radians)
        // Already Orthogonal 
        return arbitrary;

    } else if ( angle < 90.0f ) {
        desiredAngle = 90.0f - angle;  

    } else if ( angle > 90.0f && angle < 180.0f ) {
        desiredAngle = angle - 90.0f;

    } 
    // With this new angle we should be able to find the orthogonal vector      
    // Rotate The Arbitrary Vector by desiredAngle
    return orthogonal = arbitrary.rotate( desiredAngle );
}

上述算法中的以下方法未显示:

  • someTrigFuncToGiveAngle( ... );
  • clampAngle( ... );
  • vector.rotate( ... );

这些可以留给 OP 做为作业。

所以基本算法如下:

  • 取原始向量和任意向量都不能是零向量
  • 如果其中一个是 0 个向量,则只需返回一个零向量并使用函数完成
  • 标准化两个向量
  • 计算两个向量之间的余弦角
  • 从角度的余弦获取实际角度
  • 夹角在 [0,180] 之间
  • 检查角度是否 = 0;如果为真 - 并行将 x 分量更改一并再次调用,传入更新后的任意向量
  • 检查角度是否 = 90;如果为真 - 已经正交返回任意
  • 如果角度
  • 如果角度 > 90;通过从中减去 90 来计算所需的角度,然后通过它旋转任意角度并返回正交或所需的向量

【讨论】:

  • 令人惊奇的是,一只老鼠是如何生下一座山的!您的解决方案看起来非常低效。
  • @YvesDaoust 我提前展示了很多数学知识,除了我没有进入线性方程组和 3D 仿射变换矩阵...
  • @YvesDaoust 接受的答案和这个答案之间的主要区别在于,如果选择了任意向量并且其中一个是 0 向量,那么如果向量是并行,代码将修改任意向量的 x 分量,生成一个新的任意向量并再次调用该函数。最大递归调用的数量应仅为 1。否则,它会检查两者之间的锐角和非锐角,并将任意值旋转该数量,从而为您提供垂直或正交向量...
  • @YvesDaoust 我可以进一步完善它,但这是来自我在 Legacy OpenGL 1.0 中工作时的一个旧数学库
  • 这完全是矫枉过正。有一个解决方案需要 5 次比较和 2 到 5 次符号变化,没有别的(没有标准化)。在琐碎的情况下使用通用库是个坏主意。
猜你喜欢
  • 2017-06-18
  • 1970-01-01
  • 2022-01-15
  • 1970-01-01
  • 1970-01-01
  • 2012-05-28
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多