Click here to Skip to main content
Click here to Skip to main content

A Vector Type for C#

By , 24 Jul 2007
 

Introduction

For years I have seen people struggle with vector mathematics. This guide should walk you through the creation of a reusable Vector3 type and the mathematics behind it all. The post-fixed 3 simply refers to the vector being in 3-dimensions (x,y,z).

The code is not designed to be fast or efficient but is to be as simple and understandable as possible. To this end, and as a personal preference, the Vector3 type is packed with relevent functionality and multiple interfaces to methods (e.g. static and non-static variants of methods). Many would deem this bloated code, however, I think that it makes the code programmer friendly. Obviously, as a projects grows I would tend to refactor functionality back out as I create objects and types for which the functions better fit. I see this as achieving maximum cohesion and minimising coupling and dependencies.

I have used the Cartesian coordinate system in three-dimensions (i.e. three perpendicular axis of x, y and z) and Euclidian geometry. Don't worry about these terms; they are just the formal names for some of the maths covered at senior school. The vector space is volumetric (cube); note that you can use other vector spaces, such as a cylindrical space where one axis (usually z) relates to the radius of the cylinder.

You may have guessed that computers are quite slow with this type of math. Matrix mathematics is more efficient but much harder to understand. You will need a basic grasp of trigonometry and algebra to understand this guide.

Unless stated otherwise I assume that the vector is positional, originating at point (0,0,0). Alternatives to positional vectors are: unit vectors, which can be interpreted as either having no magnitude or an infinite magnitude; and vector pairs where the origin of the vector is another vector, magnitude being a distance from the origin vector.

Please note that this guide is extremely verbose and may seem patronising to experienced C# programmers. Please do not be offended, I have written the guide for a wide audience.

A quick glossary:

  • Operator, this is the symbol used to define an operation such as plus (+) in (a+b)
  • Operand, these are the variables used in an operation such as (a) and (b) in (a+b). The left-hand-side (LHS) operand is (a) where as the right-hand-side (RHS) operand is (b).

All of the equations in this guide assume A (or v1) and B (or v2) can be broken down into:

(a b c) = A (d e f) = B

Using the code

To begin with, let us define how the vector information will be stored. I don't often create structs when coding, but for our Vector3 this is perfect. If you are reading this article you probably already know that a vector represents values along a number of axes. For this tutorial we will be developing a three-dimensional type so... thee variables and three axis.

public struct Vector3
{
   private double x, y, z;
}

What orientation are the axes in? Being from a visualization background I always assume:

You may have noticed that Z is negative as you look down the axis. This is a common convention in graphics libraries such as OpenGL. This will become important later on when considering pitch, roll and yaw methods.

A quick diversion: Why a struct instead of a class?

The differences between struct and class:

  • A struct is a value type created on the stack instead of the heap, thus reducing garbage collection overheads.
  • They are passed by value not by reference.
  • They are created and disposed of quickly and efficiently.
  • You cannot derive other types from them (i.e. non-inheritable).
  • They are only appropriate for types with a small number of members (variables). Microsoft recommends a struct should be less than 16 bytes.
  • You do not need the new keyword to instantiate a struct.

Basically, it looks like, acts like, and is a primitive type. Although, there is no reason why the vector type could not be created as a class. A drawback of developing a struct is that collection classes in the .NET framework cast structs as classes. This means that a large collection of Vector3's will have a high casting overhead.

A more in-depth article on structs has been written by S. Senthil Kumar here

Accessing the variables

Did you notice that the variables were private?

While I have chosen to build a struct, I habitually hide my variables and create public accessor and mutator properties. This is not strictly good practice for structs, but I have created them in case I feel the need to convert to a class at a later date (this is good practice for class structures).

In addition to the properties, an array style interface has also been provided. This allows the user call the Vector3 with myVector[x], myVector[y], myVector[z]. Additionally, the user can get or set all of the components as an array using the Array property, i.e. myVector.Array = {x,y,z}.

public double X
{
   get{return x;}
   set{x = value;}
}

public double Y
{
   get{return y;}
   set{y = value;}
}

public double Z
{
   get{return z;}
   set{z = value;}
}

public double[] Array
{
   get{return new double[] {x,y,z};}
   set
   {
      if(value.Length == 3)
      {
         x = value[0];
         y = value[1];
         z = value[2];
      }
      else
      {
         throw new ArgumentException(THREE_COMPONENTS);
      }
   }
}

public double this[ int index ]
{
   get
   {
      switch (index)
      {
         case 0: {return X; }
         case 1: {return Y; }
         case 2: {return Z; }
         default: throw new ArgumentException(THREE_COMPONENTS, "index");
     }
   }
   set
   {
       switch (index)
       {
          case 0: {X = value; break;}
          case 1: {Y = value; break;}
          case 2: {Z = value; break;}
          default: throw new ArgumentException(THREE_COMPONENTS, "index");
      }
   }
}
private const string THREE_COMPONENTS = 
   "Array must contain exactly three components, (x,y,z)";

A property has also been provided to access and manipulate the magnitude of a vector. The magnitude (or absolute value) of a vector is its length , irrespective of direction and can be determined using the formula:

The SumComponentSquares method (used below) can be seen later in this article. Please note that the magnitude is always positive and the magnitude of a vector (0,0,0) cannot be set.

public double Magnitude
{
   get 
   {
      return Math.Sqrt ( SumComponentSqrs() );
   }
   set 
   {
      if (value < 0)
      { throw new ArgumentOutOfRangeException("value", value, 
          NEGATIVE_MAGNITUDE); }

      if (this == origin)
      { throw new ArgumentException(ORAGIN_VECTOR_MAGNITUDE, "this"); }

      this = this * (value / Magnitude);
   }
}

private const string NEGATIVE_MAGNITUDE = 
  "The magnitude of a Vector must be a positive value, (i.e. greater than 0)";

private const string ORAGIN_VECTOR_MAGNITUDE = 
   "Cannot change the magnitude of Vector(0,0,0)";

Constructing a Vector3

To construct the type using typical class syntax the following constructor methods have been provided:

public Vector3(double x, double y, double z)
{
   this.x = 0;
   this.y = 0;
   this.z = 0;

   X = x;
   Y = y;
   Z = z;
}

public Vector3 (double[] xyz)
{
   this.x = 0;
   this.y = 0;
   this.z = 0;

   Array = xyz;
}

public Vector3(Vector3 v1)
{
   this.x = 0;
   this.y = 0;
   this.z = 0;

   X = v1.X;
   Y = v1.Y;
   Z = v1.Z;
}

You may be wondering why I have set x, y, and z to 0 before assigning values. A struct will not allow properties (uppercase X, Y, Z) to be accessed before each of the variables (lowercase x, y, z) have been initialized. As Michał Bryłka commented, it is unusual to use properties from the constructor when assigning values. As a matter of personal taste, I always try to use properties in this way. It ensures that any validation code written into the set method is picked up by the constructor. I find that this is helpful when maintaining code. In the Vector3 type, as it stands, there is no validation in the properties so the issue is academic. Either paradigm would be valid.

Operator overloading

We now have a framework for storing, accessing and mutating the Vector3 and its components (x,y,z). We can now consider mathematical operations applicable to a vector. Let's begin by overloading the basic mathematical operators.

