I am guessing there is some trace of "proper" game programming mentality in me after all. :) Going against my previous rant, I've designed "yet another" vector/matrix math library and I've submitted it for a preliminary Boost review.
Why do I think that writing this library was a good idea? Because it makes no sense for any of us to have to spell out how a 3x3 matrix is multiplied by another 3x3 matrix; there should be a way to express that algorithm generically and apply it to any and all 3x3 matrix types in the world.
The only tricky part is that operations such as matrix multiplication should use operator overloads (seriously, it's retarded not to) and that presents a challenge: how do you define type-safe operator overloads without using specific matrix types? That's basically what (Boost) LA pulls off, using SFINAE.
The scoop
A user-defined vector type float3 can be introduced to (Boost) LA like this:
#include <boost/la/vector_traits.hpp> //Note: this library is NOT part of Boost
struct float3 { float a[3]; };
namespace
boost
{
namespace
la
{
template <>
struct
vector_traits<float3>
{
static int const dim=3;
typedef float scalar_type;
template <int I> static inline scalar_type & w( float3 & v )
{ return v.a[I]; }
template <int I> static inline scalar_type r( float3 const & v )
{ return v.a[I]; }
static inline scalar_type & iw( int i, float3 & v )
{ return v.a[i]; }
static inline scalar_type ir( int i, float3 const & v )
{ return v.a[i]; }
};
}
}
After a similar specialization of the matrix_traits template for a user-defined 3x3 matrix type float33, a full range of vector and matrix operations defined in (Boost) LA headers become available automatically:
float3 v;
v|X = 0;
v|Y = 0;
v|Z = 7;
float vmag = magnitude(v);
float33 m = rotx_matrix<3>(3.14159f);
float3 vrot = m * v;
The full documentation and source code, released under the Boost Software License, is here.
Arseny Kapoulkine — 20 October 2009, 22:16
This suits boost very much, but is anything but proper. IMHO.
gemicha — 20 October 2009, 23:02
v|X = 0; .... You gotta be kidding me
Emil — 21 October 2009, 01:38
I had posted a somewhat sarcastic reply to gemicha, but I realize that his comment was not unreasonable with only this example in mind.
The expression v|X should be read as "v pipe X". It is logical part of a more general system of view proxies in Boost LA. For example, you could say m|col<2>|YXZ which gets you a (mutable) reference to column 2 of m with its X and Y components swapped.
Gemicha's comment also prompted me to write this rationale.
Alex — 02 January 2010, 22:07
I've experimented with using this library in a project, and so far I like what I see. I do have to agree that the pipe syntax is rather unusual. IMHO the following would be nicer syntax and accomplish the same stuff as detailed in the rationale:
v[X] = 0; // before v|X = 0;
m2[col<1>][YXZ] = m1 * v; // before: m2|col<1>|YXZ = m1 * v;
m2[col<1>] = m1 * v[YXZ]; // no precedence issues. before: m2|col<1> = m1 * (v|YXZ);
This would, of course, be accomplished by overloading the [] operator for typeof(XYZ) index type variants etc. In fact, this syntax can be taken slightly further:
v2 = v1[Z,X,Y]; // instead of: v2 = v1|ZXY;
v2 = v1[Y,-X]; // v1 and v2 are now perpendicular. Not possible with current syntax.
Here, the structs X, Y, and Z would inherit from a common base that overloads the comma and negation operators. I like this, not only because it's more flexible, but also because it looks more natural.
I'd love to hear your thoughts on this!
Alex — 02 January 2010, 22:15
Sorry, for the very last indexing example: v1 and v2 are 2D vectors, unlike the previous examples. I realized I should've named them differently so as to avoid any confusion.
Emil — 08 January 2010, 13:02
Using op[] is better indeed, but can not be done because it must be a member. Otherwise, it would be the natural choice, same as in HLSL.
Gregory — 14 January 2010, 16:17
From ISO-IEC 14882-2003
"13.5.5 Subscripting
operator[] shall be a non-static member function with exactly one parameter."
Hence, v1[Z,X,Y] is not possible.
Alex — 14 January 2010, 21:21
@Emil
Oh, that's right. Bummer. I guess the only way around that is by way of a macro that would need to be placed inside the vector class for op[] to become available. It's slightly more intrusive than the current way of registering a custom vector class, but I can't seem to think of any real downsides other than that. What do you think?
@Gregory
It is possible as the comma operator can be overloaded for *structs* X/Y/Z. So (X,Y,Z) would return some callable type that is passed to the [] operator, much like XYZ is passed to the |operator in the current implementation.
Alex — 14 January 2010, 22:02
Hm, I realized that the macro is of no use if you're employing a vector class as defined by some external library. Being able to only use op[] for user but not library classes would probably be more confusing than the pipe syntax and its precedence issues.
Ultimately, the end the user is free to define their own op[] that simply forwards to op|, and this is what I'll most likely do for my own project.
Emil — 15 January 2010, 11:05
John Smith — 15 July 2010, 17:44
Just wanted to add that we are using Boost LA in conjunction with some OpenGL code and it is really nice. We were able to roll our own Matrix/Vector types that were compatible with OpenGL's matrix/vector types without being float[i][j] arrays that are way too complicated to work with.
Good luck with this library, hopefully it makes it into boost.
Though it wouldn't hurt to have quite a few more functions, even something as simple as normalize or unit vector are missing.
Emil — 16 July 2010, 14:52
There is a new version that I haven't released yet which has the few missing bits plus quaternion support. It will be released as soon as I get around to write the documentation.
OvermindDL1 — 16 August 2010, 17:19
Actually the:
op[X,Y,-Z]
syntax and such would be easy to pull off if you used Boost.Proto instead of rolling your own, plus you could create a host of transforms that could optimize things as well.
Note: Yes, something like op[-Y,X*2,log(Z)] is completely possible using Boost.Proto.
Daniel — 16 September 2010, 03:36
@OvermindDL1: This sounds really interesting. How would you apply it to the mat/vec type? vec3|op[-Y,X*2,log(Z)] I guess? For custom types operator| then could be forwarded as Alex wrote.
@Emil: I am eagerly looking forward to the new version.
Alex — 18 December 2010, 14:54
Hey Emil,
Any news on the status of the new Boost LA and when/if it's going to be part of boost?
Formatting hint: when posting comments, surround code blocks in [@ and @].