So, here is the problem. We have a Vec2, that is, a two component vector. It should only be able to use swizzle components for the two components it has, that is, X and Y. Something like:
vector2( x, y, x);
is valid but:
vector2( x, z, y );
should not be and ideally should throw some error at compile time. Also, every vector type can use the swizzle components of the vector types that are smaller than it:
vector3( x, z, y );
It is clear that we can use inheritance to enforce this, but we must be careful. When I first tried this, I made the mistake of inheriting in the direction that seemed the most logical, but that was in fact wrong. Your gut instinct is to have the 3 component swizzles inherit from the 2 component swizzles:
class Swizz2;
class Swizz3 : public Swizz2;
class Swizz4 : public Swizz3;
class Swizz4 : public Swizz3;
But this does not solve the problem: it would mean that a Vec4 can only use the 4th component swizzle, W. We are not following the Is A... principle here because we went with what felt natural. It is correctly written:
class Swizz4;
class Swizz3 : public Swizz4;
class Swizz2 : public Swizz3;
And when we implement the functional operator for a Vec2 or a Vec3:
Vec2 operator() ( Swizz2 const& a, Swizz2 const& b ) const;
Vec2 operator() ( Swizz3 const& a, Swizz3 const& b ) const;
we can be sure that the operator will take only X, Y, and Z components in the Vec3 but only X and Y in the Vec2.
The final step to make all this safe from the user is to lock down the swizzle classes so that no one can alter what they mean (well, at least not easily). I did this first by making the internal fields that govern how a particular instance of the Swizz2, Swizz3, and Swizz4 classes are used to swap components around private. The swizzle classes are declared in the header file but omit their implementation, along with a class called SwizzleFactory:
class SwizzleFactory;
class Swizz4;
class Swizz3;
class Swizz2;This makes it so that any other translation unit can use the classes but cannot do much with them. In the implementation file, SwizzleFactory is declared a friend of the Swizz# classes, whose only useful constructors are private. The swizzle components names are also declared in the header file:
extern Swizz2 const x;
/** etc. */
And then defined in the implementation by using the factory methods of a static SwizzleFactory instance:
Swizz2 const x = factory.MakeSwizz2( 1 );
Since the implementation file is the only place where the factory is defined, it is the only place where it can be instantiated. Thus, that translation unit is the only place where the swizzle classes can be defined and the whole system remains very safe from user intervention, unless they do something stupid and ugly. To top it all off, the whole things lives in the namespace GFX, just in case you really need to use x as a variable name (do not do that).
No comments:
Post a Comment