Overloading operators allows the programmer to define how a type is used in the code. Take, for example, the plus operator (+). For numeric types this would suggest addition of two numbers. For strings it represents the concatenation of two strings. Operator overloading is of huge benefit to programmers when describing how a type should interact with the system. In C# the following operators can be overloaded:

  • Addition, concatenation, and reinforcement (+)
  • Subtraction and negation (-)
  • Logical negation (!)
  • Bitwise complement (~)
  • Increment (++)
  • Decrement (--)
  • Boolean Truth (true)
  • Boolean false (false)
  • Multiplication (*)
  • Division (/)
  • Division remainder (%)
  • Logical AND (&)
  • Logical OR (|)
  • Logical Exclusive-OR (^)
  • Binary shift left (<<)
  • Binary shift right (>>)
  • Equality operators, equal and not-equal (== and !=)
  • Difference\comparison operators, less-than and greater-than(< and >)
  • Difference\comparison operators, less-than or equal-to and greater-than or equal-to(<= and >=)

Addition (v3 = v1 + v2)

The addition of two vectors is achieved by simply adding the x, y, and z components of one vector to the other (i.e. x+x, y+y, z+z).

public static Vector3 operator+(Vector3 v1, Vector3 v2)
{
   return
   (
      new Vector3
      (
         v1.X + v2.X,
         v1.Y + v2.Y,
         v1.Z + v2.Z
      )
   );
}

Subtraction (v3 = v1 - v2)

Subtraction of two vectors is simply the subtraction of the x, y, and z components of one vector from the other (i.e. x-x, y-y, z-z).

public static Vector3 operator-(Vector3 v1, Vector3 v2 )
{
   return
   (
      new Vector3
      (
          v1.X - v2.X,
          v1.Y - v2.Y,
          v1.Z - v2.Z
      )
   );
}

Negation (v2 = -v1)

Negation of a vector inverts its direction. This is achieved by simply negating each of the component parts of the vector.

public static Vector3 operator-(Vector3 v1)
{
   return
   (
      new Vector3
      (
         - v1.X,
         - v1.Y,
         - v1.Z
      )
   );
}

Reinforcement (v2 = +v1)

Reinforcement of a vector actually does nothing but return the original vector given the rules of addition, (i.e. +-x = -x and ++x = +x).

public static Vector3 operator+(Vector3 v1)
{
   return
   (
      new Vector3
      (
         + v1.X,
         + v1.Y,
         + v1.Z
      )
   );
}

Comparison (<, >, <=, and >=)

When comparing two vectors we use magnitude (as seen previously).

Less-than (result = v1 < v2)

Less-than compares two vectors, returning true only if the magnitude of the left-hand-side vector (v1) is less than the magnitude of the other (v2).

public static bool operator<(Vector3 v1, Vector3 v2)
{
   return v1.Magnitude < v2.Magnitude;
}

Less-than or Equal-to (result = v1 <= v2)

Less-than or equal-to compares two vectors returning true only if the magnitude of the left-hand-side vector (v1) is less than the magnitude of the other (v2) or the two magnitudes are equal.

public static bool operator<=(Vector3 v1, Vector3 v2)
{
   return v1.Magnitude <= v2.Magnitude;
}

Greater-than (result = v1 > v2)

Greater-than compares two vectors returning true only if the magnitude of the left-hand-side vector (v1) is greater than the magnitude of the other (v2).

public static bool operator>(Vector3 v1, Vector3 v2)
{
   return v1.Magnitude > v2.Magnitude;
}

Greater-than or Equal-to (result = v1 >= v2)

Greater-than or equal-to compares two vectors returning true only if the magnitude of the left-hand-side vector (v1) is greater than the magnitude of the other (v2) or the two magnitudes are equal.

public static bool operator>(Vector3 v1, Vector3 v2)
{
   return v1.Magnitude >= v2.Magnitude;
}

Equality (result = v1 == v2)

To check if two vectors are equal we simply check the component pairs. We AND the results so that any pair which is not equal will result in false.

public static bool operator==(Vector3 v1, Vector3 v2)
{
   return
   (
      (v1.X == v2.X)&&
      (v1.Y == v2.Y)&&
      (v1.Z == v2.Z)
   );
}

There has been a lot of discussion in the comments of this article regarding the use of a threshold when calculating equality. The need for this stems from precision problems associated with storing fractional numbers (i.e. float, double, and decimal variables). I personally agree with red Baron who suggests:

"... you should not implement a tolerance value inside your code.
What is a suitable value for this tolerance?
It is depending on the problem ..."

However, to compromise I have implemented equality tolerance as a constant which can be set to zero (ignored).

public static bool operator==(Vector3 v1, Vector3 v2)
{
   return
   (
      Math.Abs(v1.X - v2.X) <= EqualityTolerence &&
      Math.Abs(v1.Y - v2.Y) <= EqualityTolerence &&
      Math.Abs(v1.Z - v2.Z) <= EqualityTolerence
   );
}

public const double EqualityTolerence = Double.Epsilon;

You should now find that the following code produces:
line 1: false
line 2: true

static void Main(string[] args)
{
    Vector3 vect = new Vector3(1, 2, 3);
    Vector3 vect2 = new Vector3(1, 2, 3.000000000000001);
    Vector3 vect3 = new Vector3(1, 2, 3.0000000000000001);
    Console.WriteLine(vect == vect2);
    Console.WriteLine(vect == vect3);
            
    Console.ReadKey();
}

Inequality (result = v1 != v2)

If the operator == (equal) is overridden, C# forces us to override != (not-equal). This is simply the inverse of equality.

public static bool operator!=(Vector3 v1, Vector3 v2)
{
   return !(v1==v2);
}

Division (v3 = v1 / s2)

Division of a vector by a scalar number (e.g. 2) is achieved by dividing each of the component parts by the divisor (s2).

public static Vector3 operator/(Vector3 v1, double s2)
{
   return
   (
      new Vector3
      (
         v1.X / s2,
         v1.Y / s2,
         v1.Z / s2
      )
   );
}

Multiplication (dot, cross, and by scalar)

Multiplication of vectors is tricky. There are three distinct types of vector multiplication:

  • Multiplication by a scalar (v3 = v1 * s2)
  • Dot product (s3 = v1 . v2)
  • Cross product (v3 = v1 * v2)

Only multiplication by scalar and division by scalar have been implemented as operator overloads. I have seen operators such as ~ overloaded for the dot product to distinguish it from cross product; I believe that this can lead to confusion and have chosen not to provide operators for dot and cross products leaving the user to call the appropriate method instead.

Multiplication by scalar is achieved by multiplying each of the component parts by the scalar value.

public static Vector3 operator*(Vector3 v1, double s2)
{
   return
   (
      new Vector3
      (
         v1.X * s2,
         v1.Y * s2,
         v1.Z * s2
      )
   );
}

The order of operands in multiplication can be reversed; this is known as being commutable.

public static Vector3 operator*(double s1, Vector3 v2)
{
   return v2 * s1;
}

The cross product of two vectors produces a normal to the plane created by the two vectors given.

The formula for this (where v1 = A and v2 = B) is

This equation always produces a vector as the result.

The sine of theta is used to account for the direction of the vector. Theta always takes the smallest angle between A and B (i.e. ).

The right hand side of the formula is arrived at by expanding and simplifying the left hand side using the rules:

Sin 0° = 0

Sin 90° = 1

In a matrix style notation this looks like:

You should be aware that this equation is non-commutable. This means that v1 cross-product v2 is NOT the same as v2 cross-product v1.

The C# code for all of this is:

public static Vector3 CrossProduct(Vector3 v1, Vector3 v2)
{
   return
   (
      new Vector3
      (
         v1.Y * v2.Z - v1.Z * v2.Y,
         v1.Z * v2.X - v1.X * v2.Z,
         v1.X * v2.Y - v1.Y * v2.X
      )
   );
}

