Mathematische Vector2-Klassenimplementierung

  • Dies ist mein erster Versuch, eine Vector2 -Klasse zu erstellen. Ich habe das Netz nach allem durchsucht, was diese Klasse effizienter machen könnte, aber ich weiß, dass ich an dem Punkt bin, an dem ich bereit bin zu teilen. Auf diese Weise kann ich mich beraten lassen, wie ich es weiter verbessern kann. Ich wollte das tun, bevor ich meine Klasse Vector3 und Vector4 mache.

    Vector2.h

     //VECTOR2 H
    #ifndef VECTOR2_H
    #define VECTOR2_H
    
    //INCLUDES
    #include <math.h>
    
    //DEFINE TYPES
    typedef float float32;
    
    //VECTOR2 CLASS
    class Vector2
    {
    public:
    //MEMBERS
    float32 x;
    float32 y;
    
    //CONSTRUCTORS
    Vector2(void);
    Vector2(float32 xValue, float32 yValue);
    Vector2(const Vector2 & v);
    Vector2(const Vector2 * v);
    
    //DECONSTRUCTOR
    ~Vector2(void);
    
    //METHODS
    void Set(float32 xValue, float32 yValue);
    
    float32 Length() const;
    float32 LengthSquared() const;
    float32 Distance(const Vector2 & v) const;
    float32 DistanceSquared(const Vector2 & v) const;
    float32 Dot(const Vector2 & v) const;
    float32 Cross(const Vector2 & v) const;
    
    Vector2 & Normal();
    Vector2 & Normalize();
    
    //ASSINGMENT AND EQUALITY OPERATIONS
    inline Vector2 & Vector2::operator = (const Vector2 & v) { x = v.x; y = v.y; return *this; }
    inline Vector2 & Vector2::operator = (const float32 & f) { x = f; y = f; return *this; }
    inline Vector2 & Vector2::operator - (void) { x = -x; y = -y; return *this; }
    inline bool Vector2::operator == (const Vector2 & v) const { return (x == v.x) && (y == v.y); }
    inline bool Vector2::operator != (const Vector2 & v) const { return (x != v.x) || (y != v.y); }
    
    //VECTOR2 TO VECTOR2 OPERATIONS
    inline const Vector2 Vector2::operator + (const Vector2 & v) const { return Vector2(x + v.x, y + v.y); }
    inline const Vector2 Vector2::operator - (const Vector2 & v) const { return Vector2(x - v.x, y - v.y); }
    inline const Vector2 Vector2::operator * (const Vector2 & v) const { return Vector2(x * v.x, y * v.y); }
    inline const Vector2 Vector2::operator / (const Vector2 & v) const { return Vector2(x / v.x, y / v.y); }
    
    //VECTOR2 TO THIS OPERATIONS
    inline Vector2 & Vector2::operator += (const Vector2 & v) { x += v.x; y += v.y; return *this; }
    inline Vector2 & Vector2::operator -= (const Vector2 & v) { x -= v.x; y -= v.y; return *this; }
    inline Vector2 & Vector2::operator *= (const Vector2 & v) { x *= v.x; y *= v.y; return *this; }
    inline Vector2 & Vector2::operator /= (const Vector2 & v) { x /= v.x; y /= v.y; return *this; }
    
    //SCALER TO VECTOR2 OPERATIONS
    inline const Vector2 Vector2::operator + (float32 v) const { return Vector2(x + v, y + v); }
    inline const Vector2 Vector2::operator - (float32 v) const { return Vector2(x - v, y - v); }
    inline const Vector2 Vector2::operator * (float32 v) const { return Vector2(x * v, y * v); }
    inline const Vector2 Vector2::operator / (float32 v) const { return Vector2(x / v, y / v); }
    
    //SCALER TO THIS OPERATIONS
    inline Vector2 & Vector2::operator += (float32 v) { x += v; y += v; return *this; }
    inline Vector2 & Vector2::operator -= (float32 v) { x -= v; y -= v; return *this; }
    inline Vector2 & Vector2::operator *= (float32 v) { x *= v; y *= v; return *this; }
    inline Vector2 & Vector2::operator /= (float32 v) { x /= v; y /= v; return *this; }
    };
    
    #endif
    //ENDFILE
     

    Vector2.cpp

     //VECTOR2 CPP
    #include "Vector2.h"
    
    //CONSTRUCTORS
    Vector2::Vector2(void) : x(0), y(0) { }
    Vector2::Vector2(float32 xValue, float32 yValue) : x(xValue), y(yValue) { }
    Vector2::Vector2(const Vector2 & v) : x(v.x), y(v.y) { }
    Vector2::Vector2(const Vector2 * v) : x(v->x), y(v->y) { }
    
    //DECONSTRUCTOR
    Vector2::~Vector2(void) { }
    
    //METHODS
    void Vector2::Set(float32 xValue, float32 yValue) { x = xValue; y = yValue; }
    
    float32 Vector2::Length() const { return sqrt(x * x + y * y); }
    float32 Vector2::LengthSquared() const { return x * x + y * y; }
    float32 Vector2::Distance(const Vector2 & v) const { return sqrt(((x - v.x) * (x -     v.x)) + ((y - v.y) * (y - v.y))); }
    float32 Vector2::DistanceSquared(const Vector2 & v) const { return ((x - v.x) * (x -     v.x)) + ((y - v.y) * (y - v.y)); }
    float32 Vector2::Dot(const Vector2 & v) const { return x * v.x + y * v.y; }
    float32 Vector2::Cross(const Vector2 & v) const { return x * v.y + y * v.x; }
    
    Vector2 & Vector2::Normal() { Set(-y, x); return *this; }
    Vector2 & Vector2::Normalize()
    {
    if(Length() != 0)
    {
        float32 length = LengthSquared();
        x /= length; y /= length;
        return *this;
    }
    
    x = y = 0;
    return *this;
    }
    
    //ENDFILE
     

    Erste Änderung:

    Geändert Cross in OrthoVector:

     Vector2 & Vector2::Ortho() { Set(-y, x); return *this; }
     

    Der Code Normal wurde geändert, um die Normalen des Vektors zu erhalten:

     Vector2 & Vector2::Normal() { float32 len = Length(); Set(x / len, y / len); return *this; }
     

    Zweite Änderung:

    Der normale Code wurde erneut geändert, um die Division durch Null zu überprüfen:

     Vector2 & Vector2::Normal()
    {
    if(Length() != 0)
    {
        float32 len = Length();
        x /= len; y /= len;
        return *this;
    }
    
    x = y = 0;
    return *this;
    }
     

    Ich habe meinen Pointer Constructor und meinen Empty Deconstructor ebenfalls losgelassen.

    20 May 2014
    Jamal
