Verbesserung und Tipps zu Vektor Klasse



  • sollte man mMn auf keinen Fall per Operatorüberladung behandeln.

    Warum?



  • Weil die Semantik eines Operators unmittelbar klar sein sollte. Siehe z.B. 13.9.21 auf http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-13.2

    +,-,unäres- usw. arbeiten alle Komponentenweise, also sollte es auch * tun. Ansonsten wird es unintutiv:
    v2 * v3 * v4; // Ist das nun ein Skalarprodukt gefolgt von einer Skalierung? Oder ein zweimaliges Kreuzprodukt? Oder...



  • this->that schrieb:

    Weil die Semantik eines Operators unmittelbar klar sein sollte. Siehe z.B. 13.9.21 auf http://www.parashift.com/c++-faq-lite/operator-overloading.html#faq-13.2

    +,-,unäres- usw. arbeiten alle Komponentenweise, also sollte es auch * tun. Ansonsten wird es unintutiv:
    v2 * v3 * v4; // Ist das nun ein Skalarprodukt gefolgt von einer Skalierung? Oder ein zweimaliges Kreuzprodukt? Oder...

    Skalierung eines Vektors durch einen Vektor? Zeig mal Code, wo das verwendet wird. Es könnte nämlich sein, daß das eine Niemalsaufgerufene Funktion wird.

    Also klar ist das komponentenweise * schonmal gar nicht, denn das Gips in der Schulmathematik bis zum Abitur doch gar nicht.



  • Die Klasse bildet ein matehmatisches Objekt ab dessen Operationen eindeutig definiert sind.
    Das Produkt zweier Vektoren als Skalarprodukt anzunehmen halte ich fuer durchaus intuitiv.
    Einen Vektor per Template in unterschiedlichen Praezisionen anzubieten eher weniger.
    Skalierungsvektoren gehoeren fuer mich eher in die Schublade "Transformation".
    Aber das ist vielleicht Geschmackssache...



  • danke an alle! Vor allem an this->that!
    Die Klasse sieht jetzt so aus:

    // header
    #pragma once
    #include <D3dx9math.h>
    
    class Vector3d
    {
    public:
    	float x, y, z;
    
    	Vector3d() { }
    	Vector3d(const Vector3d& vector3d) : x(vector3d.x), y(vector3d.y), z(vector3d.z) { }
    	Vector3d(float value) : x(value), y(value), z(value) { }
    	Vector3d(float xCoord, float yCoord, float zCoord) : x(xCoord), y(yCoord), z(zCoord) { }
    	Vector3d(float fArr[3]) : x(fArr[0]), y(fArr[1]), z(fArr[2]) { }
    	Vector3d(const D3DVECTOR& d3dvec) : x(d3dvec.x), y(d3dvec.y), z(d3dvec.z) { }
    
    	float& operator[](unsigned index);
    	Vector3d& operator = (const Vector3d& vector3d);
    	Vector3d& operator += (const Vector3d& vector3d);
    	Vector3d& operator -= (const Vector3d& vector3d);	
    	Vector3d& operator *= (const Vector3d& vector3d);	
    	Vector3d& operator *= (float value);
    	Vector3d& operator /= (const Vector3d& vector3d);
    	Vector3d& operator /= (float value);
    
    	bool operator == (const Vector3d& vector3d);
    	bool operator != (const Vector3d& vector3d);
    };
    
    float Length(Vector3d vector3d);
    float LengthSq(Vector3d vector3d);
    Vector3d Normalize(Vector3d vector3d);
    Vector3d Cross(Vector3d vector3d1, Vector3d vector3d2);
    float Dot(Vector3d vector3d1, Vector3d vector3d2);
    float Angle(Vector3d vector3d1, Vector3d vector3d2);
    Vector3d Random();
    Vector3d InterpolateCoords(Vector3d vector3d1, Vector3d vector3d2, float value);
    Vector3d InterpolateNormal(Vector3d vector3d1, Vector3d vector3d2, float value);
    Vector3d Min(Vector3d vector3d1, Vector3d vector3d2);
    Vector3d Max(Vector3d vector3d1, Vector3d vector3d2);
    
    Vector3d operator - (const Vector3d& vector3d);
    Vector3d operator * (const Vector3d& vector3d, float value);
    Vector3d operator * (float value, const Vector3d& vector3d);
    Vector3d operator / (const Vector3d& vector3d, float value);
    Vector3d operator + (const Vector3d& vector3d1, const Vector3d& vector3d2);
    Vector3d operator - (const Vector3d& vector3d1, const Vector3d& vector3d2);
    Vector3d operator * (const Vector3d& vector3d1, const Vector3d& vector3d2);
    Vector3d operator / (const Vector3d& vector3d1, const Vector3d& vector3d2);
    
    // cpp 
    #include "Vector3d.h"
    
    float& Vector3d::operator[](unsigned index)
    {
    	if(index == 0)
    		return x;
    	if(index == 1)
    		return y;
    	if(index == 2)
    		return z;
    }
    
    Vector3d& Vector3d::operator = (const Vector3d& vector3d)
    {
    	x = vector3d.x;
    	y = vector3d.y;
    	z = vector3d.z;
    	return *this;
    }
    
    Vector3d& Vector3d::operator += (const Vector3d& vector3d)
    {
    	x += vector3d.x;
    	y += vector3d.y;
    	z += vector3d.z;
    	return *this;
    }
    
    Vector3d& Vector3d::operator -= (const Vector3d& vector3d)
    {
    	x -= vector3d.x;
    	y -= vector3d.y;
    	z -= vector3d.z;
    	return *this;
    }
    
    Vector3d& Vector3d::operator *= (const Vector3d& vector3d)
    {
    	x *= vector3d.x;
    	y *= vector3d.y;
    	z *= vector3d.z;
    	return *this;
    }
    
    Vector3d& Vector3d::operator *= (float value)
    {
    	x *= value;
    	y *= value;
    	z *= value;
    	return *this;
    }
    
    Vector3d& Vector3d::operator /= (const Vector3d& vector3d)
    {
    	x /= vector3d.x;
    	y /= vector3d.y;
    	z /= vector3d.z;
    	return *this;
    }
    
    Vector3d& Vector3d::operator /= (float value)
    {
    	x /= value;
    	y /= value;
    	z /= value;
    	return *this;
    }
    
    bool Vector3d::operator == (const Vector3d& vector3d)
    {
    	return (x == vector3d.x && y == vector3d.y && z == vector3d.z);
    }
    
    bool Vector3d::operator != (const Vector3d& vector3d)
    {
    	return (x != vector3d.x || y != vector3d.y || z != vector3d.z);
    }
    
    float Length(Vector3d vector3d)
    {
    	return sqrtf(LengthSq(vector3d));
    }
    
    float LengthSq(Vector3d vector3d)
    {
    	return vector3d.x * vector3d.x + vector3d.y * vector3d.y + vector3d.z * vector3d.z;
    }
    
    Vector3d Normalize(Vector3d vector3d)
    {
    	float lengthVec = Length(vector3d);
    	if(lengthVec != 0)
    		return vector3d / lengthVec;
    	return vector3d / (lengthVec + 0.0001f);
    }
    
    Vector3d Cross(Vector3d vector3d1, Vector3d vector3d2)
    {
    	return Vector3d(vector3d1.y * vector3d2.z - vector3d1.z * vector3d2.y, vector3d1.z * vector3d2.x - vector3d1.x * vector3d2.z, vector3d1.x * vector3d2.y - vector3d1.y * vector3d2.x);
    }
    
    float Dot(Vector3d vector3d1, Vector3d vector3d2)
    {
    	return vector3d1.x * vector3d2.x + vector3d1.y * vector3d2.y + vector3d1.z * vector3d2.z;
    }
    
    float Angle(Vector3d vector3d1, Vector3d vector3d2)
    {
    	return acosf(Dot(vector3d1, vector3d2) / Length(vector3d1) * Length(vector3d2));
    }
    
    Vector3d Random()
    {
    	return Vector3d((float)rand(), (float)rand(), (float)rand());
    }
    
    Vector3d InterpolateCoords(Vector3d vector3d1, Vector3d vector3d2, float value)
    {
    	return vector3d1 + value * (vector3d2 - vector3d1);
    }
    
    Vector3d InterpolateNormal(Vector3d vector3d1, Vector3d vector3d2, float value)
    {
    	Vector3d interpolate = vector3d1 + value * (vector3d2 - vector3d1);
    	return interpolate / (sqrtf(interpolate.x * interpolate.x + interpolate.y * interpolate.y + interpolate.z * interpolate.z) + 0.0001f);
    }
    
    Vector3d Min(Vector3d vector3d1, Vector3d vector3d2)
    {
    	Vector3d buffer;
    	if(vector3d1.x < vector3d2.x)
    		buffer.x = vector3d1.x;
    	else
    		buffer.x = vector3d2.x;
    	if(vector3d1.y < vector3d2.y)
    		buffer.y = vector3d1.y;
    	else
    		buffer.y = vector3d2.y;
    	if(vector3d1.z < vector3d2.z)
    		buffer.z = vector3d1.z;
    	else
    		buffer.z = vector3d2.z;
    	return buffer;
    }
    
    Vector3d Max(Vector3d vector3d1, Vector3d vector3d2)
    {
    	Vector3d buffer;
    	if(vector3d1.x > vector3d2.x)
    		buffer.x = vector3d1.x;
    	else
    		buffer.x = vector3d2.x;
    	if(vector3d1.y > vector3d2.y)
    		buffer.y = vector3d1.y;
    	else
    		buffer.y = vector3d2.y;
    	if(vector3d1.z > vector3d2.z)
    		buffer.z = vector3d1.z;
    	else
    		buffer.z = vector3d2.z;
    	return buffer;
    }
    
    Vector3d operator - (const Vector3d& vector3d)
    {
    	return Vector3d(-vector3d.x, -vector3d.y, -vector3d.z);
    }
    
    Vector3d operator * (const Vector3d& vector3d, float value)
    {
    	return Vector3d(vector3d.x * value, vector3d.y * value, vector3d.z * value);
    }
    
    Vector3d operator * (float value, const Vector3d& vector3d)
    {
    	return Vector3d(vector3d.x * value, vector3d.y * value, vector3d.z * value);
    }
    
    Vector3d operator / (const Vector3d& vector3d, float value)
    {
    	return Vector3d(vector3d.x / value, vector3d.y / value, vector3d.z / value);
    }
    
    Vector3d operator + (const Vector3d& vector3d1, const Vector3d& vector3d2)
    {
    	return Vector3d(vector3d1.x + vector3d2.x, vector3d1.y + vector3d2.y, vector3d1.z + vector3d2.z);
    }
    
    Vector3d operator - (const Vector3d& vector3d1, const Vector3d& vector3d2)
    {
    	return Vector3d(vector3d1.x - vector3d2.x, vector3d1.y - vector3d2.y, vector3d1.z - vector3d2.z);
    }
    
    Vector3d operator * (const Vector3d& vector3d1, const Vector3d& vector3d2)
    {
    	return Vector3d(vector3d1.x * vector3d2.x, vector3d1.y * vector3d2.y, vector3d1.z * vector3d2.z);
    }
    
    Vector3d operator / (const Vector3d& vector3d1, const Vector3d& vector3d2)
    {
    	return Vector3d(vector3d1.x / vector3d2.x, vector3d1.y / vector3d2.y, vector3d1.z / vector3d2.z);
    }
    

    ich bin mir ziemlich sicher, dass der [] Operator falsch überladen ist. Wie geht es richtig?
    Eine Templateklasse brauche ich eigentlich nicht, da ich die Klasse sowieso nur mit float Werten benutze. Und habt ihr noch andere Anmerkungen?



  • volkard schrieb:

    Skalierung eines Vektors durch einen Vektor?

    Habe ich nirgends geschrieben. Ich schrieb Skalarprodukt gefolgt von einer Skalierung. Denn wenn operator* das Skalarprodukt darstellt, dann ist v2*v2 ein skalar s und folglich die 2. Auswertung von operator*(float,Vector) eine Skalierung.[/quote]

    Die Klasse bildet ein matehmatisches Objekt ab dessen Operationen eindeutig definiert sind.

    So eindeutig ist das nicht. In manchen Skripts ist das * die Komponentenweise Multiplikation, manchmal das Skalarprodukt. Meistens werden 2 verschiedene Zeichen benutzt (* und .)

    Das Produkt zweier Vektoren als Skalarprodukt anzunehmen halte ich fuer durchaus intuitiv aber das ist wohl Geschmackssache.

    Ja, das ist Geschmackssache. Aber ich finde die Fausregel, dass die Semantik unmittelbar klar sein sollte, sinnvoll. Man könnte auch den Operator += eines Buttons überladen, um einen EventHandler zu registrieren. Finde ich aber unschön und ich würde eine registerEventHandler() Methode schreiben.



  • hellihjb schrieb:

    Die Klasse bildet ein matehmatisches Objekt ab dessen Operationen eindeutig definiert sind.
    Das Produkt zweier Vektoren als Skalarprodukt anzunehmen halte ich fuer durchaus intuitiv.

    Durchaus nicht.

    Siehe: x^T*y vs x*y^T. Beides Vektormultiplikationen, aber beides unterschiedliche Ergebnisse. dann gibts natürlich noch x kreuz y. Auch das wird gerne als Multiplikation bezeichnet. Lustig wirds ja, wnen man dann fragt, was x*=y ist. Skalar kanns nicht sein. Matrix auch nicht...

    Die komponentenweise multiplikation ist zwar auch nicht toll, aber im Bereich Farbe wird das gerne verwendet. Und macht zumindest mit der Operatorsemantik Sinn.



  • ich bin ziemlich sicher, daß der op[] von dir richtig ist. je nach compiler und geschmack kann man über sowas nachdenken

    float& Vector3d::operator[](unsigned index) 
    { 
       assert(0<=index && index<=2);
       switch(index)
       {
          case 0: return x;
          case 1: return y;
          case 2: return z;
          default: __assume(false);   
       }
    }
    

    oder

    float& Vector3d::operator[](unsigned index) 
    { 
       assert(0<=index && index<=2);
       assert(sizeof(*this)==3*sizeof(float));
       return index[&x];
    }
    

    Nett ist auch zusätzlich ein

    float const& Vector3d::operator[](unsigned index) const;
    


  • @Melan: Jetzt find ichs auch ne schöne Vektor Klasse. Durch das Auslagern der "Algorithmen" schön schlank und übersichtlich. 👍

    Die Funktion Length() könntest du im Vektor lassen, denn die Länge ist eher eine Eigenschaft des Vektors, als eine Operation. Zusätzlich könntest du dann noch eine freie Funktion anbieten, die die Methode aufruft. Aus meiner Vektor Klasse:

    template <class T>
    inline float Vec3Length(const Vector3<T>& v) {
       return v.getLength();
    }
    

    Dein operator[] ist richtig. Ich würde aber noch ein assert(index < 3) einbauen.
    Und die Parameter deiner freien Funktionen würde ich nicht by-value sondern als konstante Referenzen übergeben.



  • this->that schrieb:

    Die Funktion Length() könntest du im Vektor lassen, denn die Länge ist eher eine Eigenschaft des Vektors, als eine Operation.

    Nö. Es gibt ziemlich viele verschiedene Längendefinitionen. Die bekanntesten sind norm1 (City-Block Metric), norm2( Euklidische Distanz) und normInf(maximum). Es gibt nicht "die" Länge. Deswegen kann sie keine Eigenschaft des Vektors sein.



  • otze schrieb:

    this->that schrieb:

    Die Funktion Length() könntest du im Vektor lassen, denn die Länge ist eher eine Eigenschaft des Vektors, als eine Operation.

    Nö. Es gibt ziemlich viele verschiedene Längendefinitionen. Die bekanntesten sind norm1 (City-Block Metric), norm2( Euklidische Distanz) und normInf(maximum). Es gibt nicht "die" Länge. Deswegen kann sie keine Eigenschaft des Vektors sein.

    Oder man sagt: Es gibt nur den euklidischen Raum. Angle und InterpolateCoords riechen mir auch danach.



  • otze schrieb:

    this->that schrieb:

    Die Funktion Length() könntest du im Vektor lassen, denn die Länge ist eher eine Eigenschaft des Vektors, als eine Operation.

    Nö. Es gibt ziemlich viele verschiedene Längendefinitionen. Die bekanntesten sind norm1 (City-Block Metric), norm2( Euklidische Distanz) und normInf(maximum). Es gibt nicht "die" Länge. Deswegen kann sie keine Eigenschaft des Vektors sein.

    Definition:

    a vector (sometimes called a geometric[1] or spatial vector[2]) is a geometric object that has both a magnitude (or length) and direction

    In der 3D-Grafik wird die benötigte Länge immer die Euklidsche Distanz sein. Für den Fall das man irgend eine freakigere Länge braucht (was nie der Fall sein wird), kann man sich dafür ja ne Extra Funktion schreiben.



  • - Die Konstruktoren, die nur 1 Parameter erwarten (außer Kopierkonstruktor), würde ich explicit machen. Das hilft Fehlern vorzubeugen!
    - Angle und Dot sollten konstante Referenzen als Parameter erwarten!



  • this->that schrieb:

    In der 3D-Grafik wird die benötigte Länge immer die Euklidsche Distanz sein. Für den Fall das man irgend eine freakigere Länge braucht (was nie der Fall sein wird), kann man sich dafür ja ne Extra Funktion schreiben.

    Aber wieso sollte man v.length() schreiben können, aber nicht v.norm1()? Wieso nicht nur length(v) dann passt auch norm1(v) intuitiv. (nebenbei frage ich mich, was die Definition soll. Ich bezweifle doch gar nicht, dass es eine Länge gibt, sondern sag nur, dass diese frei wählbar ist, solange sie bestimmte Eigenschaften erfüllt)

    @Volkard den Winkel kriegt man auch in anderen Räumen hin, wenn zwischen Länge eines Vektors und Skalarprodukt die Beziehung sqrt(<v,v>) besteht.



  • otze schrieb:

    Aber wieso [...]

    Weil man es einfach nicht tut.
    Kontext beachten:

    #include <D3dx9math.h>
    


  • otze schrieb:

    (nebenbei frage ich mich, was die Definition soll. Ich bezweifle doch gar nicht, dass es eine Länge gibt, sondern sag nur, dass diese frei wählbar ist, solange sie bestimmte Eigenschaften erfüllt)

    In der Definition ist der Begriff Length direkt verlinkt mit der euklidschen Distanz.

    hellihjb schrieb:

    Weil man es einfach nicht tut.

    Eben. Jeder weiß, dass das euklidsche Vektoren sind und niemand wird erwarten, dass ein length ein City-Block Metric (was auch immer das sein soll) liefert.



  • this->that schrieb:

    Eben. Jeder weiß, dass das euklidsche Vektoren sind und niemand wird erwarten, dass ein length ein City-Block Metric (was auch immer das sein soll) liefert.

    Fall Interesse besteht, was das ist: Ich kann mir nichst anderes denken, als daß http://en.wikipedia.org/wiki/Taxicab_geometry gemeint ist.



  • @volkard: Gibt es eigentlich einen Grund, wieso du im assert() 0<=index prüfst, obwohl index unsigned ist?



  • this->that schrieb:

    In der Definition ist der Begriff Length direkt verlinkt mit der euklidschen Distanz.

    Nein. In der Definition steht nur Länge. Dass aber die Länge innerhalb eines Vektorraums frei wählbar ist, hatte ich bereits gesagt. Um mal einen Prof zu zitieren: "Ein Vektor ist ein Ding, das bestimmte Operationen hat und für das eine Länge definiert ist". Danach hat er mehrere Funktionen an die Tafel geschrieben, ein Integral als Längendefinition gebracht und gesagt: das sind Vektoren. Kurz danach hat er übrigens den Winkel zwischen verschobenen Sinus-Funktionen anhand dieser Definition bestimmt.

    hellihjb schrieb:

    Weil man es einfach nicht tut.

    Eben. Jeder weiß, dass das euklidsche Vektoren sind

    Auch wenn DirectX den Projektionsraum verwendet und durchaus alle Matrizen im Projektionsraum angegeben werden müssen? Auch dieser Raum hat eine eigene Längendefinition. Und je nachdem was man machen will, ist das auch eine wichtige Sache.

    @volkard Jo. Das Ding hat verschiedene Namen. Manhatten Distanz ist auch gebräuchlich.



  • otze schrieb:

    this->that schrieb:

    In der Definition ist der Begriff Length direkt verlinkt mit der euklidschen Distanz.

    Nein. In der Definition steht nur Länge. Dass aber die Länge innerhalb eines Vektorraums frei wählbar ist, hatte ich bereits gesagt. Um mal einen Prof zu zitieren: "Ein Vektor ist ein Ding, das bestimmte Operationen hat und für das eine Länge definiert ist". Danach hat er mehrere Funktionen an die Tafel geschrieben, ein Integral als Längendefinition gebracht und gesagt: das sind Vektoren. Kurz danach hat er übrigens den Winkel zwischen verschobenen Sinus-Funktionen anhand dieser Definition bestimmt.

    Und inwiefern bringt uns (vor allem den OP) das hier irgendwie weiter? Ich kann einen Vektor auch maximal abstrakt als Element eines Vektorraums über einem Körper K betrachten, für den eine Multiplikation mit bestimmten Eigenschaften definiert ist. Ich kann ihn auch als einen Tensor 1. Stufe betrachten. Was bringt mir das für die PRAKTISCHE Umsetzung? Absolut nichts. In einer 3D Anwendung habe ich euklidsche Vektoren und jeder weiß, was die Länge eines Vektors bedeutet. Bei etwas so trivialem wie der Länge eines Vektors Begriffe wie "City-Block Metric" aufwerfen riecht stark nach Theoretiker, der alles abdecken will aber letztlich zu nichts kommt;)


Anmelden zum Antworten