Where possible I have created static methods to extend the programmer's options when making use of the type. Methods which directly affect or are effected by the instance simply call the static methods. As such the instance counterpart of the static method is:

public Vector3 CrossProduct(Vector3 other)
{
   return CrossProduct(this, other);
}

Note that this instance method does not affect the instance from which it is called but returns a new Vector3 object. I have chosen to implement cross product in this fashion for two reasons; one, to make it consistent with dot product which cannot produce a vector, and two, because cross product is usually used to generate a normal used somewhere else, the original Vector3 needing to be left intact.

[Side note] A quick template for manually calculating the cross product of two vectors is:

The dot product of two vectors is a scalar value defined by the formula;

The equation should always produce a scalar as the result.

Cosine theta is used to account for the direction of the vector. Theta always takes the smallest angle between A and B (i.e. ).

The right hand side of the formula is arrived at by expanding and simplifying the left hand side using the rules:

Cos 0° =1

Cos 90° = 0

The C# code for this is:

public static double DotProduct(Vector3 v1, Vector3 v2)
{
   return
   (
      v1.X * v2.X +
      v1.Y * v2.Y +
      v1.Z * v2.Z
   );
}

And its counterpart:

public double DotProduct(Vector3 other)
{
   return DotProduct(this, other);
}

Extended functionality

We now have all the basic functionality required of a Vector3 type. To make this type really useful I have provided additional functionality.

Normalisation and Unit Vector

A unit vector has a magnitude of 1. To test if a vector is a unit vector we simply check for 1 against the magnitude method already defined.

public static bool IsUnitVector(Vector3 v1)
{
   return v1.Magnitude == 1;
}

public bool IsUnitVector()
{
   return IsUnitVector(this);
}

Having altered the equality (==) operator to allow a threshold, we must do the same with the static IsUnitVector method (the instance method remains unchanged):

public static bool IsUnitVector(Vector3 v1)
{
   return Math.Abs(v1.Magnitude -1) <= EqualityTolerence;
}

Normalization is the process of converting some vector to a unit vector. The formula for this is:

public static Vector3 Normalize(Vector3 v1)
{
   // Check for divide by zero errors
   if ( v1.Magnitude == 0 )
   {
      throw new DivideByZeroException( NORMALIZE_0 );
   }
   else
   {
      // find the inverse of the vectors magnitude
      double inverse = 1 / v1.Magnitude;
      return
      (
         new Vector3
         (
            // multiply each component by the inverse of the magnitude
            v1.X * inverse,
            v1.Y * inverse,
            v1.Z * inverse
         )
      );
   }
}

public void Normalize()
{
   this = Normalize(this);
}

private const string NORMALIZE_0 = "Can not normalize a vector when" + 
    "it's magnitude is zero";

The normalization instance method directly affects the instance.

Interpolation

This method takes an interpolated value from between two vectors. This method takes three arguments, a starting point (vector v1), and end point (Vector v2), and a control which is a fraction between 1 and 0. The control determines which point between v1 and v2 is taken. A control of 0 will return v1 and a control of 1 will return v2.

n = n1(1-t) + n2t
or:
n = n1 + t(n2-n1)
or:
n = n1 + tn2 -tn1
or:

where:

n = Current value
n1 = Initial value (v1)
n2 = Final value (v2)
t = Control parameter, where , and where, ,

public static Vector3 Interpolate(Vector3 v1, Vector3 v2, double control)
{
   if (control >1 || control <0)
   {
      // Error message includes information about the actual value of the 
      // argument
      throw new ArgumentOutOfRangeException
      (
          "control",
          control,
          INTERPOLATION_RANGE + "\n" + ARGUMENT_VALUE + control
      );
   }
   else
   {
      return
      (
         new Vector3
         (
             v1.X * (1-control) + v2.X * control,
             v1.Y * (1-control) + v2.Y * control,
             v1.Z * (1-control) + v2.Z * control
          )
      );
   }
}

public Vector3 Interpolate(Vector3 other, double control)
{
   return Interpolate(this, other, control);
}

private const string INTERPOLATION_RANGE = "Control parameter must be a" + 
    "value between 0 & 1";

Distance

This method finds the distance between two positional vectors using Pythagoras theorem.

public static double Distance(Vector3 v1, Vector3 v2)
{
   return
   (
      Math.Sqrt
      (
          (v1.X - v2.X) * (v1.X - v2.X) +
          (v1.Y - v2.Y) * (v1.Y - v2.Y) +
          (v1.Z - v2.Z) * (v1.Z - v2.Z)
      )
   );
}

public double Distance(Vector3 other)
{
   return Distance(this, other);
}

Absolute

The absolute value of a vector is its magnitude. The Abs method has been provided to help programmers who are not aware that the two functions are the same and provide a static interface to the magnitude operator.

public static Double Abs(Vector3 v1)
{
  return v1.Magnitude;
}
</implementation />
public double Abs()
{
  return this.Magnitude;
}

Angle

This method finds the angle between two vectors using normalization and dot product.

^ refers to a normalized (unit) vector.
|| refers to the magnitude of a Vector.

public static double Angle(Vector3 v1, Vector3 v2)
{
   return
   (
      Math.Acos
      (
         Normalize(v1).DotProduct(Normalize(v2))
      )
   );
}

public double Angle(Vector3 other)
{
   return Angle(this, other);
}

Max and Min

These methods compare the magnitude of two vectors and return the vector with the largest or smallest magnitude respectively.

public static Vector3 Max(Vector3 v1, Vector3 v2)
{
   if (v1 >= v2){return v1;}
   return v2;
}

public Vector3 Max(Vector3 other)
{
   return Max(this, other);
}

public static Vector3 Min(Vector3 v1, Vector3 v2)
{
   if (v1 <= v2){return v1;}
   return v2;
}

public Vector3 Min(Vector3 other)
{
   return Min(this, other);
}

Rotation

Euler rotation around axis x,y,z is performed using the methods pitch, yaw, and roll respectively.

Screenshot - imageRotate.gif

Eric__ commented that he would expect a different configuration.

"... Roll, pitch and yaw refer back to the concept of an aircraft's motion.
It is standard notation that X is forward (out the nose), Y is out the right wing and Z is down (toward Earth for level flight).
Therefore it follows that roll is positive about +X, Pitch is positive about +Y (pitch-up means climb), and Yaw is positive around +Z (positive yaw is when the aircraft nose moves to the right)."

To illustrate his point consider the following diagram:

Screenshot - real.gif

This would appear to make perfect sense. It does! But only when considering the single aeroplane object. When we consider a virtual scene with multiple objects (for example a computer game or virtual reality environment), all objects must be relevant to the user perceiving them. The standard axis for a virtual scene have the user look down the Z axis.

Screenshot - axisOrient.gif

So taking the aeroplane example, it is quite probable that we will be following the aircraft as it flies into the scene:
Screenshot - view.gif

Hopefully, this explains why the axis are as described and the pitch, yaw, roll configuration is such.

Pitch

This method rotates a vector around the X axis by a given number of degrees (Euler rotation around X).

The hypotenuse (R) cancels out in the equation.

public static Vector3 Pitch(Vector3 v1, double degree)
{
   double x = v1.X;
   double y = ( v1.Y * Math.Cos(degree) ) - ( v1.Z * Math.Sin(degree) );
   double z = ( v1.Y * Math.Sin(degree) ) + ( v1.Z * Math.Cos(degree) );
   return new Vector3(x, y, z);
}

