使用正确的优化算法 C++ 在 3D space 中查找点之间的距离

Finding distance between points in 3D space using correct optimization algorithm C++

使用数学公式很容易找到 3D 中任意两点之间的距离 space 即:√((x2-x1)^2 )+(y2-y1)^2+(z2-z1)^2

计算 space 中任意两点之间的距离非常容易,其中每个点在 3D-space.

中具有 (x, y z) 坐标

我的问题是,将此公式扩展到我面临优化问题的大规模范围内。我所做的是创建一个简单的 Vector class 并使用这个用 C++ 编码的公式来计算距离。

如果有1000分呢?我该怎么做?我在考虑使用简单的 std::vector 来存储这些 3D 点不会是计算这 1000 个点之间距离的优化方式。

有不同级别的优化,具体取决于实际情况。

在实现级别,您希望以对处理器友好的方式布置数据。 即。

  1. 连续
  2. 正确对齐
  3. 向量(SSE)友好(SoA,如果你使用垂直 SSE(通常),或 AoS 用于水平处理)
  4. 实际启用矢量化(编译器选项,或手工制作)

这可能会给您带来高达 10% 的提升。

在算法级别上,你可能又要思考为什么需要核心循环中每个点的距离

  1. 您是否需要非常频繁地重新计算它(在 模拟),或者您可以接受过时的值并可能重新计算 在后台(例如在游戏中)
  2. 你能用距离的平方来做吗,你可以节省一个sqrt?
  3. 如果你的数据集不经常变化,如何将其转换为极坐标,你可以缓存每个点的r^2, 距离平方公式然后减少到大约一个 cos() 操作: r1^2 + r2^2 - 2r1r2 cos(a1-a2).

编辑:3D 中的 Opps space 复杂度增长到与笛卡尔坐标相同的水平,忽略点 3。

GPU 考虑

虽然 1000 点数不足以换取开销,但如果您将来有更多点数,您仍可以考虑将工作卸载到 GPU。

这是 Vector3 class 对象的一个​​版本,可能会对您有所帮助。

Vector3.h

#ifndef VECTOR3_H
#define VECTOR3_H

#include "stdafx.h"
#include "GeneralMath.h"

class Vector3 { 

public:
    union {
        float m_f3[3];
        struct {
            float m_fX;
            float m_fY;
            float m_fZ;
        };  
    };

    inline Vector3();
    inline Vector3( float x, float y, float z );
    inline Vector3( float *pfv );
    ~Vector3();

    // Operators
    inline Vector3  operator+( const Vector3 &v3 ) const;
    inline Vector3  operator+() const;
    inline Vector3& operator+=( const Vector3 &v3 );
    inline Vector3  operator-( const Vector3 &v3 ) const;
    inline Vector3  operator-() const;
    inline Vector3& operator-=( const Vector3 &v3 );
    inline Vector3  operator*( const float &fValue ) const;
    inline Vector3& operator*=( const float &fValue );
    inline Vector3  operator/( const float &fValue ) const;
    inline Vector3& operator/=( const float &fValue );

    // -------------------------------------------------------------------
    // operator*()
    // Pre Multiple Vector By A Scalar
    inline friend Vector3 Vector3::operator*( const float &fValue, const Vector3 v3 ) {

        return Vector3( fValue*v3.m_fX, fValue*v3.m_fY, fValue*v3.m_fZ );

    } // operator*

    // -------------------------------------------------------------------
    // operator/()
    // Pre Divide Vector By A Scalar Value
    inline friend Vector3 Vector3::operator/( const float &fValue, const Vector3 v3 ) {
        Vector3 vec3;
        if ( Math::isZero( v3.m_fX ) ) {
            vec3.m_fX = 0.0f;
        } else {
            vec3.m_fX = fValue / v3.m_fX;
        }

        if ( Math::isZero( v3.m_fY ) ) {
            vec3.m_fY = 0.0f;
        } else {
            vec3.m_fY = fValue / v3.m_fY;
        }

        if ( Math::isZero( v3.m_fZ ) ) {
            vec3.m_fZ = 0.0f;
        } else {
            vec3.m_fZ = fValue / v3.m_fZ;
        }

        return vec3;
    } // operator/

    // Functions    
    inline Vector3 rotateX( float fRadians );
    inline Vector3 rotateY( float fRadians );
    inline Vector3 rotateZ( float fRadians );

