1 /**
2  * Copyright:       Copyright (C) 2018 Gabriel Gheorghe, All Rights Reserved
3  * Authors:         $(Gabriel Gheorghe)
4  * License:         $(LINK2 https://www.gnu.org/licenses/gpl-3.0.txt, GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007)
5  * Source:			    $(LINK2 https://github.com/GabyForceQ/LibertyEngine/blob/master/source/liberty/math/shapes.d)
6  * Documentation:
7  * Coverage:
8 **/
9 module liberty.math.shapes;
10 
11 import std.traits : isFloatingPoint;
12 
13 import liberty.math.vector;
14 import liberty.math.box;
15 ///
16 struct Segment(T, int N) if (N == 2 || N == 3) {
17 	///
18 	alias PointType = Vector!(T, N);
19 	///
20 	PointType a, b;
21 	///
22 	static if (N == 3 && isFloatingPoint!T) {
23 		///
24 		bool intersect(Plane!T plane, out PointType intersection, out T progress)   const {
25 			import liberty.math.functions : abs;
26 			import liberty.math.vector : dot;
27 			PointType dir = b - a;
28 			T dp = dot(plane.n, dir);
29 			if (abs(dp) < T.epsilon) {
30 				progress = T.infinity;
31 				return false;
32 			}
33 			progress = -(dot(plane.n, a) - plane.d) / dp;
34 			intersection = progress * dir + a;
35 			return progress >= 0 && progress <= 1;
36 		}
37 	}
38 }
39 ///
40 alias Segment2I = Segment!(int, 2);
41 ///
42 alias Segment3I = Segment!(int, 3);
43 ///
44 alias Segment2F = Segment!(float, 2);
45 ///
46 alias Segment3F = Segment!(float, 3);
47 ///
48 alias Segment2D = Segment!(double, 2);
49 ///
50 alias Segment3D = Segment!(double, 3);
51 ///
52 struct Triangle(T, int N) if (N == 2 || N == 3) {
53 	///
54 	alias PointType = Vector!(T, N);
55 	///
56 	PointType a, b, c;
57 	static if (N == 2) {
58 		/// Returns area of a 2D triangle.
59 		T area()   const {
60 			import liberty.math.functions : abs;
61 			return abs(signedArea());
62 		}
63 		/// Returns signed area of a 2D triangle.
64 		T signedArea()   const {
65 			return ((b.x * a.y - a.x * b.y) + (c.x * b.y - b.x * c.y) + (a.x * c.y - c.x * a.y)) / 2;
66 		}
67 	}
68 	static if (N == 3 && isFloatingPoint!T) {
69 		/// Returns triangle normal.
70 		Vector!(T, 3) computeNormal()   const {
71 			import liberty.math.vector : cross;
72 			return cross(b - a, c - a).normalized();
73 		}
74 	}
75 }
76 ///
77 alias Triangle2I = Triangle!(int, 2);
78 ///
79 alias Triangle3I = Triangle!(int, 3);
80 ///
81 alias Triangle2F = Triangle!(float, 2);
82 ///
83 alias Triangle3F = Triangle!(float, 3);
84 ///
85 alias Triangle2D = Triangle!(double, 2);
86 ///
87 alias Triangle3D = Triangle!(double, 3);
88 ///
89 struct Sphere(T, int N) if (N == 2 || N == 3) {
90 	///
91 	alias PointType = Vector!(T, N);
92 	///
93 	PointType center;
94 	///
95 	T radius;
96 	///
97 	this(in PointType center_, T radius_)   {
98 		center = center_;
99 		radius = radius_;
100 	}
101 	///
102 	bool contains(in Sphere s)   const {
103 		if (s.radius > radius) {
104 			return false;
105 		}
106 		T innerRadius = radius - s.radius;
107 		return squaredDistanceTo(s.center) < innerRadius * innerRadius;
108 	}
109 	///
110 	T squaredDistanceTo(PointType p)   const {
111 		return center.squaredDistanceTo(p);
112 	}
113 	///
114 	bool intersects(Sphere s)   const {
115 		T outerRadius = radius + s.radius;
116 		return squaredDistanceTo(s.center) < outerRadius * outerRadius;
117 	}
118 	static if (isFloatingPoint!T) {
119 		///
120 		T distanceTo(PointType p)   const {
121 			return center.distanceTo(p);
122 		}
123 		static if(N == 2) {
124 			/// Returns circle area.
125 			T area()   const {
126 				import liberty.math.functions : PI;
127 				return PI * (radius * radius);
128 			}
129 		}
130 	}
131 }
132 ///
133 alias Sphere2I = Sphere!(int, 2);
134 ///
135 alias Sphere3I = Sphere!(int, 3);
136 ///
137 alias Sphere2F = Sphere!(float, 2);
138 ///
139 alias Sphere3F = Sphere!(float, 3);
140 ///
141 alias Sphere2D = Sphere!(double, 2);
142 ///
143 alias Sphere3D = Sphere!(double, 3);
144 ///
145 struct Ray(T, int N) if (N == 2 || N == 3) {
146 	///
147 	alias PointType = Vector!(T, N);
148 	///
149 	PointType origin;
150 	///
151 	PointType direction;
152 	///
153 	PointType progress(T t)  const 
154 	{
155 		return origin + direction * t;
156 	}
157 	static if (N == 3 && isFloatingPoint!T) {
158 		///
159 		bool intersect(Triangle!(T, 3) triangle, out T t, out T u, out T v)   const {
160 			import liberty.math.functions : abs;
161 			import liberty.math.vector : dot, cross;
162 			PointType edge1 = triangle.b - triangle.a;
163 			PointType edge2 = triangle.c - triangle.a;
164 			PointType pvec = cross(direction, edge2);
165 			T det = dot(edge1, pvec);
166 			if (abs(det) < T.epsilon) {
167 				return false;
168 			}
169 			const T invDet = 1 / det;
170 			PointType tvec = origin - triangle.a;
171 			u = dot(tvec, pvec) * invDet;
172 			if (u < 0 || u > 1) {
173 				return false;
174 			}
175 			PointType qvec = cross(tvec, edge1);
176 			v = dot(direction, qvec) * invDet;
177 			if (v < 0.0 || u + v > 1.0) {
178 				return false;
179 			}
180 			t = dot(edge2, qvec) * invDet;
181 			return true;
182 		}
183 		///
184 		bool intersect(Plane!T plane, out PointType intersection, out T distance)   const {
185 			import liberty.math.functions : abs;
186 			import liberty.math.vector : dot;
187 			T dp = dot(plane.n, direction);
188 			if (abs(dp) < T.epsilon) {
189 				distance = T.infinity;
190 				return false;
191 			}
192 			distance = -(dot(plane.n, origin) - plane.d) / dp;
193 			intersection = distance * direction + origin;
194 			return distance >= 0;
195 		}
196 	}
197 }
198 ///
199 alias Ray2I = Ray!(int, 2);
200 ///
201 alias Ray3I = Ray!(int, 3);
202 ///
203 alias Ray2F = Ray!(float, 2);
204 ///
205 alias Ray3F = Ray!(float, 3);
206 ///
207 alias Ray2D = Ray!(double, 2);
208 ///
209 alias Ray3D = Ray!(double, 3);
210 ///
211 struct Plane(T) if (isFloatingPoint!T) {
212 	///
213 	alias type = T;
214 	///
215 	Vector!(T, 3) n;
216 	///
217 	T d;
218 	///
219 	this(Vector!(T, 4) abcd)   {
220 		n = Vector!(T, 3)(abcd.x, abcd.y, abcd.z).normalized();
221 		d = abcd.w;
222 	}
223 	///
224 	this(Vector!(T, 3) origin, Vector!(T, 3) normal)   {
225 		import liberty.math.vector : dot;
226 		n = normal.normalized();
227 		d = -dot(origin, n);
228 	}
229 	///
230 	this(Vector!(T, 3) A, Vector!(T, 3) B, Vector!(T, 3) C)   {
231 		import liberty.math.vector : cross;
232 		this(C, cross(B - A, C - A));
233 	}
234 	///
235 	ref Plane opAssign(Plane other)   {
236 		n = other.n;
237 		d = other.d;
238 		return this;
239 	}
240 	///
241 	T signedDistanceTo(Vector!(T, 3) point)   const {
242 		import liberty.math.vector : dot;
243 		return dot(n, point) + d;
244 	}
245 	///
246 	T distanceTo(Vector!(T, 3) point)   const {
247 		import liberty.math.functions : abs;
248 		return abs(signedDistanceTo(point));
249 	}
250 	///
251 	bool isFront(Vector!(T, 3) point)   const {
252 		return signedDistanceTo(point) >= 0;
253 	}
254 	///
255 	bool isBack(Vector!(T, 3) point)   const {
256 		return signedDistanceTo(point) < 0;
257 	}
258 	///
259 	bool isOn(Vector!(T, 3) point, T epsilon)   const {
260 		T sd = signedDistanceTo(point);
261 		return (-epsilon < sd) && (sd < epsilon);
262 	}
263 }
264 ///
265 alias PlaneF = Plane!float;
266 ///
267 alias PlaneD = Plane!double;
268 ///
269 enum FrustumSide : ubyte {
270 	///
271 	Left = 0x00,
272 	///
273 	Right = 0x01,
274 	///
275 	Top = 0x02,
276 	///
277 	Bottom = 0x03,
278 	///
279 	Near = 0x04,
280 	///
281 	Far = 0x05
282 }
283 ///
284 enum FrustumScope : ubyte {
285 	/// Object is outside the frustum.
286 	Outside = 0x00,
287 	/// Object intersects with the frustum.
288 	Intersect = 0x01,
289 	/// Object is inside the frustum.
290 	Inside = 0x02
291 }
292 ///
293 struct Frustum(T) if (isFloatingPoint!T) {
294 	///
295 	enum sideCount = 6;
296 	///
297 	enum vertexCount = 8;
298 	///
299 	alias type = T;
300 	///
301 	Plane!T[6] planes;
302 	/// Create a frustum from 6 planes.
303 	this(Plane!T left, Plane!T right, Plane!T top, Plane!T bottom, Plane!T near, Plane!T far)   {
304 		planes[FrustumSide.Left] = left;
305 		planes[FrustumSide.Right] = right;
306 		planes[FrustumSide.Top] = top;
307 		planes[FrustumSide.Bottom] = bottom;
308 		planes[FrustumSide.Near] = near;
309 		planes[FrustumSide.Far] = far;
310 	}
311 	///
312 	bool contains(Vector!(T, 3) point)   const {
313 		T distance = 0;
314 		static foreach (i; 0..sideCount) {
315 			distance = planes[i].signedDistanceTo(point);
316 			if (distance < 0) {
317 				return false;
318 			}
319 		}
320 		return true;
321 	}
322 	///
323 	FrustumScope contains(Sphere!(T, 3) sphere)   const {
324 		T distance = 0;
325 		static foreach (i; 0..sideCount) {
326 			distance = planes[i].signedDistanceTo(sphere.center);
327 			if(distance < -sphere.radius) {
328 				return FrustumScope.Outside;
329 			} else if (distance < sphere.radius) {
330 				return FrustumScope.Intersect;
331 			}
332 		}
333 		return FrustumScope.Inside;
334 	}
335 	///
336 	int contains(Box!(T, 3) box)   const {
337 		Vector!(T, 3)[8] corners;
338 		int totalIn = 0;
339 		T x, y, z;
340 		static foreach (i; 0..2) {
341 			static foreach (j; 0..2) {
342 				static foreach (k; 0..2) {
343 					x = i == 0 ? box.min.x : box.max.x;
344 					y = j == 0 ? box.min.y : box.max.y;
345 					z = k == 0 ? box.min.z : box.max.z;
346 					corners[i * 4 + j * 2 + k] = Vector!(T, 3)(x, y, z);
347 				}
348 			}
349 		}
350 		int inCount = 0, ptIn = 0;
351 		static foreach (p; 0..sideCount) {
352 			inCount = vertexCount;
353 			ptIn = 1;
354 			static foreach (i; 0..vertexCount) {
355 				if (planes[p].isBack(corners[i])) {
356 					ptIn = 0;
357 					inCount--;
358 				}
359 			}
360 			if (inCount == 0) {
361 				return FrustumScope.Outside;
362 			}
363 			totalIn += ptIn;
364 		}
365 		if(totalIn == sideCount) {
366 			return FrustumScope.Inside;
367 		}
368 		return FrustumScope.Intersect;
369 	}
370 }
371 ///
372 alias FrustumF = Frustum!float;
373 ///
374 alias FrustumD = Frustum!double;
375 ///
376 struct Rect(T) {
377 	///
378 	alias type = T;
379 	///
380 	T x;
381 	///
382 	T y;
383 	///
384 	T width;
385 	///
386 	T height;
387 	///
388 	static const Rect!T defaultData = Rect!T(10, 10, 50, 50);
389 	///
390 	this(T x, T y, T width, T height) {
391 		this.x = x;
392 		this.y = y;
393 		this.width = width;
394 		this.height = height;
395 	}
396 }
397 ///
398 alias RectI = Rect!int;
399 ///
400 alias RectF = Rect!float;
401 ///
402 alias RectD = Rect!double;