public void Pitch(double degree)
{
   this = Pitch(this, degree);
}

This method directly affects the instance from which the method was called.

Yaw

This method rotates a vector around the Y axis by a given number of degrees (Euler rotation around Y).

The hypotenuse (R) cancels out in the equation.

public static Vector3 Yaw(Vector3 v1, double degree)
{
   double x = ( v1.Z * Math.Sin(degree) ) + ( v1.X * Math.Cos(degree) );
   double y = v1.Y;
   double z = ( v1.Z * Math.Cos(degree) ) - ( v1.X * Math.Sin(degree) );
   return new Vector(x, y, z);
}

public void Yaw(double degree)
{
   this = Yaw(this, degree);
}

This method directly affects the instance from which the method was called.

Roll

This method rotates a vector around the Z axis by a given number of degrees (Euler rotation around Z).

The hypotenuse (R) cancels out in the equation.

public static Vector3 Roll(Vector3 v1, double degree)
{
   double x = ( v1.X * Math.Cos(degree) ) - ( v1.Y * Math.Sin(degree) );
   double y = ( v1.X * Math.Sin(degree) ) + ( v1.Y * Math.Cos(degree) );
   double z = v1.Z;
   return new Vector3(x, y, z);
}

public void Roll(double degree)
{
   this = Roll(this, degree);
}

This method directly affects the instance from which the method was called.

Back-face

This method interprets a vector as a face normal and determines whether the normal represents a back facing plane given a line-of-sight vector. A back facing plane will be invisible in a rendered scene and as such can be except from many scene calculations.

If then if

If then if

public static bool IsBackFace(Vector3 normal, Vector3 lineOfSight)
{
   return normal.DotProduct(lineOfSight) < 0;
}

public bool IsBackFace(Vector3 lineOfSight)
{
   return IsBackFace(this, lineOfSight);
}

Perpendicular

This method checks if two vectors are perpendicular (i.e. if one vector is the normal of the other).

public static bool IsPerpendicular(Vector3 v1, Vector3 v2)
{
  return v1.DotProduct(v2) == 0;
}

public bool IsPerpendicular(Vector3 other)
{
   return IsPerpendicular(this, other);
}

Mixed Product

The code for this method was provided by Michał Bryłka. The method calculates the scalar triple product of three vectors. This is the volume of a parallelepiped geometric shape. More information is available on Wikipedia. This method is non-commutable.

public static double MixedProduct(Vector3 v1, Vector3 v2, Vector3 v3)
{
   return DotProduct(CrossProduct(v1, v2), v3);
}

public double MixedProduct(Vector3 other_v1, Vector3 other_v2)
{
   return DotProduct(CrossProduct(this, other_v1), other_v2);
}

Component Functions

I have provided a number of functions which target the vectors components. These are not mathematically valid for the vector as a whole. For example, there is no concept of raising a vector to a power (that I know of) however the PowComponents method can be used to raise each of x,y,z to a given power.

Sum components

This method simply adds together the vector components (x, y, z).

public static double SumComponents(Vector3 v1)
{
   return (v1.X + v1.Y + v1.Z);
}

public double SumComponents()
{
   return SumComponents(this);
}

To Power

This method multiplies the vectors components to a given power.

public static Vector3 PowComponents(Vector3 v1, double power)
{
   return
   (
      new Vector
      (
         Math.Pow(v1.X, power),
         Math.Pow(v1.Y, power),
         Math.Pow(v1.Z, power)
      )
   );
}

public void PowComponents(double power)
{
   this = PowComponents(this, power);
}

Square root

This method applies the square root function to each of the vectors components.

public static Vector3 SqrtComponents(Vector3 v1)
{
   return
   (
      new Vector3
      (
         Math.Sqrt(v1.X),
         Math.Sqrt(v1.Y),
         Math.Sqrt(v1.Z)
      )
   );
}

public void SqrtComponents()
{
   this = SqrtComponents(this);
}

Square

This method squares to each of the vectors components.

public static Vector3 SqrComponents(Vector3 v1)
{
    return
    (
       new Vector3
       (
           v1.X * v1.X,
           v1.Y * v1.Y,
           v1.Z * v1.Z
       )
     );
}

public void SqrComponents()
{
   this = SqrtComponents(this);
}

Sum of squares

This method finds the sum of each of the vectors components squared.

public static double SumComponentSqrs(Vector3 v1)
{
   Vector3 v2 = SqrComponents(v1);
   return v2.SumComponents();
}

public double SumComponentSqrs()
{
   return SumComponentSqrs(this);
}

Usability functions

For completeness a number of standardised methods have been added complete the type. These implement IComparable, IComparable<Vector3>, IEquatable<Vector3>, IFormattable.

Methods to get a textual description of the type and implement IFormattable.
VerbString provides a verbose textual description. ToString can accept a numeric format string optionally proceeded by a character x, y or z which indicates the relevant vector component to describe.

public string ToVerbString()
{
   string output = null;
   
   if (IsUnitVector()) 
   { 
      output += UNIT_VECTOR; 
   } 
   else 
   { 
      output += POSITIONAL_VECTOR; 
   } 
   
   output += string.Format("( x={0}, y={1}, z={2})", X, Y, Z); 
   output += MAGNITUDE + Magnitude; 
   return output; 
} 

private const string UNIT_VECTOR = 
   "Unit vector composing of "; 

private const string POSITIONAL_VECTOR = 
   "Positional vector composing of "; 

private const string MAGNITUDE = 
   " of magnitude "; 

public string ToString(string format, IFormatProvider formatProvider) 
{ 
   // If no format is passed 
   if (format == null || format == "") 
      return String.Format("({0}, {1}, {2})", X, Y, Z); 

   char firstChar = format[0]; 
   string remainder = null; 

   if (format.Length > 1) 
      remainder = format.Substring(1);

   switch (firstChar) 
   { 
      case 'x': 
         return X.ToString(remainder, formatProvider); 
      case 'y': 
         return Y.ToString(remainder, formatProvider); 
      case 'z': 
         return Z.ToString(remainder, formatProvider); 
      default: 
         return 
            String.Format
            (
               "({0}, {1}, {2})", 
               X.ToString(format, formatProvider), 
               Y.ToString(format, formatProvider), 
               Z.ToString(format, formatProvider) 
            ); 
   } 
}

public override string ToString() 
{ 
   return ToString(null, null); 
}

To produce a hash code for system use (required in order to implement comparator operations (i.e. ==, !=)):

public override int GetHashCode()
{
   return
   (
      (int)((X + Y + Z) % Int32.MaxValue)
   );
}

Check for equality (standardised version of == operator) and implement IEquatable<Vector3>:

public override bool Equals(object other)
{
   // Check object other is a Vector3 object
   if(other is Vector3)
   {
     // Convert object to Vector3
     Vector3 otherVector = (Vector3)other;

     // Check for equality
     return otherVector == this;
   }
   else
   {
     return false;
   }
 }

public bool Equals(Vector3 other)
{
   return other == this;
}

Comparison method for two vectors which returns:

  • -1 if the magnitude is less than the others magnitude
  • 0 if the magnitude equals the magnitude of the other
  • 1 if the magnitude is greater than the magnitude of the other

This allows the Vector type to implement the IComparable and IComparable<Vector3> interfaces.

Public int CompareTo(object other)
{
   if(other is Vector3)
   {
      Vector3 otherVector = (Vector3)other;

      if( this < otherVector ) { return -1; }
      else if( this > otherVector ) { return 1; }

      return 0;
   }
   else
   {
      // Error condition: other is not a Vector object
      throw new ArgumentException
      (
         // Error message includes information about the actual type of the 
         // argument
         NON_VECTOR_COMPARISON + "\n" + ARGUMENT_TYPE + 
             other.GetType().ToString(),
         "other"
      );
   }
}

