1 module dvec.vector; 2 3 import std.traits : isNumeric, isFloatingPoint; 4 5 /** 6 * Generic struct that represents a vector holding `size` elements of type `T`. 7 * A vector must contain at least 1 element, and has no upper-bound on the size 8 * beyond restrictions imposed by the system. 9 */ 10 struct Vec(T, size_t size) if (isNumeric!T && size > 0) { 11 /** 12 * A static contstant that can be used to get the size of the vector. 13 */ 14 public static const size_t SIZE = size; 15 16 /** 17 * The internal static array storing the elements of this vector. 18 */ 19 public T[size] data; 20 21 /** 22 * Constructs a vector from an array of elements. 23 * Params: 24 * elements = The elements to put in the vector. 25 */ 26 public this(T[size] elements) { 27 static foreach (i; 0 .. size) data[i] = elements[i]; 28 } 29 30 /** 31 * Constructs a vector from a variadic list of elements. 32 * Params: 33 * elements = The elements to put in the vector. 34 */ 35 public this(T[] elements...) { 36 if (elements.length != size) assert(false, "Invalid number of elements provided to Vec constructor."); 37 static foreach (i; 0 .. size) data[i] = elements[i]; 38 } 39 40 /** 41 * Constructs a vector where all elements have the given value. 42 * Params: 43 * value = The value to assign to all elements in the vector. 44 */ 45 public this(T value) { 46 static foreach (i; 0 .. size) data[i] = value; 47 } 48 49 /** 50 * Constructs a vector as a copy of the other. 51 * Params: 52 * other = The vector to copy. 53 */ 54 public this(Vec!(T, size) other) { 55 this(other.data); 56 } 57 58 /** 59 * Constructs a vector containing all 0's. 60 * Returns: An vector containing all 0's. 61 */ 62 public static Vec!(T, size) empty() { 63 Vec!(T, size) v; 64 static foreach (i; 0 .. size) v[i] = 0; 65 return v; 66 } 67 68 public T opIndex(size_t i) { 69 return data[i]; 70 } 71 72 public void opIndexAssign(T value, size_t i) { 73 data[i] = value; 74 } 75 76 public void add(V)(Vec!(V, size) other) if (isNumeric!V) { 77 static foreach (i; 0 .. size) data[i] += other[i]; 78 } 79 80 public void sub(V)(Vec!(V, size) other) if (isNumeric!V) { 81 static foreach (i; 0 .. size) data[i] -= other[i]; 82 } 83 84 alias subtract = sub; 85 86 public void mul(V)(V factor) if (isNumeric!V) { 87 static foreach (i; 0 .. size) data[i] *= factor; 88 } 89 90 alias multiply = mul; 91 92 public void div(V)(V factor) if (isNumeric!V) { 93 static foreach (i; 0 .. size) data[i] /= factor; 94 } 95 96 alias divide = div; 97 98 public double mag() { 99 import std.math : sqrt; 100 double sum = 0; 101 static foreach (i; 0 .. size) sum += data[i] * data[i]; 102 return sqrt(sum); 103 } 104 105 alias magnitude = mag; 106 alias length = mag; 107 alias len = mag; 108 109 public T dot(Vec!(T, size) other) { 110 T sum = 0; 111 static foreach (i; 0 .. size) sum += data[i] * other[i]; 112 return sum; 113 } 114 115 alias dotProduct = dot; 116 117 // TODO: Make this @nogc compatible! 118 public string toString() const { 119 import std.conv : to; 120 string s = "["; 121 static foreach (i; 0 .. size - 1) { 122 s ~= data[i].to!string; 123 s ~= ", "; 124 } 125 s ~= data[size - 1].to!string; 126 s ~= "]"; 127 return s; 128 } 129 130 static if (isFloatingPoint!T) { 131 public void norm() { 132 const double mag = mag(); 133 static foreach (i; 0 .. size) { 134 data[i] /= mag; 135 } 136 } 137 138 alias normalize = norm; 139 } 140 141 static if (isFloatingPoint!T && size == 2) { 142 public void toPolar() { 143 import std.math : atan2; 144 T radius = mag(); 145 T angle = atan2(data[1], data[0]); 146 data[0] = radius; 147 data[1] = angle; 148 } 149 150 public void toCartesian() { 151 import std.math : cos, sin; 152 T x = data[0] * cos(data[1]); 153 T y = data[0] * sin(data[1]); 154 data[0] = x; 155 data[1] = y; 156 } 157 } 158 } 159 160 // Aliases for common vector types. 161 alias Vec2f = Vec!(float, 2); 162 alias Vec3f = Vec!(float, 3); 163 alias Vec4f = Vec!(float, 4); 164 165 alias Vec2d = Vec!(double, 2); 166 alias Vec3d = Vec!(double, 3); 167 alias Vec4d = Vec!(double, 4); 168 169 alias Vec2i = Vec!(int, 2); 170 alias Vec3i = Vec!(int, 3); 171 alias Vec4i = Vec!(int, 4); 172 173 unittest { 174 import std.stdio; 175 import std.math; 176 177 void assertFP(double actual, double expected, double delta = 1e-06, string msg = "Assertion failed.") { 178 double lowBound = expected - delta; 179 double highBound = expected + delta; 180 assert(actual > lowBound && actual < highBound, msg); 181 } 182 183 // Test constructors. 184 auto v1 = Vec2d([1.0, 2.0]); 185 assert(v1.data == [1.0, 2.0]); 186 auto v2 = Vec2d(1.0, 2.0); 187 assert(v2.data == [1.0, 2.0]); 188 auto v3 = Vec3f(3.14f); 189 assert(v3.data == [3.14f, 3.14f, 3.14f]); 190 auto v4 = Vec3f(v3); 191 assert(v4.data == v3.data); 192 auto v5 = Vec2i.empty(); 193 assert(v5.data == [0, 0]); 194 195 // Test basic methods. 196 v1.add(v2); 197 assert(v1.data == [2.0, 4.0]); 198 assert(v1[0] == 2.0); 199 assert(v1[1] == 4.0); 200 v1.sub(v2); 201 assert(v1.data == [1.0, 2.0]); 202 v1.mul(3.0); 203 assert(v1.data == [3.0, 6.0]); 204 v1.div(6.0); 205 assert(v1.data == [0.5, 1.0]); 206 assert(Vec2f(3, 4).mag == 5.0f); 207 assert(Vec2d.empty.mag == 0); 208 assert(Vec2d(1.0, 1.0).dot(Vec2d(1.0, 1.0)) == 2.0); 209 210 // Test floating-point specific methods. 211 auto v6 = Vec2f(3, 3); 212 v6.norm(); 213 assertFP(v6.mag, 1); 214 assertFP(v6[0], sqrt(2f) / 2f); 215 v6 = Vec2f(1, 0); 216 auto v6Copy = Vec2f(v6); 217 v6.norm(); 218 assert(v6.mag == 1); 219 assert(v6.data == v6Copy.data); 220 221 // Test toPolar 222 auto vCart = Vec2d(1, 0); 223 vCart.toPolar(); 224 assert(vCart.data == [1.0, 0.0]); 225 vCart.toCartesian(); 226 assert(vCart.data == [1.0, 0.0]); 227 vCart = Vec2d(1, 1); 228 vCart.toPolar(); 229 assertFP(vCart[0], sqrt(2f)); 230 assertFP(vCart[1], PI_4); 231 vCart.toCartesian(); 232 assertFP(vCart[0], 1); 233 assertFP(vCart[1], 1); 234 vCart = Vec2d(-1, 1); 235 vCart.toPolar(); 236 assertFP(vCart[0], sqrt(2f)); 237 assertFP(vCart[1], 3 * PI_4); 238 }