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);