public int CompareTo(Vector3 other)
{
   if (this < other)
   {
      return -1;
   }
   else if (this > other)
   {
      return 1;
   }

   return 0;
}

private const string NON_VECTOR_COMPARISON = 
"Cannot compare a Vector to a non-Vector";

private const string ARGUMENT_TYPE = 
"The argument provided is a type of ";

Standard Cartesian vectors and constants

Finally four standard vector constants are defined:

public static readonly Vector3 origin = new Vector3(0,0,0);
public static readonly Vector3 xAxis = new Vector3(1,0,0);
public static readonly Vector3 yAxis = new Vector3(0,1,0);
public static readonly Vector3 zAxis = new Vector3(0,0,1);

And miscellaneous read-only values:

public static readonly Vector3 MinValue = 
   new Vector3(Double.MinValue, Double.MinValue, Double.MinValue);

public static readonly Vector3 MaxValue = 
   new Vector3(Double.MaxValue, Double.MaxValue, Double.MaxValue);

public static readonly Vector3 Epsilon = 
   new Vector3(Double.Epsilon, Double.Epsilon, Double.Epsilon);

Serialization

Vector3 implements the [Serializable] attribute and can therefore be written to file. I suggest the following:

static void Main(string[] args)
{
   Vector3 vect = new Vector3(1, 2, 3);
   XmlSerializer x = new XmlSerializer(vect.GetType());
   x.Serialize
   (
      new System.IO.FileStream("test.xml", System.IO.FileMode.Create), 
      vect
   );
}

Which produces an XML file containing:

<?xml version="1.0"?>
<Vector
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:xsd="http://www.w3.org/2001/XMLSchema">
   <X>1</X>
   <Y>2</Y>
   <Z>3</Z>
   <Magnitude>3.7416573867739413</Magnitude>
</Vector>

Summary

We now have a Vector3 type with the following functionality:

Screenshot - ClassDiagram.png
    Constructors
  • Vector3(double x, double y, double z)
  • Vector3(double[] xyz)
  • Vector3(Vector v1)
    Properties
  • X
  • Y
  • Z
  • Magnitude
    Operators
  • this[] Indexer
  • +
  • -
  • ==
  • !=
  • *
  • /
  • <
  • >
  • <=
  • >=
    Static methods
  • CrossProduct
  • DotProduct
  • MixedProduct
  • Normalize
  • IsUnitVector
  • Interpolate
  • Distance
  • Abs
  • Angle
  • Max
  • Min
  • Yaw
  • Pitch
  • Roll
  • IsBackFace
  • IsPerpendicular
  • SumComponents
  • SumComponentSqrs
  • SqrComponents
  • SqrtComponents
  • PowComponents
    Instance methods which directly affect the instance variables
  • Normalize
  • Yaw
  • Pitch
  • Roll
  • SqrComponents
  • SqrtComponents
  • PowComponents
    Instance methods which return a new object or type
  • CrossProduct
  • DotProduct
  • MixedProduct
  • IsUnitVector
  • Interpolate
  • Distance
  • Abs
  • Angle
  • Max
  • Min
  • IsBackFace
  • IsPerpendicular
  • SumComponents
  • SumComponentSqrs
  • CompareTo
  • Equals
  • ToString
  • GetHashCode
    Readonly and constant values
  • MaxValue
  • MinValue
  • Epsilon
  • origin
  • xAxis
  • yAxis
  • zAxis

Points of Interest

There were a number of resources I used during the development of this article and source code provided, I would like to acknowledge the following:

  • CSOpenGL Project - Lucas Viñas Livschitz
  • Exocortex Project - Ben Houston
  • Essential Mathematics for Computer Graphics - John Vince (ISBN 1-85233-380-4)

History

To-Do:

  • Method to reflect a Vector3 about a given normal
  • A graphical testing\demonstration application
Changes: (v1.00-v1.20)
  • Magnitude methods are now encapsulated in a propertiy
  • Incorrect serialization attributes have been removed
  • Equality and IsUnitVector methods allow a tolerance
  • Abs method now returns magnitude
  • Generic IEquatable and IComparable interfaces have been implemented
  • IFormattable interface has been implemented
  • Mixed Product function implemented
  • Added and renamed additional component based functions (e.g. SumComponentSquares)
  • Vector renamed to Vector3

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here

About the Author

R Potter
Engineer Deep Sea Electronics Plc.
United Kingdom United Kingdom
Member
Currently working as a Software Engineer at Deep Sea Electronics Plc. on Power Control Systems. I work primarily with C# and .Net though often have to dip into other languages.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
QuestionIt's looks perfect, but it's work terrible!memberthe_brans31 Jul '12 - 21:56 
I do not advise anyone to use this beautifully designed, but full of errors the code in real-world projects. I have tried - and lost a lot of time wasted.
1) This is mutable struct. N-E-V-E-R use mutable structures. In multi-threaded applications, they give a terrible mistake. And this(Vector3 structure) also gives errors . I was convinced in this.
2) Angel method incorrect. Please test - v1.Angle(v1) - it gives NaN...
 
Here everything has to be redone.
Questionvery thanksmemberBigOrkPapa17 Jul '12 - 5:16 
Smile | :)
GeneralMy vote of 4.memberSabrian28 Apr '12 - 12:53 
Vector a = new Vector(1,1,1);
Vector b = new Vector(-1,-1,-1);
 
bool b1 = a == b; //b1 == false
bool b2 = a < b;  //b2 == false
bool b3 = a > b;  //b3 == false
bool b4 = a <= b; //b4 == true;
bool b5 = a >= b; //b5 == true;
Meaningless and dangerous. Same problem with Equals + CompareTo
QuestionClarity (roll, pitch and yaw, and degrees)memberClive D. Pottinger20 Nov '11 - 8:39 
First - thanks for the library. It will prove very useful in my current project.
 
I can see your point about your definitions for "roll", "pitch" and "yaw"... but I also see the point made by Eric__ (and I tend to agree with him more - when you use those terms you are talking aboout a subjective view of the axes of rotation, as in his aeroplane example).
 
For this reason, the very first thing I did when I downloaded your code was change the method names from Roll(), Pitch() and Yaw() to RotateOnZ(), RotateOnX and RotateOnY() respectively. That removes all ambiguity and makes the methods usable regardless of your personal view on what "roll", "pitch" and "yaw" mean.
 
The second thing I did was change your parameters from degree to angle. Again that removes the ambiguity about whether the routines expect radians, degrees, mills, or grads.
Clive Pottinger
Victoria, BC

Questiondegrees or radiansmemberutkugenc7 Jul '11 - 4:29 
Both the article and the code comments say that the roll, pitch and yaw methods rotate about the axis in degrees but i guess all of these methods expect angle parameters in radians.
AnswerRe: degrees or radiansmemberMember 80606427 Jul '11 - 6:57 
Same comment. No big for someone that has figured it out, but it would save some small confusion for others.
 
It is definitely a very well written class, and I think that is why people may assume it doesnt have obvious bugs like this one.
GeneralMy vote of 5memberxcho209 Jun '11 - 4:24 
Excellent article! Very detailed and understandable.
GeneralRe: My vote of 5memberMember 841601919 Nov '11 - 4:52 
Ditto, thank you so much for sharing this
GeneralMy vote of 5memberggraham41225 Oct '10 - 2:41 
Very comprehensive
GeneralTypo in SqrComponents()memberga5p0d321 Oct '10 - 1:24 
Instance square is calling static square-root.
 