    inline void setPerpendicularXZ( Vector3 v3 );
    inline void setPerpendicularXY( Vector3 v3 );
    inline void setPerpendicularYZ( Vector3 v3 );

    inline Vector3  cross( const Vector3 v3 ) const;
    inline float    dot( const Vector3 v3 ) const;
    inline float    getAngle( const Vector3 &v3, const bool bNormalized = false, bool bRadians = true );
    inline float    getCosAngle( const Vector3 &v3, const bool bNormalized = false );
    inline float    length() const;
    inline float    length2() const;
    inline void     normalize();
    inline void     zero();
    inline bool     isZero() const;

}; // Vector3

// -----------------------------------------------------------------------
// Vector3()
// Constructor
inline Vector3::Vector3() {
    m_fX = 0.0f;
    m_fY = 0.0f;
    m_fZ = 0.0f;
} // Vector3

// -----------------------------------------------------------------------
// Vector3()
// Constructor
inline Vector3::Vector3( float x, float y, float z ) {
    m_fX = x;
    m_fY = y;
    m_fZ = z;
} // Vector3

// -----------------------------------------------------------------------
// Vector3()
// Constructor
inline Vector3::Vector3( float *pfv ) {
    m_fX = pfv[0];
    m_fY = pfv[1];
    m_fZ = pfv[2];
} // Vector3

// -----------------------------------------------------------------------
// operator+()
// Unary - Operator:
inline Vector3 Vector3::operator+() const {
    return *this;
} // operator+

// -----------------------------------------------------------------------
// operator+()
// Binary - Add Two Vectors Together
inline Vector3 Vector3::operator+( const Vector3 &v3 ) const {
    return Vector3( m_fX + v3.m_fX, m_fY + v3.m_fY, m_fZ + v3.m_fZ );
} // operator+

// -----------------------------------------------------------------------
// operator+=()
// Add Two Vectors Together
inline Vector3 &Vector3::operator+=( const Vector3 &v3 ) {
    m_fX += v3.m_fX;
    m_fY += v3.m_fY;
    m_fZ += v3.m_fZ;
    return *this;
} // operator+=

// -----------------------------------------------------------------------
// operator-()
// Unary - Operator: Negate Each Value
inline Vector3 Vector3::operator-() const {
    return Vector3( -m_fX, -m_fY, -m_fZ );
} // operator-

// -----------------------------------------------------------------------
// operator-()
// Binary - Take This Vector And Subtract Another Vector From It
inline Vector3 Vector3::operator-( const Vector3 &v3 ) const {
    return Vector3( m_fX - v3.m_fX, m_fY - v3.m_fY, m_fZ -v3.m_fZ );
} // operator-

// -----------------------------------------------------------------------
// operator-=()
// Subtract Two Vectors From Each Other
inline Vector3 &Vector3::operator-=( const Vector3 &v3 ) {
    m_fX -= v3.m_fX;
    m_fY -= v3.m_fY;
    m_fZ -= v3.m_fZ;
    return *this;
} // operator-=

// -----------------------------------------------------------------------
// operator*()
// Post Multiply Vector By A Scalar
inline Vector3 Vector3::operator*( const float &fValue ) const {
    return Vector3( m_fX*fValue, m_fY*fValue, m_fZ*fValue );
} // operator*

// -----------------------------------------------------------------------
// operator*=()
// Multiply This Vector By A Scalar
inline Vector3& Vector3::operator*=( const float &fValue ) {
    m_fX *= fValue;
    m_fY *= fValue;
    m_fZ *= fValue;
    return *this;
} // operator*=

// -----------------------------------------------------------------------
// operator/()
// Post Divide Vector By A Scalar
inline Vector3 Vector3::operator/( const float &fValue ) const {
    Vector3 v3;
    if ( Math::IsZero( fValue ) ) {
        v3.m_fX = 0.0f;
        v3.m_fY = 0.0f;
        v3.m_fZ = 0.0f;
    } else {
        float fValue_Inv = 1/fValue;
        v3.m_fX = v3.m_fX * fValue_Inv;
        v3.m_fY = v3.m_fY * fValue_Inv;
        v3.m_fZ = v3.m_fZ * fValue_Inv;
    }
    return v3;
} // operator/

