1 // Copyright 2018 Google Inc. All Rights Reserved. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS-IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 // Converted to D: madric@gmail.com (Vijay Nayar) 16 17 module s2.util.math.vector; 18 19 // Simple classes to handle vectors in 2D, 3D, and 4D. 20 // 21 // Maintainers: Please be mindful of extreme degradations in unoptimized 22 // performance here. 23 24 import algorithm = std.algorithm; 25 import conv = std.conv; 26 import format = std.format; 27 import std.math; 28 import range = std.range; 29 import traits = std.traits; 30 import s2.util.hash.mix; 31 32 // CRTP base class for all Vector templates. 33 struct Vector(ElemT, size_t SizeV) 34 if (SizeV >= 1) { 35 protected: 36 37 alias ThisT = Vector!(ElemT, SizeV); 38 // FloatType is the type returned by Norm() and Angle(). These methods are 39 // special because they return floating-point values even when VType is an 40 // integer. 41 static if (traits.isIntegral!ElemT) { 42 alias FloatT = double; 43 } else { 44 alias FloatT = ElemT; 45 } 46 47 static if (traits.isNumeric!ElemT) { 48 ElemT[SizeV] _data = 0; 49 } else { 50 ElemT[SizeV] _data; 51 } 52 53 public: 54 55 this(ThisT v) { 56 _data = v.data; 57 } 58 59 this(ElemT v) { 60 _data[] = v; 61 } 62 63 this(ElemT[SizeV] v ...) { 64 _data = v; 65 } 66 67 this(RangeT)(RangeT data) 68 if (range.isInputRange!RangeT) { 69 _data[] = conv.to!(ElemT[])(range.take(range.array(data), SizeV)); 70 } 71 72 @property 73 ref ElemT[SizeV] data() { 74 return _data; 75 } 76 77 static if (SizeV >= 1) { 78 @property 79 void x(ElemT v) { 80 _data[0] = v; 81 } 82 83 @property 84 ElemT x() const { 85 return _data[0]; 86 } 87 } 88 89 static if (SizeV >= 2) { 90 @property 91 void y(ElemT v) { 92 _data[1] = v; 93 } 94 95 @property 96 ElemT y() const { 97 return _data[1]; 98 } 99 } 100 101 static if (SizeV >= 3) { 102 @property 103 void z(ElemT v) { 104 _data[2] = v; 105 } 106 107 @property 108 ElemT z() const { 109 return _data[2]; 110 } 111 } 112 113 static if (SizeV >= 4) { 114 @property 115 void w(ElemT v) { 116 _data[3] = v; 117 } 118 119 @property 120 ElemT w() const { 121 return _data[3]; 122 } 123 } 124 125 static size_t size() { return SizeV; } 126 127 // Convert from another vector type 128 static ThisT from(Elem2T)(in Vector!(Elem2T, SizeV) b) { 129 return ThisT(algorithm.map!(elem => cast(ElemT) elem)(b._data[])); 130 } 131 132 static if (is(typeof(ElemT.nan) : ElemT)) { 133 // A Vector populated with all NaN values. 134 static ThisT nan() { 135 ThisT retvalue = ThisT(); 136 retvalue._data[] = ElemT.nan; 137 return retvalue; 138 } 139 } 140 141 void clear() { _data = _data.init; } 142 143 // Implement index assignment of the form: v[i1] = value 144 void opIndexAssign(ElemT value, size_t i1) 145 in { 146 assert(i1 >= 0); 147 assert(i1 < SizeV); 148 } do { 149 _data[i1] = value; 150 } 151 152 // Implement indexing of the form: v[i1] 153 ref inout(ElemT) opIndex(size_t i1) inout 154 in { 155 assert(i1 >= 0); 156 assert(i1 < SizeV); 157 } do { 158 return _data[i1]; 159 } 160 161 // Support the == and != operators. 162 bool opEquals(in ThisT v) const { 163 return _data == v._data; 164 } 165 166 // Support the <, <=, >, and >= operators. 167 int opCmp(in ThisT v) const { 168 foreach (size_t i; 0 .. SizeV) { 169 if (_data[i] == v._data[i]) { 170 continue; 171 } 172 return _data[i] > v._data[i] ? 1 : -1; 173 } 174 return 0; 175 } 176 177 // Support the +=, -=, *=, and /= operators. 178 ThisT opOpAssign(string op)(ThisT v) { 179 static if (op == "+") { 180 _data[] += v._data[]; 181 } else static if (op == "-") { 182 _data[] -= v._data[]; 183 } else static if (op == "*") { 184 _data[] *= v._data[]; 185 } else static if (op == "/") { 186 _data[] /= v._data[]; 187 } else { 188 static assert(false, "Operator " ~ op ~ "= not implemented!"); 189 } 190 return cast(ThisT) this; 191 } 192 193 ThisT opOpAssign(string op, ScalarT)(ScalarT v) { 194 mixin("_data[] " ~ op ~ "= v;"); 195 return this; 196 } 197 198 // Support the +, -, *, and / operators. 199 ThisT opBinary(string op)(ThisT v) const { 200 ThisT retval = ThisT(this); 201 static if (op == "+") { 202 retval += v; 203 } else static if (op == "-") { 204 retval -= v; 205 } else static if (op == "*") { 206 retval *= v; 207 } else static if (op == "/") { 208 retval /= v; 209 } else { 210 static assert(0, "Operator " ~ op ~ " not implemented."); 211 } 212 return retval; 213 } 214 215 // Support the +, -, *, and / operators. 216 ThisT opBinary(string op)(in ElemT k) const 217 if (op == "+" || op == "-" || op == "*" || op == "/") { 218 ThisT retval = ThisT(); 219 static foreach(i; 0..SizeV) { 220 mixin("retval._data[i] = _data[i] " ~ op ~ " k;"); 221 } 222 return retval; 223 } 224 225 // Support the +, -, *, and / operators. 226 ThisT opBinaryRight(string op)(in ElemT k) const 227 if (op == "+" || op == "-" || op == "*" || op == "/") { 228 ThisT retval = ThisT(); 229 static foreach(i; 0..SizeV) { 230 mixin("retval._data[i] = k " ~ op ~ " _data[i];"); 231 } 232 return retval; 233 } 234 235 // Support negation. 236 ThisT opUnary(string op)() const { 237 ThisT retval = ThisT(this); 238 static if (op == "-") { 239 retval._data[] = -retval._data[]; 240 } else { 241 static assert(0, "Unary operator " ~ op ~ " not implemented."); 242 } 243 return retval; 244 } 245 246 // Element-wise max. {max(a[0],b[0]), max(a[1],b[1]), ...} 247 static ThisT max(in ThisT a, in ThisT b) { 248 return ThisT(algorithm.map!(vals => algorithm.max(vals[0], vals[1]))( 249 range.zip(a._data[], b._data[]))); 250 } 251 252 // Element-wise min. {min(a[0],b[0]), min(a[1],b[1]), ...} 253 static ThisT min(in ThisT a, in ThisT b) { 254 return ThisT(algorithm.map!(vals => algorithm.min(vals[0], vals[1]))( 255 range.zip(a._data[], b._data[]))); 256 } 257 258 ElemT dotProd(in ThisT b) const { 259 return cast(ElemT) algorithm.sum(algorithm.map!(vals => vals[0] * vals[1])( 260 range.zip(_data[], b._data[]))); 261 } 262 263 // Squared Euclidean norm (the dot product with itself). 264 ElemT norm2() const { 265 return cast(ElemT) dotProd(cast(ThisT) this); 266 } 267 268 // Normalized vector if the norm is nonzero. Not for integer types. 269 static if (traits.isFloatingPoint!ElemT) { 270 // Euclidean norm. For integer T, correct only if Norm2 does not overflow. 271 ElemT norm() const { 272 return norm2().sqrt(); 273 } 274 275 ThisT normalize() const { 276 ElemT n = norm(); 277 if (n != cast(ElemT) 0.0) { 278 n = cast(ElemT) 1.0 / n; 279 } 280 return ThisT(algorithm.map!(a => a * n)(_data[])); 281 } 282 283 // Compose a vector from the sqrt of each component. 284 ThisT sqrt() const { 285 return ThisT(algorithm.map!(a => a.sqrt())(_data[])); 286 } 287 288 // Take the floor of each component. 289 ThisT floor() const { 290 return ThisT(algorithm.map!(a => a.floor())(_data[])); 291 } 292 293 // Take the ceil of each component. 294 ThisT ceil() const { 295 return ThisT(algorithm.map!(a => a.ceil())(_data[])); 296 } 297 298 // Round of each component. Formerly FRound(). 299 ThisT round() const { 300 return ThisT(algorithm.map!(a => a.rint())(_data[])); 301 } 302 303 // Round of each component and return an integer vector. Formerly IRound(). 304 Vector!(int, SizeV) toIntVector() const { 305 return Vector!(int, SizeV)(algorithm.map!(a => cast(int) a.lrint())(_data[])); 306 } 307 308 // True if any of the components is not a number. 309 bool isNaN() const { 310 return algorithm.any!(a => a.isNaN())(_data[]); 311 } 312 313 ThisT fabs() const { 314 return ThisT(algorithm.map!(a => a.fabs())(_data[])); 315 } 316 } 317 318 // Compute the absolute value of each component. 319 ThisT abs() const { 320 return ThisT(algorithm.map!(a => a.abs())(_data[])); 321 } 322 323 // Approximately equal. 324 bool aequal(in ThisT vb, FloatT margin) const { 325 return algorithm.all!(vals => (vals[0] - vals[1]).abs() < margin)( 326 range.zip(_data[], vb._data[])); 327 } 328 329 string toString() const { 330 string val = "["; 331 string sep = ""; 332 foreach (int i; 0 .. SizeV) { 333 val ~= sep ~ conv.to!string(_data[i]); 334 sep = ", "; 335 } 336 return val ~ "]"; 337 } 338 339 static if (traits.isNumeric!ElemT) { 340 size_t toHash() const nothrow @safe { 341 HashMix h = HashMix(cast(size_t) _data[0]); 342 foreach (d; _data[1 .. $]) { 343 h.mix(cast(size_t) d); 344 } 345 return h.get(); 346 } 347 } else { 348 size_t toHash() const nothrow @safe { 349 HashMix h = HashMix(_data[0].toHash()); 350 foreach (d; _data[1 .. $]) { 351 h.mix(d.toHash()); 352 } 353 return h.get(); 354 } 355 } 356 357 // Function implementations specific to 2-dimentional vectors. 358 static if (SizeV == 2 && traits.isSigned!ElemT) { 359 // Cross product. Be aware that if T is an integer type, the high bits 360 // of the result are silently discarded. 361 ElemT crossProd(in ThisT vb) const { 362 return x * vb.y - y * vb.x; 363 } 364 365 // return the angle between "this" and v in radians 366 FloatT angle(in ThisT v) const { 367 return atan2(cast(FloatT) crossProd(v), cast(FloatT) dotProd(v)); 368 } 369 370 // return a vector orthogonal to the current one 371 // with the same norm and counterclockwise to it 372 ThisT ortho() const { 373 return ThisT([-y, x]); 374 } 375 } 376 377 // Function implementations speficif to 3-dimentional vectors. 378 static if (SizeV == 3) { 379 // Cross product. Be aware that if VType is an integer type, the high bits 380 // of the result are silently discarded. 381 ThisT crossProd(in ThisT vb) const { 382 return ThisT([ 383 y * vb.z - z * vb.y, 384 z * vb.x - x * vb.z, 385 x * vb.y - y * vb.x]); 386 } 387 388 static if (traits.isFloatingPoint!ElemT) { 389 // Returns a unit vector orthogonal to this one. 390 ThisT ortho() const { 391 int k = largestAbsComponent() - 1; 392 if (k < 0) { 393 k = 2; 394 } 395 ThisT temp = ThisT(0); 396 temp[k] = cast(ElemT) 1; 397 return crossProd(temp).normalize(); 398 } 399 400 // return the angle between two vectors in radians 401 FloatT angle(in ThisT va) const { 402 return cast(FloatT) crossProd(va).norm().atan2(dotProd(va)); 403 } 404 } 405 406 // return the index of the largest component (fabs) 407 int largestAbsComponent() const { 408 ThisT temp = abs(); 409 return temp[0] > temp[1] 410 ? temp[0] > temp[2] ? 0 : 2 411 : temp[1] > temp[2] ? 1 : 2; 412 } 413 414 // return the index of the smallest, median ,largest component of the vector 415 Vector!(int, 3) componentOrder() const { 416 auto temp = Vector!(int, 3)([0, 1, 2]); 417 if (_data[temp[0]] > _data[temp[1]]) { 418 algorithm.swap(temp.data[0], temp.data[1]); 419 } 420 if (_data[temp[1]] > _data[temp[2]]) { 421 algorithm.swap(temp.data[1], temp.data[2]); 422 } 423 if (_data[temp[0]] > _data[temp[1]]) { 424 algorithm.swap(temp.data[0], temp.data[1]); 425 } 426 return temp; 427 } 428 429 } 430 } 431 432 template Vector2(ElemT) { 433 alias Vector2 = Vector!(ElemT, 2); 434 } 435 alias Vector2_b = Vector!(ubyte, 2); 436 alias Vector2_i = Vector!(int, 2); 437 alias Vector2_f = Vector!(float, 2); 438 alias Vector2_d = Vector!(double, 2); 439 alias Vector2_r = Vector!(real, 2); 440 441 template Vector3(ElemT) { 442 alias Vector3 = Vector!(ElemT, 3); 443 } 444 alias Vector3_b = Vector!(ubyte, 3); 445 alias Vector3_i = Vector!(int, 3); 446 alias Vector3_f = Vector!(float, 3); 447 alias Vector3_d = Vector!(double, 3); 448 alias Vector3_r = Vector!(real, 3); 449 450 template Vector4(ElemT) { 451 alias Vector4 = Vector!(ElemT, 4); 452 } 453 alias Vector4_b = Vector!(ubyte, 4); 454 alias Vector4_i = Vector!(int, 4); 455 alias Vector4_f = Vector!(float, 4); 456 alias Vector4_d = Vector!(double, 4); 457 alias Vector4_r = Vector!(real, 4);