public void SqrComponents()
{
this = SqrtComponents(this);
}
 
Nice class though. Smile | :)
GeneralRe: Typo in SqrComponents()memberRob Achmann26 Jan '11 - 5:54 
Read more.
 
One function is "The Vector3's components squared".
The other is "The individual square root of a Vector3's components"
 
Your 'typo' is incorrect.
GaltSalt
maker of .Net thingys

GeneralWatch Live streaming of fifa Worldcup 2010memberned_cis28 Jun '10 - 6:10 
http://www.usmanweb.com/football-fifa-world-cup-2010-south-africa-live-streaming.html[^]
GeneralWind Force and its effect on Yaw,Pitch,Rollmemberasif rehman baber7 Feb '10 - 22:51 
It was a great article regarding Aerodynamics...
 
I want to ask 2 question...
 
i want to calculate that...
 
1)when an aircraft is flying and air strikes on it from aynwhere then how i could be able to calculate the angle of Yaw,Pitch,Roll...Does this wind cause the angle of yaw ,pitch and roll to change...?
 
2)In ur Vector3.cs class,Function "Yaw" is taking an angle as an input...this surely be the angle after the wind strikes the aircraft?
 
Blow is the code that i found relevant...but it is without wind effect (without Code too Cry )...
 
http://www.euclideanspace.com/maths/geometry/rotations/euler/program/index.htm
 
i would be glad if u can provide some code...
GeneralWind Effect on Yaw,Pitch,Rollmemberasif rehman baber7 Feb '10 - 22:50 
It was a great article regarding Aerodynamics...
 
I want to ask 2 question...
 
i want to calculate that...
 
1)when an aircraft is flying and air strikes on it from aynwhere then how i could be able to calculate the angle of Yaw,Pitch,Roll...Does this wind cause the angle of yaw ,pitch and roll to change...?
 
2)In ur Vector3.cs class,Function "Yaw" is taking an angle as an input...this surely be the angle after the wind strikes the aircraft?
 
Blow is the code that i found relevant...but it is without wind effect (without Code too Cry )...
 
http://www.euclideanspace.com/maths/geometry/rotations/euler/program/index.htm
 
i would be glad if u can provide some code...
GeneralAngle calculation can sometimes failmemberBret Mulvey21 Oct '09 - 9:55 
The use of Math.Acos in the Angle method can fail when the two vectors are parallel. Due to floating-point rounding, after doing the normalization and dot-product you can end up with a result slightly greater than 1.0, and Math.Acos will return double.NaN for that. It would be better to return 0.0 in that case.
GeneralVector projection [modified]memberPoggel, Steven Buraje10 Jul '09 - 9:38 
Here's an additional method for projecting one vector onto the other.
 
/// <summary>
/// Projects the specified v1 onto the specified v2
/// </summary>
/// <param name="v1">The vector that will be projected.</param>
/// <param name="v2">The vector that will be projected upon.</param>
/// <returns></returns>
public static Vector3 Projection(Vector3 v1, Vector3 v2)
{
    // http://de.wikibooks.org/wiki/Ing_Mathematik:_Vektoren#Vektorprojektion
    // http://mathworld.wolfram.com/Reflection.html
    // V1_projectedOn_V2 = v2 * (v1 * v2 / (|v2| ^ 2))
 
    return new Vector3(v2 * ( v1.DotProduct(v2) / Math.Pow(v2.Magnitude, 2) ) );
}
 
/// <summary>
/// Projects this vector onto the specified v2
/// </summary>
/// <param name="v2">The vector that will be projected upon.</param>
/// <returns></returns>
public Vector3 Projection(Vector3 v2)
{
    return Vector3.Projection(this, v2);
}
 

 
Btw, the world is a great place, actually. Must see it!

GeneralVector reflectionmemberPoggel, Steven Buraje10 Jul '09 - 15:36 
And here's the reflection method that was still on the TODO list for this project.
 

///
/// Reflect a Vector3 about a given other vector
///

/// The Vector3 to reflect about
///
/// The reflected Vector3
///

public Vector3 Reflection(Vector3 reflector)
{
this = Vector3.Reflection(this, reflector);
return this;
}
 
///
/// Reflect a Vector3 about a given other vector
///

/// The Vector3 to reflect about
///
/// The reflected Vector3
///

public static Vector3 Reflection(Vector3 vector, Vector3 reflector)
{
// if reflector has a right angle to vector, return -vector and don't do all
// the other calculations
if (Math.Abs(Math.Abs(vector.Angle(reflector)) - Math.PI / 2) < Double.Epsilon)
{
return -vector;
}
else
{
Vector3 retval = new Vector3(2 * vector.Projection(reflector) - vector);
retval.Magnitude = vector.Magnitude;
return retval;
}
}

 
Thanks for the class, it was quite helpful for me even though I needed a 2D-Vector class,
but I was able to do it with little modifications.
Notice that both my projection and reflection methods are independent of arity of the vector
and can both be used for the 2D or 3D version.
 
Btw, the world is a great place, actually. Must see it!

GeneralThanksmembernahuelgq1 Dec '08 - 13:58 
Just what I was looking for. Thankss
Generalbrilliant and I hope to see more from you ...memberBillWoodruff30 Jul '07 - 9:40 
This has got to be one of the finest tutorials on CP ! And so "fully fleshed" Smile | :)
 
Wish I were young enough to enroll at the U of Hull and sit in your class.
 
Hope you will continue evolving this, and that in the future we will have more from you.
 
regards, Bill
 
"The greater the social and cultural distances between people, the more magical the light that can spring from their contact." Milan Kundera in Testaments Trahis

GeneralMessage to Microsoft!memberwoudwijk29 Jul '07 - 21:31 
When are you ( microsoft ) going to put something like this in the framework ?
And when youre at it, for this/these type(s) also add sse/sse2/sse3/sse4/ 3d-now / ..... suport for them.
Also redesign the System.Color to make use of this to.
 
This would make the framework a whole lot more usefuller for sound/graph processing.
 

 
My second computer is your linux box.

GeneralVery NicememberPaul Conrad25 Jul '07 - 17:40 

Article is very nicely done. Great job Big Grin | :-D
 

"Any sort of work in VB6 is bound to provide several WTF moments." - Christian Graus

GeneralGood article........ I too disagree with epsilonmemberblackstorm25 Jul '07 - 1:37 
Hi,
 
Very nice article you had posted here. I had rated it 5. I too share your views about using properties inside the constructor code, and about fat interfaces - "as programmer friendly as possible".
 
But I think that vector arithmetic should be as perfect as possible, like any other numeric calculations; if you ever use this class to position objects in a 3-D environment - say, a game engine - this would become extremely important. Quoting Red Baron, "... you should not implement a tolerance value inside your code. What is a suitable value for this tolerance?"
 
Cheers!A
 
Ajith Kumar
GeneralRe: Good article........ I too disagree with epsilonmemberthe_brans3 Aug '12 - 5:53 
sometimes you need Epsilan as when you got vectors from sensors or you calc in multithreading
GeneralMore suggestionsmembertaumuon24 Apr '07 - 2:32 
Where you say:
"A drawback of developing a struct is that collection classes in the .Net framework cast structs as classes. This means that a large collection of Vector3's will have a high casting overhead." This isn't true for generic collection classes (hence their introduction).
 
Maybe discuss usage differences between structs and classes in your article. For example, if you have a method that exposes a Vector:
 
