1 /** 2 This module implements some abstract geometric shapes: 3 $(UL 4 $(LI Line segments.) 5 $(LI Triangle.) 6 $(LI Circles/spheres.) 7 $(LI Rays) 8 $(LI Planes) 9 $(LI Frustum) 10 ) 11 */ 12 module gfm.math.shapes; 13 14 import std.math, 15 std.traits; 16 17 import gfm.math.vector, 18 gfm.math.box; 19 20 static if( __VERSION__ < 2066 ) private enum nogc = 1; 21 22 /// A Segment is 2 points. 23 /// When considered like a vector, it represents the arrow from a to b. 24 align(1) struct Segment(T, int N) 25 { 26 align(1): 27 public 28 { 29 alias Vector!(T, N) point_t; 30 point_t a, b; 31 } 32 } 33 34 alias Segment!(float, 2) seg2f; /// 2D float segment. 35 alias Segment!(float, 3) seg3f; /// 3D float segment. 36 alias Segment!(double, 2) seg2d; /// 2D double segment. 37 alias Segment!(double, 3) seg3d; /// 3D double segment. 38 alias Segment!(int, 2) seg2i; /// 2D integer segment. 39 alias Segment!(int, 3) seg3i; /// 3D integer segment. 40 41 /// A Triangle is 3 points. 42 align(1) struct Triangle(T, int N) 43 { 44 align(1): 45 public 46 { 47 alias Vector!(T, N) point_t; 48 point_t a, b, c; 49 50 static if (N == 2) 51 { 52 /// Returns: Area of a 2D triangle. 53 @nogc T area() pure const nothrow 54 { 55 return abs(signedArea()); 56 } 57 58 /// Returns: Signed area of a 2D triangle. 59 @nogc T signedArea() pure const nothrow 60 { 61 return ((b.x * a.y - a.x * b.y) 62 + (c.x * b.y - b.x * c.y) 63 + (a.x * c.y - c.x * a.y)) / 2; 64 } 65 } 66 67 static if (N == 3) 68 { 69 /// Returns: Triangle normal. 70 @nogc Vector!(T, 3) computeNormal() pure const nothrow 71 { 72 return cross(b - a, c - a).normalized(); 73 } 74 } 75 } 76 } 77 78 alias Triangle!(float, 2) triangle2f; /// 2D float triangle. 79 alias Triangle!(float, 3) triangle3f; /// 3D float triangle. 80 alias Triangle!(double, 2) triangle2d; /// 2D double triangle. 81 alias Triangle!(double, 3) triangle3d; /// 3D double triangle. 82 83 /// A Sphere is a point + a radius. 84 align(1) struct Sphere(T, int N) 85 { 86 align(1): 87 public nothrow 88 { 89 alias Vector!(T, N) point_t; 90 point_t center; 91 T radius; 92 93 /// Creates a sphere from a point and a radius. 94 @nogc this(in point_t center_, T radius_) pure nothrow 95 { 96 center = center_; 97 radius = radius_; 98 } 99 100 /// Sphere contains point test. 101 /// Returns: true if the point is inside the sphere. 102 @nogc bool contains(in Sphere s) pure const nothrow 103 { 104 if (s.radius > radius) 105 return false; 106 107 T innerRadius = radius - s.radius; 108 return squaredDistanceTo(s.center) < innerRadius * innerRadius; 109 } 110 111 /// Sphere vs point Euclidean distance squared. 112 @nogc T squaredDistanceTo(point_t p) pure const nothrow 113 { 114 return center.squaredDistanceTo(p); 115 } 116 117 /// Sphere vs sphere intersection. 118 /// Returns: true if the spheres intersect. 119 @nogc bool intersects(Sphere s) pure const nothrow 120 { 121 T outerRadius = radius + s.radius; 122 return squaredDistanceTo(s.center) < outerRadius * outerRadius; 123 } 124 125 static if (isFloatingPoint!T) 126 { 127 /// Sphere vs point Euclidean distance. 128 @nogc T distanceTo(point_t p) pure const nothrow 129 { 130 return center.distanceTo(p); 131 } 132 133 static if(N == 2) 134 { 135 /// Returns: Circle area. 136 @nogc T area() pure const nothrow 137 { 138 return PI * (radius * radius); 139 } 140 } 141 } 142 } 143 } 144 145 alias Sphere!(float, 2) sphere2f; /// 2D float sphere (ie. a circle). 146 alias Sphere!(float, 3) sphere3f; /// 3D float sphere. 147 alias Sphere!(double, 2) sphere2d; /// 2D double sphere (ie. a circle). 148 alias Sphere!(double, 3) sphere3d; /// 3D double sphere (ie. a circle). 149 150 151 /// A Ray ir a point + a direction. 152 align(1) struct Ray(T, int N) 153 { 154 align(1): 155 nothrow: 156 public 157 { 158 alias Vector!(T, N) point_t; 159 point_t orig; 160 point_t dir; 161 162 /// Returns: A point further along the ray direction. 163 @nogc point_t progress(T t) pure const nothrow 164 { 165 return orig + dir * t; 166 } 167 168 static if (N == 3) 169 { 170 /// Ray vs triangle intersection. 171 /// See_also: "Fast, Minimum Storage Ray/Triangle intersection", Mommer & Trumbore (1997) 172 /// Returns: Barycentric coordinates, the intersection point is at $(D (1 - u - v) * A + u * B + v * C). 173 @nogc bool intersect(Triangle!(T, 3) triangle, out T t, out T u, out T v) pure const nothrow 174 { 175 point_t edge1 = triangle.b - triangle.a; 176 point_t edge2 = triangle.c - triangle.a; 177 point_t pvec = cross(dir, edge2); 178 T det = dot(edge1, pvec); 179 if (abs(det) < T.epsilon) 180 return false; // no intersection 181 T invDet = 1 / det; 182 183 // calculate distance from triangle.a to ray origin 184 point_t tvec = orig - triangle.a; 185 186 // calculate U parameter and test bounds 187 u = dot(tvec, pvec) * invDet; 188 if (u < 0 || u > 1) 189 return false; 190 191 // prepare to test V parameter 192 point_t qvec = cross(tvec, edge1); 193 194 // calculate V parameter and test bounds 195 v = dot(dir, qvec) * invDet; 196 if (v < 0.0 || u + v > 1.0) 197 return false; 198 199 // calculate t, ray intersects triangle 200 t = dot(edge2, qvec) * invDet; 201 return true; 202 } 203 } 204 } 205 } 206 207 alias Ray!(float, 2) ray2f; /// 2D float ray. 208 alias Ray!(float, 3) ray3f; /// 3D float ray. 209 alias Ray!(double, 2) ray2d; /// 2D double ray. 210 alias Ray!(double, 3) ray3d; /// 3D double ray. 211 212 213 /// 3D plane. 214 /// See_also: Flipcode article by Nate Miller $(WEB www.flipcode.com/archives/Plane_Class.shtml). 215 align(1) struct Plane(T) if (isFloatingPoint!T) 216 { 217 align(1): 218 public 219 { 220 vec3!T n; /// Normal (always stored normalized). 221 T d; 222 223 /// Create from four coordinates. 224 @nogc this(vec4!T abcd) pure nothrow 225 { 226 n = vec3!T(abcd.x, abcd.y, abcd.z).normalized(); 227 d = abcd.z; 228 } 229 230 /// Create from a point and a normal. 231 @nogc this(vec3!T origin, vec3!T normal) pure nothrow 232 { 233 n = normal.normalized(); 234 d = -dot(origin, n); 235 } 236 237 /// Create from 3 non-aligned points. 238 @nogc this(vec3!T A, vec3!T B, vec3!T C) pure nothrow 239 { 240 this(C, cross(B - A, C - A)); 241 } 242 243 /// Assign a plane with another plane. 244 @nogc ref Plane opAssign(Plane other) pure nothrow 245 { 246 n = other.n; 247 d = other.d; 248 return this; 249 } 250 251 /// Returns: signed distance between a point and the plane. 252 @nogc T signedDistanceTo(vec3!T point) pure const nothrow 253 { 254 return dot(n, point) + d; 255 } 256 257 /// Returns: absolute distance between a point and the plane. 258 @nogc T distanceTo(vec3!T point) pure const nothrow 259 { 260 return abs(signedDistanceTo(point)); 261 } 262 263 /// Returns: true if the point is in front of the plane. 264 @nogc bool isFront(vec3!T point) pure const nothrow 265 { 266 return signedDistanceTo(point) >= 0; 267 } 268 269 /// Returns: true if the point is in the back of the plane. 270 @nogc bool isBack(vec3!T point) pure const nothrow 271 { 272 return signedDistanceTo(point) < 0; 273 } 274 275 /// Returns: true if the point is on the plane, with a given epsilon. 276 @nogc bool isOn(vec3!T point, T epsilon) pure const nothrow 277 { 278 T sd = signedDistanceTo(point); 279 return (-epsilon < sd) && (sd < epsilon); 280 } 281 } 282 } 283 284 alias Plane!float planef; /// 3D float plane. 285 alias Plane!double planed; /// 3D double plane. 286 287 unittest 288 { 289 auto p = planed(vec4d(1.0, 2.0, 3.0, 4.0)); 290 auto p2 = planed(vec3d(1.0, 0.0, 0.0), 291 vec3d(0.0, 1.0, 0.0), 292 vec3d(0.0, 0.0, 1.0)); 293 294 assert(p2.isOn(vec3d(1.0, 0.0, 0.0), 1e-7)); 295 assert(p2.isFront(vec3d(1.0, 1.0, 1.0))); 296 assert(p2.isBack(vec3d(0.0, 0.0, 0.0))); 297 } 298 299 300 301 /// 3D frustum. 302 /// See_also: Flipcode article by Dion Picco $(WEB www.flipcode.com/archives/Frustum_Culling.shtml). 303 /// Bugs: verify proper signedness of half-spaces 304 align(1) struct Frustum(T) if (isFloatingPoint!T) 305 { 306 align(1): 307 public 308 { 309 enum int LEFT = 0, 310 RIGHT = 1, 311 TOP = 2, 312 BOTTOM = 3, 313 NEAR = 4, 314 FAR = 5; 315 316 Plane!T[6] planes; 317 318 /// Create a frustum from 6 planes. 319 @nogc this(Plane!T left, Plane!T right, Plane!T top, Plane!T bottom, Plane!T near, Plane!T far) pure nothrow 320 { 321 planes[LEFT] = left; 322 planes[RIGHT] = right; 323 planes[TOP] = top; 324 planes[BOTTOM] = bottom; 325 planes[NEAR] = near; 326 planes[FAR] = far; 327 } 328 329 enum : int 330 { 331 OUTSIDE, /// object is outside the frustum 332 INTERSECT, /// object intersects with the frustum 333 INSIDE /// object is inside the frustum 334 } 335 336 /// Point vs frustum intersection. 337 @nogc bool contains(vec3!T point) pure const nothrow 338 { 339 for(int i = 0; i < 6; ++i) 340 { 341 T distance = planes[i].signedDistanceTo(point); 342 343 if(distance < 0) 344 return false; 345 } 346 return true; 347 } 348 349 /// Sphere vs frustum intersection. 350 /// Returns: Frustum.OUTSIDE, Frustum.INTERSECT or Frustum.INSIDE. 351 @nogc int contains(Sphere!(T, 3) sphere) pure const nothrow 352 { 353 // calculate our distances to each of the planes 354 for(int i = 0; i < 6; ++i) 355 { 356 // find the distance to this plane 357 T distance = planes[i].signedDistanceTo(sphere.center); 358 359 if(distance < -sphere.radius) 360 return OUTSIDE; 361 362 else if (distance < sphere.radius) 363 return INTERSECT; 364 } 365 366 // otherwise we are fully in view 367 return INSIDE; 368 } 369 370 /// AABB vs frustum intersection. 371 /// Returns: Frustum.OUTSIDE, Frustum.INTERSECT or Frustum.INSIDE. 372 @nogc int contains(box3!T box) pure const nothrow 373 { 374 vec3!T[8] corners; 375 int totalIn = 0; 376 377 for (int i = 0; i < 2; ++i) 378 for (int j = 0; j < 2; ++j) 379 for (int k = 0; k < 2; ++k) 380 { 381 auto x = i == 0 ? box.min.x : box.max.x; 382 auto y = i == 0 ? box.min.y : box.max.y; 383 auto z = i == 0 ? box.min.z : box.max.z; 384 corners[i*4 + j*2 + k] = vec3!T(x, y, z); 385 } 386 387 // test all 8 corners against the 6 sides 388 // if all points are behind 1 specific plane, we are out 389 // if we are in with all points, then we are fully in 390 for(int p = 0; p < 6; ++p) 391 { 392 int inCount = 8; 393 int ptIn = 1; 394 395 for(int i = 0; i < 8; ++i) 396 { 397 // test this point against the planes 398 if (planes[p].isBack(corners[i])) 399 { 400 ptIn = 0; 401 --inCount; 402 } 403 } 404 405 // were all the points outside of plane p? 406 if (inCount == 0) 407 return OUTSIDE; 408 409 // check if they were all on the right side of the plane 410 totalIn += ptIn; 411 } 412 413 // so if totalIn is 6, then all are inside the view 414 if(totalIn == 6) 415 return INSIDE; 416 417 // we must be partly in then otherwise 418 return INTERSECT; 419 } 420 421 } 422 } 423 424 unittest 425 { 426 seg2f se; 427 triangle3f tr; 428 Frustum!double frust; 429 planed pl; 430 }