Recall the vertex structure in
HelloArrow:struct Vertex {
float Position[2];
float Color[4];
};
If we kept using vanilla C arrays like this
throughout this book, life would become very tedious! What we really want
is something like this:
struct Vertex {
vec2 Position;
vec4 Color;
};
This is where the power of C++ operator
overloading and class templates really shines. It makes it possible (in
fact, it makes it easy) to write a small class
library that makes much of your application code look like it’s written in
a vector-based language. In fact, that’s what we’ve done for the samples
in this book. Our entire library consists of only three header files and
no .cpp files:
Vector.hpp
Defines a suite of 2D, 3D, and 4D vector
types that can be either float-based or integer-based. Has no
dependencies on any other header.
Matrix.hpp
Defines classes for 2×2, 3×3, and 4×4
matrices. Depends only on Vector.hpp.
Quaternion.hpp
Defines a class for quaternions and
provides several methods for interpolation and construction. Depends
on Matrix.hpp.
These files are listed in their entirety in the
appendix, but to give you a taste of how the library is structured, Example 1 shows portions of
Vector.hpp.
Example 1. Vector.hpp
#pragma once #include <cmath>
template <typename T> struct Vector2 { Vector2() {} Vector2(T x, T y) : x(x), y(y) {} T x; T y; ... };
template <typename T> struct Vector3 { Vector3() {} Vector3(T x, T y, T z) : x(x), y(y), z(z) {} void Normalize() { float length = std::sqrt(x * x + y * y + z * z); x /= length; y /= length; z /= length; } Vector3 Normalized() const { Vector3 v = *this; v.Normalize(); return v; } Vector3 Cross(const Vector3& v) const { return Vector3(y * v.z - z * v.y, z * v.x - x * v.z, x * v.y - y * v.x); } T Dot(const Vector3& v) const { return x * v.x + y * v.y + z * v.z; } Vector3 operator-() const { return Vector3(-x, -y, -z); } bool operator==(const Vector3& v) const { return x == v.x && y == v.y && z == v.z; } T x; T y; T z; };
template <typename T> struct Vector4 { ... };
typedef Vector2<int> ivec2; typedef Vector3<int> ivec3; typedef Vector4<int> ivec4;
typedef Vector2<float> vec2; typedef Vector3<float> vec3; typedef Vector4<float> vec4;
|
Note how we parameterized each vector type
using C++ templates. This allows the same logic to be used for both
float-based vectors and integer-based vectors.
Even though a 2D vector has much in common with
a 3D vector, we chose not to share logic between them. This could’ve been
achieved by adding a second template argument for dimensionality, as in
the following:
template <typename T, int Dimension>
struct Vector {
...
T components[Dimension];
};
When designing a vector library, it’s important
to strike the right balance between generality and readability. Since
there’s relatively little logic in each vector class and since we rarely
need to iterate over vector components, defining separate classes seems
like a reasonable way to go. It’s also easier for readers to understand
the meaning of, say, Position.y than
Position[1].
Since a good bit of application code will be
making frequent use of these types, the bottom of Example 2-5 defines some abbreviated names using typedefs.
Lowercase names such as vec2 and
ivec4 break the naming convention we’ve established for
types, but they adopt a look and feel similar to native types in the
language itself.
The vec2/ivec2 style names
in our C++ vector library are directly pilfered from keywords in GLSL.
Take care not to confuse this book’s C++ listings with shader
listings.
Warning: In GLSL shaders, types such as
vec2 and mat4 are built into the
language itself. Our C++ vector library merely mimics them.