Quaternion.Vector Axis
{
get;
}
 
and a user modifies that value:
 
myQuaternion.Axis.X = 1.0;
 
then for a class this modifies the quaternion, but for a struct this modifies the temporary struct copy returned from the Axis method, and the changes will be discarded.
 
Beginners may trip up on this, so it might be worth mentioning in the article. You may even consider making your Vector immutable for this reason.
 
Near the beginning when you show how your axes are arranged, state that it is a left hand coordinate system.
 
ORAGIN_VECTOR_MAGNITUDE should be ORIGIN_VECTOR_MAGNITUDE
 
As I said in my earlier comment:
Greater than or equal to in the article text is:
public static bool operator>(Vector3 v1, Vector3 v2)
and should be:
public static bool operator>=(Vector3 v1, Vector3 v2)
 
You are using a struct for performance - as such, you should have overloads methods allowing structs to be passed in by ref (to avoid unnecessary copying) e.g. also have
public static Vector3 CrossProduct(ref Vector3 v1, ref Vector3 v2)
that the user can choose to call if they care about performance (and similarly, provide methods taking ref parameters as an alternative to the overridden operators).
 
Where you quote
"... you should not implement a tolerance value inside your code.
What is a suitable value for this tolerance?
It is depending on the problem ..."
 
You should still compare for equality within a tolerance, but provide a mechanism for the client of the class to specify a tolerance.
 
Your Equals(object Other) should call Equals(Vector3 other) & similarly CompareTo(object other) should call CompareTo(Vector3 other)
 
http://www.taumuon.co.uk/jabuka/
taumuon.blogspot.com

QuestionDegrees or Radians?memberkangaloosh18 Apr '07 - 9:37 
Hi,
 
Excellent Vector3 class, thank you.
 
I just want to clarify the .Angle and yaw / pitch / rotate methods - the parameters are all called 'degree', but I can find no conversion to radians. You are using Math.Sin, .Cos etc. so radians are implied.
 
Could you confirm which they should be please,
 
Thanks again
 
kangaloosh.
AnswerRe: Degrees or Radians?memberR Potter29 May '07 - 7:06 
Your absolutely correct they are radians. I'm making the corrections now.
Sorry about the delayed response, I have been busy on another project.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralSmall Corrections in ArticlesmemberNavajoKnight10 Apr '07 - 8:18 
I really like this code and it came across better then I was implementing it. I only have a minor comment about the article which is that Magnitude() should be Magnitude since it is a property now not a function.
 
Carry on with the great work.
AnswerRe: Small Corrections in ArticlesmemberR Potter15 Apr '07 - 22:04 
Thank you. Corrections made, I hope I got all of them.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

Generallook at DirectX librarymemberDesinderlase3 Mar '07 - 12:06 
Have u looked at Direct X library classes? They arleady have all u need, Vector2, Vector3, Vector4,... (additionaly Matrix, Quaternions....)
 
Maybe u should think on making variation of Vector with float type members...
 
(anyway good job )
GeneralRe: look at DirectX librarymemberKuryn6 Aug '08 - 17:26 
Maybe because the authour wants to for certain reasons instead of having dependency issues. And sometimes its very helpful. I have to write my own for OpenGL. Beecause it gives me better control over OpenGL's and added functionality. And DirectX math calls are ugly. Have to pass through a parameter because COM "demands" a HRESULT function result. COM made DirectX library ugly. And Classes are better than normal functions.
General[Serializable] attributemembervanad.min26 Feb '07 - 23:48 
Did you know that Serializable attribute is only needed for binary serialization, not for XML serialization?
GeneralRe: [Serializable] attributememberR Potter27 Feb '07 - 23:01 

vanad.min wrote:
Did you know that Serializable attribute is only needed for binary serialization, not for XML serialization?

 

No, I had no idea.
Thank you.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralSome thoughtsmemberMalicious Demon16 Feb '07 - 22:53 
I'm going to pick a few faults here with the goal of having a better vector class.
 
1. Nomenclature. Surely this should be named "Vector3" and leave "Vector" for the general case? After all we might want a Vector2 or Vector4 too!
 
2. Precision. You're using doubles and that's fine. However you are making direct comparisons of elements in the == operator and that's not so good. The test for element comparison should be whether each component is within some small epsilon of the other: |x1-x2| < e. Similarly "IsUnitVector" should be testing for ||v|-1| < e.
 
2a It may be smart to genericise the vector so that client code can use an appropriate representation of elements. Eg if they wanted to use your code with an application that used floats elsewhere there would be a hit for lots of type conversions.
 
3. Fat interface. There's too much in there. For example the "IsBackFace" method is related to a specific use of a vector and should therefore really be delegated to a component that deals with ray casting. Ditto the roll/pitch/yaw stuff. A class (IMHO) should contain just the methods required to implement its functionality and no more. If you can do an operation solely with access to public methods (assuming good encapsulation) then the operation may well merit placement elsewhere. Also, it's not really necessary to provide static and non-static variants of methods, that's just bloating an otherwise clean interface.
 
4. Expensive arithmetic. Since vectors are typically used in code performing many complex calculations, over-use of "new Vector()" is really going to hit hard. Whilst operator overloading is syntactically sweet, in C# it hides the allocation issues (it's not the same in C++ where the issue is the copying of temporaries). It's far better to provide a set of in-place arithmetic operations, eg
 

public void Add( Vector that )
{
this.x += that.x;
this.y += that.y;
this.z += that.z;
}

 
This means that you generally have to allocate your vectors in advance (including several workspace variables for intermediate results). But hey, this is the hallmark of performant code - a space versus time trade-off.
 
5. Mathematical oddities. In Interpolate, there's nothing mathematically wrong with t being > 1 or < 0. Technically you could call values outside this range an "extrapolation", but that would be pushing it.
 
There is no mathematical interpretation of "raising a vector to a power". And if there were, it certainly wouldn't be (x^n,y^n,z^n). Again, it seems to me that these sorts of methods just make your class a "kitchen sink" rather than a clean implementation.
 
---
 
As you pointed out, writing a good vector class is important and tricky. Perhaps even trickier than you imagined! Big Grin | :-D
 
"If our brains were so simple that we could understand them, then we wouldn't be able to"

GeneralRe: Some thoughtsmemberR Potter18 Feb '07 - 23:12 
Thanks for the in depth review of my article, I appreciate the comments. I am actually already fixing many of them Smile | :)
You mention fat interfaces; I actually like them like this. I am aware that others do not but I love to cram as much relevant functionality into an object\type as possible. Obviously though, as my projects grow I tend to refactor functionality back out as I create objects\types for which the functions better fit. In my eyes this achieves maximum cohesion and minimises coupling\dependencies.
A very good point though.

 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralRe: Some thoughtsmemberR Potter18 Feb '07 - 23:55 
Another reason why I love fat interfaces. I try to make any code I write as programmer friendly as possible. Ease of use is my priority not efficiency. If it were I would write C\C++ code.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralRoll Pitch YawmemberEric__13 Feb '07 - 21:28 
From my aerospace engineering background, I have always referred to roll-pitch-yaw as the rotations about the X-Y-Z axes respectively. Roll, pitch and yaw refer back to the concept of an aircraft's motion. It is standard notation that X is forward (out the nose), Y is out the right wing and Z is down (toward Earth for level flight). Therefore it follows that roll is positive about +X, Pitch is positive about +Y (pitch-up means climb), and Yaw is positive around +Z (positive yaw is when the aircraft nose moves to the right).
 
I have never seen the version of roll-pitch-yaw that you mention.
 
