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;