// -----------------------------------------------------------------------
// operator/=()
// Divide This Vector By A Scalar
inline Vector3& Vector3::operator/=( const float &fValue ) {
    if ( Math::isZero( fValue ) ) {
        m_fX = 0.0f;
        m_fY = 0.0f;
        m_fZ = 0.0f;
    } else {
        float fValue_Inv = 1/fValue;
        m_fX *= fValue_Inv;
        m_fY *= fValue_Inv;
        m_fZ *= fValue_Inv;
    }
    return *this;
} // operator/=

// -----------------------------------------------------------------------
// rotateX()
// Rotate This Vector About The X Axis
inline Vector3 Vector3::rotateX( float fRadians ) {
    Vector3 v3;
    v3.m_fX =  m_fX;
    v3.m_fY =  m_fY * cos( fRadians ) - m_fZ * sin( fRadians );
    v3.m_fZ =  m_fY * sin( fRadians ) + m_fZ * cos( fRadians );
    return v3;
} // rotateX

// -----------------------------------------------------------------------
// rotateY()
// Rotate This Vector About The Y Axis
inline Vector3 Vector3::rotateY( float fRadians ) {
    Vector3 v3;
    v3.m_fX =  m_fX * cos( fRadians ) + m_fZ * sin( fRadians );
    v3.m_fY =  m_fY;
    v3.m_fZ = -m_fX * sin( fRadians ) + m_fZ * cos( fRadians );
    return v3;
} // rotateY

// -----------------------------------------------------------------------
// rotateZ()
// Rotate This Vector About The Z Axis
inline Vector3 Vector3::rotateZ( float fRadians ) {
    Vector3 v3;
    v3.m_fX = m_fX * cos( fRadians ) - m_fY * sin( fRadians );
    v3.m_fY = m_fX * sin( fRadians ) + m_fY * cos( fRadians );
    v3.m_fZ = m_fZ;
    return v3;
} // rotateZ

// -----------------------------------------------------------------------
// setPerpendicularXY()
// Make This Vector Perpendicular To Vector3
inline void Vector3::setPerpendicularXY( Vector3 v3 ) {
    m_fX = -v3.m_fY;
    m_fY =  v3.m_fX;
    m_fZ =  v3.m_fZ;
} // setPerpendicularXY

// -----------------------------------------------------------------------
// setPerpendicularXZ()
// Make This Vector Perpendicular To Vector3
inline void Vector3::setPerpendicularXZ( Vector3 v3 ) {
    m_fX = -v3.m_fZ;
    m_fY =  v3.m_fY;
    m_fZ =  v3.m_fX;
} // setPerpendicularXZ

// -----------------------------------------------------------------------
// setPerpendicularYZ()
// Make This Vector Perpendicular To Vector3
inline void Vector3::setPerpendicularYZ( Vector3 v3 ) {
    m_fX =  v3.m_fX;
    m_fY = -v3.m_fZ;
    m_fZ =  v3.m_fY;
} // setPerpendicularYZ

// -----------------------------------------------------------------------
// cross()
// Get The Cross Product Of Two Vectors
inline Vector3 Vector3::cross( const Vector3 v3 ) const {
    return Vector3( m_fY*v3.m_fZ - m_fZ*v3.m_fY,
                    v3.m_fX*m_fZ - m_fX*v3.m_fZ,
                    m_fX*v3.m_fY - m_fY*v3.m_fX );
} // cross

// -----------------------------------------------------------------------
// dot()
// Return The Dot Product Between This Vector And Another One
inline float Vector3::dot( const Vector3 v3 ) const {
    return ( m_fX * v3.m_fX + 
             m_fY * v3.m_fY +
             m_fZ * v3.m_fZ );
} // dot

// -----------------------------------------------------------------------
// normalize()
// Make The Length Of This Vector Equal To One
inline void Vector3::normalize() {
    float fMag;
    fMag = sqrt( m_fX*m_fX + m_fY*m_fY + m_fZ*m_fZ );
    if ( fMag <= Math::ZERO ) {
        m_fX = 0.0f;
        m_fY = 0.0f;
        m_fZ = 0.0f;
        return;
    }

    fMag = 1/fMag;
    m_fX *= fMag;
    m_fY *= fMag;
    m_fZ *= fMag;
} // normalize

// -----------------------------------------------------------------------
// isZero()
// Return True if Vector Is (0,0,0)
inline bool Vector3::isZero() const {
    if ( Math::isZero( m_fX ) && 
         Math::isZero( m_fY ) && 
         Math::isZero( m_fZ ) ) {
        return true;
    } else {
        return false;
    }
} // isZero

// -----------------------------------------------------------------------
// zero()
// Set The Vector To (0,0,0)
inline void Vector3::zero() {
    m_fX = 0.0f;
    m_fY = 0.0f;
    m_fZ = 0.0f;
} // zero

// -----------------------------------------------------------------------
// 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 bNormalized ) {
    // a . b = |a||b|cos(angle)
    // -> cos-1((a.b)/(|a||b|))

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

    float fValue = 0;
    if ( bNormalized ) {
        // v3 Is Already Normalized
        fValue = dot(v3)/fMagA;
    } else {
        float fMagB = v3.length();
        if ( fMagB <= Math::ZERO) {
             // B Is An Invalid Vector
             return 0;
        }
        fValue = dot(v3)/(fMagA*fMagB);
    }

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

    return fValue;
} // getCosAngle

// -----------------------------------------------------------------------
// getAngle()
// Returns The Angle Between This Vector And Vector V in Radians.
// This Is More Expensive Than Using getCosAngle
inline float Vector3::getAngle( const Vector3 &v3, const bool bNormalized, bool bRadians ) {
    // a . b = |a||b|cos(angle)
    // -> cos-1((a.b)/(|a||b|))

    if ( bRadians ) {
        return acos( this->getCosAngle( v3 ) );
    } else {
        // Convert To Degrees
        return Math::radian2Degree( acos( getCosAngle( v3, bNormalized ) ) );
    }
} // getAngle

// -----------------------------------------------------------------------
// length()
// Return The Absolute Length Of This Vector
inline float Vector3::length() const {
    return sqrtf( m_fX * m_fX + 
                  m_fY * m_fY +
                  m_fZ * m_fZ );
 } // length

 // -----------------------------------------------------------------------
 // length2()
 // Return The Relative Length Of This Vector - Doesn't Use sqrt() Less Expensive
 inline float Vector3::length2() const {
     return ( m_fX * m_fX +
              m_fY * m_fY +
              m_fZ * m_fZ );
 } // length2

#endif // VECTOR3_H

Vector3.cpp

#include "Vector3.h"

GeneralMath.h

#ifndef GENERALMATH_H
#define GENERALMATH_H

#include "stdafx.h"

class Math {
public:
    static const float PI;
    static const float PI_HALVES;
    static const float PI_THIRDS;
    static const float PI_FOURTHS;
    static const float PI_SIXTHS;
    static const float PI_2;
    static const float PI_INVx180;
    static const float PI_DIV180;
    static const float PI_INV;
    static const float ZERO;

    Math();

    inline static bool  isZero( float fValue );
    inline static float sign( float fValue );

    inline static int   randomRange( int iMin, int iMax );
    inline static float randomRange( float fMin, float fMax );

    inline static float degree2Radian( float fDegrees );
    inline static float radian2Degree( float fRadians );
    inline static float correctAngle( float fAngle, bool bDegrees, float fAngleStart = 0.0f );
    inline static float mapValue( float fMinY, float fMaxY, float fMinX, float fMaxX, float fValueX );

    template<class T>
    inline static void constrain( T min, T max, T &value );

    template<class T>
    inline static void swap( T &value1, T &value2 );

}; // Math

// -----------------------------------------------------------------------
// degree2Radian()
// Convert Angle In Degrees To Radians
inline float Math::degree2Radian( float fDegrees ) {
    return fDegrees * PI_DIV180;
} // degree2Radian

// -----------------------------------------------------------------------
// radian2Degree()
// Convert Angle In Radians To Degrees
inline float Math::radian2Degree( float fRadians ) {
    return fRadians * PI_INVx180;
} // radian2Degree