Thanks for the article,
Eric
AnswerRe: Roll Pitch YawmemberR Potter13 Feb '07 - 22:01 
Thanks for the comment. I agree that when looking at the aircraft you would expect the co-ordinate system / axis to be as you described. However, when examining a scene such as in the scientific visualization domain (my field) you would expect to be looking down the Z axis (into the distance). Thus a roll would be about the z axis not x. This is very much the standard format for virtual environments / virtual scenes. You had me worried Smile | :) I wonder if there is cause to implement some way of changing the axis system ... I'll have a think about that sometime.
 
Thanks again.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralVery timley articlememberMCofer10 Feb '07 - 4:26 
I jusr started converting a old c++ CAD addin app. to C# NET2.0
Re-writing the vector library is my first task
You article just saved many hrs.
 
Thanks

 
Mcofer
Application Developer
Halliburton Energy Services

GeneralRe: Very timley articlememberR Potter13 Feb '07 - 22:02 
I do my best Smile | :)
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

Generalmeaning of the Abs()-FunctionmemberBock7 Feb '07 - 10:03 
For what do you use your Abs()-Funktion?
In school-mathematics the "Abs()"-Function is defined
for scalars and for vectors. And in mathematics
the "Abs()"-Function for vectors has the meaning
of your Magnitude-Function. Why the Name Mangitude
for Abs?
 
By the way:
I have made similar classes/structures
in C++ (classes) and in C#(structures).
To my surprise:
C# is faster! I tested it with operator-overloading.
 
Good work!
/Bernhard
GeneralRe: meaning of the Abs()-FunctionmemberPolymorpher8 Feb '07 - 17:40 
generally in programing the Abs() function is for getting the absolute value
 
Apparently it's not OK to start a bonfire of Microsoft products in the aisles of CompUSA even though the Linuxrulz web site says so

GeneralRe: meaning of the Abs()-FunctionmemberR Potter13 Feb '07 - 22:12 
Thank you.
 
Argh. You are completely correct.
 
The magnitude and absolute value of vectors should be the same. I had assumed that the ABS is simply an 'unsigned' vector, I was wrong! Frown | :( For a vector the ABS is the magnitude (not the same as the unsigned version of a vector).
 
Oops, I'll fix this ASAP. I think I shall just remove the ABS method in favour of the magnitued, though I could just call magnitude from ABS.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralBetter "Greater-than"memberhain6 Feb '07 - 5:13 
In comparing vectors (for example your member "Greater-than"), it is more efficient to compare the sum of the squares of the components (i.e., the square of the magnitude). This removes the need for two square root evaluations, and may improve precision slightly.
 
Tom
Generalexcellent article.memberRussell Jones6 Feb '07 - 4:32 
I wrote a very small vector library sometime back and it took ages to get everything working even for the limited amount I did. I wish i'd had this available at the time. I gave up development eventually as i was coding for DirectX and realised that DX contains a fairly extensive vector library. For projects not using DirectX i'll be looking this up in the future.
 
Thanks for your article
 
Russell
GeneralNice implementationmemberfkoestner6 Feb '07 - 3:44 
Enjoyed your article. Vectors have always been a kind of black box to me, this helped me see the relationships between all the different operations.
 
One comment - I was confused by the four different Magnitude methods - 2 of them change the existing magnitude (one of which returns a new instance of Vector), 2 of them return the current magnitude. Why not make Magnitude a property? The get{} would return the current value, the set{} would change the current value. This seems like a perfect place to use that paradigm, and the two different operations are self-documenting that way.Cool | :cool:
 
Thanks again,
 

-Fred Smile | :)
GeneralSome minor glitchesmemberMichal Brylka2 Feb '07 - 17:59 
First of all - it's true that all structure fields have to initialized by constructor. However, properties are not fields. You don't have to initialize them.
Secondly, class has access to all of its instances private fields. So it is better to use this constructor:
public Vector(Vector v1)
{
x = v1.x; //x is private field
y = v1.y;
z = v1.z;
}
 

And finally: only public properties can be serialized.
Besides that, grate code.
 
Michał Bryłka

GeneralRe: Some minor glitchesmemberR Potter5 Feb '07 - 6:31 
I'm not sure where I referred to properties as fields, is it in the source code?
You are right of course they are not the same thing at all. It's the fields that are being initialised and the properties that are being called to assign values. I have updated the article to explain why I choose to use the properties from the constructor. At the end of this post I have included the updated text for quick reference.
 
To Michal Brylka, I have referred to your comment directly. If you are uncomfortable with your name\code-project handle being attached to my article in this way, for any reason, please let me know and I will remove the reference immediately.
 
As for serialization, I never tested that ... oops. I hadn’t spotted that I was trying to serialize the private variables and not the properties. I'll fix and test this soon.
 

ADDED SECTION TO ARTICLE
______________________________________________
You may be wondering why I have set x, y, and z to 0 before assigning values. A struct will not allow properties (uppercase X, Y, Z) to be accessed before each of the variables (lowercase x, y, z) have been initialized. As Michal Brylka commented, it is unusual to use properties from the constructor when assigning values. As a matter of personal taste, I always try to use properties in this way. It ensures that any validation code written into the set method is picked up by the constructor. I find that this is helpful when maintaining code. In the Vector type as it stands there is no validation in the properties so the issue is academic. Either paradigm would be valid.
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralRe: Some minor glitchesmemberMichal Brylka5 Feb '07 - 6:55 
First of all, Codeproject is a place where people can share their ideas and argue a little bit about their point of view. So please be welcome to refer to my nick any time you want.
As for the matter of fields/properties, you've made your point and I appreciate it.
Finally, object's public read/write properties are serialized by default. The attributes you've used are designed to change the name under which variable is stored. So just throw them out and everything will be ok.
 
Regards
Michał Bryłka

GeneralRe: Some minor glitchesmemberR Potter13 Feb '07 - 23:35 
Hello again,
I have just been working on the code and when I cheked the serialization XMLAttribute attributes they work fine. You can serialize private fields as so:
 
[XmlAttribute("X")]
private double x;

 
serializes to <X>1</X>
 
Richard Potter
________________________
Simulation and Visualization Research Group
University of Hull
r.potter@dcs.hull.ac.uk
_________________________
Where there is a Will there is a way, but I'm not Will!

GeneralRe: Some minor glitchesmemberMichal Brylka14 Feb '07 - 12:08 
This is just because you have public property named "X". In your case serializator just skips over private fields and uses default settings (public read/write properties mapped as xml elements, not attributes). Consider the following code:
public static class Program
    {
        [STAThread]
        static void Main()
        {
            Vector v = new Vector(15);
            XmlSerializer x = new XmlSerializer(typeof(Vector));
            using (MemoryStream ms = new MemoryStream())
            {
                x.Serialize(ms, v);
                MessageBox.Show(new string(Array.ConvertAll<byte, char>(ms.ToArray(), delegate(byte b)
                                                                              {
                                                                                  return (char)b;
                                                                              }) ));
            }
        }
    }
 
    [Serializable]
    public class Vector
    {
        [XmlAttribute("Small_x")]
        private double x;
 
        //[XmlAttribute("Not_x")]
        public double X2
        {
            get { return x; }
            set { x = value; }
        }
        
        public Vector(double x)
        {
            X2 = x;
        }
 
        public Vector(){}
    }
Now uncomment line with [XmlAttribute("Not_x")]
 
Michał Bryłka

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130516.1 | Last Updated 24 Jul 2007
Article Copyright 2007 by R Potter
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid