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;