// -----------------------------------------------------------------------
// correctAngle()
// Returns An Angle Value That Is Alway Between fAngleStart And fAngleStart + 360
// If Radians Are Used, Then Range Is fAngleStart To fAngleStart + 2PI
inline float Math::correctAngle( float fAngle, bool bDegrees, float fAngleStart ) {
    if ( bDegrees ) {
        // Using Degrees
        if ( fAngle < fAngleStart ) {
            while ( fAngle < fAngleStart ) {
                fAngle += 360.0f;
            }
        } else if ( fAngle >= (fAngleStart + 360.0f) ) {
            while ( fAngle >= (fAngleStart + 360.0f) ) {
                fAngle -= 360.0f;
            }
        }
        return fAngle;
    } else {
        // Using Radians
        if ( fAngle < fAngleStart ) {
            while ( fAngle < fAngleStart ) {
                fAngle += Math::PI_2;
            }
        } else if ( fAngle >= (fAngleStart + Math::PI_2) ) {
            while ( fAngle >= (fAngleStart + Math::PI_2) ) {
                fAngle -= Math::PI_2;
            }
        }
        return fAngle;
    }
} // correctAngle

// -----------------------------------------------------------------------
// isZero()
// Tests If Input Value Is Close To Zero
inline bool Math::isZero( float fValue ) {
    if ( (fValue > -ZERO) && (fValue < ZERO) ) {
        return true;
    }
    return false;
} // isZero

// -----------------------------------------------------------------------
// sign()
// Returns 1 If Value Is Positive, -1 If Value Is Negative Or 0 Otherwise
inline float Math::sign( float fValue ) {
    if ( fValue > 0 ) {
        return 1.0f;
    } else if ( fValue < 0 ) {
        return -1.0f;
    }
    return 0;
} // sign

// -----------------------------------------------------------------------
// randomRange()
// Return A Random Number Between iMin And iMax Where iMin < iMax
inline int Math::randomRange( int iMin, int iMax ) {
    if ( iMax < iMin ) {
        swap( iMax, iMin );
     }
     return (iMin + ((iMax - iMin +1) * rand()) / (RAND_MAX+1) );
} // randomRange

// -----------------------------------------------------------------------
// randomRange()
// Return A Random Number Between fMin And fMax Where fMin < fMax
inline float Math::randomRange( float fMin, float fMax ) {
    if ( fMax < fMin ) {
        swap( fMax, fMin );
    }
    return (fMin + (rand()/(float)RAND_MAX)*(fMax-fMin));
} // randomRange

// -----------------------------------------------------------------------
// mapValue()
// Returns The fValueY That Corresponds To A Point On The Line Going From Min To Max
inline float Math::mapValue( float fMinY, float fMaxY, float fMinX, float fMaxX, float fValueX ) {
    if ( fValueX >= fMaxX ) {
        return fMaxY;
} else if ( fValueX <= fMinX ) {
        return fMinY;
    } else {
        float fM = (fMaxY - fMinY) / (fMaxX - fMinX);
        float fB = fMaxY - fM * fMaxX;
        return (fM*fValueX + fB);
    }
} // mapValue

// -----------------------------------------------------------------------
// 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

// -----------------------------------------------------------------------
// swap()
//
template<class T>
inline void Math::swap( T &value1, T &value2 ) {
    T temp;
    temp   = value1;
    value1 = value2;
    value2 = temp;
} // swap

#endif // GENERALMATH_H

GeneralMath.cpp

#include "GeneralMath.h"

const float Math::PI           = 4.0f  * atan(1.0f); // tan(pi/4) = 1
const float Math::PI_HALVES    = 0.50f * Math::PI;
const float Math::PI_THIRDS    = Math::PI * 0.3333333333333f;
const float Math::PI_FOURTHS   = 0.25f * Math::PI;
const float Math::PI_SIXTHS    = Math::PI * 0.6666666666667f;
const float Math::PI_2         = 2.00f * Math::PI;
const float Math::PI_DIV180    = Math::PI / 180.0f;
const float Math::PI_INVx180   = 180.0f / Math::PI;
const float Math::PI_INV       = 1.0f / Math::PI;
const float Math::ZERO         = (float)1e-7;

// -----------------------------------------------------------------------
// Math()
// Default Constructor
Math::Math() {
} // Math

如果您注意到 Vector3 class 对象,它有 2 个方法 returns 向量的长度。一个使用 sqrt() ,它会给出绝对长度,但使用起来更昂贵。第二种方法 length2() 不使用 sqrt() 和 returns 廉价的相对长度。这个相同的概念可以应用于查找两点之间的距离!希望这篇 class 对您有所帮助。如果您需要知道确切的长度或距离,那么可以使用 sqrt() 版本。当涉及多个点或向量之间的比较时,A 和 B 之间的比较事实 table 使用任何一种方法都是相同的,因此最好使用此函数的第二个版本。