Most efficient way of designing a vector class in 3D

Started by
19 comments, last by MarkS_ 8 years, 7 months ago

Recently I started to use unity3d for my hobby game project and I really liked a Vector3 (and similar) classes (in C#). At the moment at work I am implementing a simple (but for large simulations) SPH solver. What I would like to achieve is a similar Vector3 class in C++ in the means of access to elements both by v.x, v.y, v.z and v[0], v[1], v[2]. In general to obtain this is very simple, but none of the solutions that came to my mind is free of flaws.

Solution 1: using references &x, &y, &z, problem: such class has three additional variables which occupy the memory. In the case of large simulation it is problematic:


template <class T>
  class Vector3
  {
    public:
        T &x,&y,&z;
        T v[3];

        Vector3(): x(v[0]), y(v[1]), z(v[2])
        {
          v[0]=0; v[1]=1;  v[2]=2;
        }

        T& operator[](int i)
        {
            return v[i];
        }
  };

The access is very elegant:


v[i] 

as well as


v.x, v.y and v.z

And this is what I woluld like to obtain - this elegant access. However the additional memory overhead is unacceptable.

Solution 2: using class fileds x,y,z and access operator with if statement: problem performance of [] operator


template <class T>
  class Vector3
  {
    public:
        T x,y,z;

        Vector3(): x(0), y(1), z(2)
        { }

        T& operator[](int i) 
        {
            if (i==0) return x;
            else if (i==1) return y;
            else if (i==2) reurn z;
            else 
            {
                //throw access error
            }
        }
  };

This solution is elegant as well, but the operator [] will be very slow.

Solution 3: Using the class functions x(), y(), z()


template <class T>
  class Vector3
  {
    public:
        T v[3];

        Vector3()
        {
          v[0]=0; v[1]=1; v[2]=2;
        }

        T& operator[](int i)
        {
            return v[i];
        }

       T& x() { return v[0]; }
       T& y() { return v[1]; }
       T& z() { return v[2]; }
  };

This solution is ideal in means of efficiency and memory overhead, but, does not allow elegant access to members, requires for example v.x() instead of v.x.

The question is: is there a way to obtain this elegant access with no efficiency and memory loss?

Advertisement

Not technically portable, but works in practice:


template <class T>
  struct Vector3
  {
        T x,y,z;
        T& operator[](int i)
        {
            return (&x)[i];
        }
  };

BTW, an efficient vector class will use SSE instructions -- i.e. xyz would be stored in a __m128. However, extracting members from a __m128 and into a regular float is slow on x86... so your getX() function should return an __m128 with the x member in all 4 elements. I've often seen this return type called something like class FloatInVec4 laugh.png

Solution 4


union 
{
    float v[3];
    struct { float x, float y, float z; };
}

Yeah union with anonymous struct has got to be the easiest way to do it,

Just to expand on imoogiBG's answer to follow suit with templates.


template <class T>
struct Vector3
{
    union
    {
        T v[3];
	struct { T x; T y; T z; };
    };
};

Couldn't you just use the array access? Will you later want to add r, g, b, a and s, t or u, v?


namespace vectors {

enum accessors {
  x,
  y,
  z,
  w
};

template <typename T>
struct Vector4
{
  T d[4];
  T& operator[](size_t index);
  const T& operator[](size_t index) const;
};

}

How does that code allow me to write "v.x"?

"I would try to find halo source code by bungie best fps engine ever created, u see why call of duty loses speed due to its detail." -- GettingNifty
There is a solution that is completely portable and last time I checked was fully optimized by the compiler. It was posted by Washu in these forums many years ago:

struct vector3 {
	float x, y, z;

	float& operator[](int index) {
		assert(index >= 0 && index < 3);
		return this->*members[index];
	}

	float operator[](int index) const {
		assert(index >= 0 && index < 3);
		return this->*members[index];
	}

	static float vector3::* const members[3];
};

float vector3::* const vector3::members[3] = { &vector3::x, &vector3::y, &vector3::z};

Sorry I forgot to include the operator to be able access like v[0].


template <class T>
struct Vector3
{
    union
    {
	T v[3];
	struct { T x;  T y; T z; };
    };

    T& operator[](int i)
    {
	// guard against accessing out-of-bounds
        return v[i];
    }
};

Vector3<float> v;
v.x = 3.0f;

std::cout << v[0]; // prints 3

How does that code allow me to write "v.x"?

It does not really need to and avoids weird workarounds/UB for a questionable convenience.

But you can easily do:


using namespace vectors;

float bar(const Vector4<float>& v) {
  return v[x];
}

Re-iterating what Hodgman wrote, these arrays of floats are functional but not ideal.

If you are looking for performance most systems will use built-in SIMD structures for the data that are specific to the system you are developing for. That can mean the intrinsic type __m128 variables in the x86 family, or the intrinsic float32x4_t variables in ARM chips.

Transferring to and from these packed, special-purpose registers is not efficient. If possible leave your data packed in the more efficient formats.

This topic is closed to new replies.

Advertisement