4 answers
  • Vektor ist ein relativ gebräuchlicher Name (wie Vektor 2/3 usw.). Daher müssen Sie möglicherweise Ihre Include-Wachen ein wenig einzigartiger machen. Ich stecke meine Sachen immer in meinen eigenen Namensraum (was zufällig mit einer Domäne übereinstimmt, die ich besitze, um die Dinge einzigartig zu machen). Ich füge dann den Namespace als Teil des Include-Guard ein. Alternativ können Sie eine GUID erstellen, die auch sicherstellt, dass sie eindeutig ist.

     //VECTOR2 H
    #ifndef VECTOR2_H
    #define VECTOR2_H
     

    Meine würde wie folgt aussehen:

     #ifndef BOBS_DOMAIN_VECTOR_2_H
    #define BOBS_DOMAIN_VECTOR_2_H
    
    namespace BobsDomain {
     

    Ist es gültig, einen Vector 2 mit Nullwerten zu erstellen? In diesem Fall bedeutet dies, dass x / y standardmäßig auf 0,0 eingestellt ist. In diesem Fall könnten die folgenden 2

     Vector2(void);
    Vector2(float32 xValue, float32 yValue);
     

    als neu definiert werden (Dann ist es jedoch möglich, einen Vector2 mit einem einzelnen Wert zu erstellen (was möglicherweise nicht wünschenswert ist).

     Vector2(float32 xValue = 0.0, float32 yValue = 0.0);
     

    Hat Ihr Standardkonstruktor für Konstruktionen etwas Besonderes?

     Vector2(const Vector2 & v);
     

    Wenn nicht, würde ich die vom Compiler generierte Version durch lassen Die von Ihnen verwendete.

    Sie sind nicht sicher, ob Sie aus einem Zeiger konstruieren können. Sehr wenige Klassen erlauben dies (abgesehen von Samart-Zeigern) des Zeigers oder Sie erstellen einfach eine Kopie des Vektors.

     Vector2(const Vector2 * v);
     

    Ich würde diesen Konstruktor löschen. < br /> Dann muss der folgende Code

     Vector2* data = /* Get Vector2 */;
    Vector2  copy(data);
     

    so geändert werden (keine wesentliche Änderung)

     Vector2* data =           /* Get Vector2 */;
    Vector2  copy(*data);     // Notice the extra star.
                              // But this is not a big cost and simplifies the interface
                              // considerably as we do not need to wory about ownership
                              // semantics.
     

    Setzen Sie die Leere nicht, wenn Sie eine leere Parameterliste haben.
    Wenn der Destruktor nichts tut, dann verwende den c ompilergenerierte Version.

     ~Vector2(void);
     

    Warum haben Sie eine Methode festgelegt, wenn die Mitglieder öffentlich sind.

     void Set(float32 xValue, float32 yValue);
     

    Alle Operatoren, die eine Zuweisungsversion haben, sind einfacher zu definieren, wenn Sie sie als Zuweisungsoperator definieren. Es hält die Bedeutung konsistent.

    Was ich meine ist: operator + ist leicht zu definieren in Operator + =

     Vector2  const operator+ (Vector2 const& rhs) const {Vector2 result(*this); return result += rhs;}
    Vector2&       operator+=(Vector2 const& rhs)       {x += v.x; y += v.y; return *this;}
     

    Da wir alle anderen Operatoren definieren, die Sie benötigen

    08 November 2011
    Joseph Daigle
  • Stellt Alternativen zu Methoden bereit, die Kopien zurückgeben.

    Bei diesen Klassen definiere ich normalerweise eine setAdd- und setSub-Methode, die den aktuellen Vektor ausfüllt mit dem Ergebnis einer Addition (resp. Subtraktion).

     void setAdd(const Vector2& v1, const Vector2& v2)
    {
        x = v1.x + v2.x;
        y = v1.y + v2.y;
    }
     

    mit dieser Art von Methode anstelle von operator+ ersetze eine Vector2-Kopie. In bestimmten Fällen ist dies eine sichtbare Auswirkung auf die Codeleistung (siehe Havok Physics math lib).

    Behandeln Sie weitere Anwendungsfälle in der Normalisierung

    Wenn Sie einen Vektor normalisieren, führen Sie häufig weitere Berechnungen anhand der vorherigen Norm durch und müssen häufig eine gewünschte Norm festlegen. Es ist einfach, die Methode Normalize für diese Anwendungsfälle zu ändern.

     float32 Vector2::Normalize(float32 desiredNorm = 1.f)
    {
        float32 len = Length();
        if(len < EPSILON)
        {
             x = y = 0.f;
             return 0.f;
        }
        else
        {
            float32 mult = desiredNorm/len;
            x *= mult; y *= mult;
            return len;
        }
     }
     

    NB. Ich habe auch folgende Änderungen vorgenommen:

    • Nur eine Berechnung von Length(), Quadratwurzelberechnung ist teuer.
    • Verwenden Sie weniger als-epsilon anstelle von different-to-0, was aufgrund der Gleitkomma-Genauigkeit zu verschiedenen Problemen führen kann. Der Wert von EPSILON sollte klein sein (beispielsweise 0,00001).

    Bereitstellen einer abgeschnittenen Methode

    Ich habe es sehr nützlich gefunden, um eine abgeschnittene Methode zu definieren, die eine erzwungene Obergrenze der Norm des Vektors.

     void Truncate(float32 upperBound)
    {
        float32 sqrLen = SqrLength();
        if(sqrLen > upperBound * upperBound)
        {
            float32 mult = upperBound/sqrt(sqrLen);
            x *= mult; y *= mult;
        }
    }
     

    NB.

    • Sie sollten wahrscheinlich behaupten, dass upperBound positiv ist.
    • Ich versuche erneut, die Anzahl der berechneten Quadratwurzeln zu minimieren.
    • ul>
    05 December 2011
    Clodéric
  • Kreuzprodukt ist in dem Kontext, in dem Sie es für 2d-Vektoren erstellt haben, ohne Bedeutung. Wenn Sie sich für den orthogonalen Vektor entscheiden, ist es einfach "(x, y) - & gt; (y, -x)".

    Ihre "Normal" -Funktion kehrt tatsächlich zurück der orthogonale Vektor. "Normal" bedeutet "Vektorland" "Vektor der Einheitslänge in dieselbe Richtung".

    Zur Berechnung der reellen Normalen ist dies

       ai + bj         double mag = sqrt((a*a) + (b*b));
    -----------  or   Vector2 normal(a / mag, b / mag);
    | ai + bj |
     
    08 November 2011
    Mranz
  • Eine grundlegende Eigenschaft von Vektoren, die Sie nicht dargestellt haben: Ein Skalar, wenn ein Vektor ein Vektor ist.

    Ich würde Ihren Vektor * vector und nicht wirklich implementieren Vektor- / Vektoroperationen überhaupt ... diese sind im Allgemeinen nicht nützlich.

    Tatsächlich gibt es ein sinnvolles Kreuzprodukt in zwei Dimensionen, aber der Rückgabewert ist ein Skalar und kein Vektor .

     float32 Cross(const Vector2& a, const Vector2& b) {
        return a.x * b.y - a.y * b.x;
    }
     

    Dies führt zu einem weiteren Punkt: Ich bevorzuge freie Funktionen den Elementfunktionen, wenn die Bedeutung grob symmetrisch ist zwei Variablen wie Dot, Cross oder sogar operator+. Neben der Ästhetik gibt es auch die Tatsache, dass operator+(const X& a, const Y& b) implizite Konvertierungen für beide Operanden verwenden kann, aber X::operator+(const Y& b) const nur implizite Konvertierungen für b.

    08 November 2011
    aschepler