646 lines
21 KiB
D
646 lines
21 KiB
D
/**
|
|
This module implements some abstract geometric shapes:
|
|
$(UL
|
|
$(LI Line segments.)
|
|
$(LI Triangle.)
|
|
$(LI Circles/spheres.)
|
|
$(LI Rays)
|
|
$(LI Planes)
|
|
$(LI Frustum)
|
|
)
|
|
*/
|
|
module gfm.math.shapes;
|
|
|
|
import std.math,
|
|
std.traits;
|
|
|
|
import gfm.math.vector,
|
|
gfm.math.box;
|
|
|
|
/// A Segment is 2 points.
|
|
/// When considered like a vector, it represents the arrow from a to b.
|
|
struct Segment(T, int N)
|
|
{
|
|
public
|
|
{
|
|
alias Vector!(T, N) point_t;
|
|
point_t a, b;
|
|
|
|
static if (N == 3 && isFloatingPoint!T)
|
|
{
|
|
/// Segment vs plane intersection.
|
|
/// See_also: "Geometry for Computer Graphics: Formulae, Examples
|
|
/// and Proofs", Vince (2005), p. 62
|
|
/// Returns: $(D true) if the segment crosses the plane exactly
|
|
/// once, or $(D false) if the segment is parallel to or does
|
|
/// not reach the plane
|
|
/// Params:
|
|
/// plane = The plane to intersect.
|
|
/// intersection = Point of intersection.
|
|
/// progress = Set to the point's progress between endpoints
|
|
/// $(D a) at 0.0 and $(D b) at 1.0, or T.infinity
|
|
/// if parallel to the plane
|
|
@nogc bool intersect(Plane!T plane, out point_t intersection, out T progress) pure const nothrow
|
|
{
|
|
// direction vector from a to b
|
|
point_t dir = b-a;
|
|
|
|
// dot product will be 0 if angle to plane is 0
|
|
T dp = dot(plane.n, dir);
|
|
if (abs(dp) < T.epsilon)
|
|
{
|
|
progress = T.infinity;
|
|
return false; // parallel to plane
|
|
}
|
|
|
|
// relative distance along segment where intersection happens
|
|
progress = -(dot(plane.n, a) - plane.d) / dp;
|
|
|
|
intersection = progress*dir + a;
|
|
return progress >= 0 && progress <= 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
alias Segment!(float, 2) seg2f; /// 2D float segment.
|
|
alias Segment!(float, 3) seg3f; /// 3D float segment.
|
|
alias Segment!(double, 2) seg2d; /// 2D double segment.
|
|
alias Segment!(double, 3) seg3d; /// 3D double segment.
|
|
alias Segment!(int, 2) seg2i; /// 2D integer segment.
|
|
alias Segment!(int, 3) seg3i; /// 3D integer segment.
|
|
|
|
/// A Triangle is 3 points.
|
|
struct Triangle(T, int N)
|
|
{
|
|
public
|
|
{
|
|
alias Vector!(T, N) point_t;
|
|
point_t a, b, c;
|
|
|
|
static if (N == 2)
|
|
{
|
|
/// Returns: Area of a 2D triangle.
|
|
@nogc T area() pure const nothrow
|
|
{
|
|
return abs(signedArea());
|
|
}
|
|
|
|
/// Returns: Signed area of a 2D triangle.
|
|
@nogc T signedArea() pure const nothrow
|
|
{
|
|
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;
|
|
}
|
|
}
|
|
|
|
static if (N == 3 && isFloatingPoint!T)
|
|
{
|
|
/// Returns: Triangle normal.
|
|
@nogc Vector!(T, 3) computeNormal() pure const nothrow
|
|
{
|
|
return cross(b - a, c - a).normalized();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
alias Triangle!(float, 2) triangle2f; /// 2D float triangle.
|
|
alias Triangle!(float, 3) triangle3f; /// 3D float triangle.
|
|
alias Triangle!(double, 2) triangle2d; /// 2D double triangle.
|
|
alias Triangle!(double, 3) triangle3d; /// 3D double triangle.
|
|
|
|
/// A Sphere is a point + a radius.
|
|
struct Sphere(T, int N)
|
|
{
|
|
public nothrow
|
|
{
|
|
alias Vector!(T, N) point_t;
|
|
point_t center;
|
|
T radius;
|
|
|
|
/// Creates a sphere from a point and a radius.
|
|
@nogc this(in point_t center_, T radius_) pure nothrow
|
|
{
|
|
center = center_;
|
|
radius = radius_;
|
|
}
|
|
|
|
/// Sphere contains point test.
|
|
/// Returns: true if the point is inside the sphere.
|
|
@nogc bool contains(in Sphere s) pure const nothrow
|
|
{
|
|
if (s.radius > radius)
|
|
return false;
|
|
|
|
T innerRadius = radius - s.radius;
|
|
return squaredDistanceTo(s.center) < innerRadius * innerRadius;
|
|
}
|
|
|
|
/// Sphere vs point Euclidean distance squared.
|
|
@nogc T squaredDistanceTo(point_t p) pure const nothrow
|
|
{
|
|
return center.squaredDistanceTo(p);
|
|
}
|
|
|
|
/// Sphere vs sphere intersection.
|
|
/// Returns: true if the spheres intersect.
|
|
@nogc bool intersects(Sphere s) pure const nothrow
|
|
{
|
|
T outerRadius = radius + s.radius;
|
|
return squaredDistanceTo(s.center) < outerRadius * outerRadius;
|
|
}
|
|
|
|
static if (isFloatingPoint!T)
|
|
{
|
|
/// Sphere vs point Euclidean distance.
|
|
@nogc T distanceTo(point_t p) pure const nothrow
|
|
{
|
|
return center.distanceTo(p);
|
|
}
|
|
|
|
static if(N == 2)
|
|
{
|
|
/// Returns: Circle area.
|
|
@nogc T area() pure const nothrow
|
|
{
|
|
return T(PI) * (radius * radius);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
alias Sphere!(float, 2) sphere2f; /// 2D float sphere (ie. a circle).
|
|
alias Sphere!(float, 3) sphere3f; /// 3D float sphere.
|
|
alias Sphere!(double, 2) sphere2d; /// 2D double sphere (ie. a circle).
|
|
alias Sphere!(double, 3) sphere3d; /// 3D double sphere (ie. a circle).
|
|
|
|
|
|
/// A Ray ir a point + a direction.
|
|
struct Ray(T, int N)
|
|
{
|
|
nothrow:
|
|
public
|
|
{
|
|
alias Vector!(T, N) point_t;
|
|
point_t orig;
|
|
point_t dir;
|
|
|
|
/// Returns: A point further along the ray direction.
|
|
@nogc point_t progress(T t) pure const nothrow
|
|
{
|
|
return orig + dir * t;
|
|
}
|
|
|
|
static if (N == 3 && isFloatingPoint!T)
|
|
{
|
|
/// Ray vs triangle intersection.
|
|
/// See_also: "Fast, Minimum Storage Ray/Triangle intersection", Mommer & Trumbore (1997)
|
|
/// Returns: Barycentric coordinates, the intersection point is at $(D (1 - u - v) * A + u * B + v * C).
|
|
@nogc bool intersect(Triangle!(T, 3) triangle, out T t, out T u, out T v) pure const nothrow
|
|
{
|
|
point_t edge1 = triangle.b - triangle.a;
|
|
point_t edge2 = triangle.c - triangle.a;
|
|
point_t pvec = cross(dir, edge2);
|
|
T det = dot(edge1, pvec);
|
|
if (abs(det) < T.epsilon)
|
|
return false; // no intersection
|
|
T invDet = 1 / det;
|
|
|
|
// calculate distance from triangle.a to ray origin
|
|
point_t tvec = orig - triangle.a;
|
|
|
|
// calculate U parameter and test bounds
|
|
u = dot(tvec, pvec) * invDet;
|
|
if (u < 0 || u > 1)
|
|
return false;
|
|
|
|
// prepare to test V parameter
|
|
point_t qvec = cross(tvec, edge1);
|
|
|
|
// calculate V parameter and test bounds
|
|
v = dot(dir, qvec) * invDet;
|
|
if (v < 0.0 || u + v > 1.0)
|
|
return false;
|
|
|
|
// calculate t, ray intersects triangle
|
|
t = dot(edge2, qvec) * invDet;
|
|
return true;
|
|
}
|
|
|
|
/// Ray vs plane intersection.
|
|
/// See_also: "Geometry for Computer Graphics: Formulae, Examples
|
|
/// and Proofs", Vince (2005), p. 62
|
|
/// Returns: $(D true) if the ray crosses the plane exactly once,
|
|
/// or $(D false) if the ray is parallel to or points away from
|
|
/// the plane
|
|
/// Params:
|
|
/// plane = Plane to intersect.
|
|
/// intersection = Point of intersection.
|
|
/// distance = set to the point's distance along the ray, or
|
|
/// T.infinity if parallel to the plane
|
|
@nogc bool intersect(Plane!T plane, out point_t intersection, out T distance) pure const nothrow
|
|
{
|
|
// dot product will be 0 if angle to plane is 0
|
|
T dp = dot(plane.n, dir);
|
|
if (abs(dp) < T.epsilon)
|
|
{
|
|
distance = T.infinity;
|
|
return false; // parallel to plane
|
|
}
|
|
|
|
// distance along ray where intersection happens
|
|
distance = -(dot(plane.n, orig) - plane.d) / dp;
|
|
|
|
intersection = distance*dir + orig;
|
|
return distance >= 0;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
alias Ray!(float, 2) ray2f; /// 2D float ray.
|
|
alias Ray!(float, 3) ray3f; /// 3D float ray.
|
|
alias Ray!(double, 2) ray2d; /// 2D double ray.
|
|
alias Ray!(double, 3) ray3d; /// 3D double ray.
|
|
|
|
|
|
/// 3D plane.
|
|
/// See_also: Flipcode article by Nate Miller $(WEB www.flipcode.com/archives/Plane_Class.shtml).
|
|
struct Plane(T) if (isFloatingPoint!T)
|
|
{
|
|
public
|
|
{
|
|
vec3!T n; /// Normal (always stored normalized).
|
|
T d;
|
|
|
|
/// Create from four coordinates.
|
|
@nogc this(vec4!T abcd) pure nothrow
|
|
{
|
|
n = vec3!T(abcd.x, abcd.y, abcd.z).normalized();
|
|
d = abcd.w;
|
|
}
|
|
|
|
/// Create from a point and a normal.
|
|
@nogc this(vec3!T origin, vec3!T normal) pure nothrow
|
|
{
|
|
n = normal.normalized();
|
|
d = -dot(origin, n);
|
|
}
|
|
|
|
/// Create from 3 non-aligned points.
|
|
@nogc this(vec3!T A, vec3!T B, vec3!T C) pure nothrow
|
|
{
|
|
this(C, cross(B - A, C - A));
|
|
}
|
|
|
|
/// Assign a plane with another plane.
|
|
@nogc ref Plane opAssign(Plane other) pure nothrow
|
|
{
|
|
n = other.n;
|
|
d = other.d;
|
|
return this;
|
|
}
|
|
|
|
/// Returns: signed distance between a point and the plane.
|
|
@nogc T signedDistanceTo(vec3!T point) pure const nothrow
|
|
{
|
|
return dot(n, point) + d;
|
|
}
|
|
|
|
/// Returns: absolute distance between a point and the plane.
|
|
@nogc T distanceTo(vec3!T point) pure const nothrow
|
|
{
|
|
return abs(signedDistanceTo(point));
|
|
}
|
|
|
|
/// Returns: true if the point is in front of the plane.
|
|
@nogc bool isFront(vec3!T point) pure const nothrow
|
|
{
|
|
return signedDistanceTo(point) >= 0;
|
|
}
|
|
|
|
/// Returns: true if the point is in the back of the plane.
|
|
@nogc bool isBack(vec3!T point) pure const nothrow
|
|
{
|
|
return signedDistanceTo(point) < 0;
|
|
}
|
|
|
|
/// Returns: true if the point is on the plane, with a given epsilon.
|
|
@nogc bool isOn(vec3!T point, T epsilon) pure const nothrow
|
|
{
|
|
T sd = signedDistanceTo(point);
|
|
return (-epsilon < sd) && (sd < epsilon);
|
|
}
|
|
}
|
|
}
|
|
|
|
alias Plane!float planef; /// 3D float plane.
|
|
alias Plane!double planed; /// 3D double plane.
|
|
|
|
unittest
|
|
{
|
|
auto p = planed(vec4d(1.0, 2.0, 3.0, 4.0));
|
|
auto p2 = planed(vec3d(1.0, 0.0, 0.0),
|
|
vec3d(0.0, 1.0, 0.0),
|
|
vec3d(0.0, 0.0, 1.0));
|
|
|
|
assert(p2.isOn(vec3d(1.0, 0.0, 0.0), 1e-7));
|
|
assert(p2.isFront(vec3d(1.0, 1.0, 1.0)));
|
|
assert(p2.isBack(vec3d(0.0, 0.0, 0.0)));
|
|
}
|
|
|
|
/// Plane intersection tests
|
|
@nogc pure nothrow unittest
|
|
{
|
|
void testR(planed p, ray3d r, bool shouldIntersect, double expectedDistance, vec3d expectedPoint = vec3d.init) pure nothrow @nogc
|
|
{
|
|
vec3d point;
|
|
double distance;
|
|
assert(r.intersect(p, point, distance) == shouldIntersect);
|
|
assert(approxEqual(distance, expectedDistance));
|
|
if (shouldIntersect)
|
|
assert(approxEqual(point.v[], expectedPoint.v[]));
|
|
}
|
|
// ray facing plane
|
|
testR(planed(vec4d(1.0, 0.0, 0.0, 1.0)), ray3d(vec3d(2.0, 3.0, 4.0), vec3d(-1.0, 0.0, 0.0)),
|
|
true, 1.0, vec3d(1.0, 3.0, 4.0));
|
|
testR(planed(vec4d(1.0, 1.0, 1.0, -sqrt(3.0))), ray3d(vec3d(1.0, 1.0, 1.0), vec3d(-1.0, -1.0, -1.0).normalized()),
|
|
true, 2.0*sqrt(3.0), vec3d(-1.0, -1.0, -1.0));
|
|
// ray facing away
|
|
testR(planed(vec4d(1.0, 0.0, 0.0, 1.0)), ray3d(vec3d(2.0, 3.0, 4.0), vec3d(1.0, 0.0, 0.0)),
|
|
false, -1.0);
|
|
testR(planed(vec4d(1.0, 0.0, 0.0, 5.0)), ray3d(vec3d(2.0, 3.0, 4.0), vec3d(-1.0, 0.0, 0.0)),
|
|
false, -3.0);
|
|
// ray parallel
|
|
testR(planed(vec4d(0.0, 0.0, 1.0, 1.0)), ray3d(vec3d(1.0, 2.0, 3.0), vec3d(1.0, 0.0, 0.0)),
|
|
false, double.infinity);
|
|
|
|
void testS(planed p, seg3d s, bool shouldIntersect, double expectedProgress, vec3d expectedPoint = vec3d.init) pure nothrow @nogc
|
|
{
|
|
vec3d point;
|
|
double progress;
|
|
assert(s.intersect(p, point, progress) == shouldIntersect);
|
|
assert(approxEqual(progress, expectedProgress));
|
|
if (shouldIntersect)
|
|
assert(approxEqual(point.v[], expectedPoint.v[]));
|
|
}
|
|
// segment crossing plane
|
|
testS(planed(vec4d(1.0, 0.0, 0.0, 2.0)), seg3d(vec3d(1.0, 2.0, 3.0), vec3d(3.0, 4.0, 5.0)),
|
|
true, 0.5, vec3d(2.0, 3.0, 4.0));
|
|
// segment too short
|
|
testS(planed(vec4d(1.0, 0.0, 0.0, 0.0)), seg3d(vec3d(1.0, 2.0, 3.0), vec3d(3.0, 4.0, 5.0)),
|
|
false, -0.5);
|
|
}
|
|
|
|
|
|
/// 3D frustum.
|
|
/// See_also: Flipcode article by Dion Picco $(WEB www.flipcode.com/archives/Frustum_Culling.shtml).
|
|
/// Bugs: verify proper signedness of half-spaces
|
|
struct Frustum(T) if (isFloatingPoint!T)
|
|
{
|
|
public
|
|
{
|
|
enum int LEFT = 0,
|
|
RIGHT = 1,
|
|
TOP = 2,
|
|
BOTTOM = 3,
|
|
NEAR = 4,
|
|
FAR = 5;
|
|
|
|
Plane!T[6] planes;
|
|
|
|
/// Create a frustum from 6 planes.
|
|
@nogc this(Plane!T left, Plane!T right, Plane!T top, Plane!T bottom, Plane!T near, Plane!T far) pure nothrow
|
|
{
|
|
planes[LEFT] = left;
|
|
planes[RIGHT] = right;
|
|
planes[TOP] = top;
|
|
planes[BOTTOM] = bottom;
|
|
planes[NEAR] = near;
|
|
planes[FAR] = far;
|
|
}
|
|
|
|
enum : int
|
|
{
|
|
OUTSIDE, /// object is outside the frustum
|
|
INTERSECT, /// object intersects with the frustum
|
|
INSIDE /// object is inside the frustum
|
|
}
|
|
|
|
/// Point vs frustum intersection.
|
|
@nogc bool contains(vec3!T point) pure const nothrow
|
|
{
|
|
for(int i = 0; i < 6; ++i)
|
|
{
|
|
T distance = planes[i].signedDistanceTo(point);
|
|
|
|
if(distance < 0)
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/// Sphere vs frustum intersection.
|
|
/// Returns: Frustum.OUTSIDE, Frustum.INTERSECT or Frustum.INSIDE.
|
|
@nogc int contains(Sphere!(T, 3) sphere) pure const nothrow
|
|
{
|
|
// calculate our distances to each of the planes
|
|
for(int i = 0; i < 6; ++i)
|
|
{
|
|
// find the distance to this plane
|
|
T distance = planes[i].signedDistanceTo(sphere.center);
|
|
|
|
if(distance < -sphere.radius)
|
|
return OUTSIDE;
|
|
|
|
else if (distance < sphere.radius)
|
|
return INTERSECT;
|
|
}
|
|
|
|
// otherwise we are fully in view
|
|
return INSIDE;
|
|
}
|
|
|
|
/// AABB vs frustum intersection.
|
|
/// Returns: Frustum.OUTSIDE, Frustum.INTERSECT or Frustum.INSIDE.
|
|
@nogc int contains(box3!T box) pure const nothrow
|
|
{
|
|
vec3!T[8] corners;
|
|
int totalIn = 0;
|
|
|
|
for (int i = 0; i < 2; ++i)
|
|
for (int j = 0; j < 2; ++j)
|
|
for (int k = 0; k < 2; ++k)
|
|
{
|
|
auto x = i == 0 ? box.min.x : box.max.x;
|
|
auto y = j == 0 ? box.min.y : box.max.y;
|
|
auto z = k == 0 ? box.min.z : box.max.z;
|
|
corners[i*4 + j*2 + k] = vec3!T(x, y, z);
|
|
}
|
|
|
|
// test all 8 corners against the 6 sides
|
|
// if all points are behind 1 specific plane, we are out
|
|
// if we are in with all points, then we are fully in
|
|
for(int p = 0; p < 6; ++p)
|
|
{
|
|
int inCount = 8;
|
|
int ptIn = 1;
|
|
|
|
for(int i = 0; i < 8; ++i)
|
|
{
|
|
// test this point against the planes
|
|
if (planes[p].isBack(corners[i]))
|
|
{
|
|
ptIn = 0;
|
|
--inCount;
|
|
}
|
|
}
|
|
|
|
// were all the points outside of plane p?
|
|
if (inCount == 0)
|
|
return OUTSIDE;
|
|
|
|
// check if they were all on the right side of the plane
|
|
totalIn += ptIn;
|
|
}
|
|
|
|
// so if totalIn is 6, then all are inside the view
|
|
if(totalIn == 6)
|
|
return INSIDE;
|
|
|
|
// we must be partly in then otherwise
|
|
return INTERSECT;
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
unittest
|
|
{
|
|
seg2f se;
|
|
triangle3f tr;
|
|
Frustum!double frust;
|
|
planed pl;
|
|
}
|
|
|
|
/// True if `T` is a kind of Segment
|
|
enum isSegment(T) = is(T : Segment!U, U...);
|
|
|
|
/// True if `T` is a kind of Triangle
|
|
enum isTriangle(T) = is(T : Triangle!U, U...);
|
|
|
|
/// True if `T` is a kind of Sphere
|
|
enum isSphere(T) = is(T : Sphere!U, U...);
|
|
|
|
/// True if `T` is a kind of Ray
|
|
enum isRay(T) = is(T : Ray!U, U...);
|
|
|
|
/// True if `T` is a kind of Plane
|
|
enum isPlane(T) = is(T : Plane!U, U);
|
|
|
|
/// True if `T` is a kind of Frustum
|
|
enum isFrustum(T) = is(T : Frustum!U, U);
|
|
|
|
/// True if `T` is a kind of 2 dimensional Segment
|
|
enum isSegment2D(T) = is(T : Segment!(U, 2), U);
|
|
|
|
/// True if `T` is a kind of 2 dimensional Triangle
|
|
enum isTriangle2D(T) = is(T : Triangle!(U, 2), U);
|
|
|
|
/// True if `T` is a kind of 2 dimensional Sphere
|
|
enum isSphere2D(T) = is(T : Sphere!(U, 2), U);
|
|
|
|
/// True if `T` is a kind of 2 dimensional Ray
|
|
enum isRay2D(T) = is(T : Ray!(U, 2), U);
|
|
|
|
/// True if `T` is a kind of 3 dimensional Segment
|
|
enum isSegment3D(T) = is(T : Segment!(U, 3), U);
|
|
|
|
/// True if `T` is a kind of 3 dimensional Triangle
|
|
enum isTriangle3D(T) = is(T : Triangle!(U, 3), U);
|
|
|
|
/// True if `T` is a kind of 3 dimensional Sphere
|
|
enum isSphere3D(T) = is(T : Sphere!(U, 3), U);
|
|
|
|
/// True if `T` is a kind of 3 dimensional Ray
|
|
enum isRay3D(T) = is(T : Ray!(U, 3), U);
|
|
|
|
unittest
|
|
{
|
|
enum ShapeType
|
|
{
|
|
segment,
|
|
triangle,
|
|
sphere,
|
|
ray,
|
|
plane,
|
|
frustum
|
|
}
|
|
|
|
void test(T, ShapeType type, int dim)()
|
|
{
|
|
with (ShapeType)
|
|
{
|
|
static assert(isSegment!T == (type == segment ));
|
|
static assert(isTriangle!T == (type == triangle));
|
|
static assert(isSphere!T == (type == sphere ));
|
|
static assert(isRay!T == (type == ray ));
|
|
static assert(isPlane!T == (type == plane ));
|
|
static assert(isFrustum!T == (type == frustum ));
|
|
|
|
static assert(isSegment2D!T == (type == segment && dim == 2));
|
|
static assert(isTriangle2D!T == (type == triangle && dim == 2));
|
|
static assert(isSphere2D!T == (type == sphere && dim == 2));
|
|
static assert(isRay2D!T == (type == ray && dim == 2));
|
|
|
|
static assert(isSegment3D!T == (type == segment && dim == 3));
|
|
static assert(isTriangle3D!T == (type == triangle && dim == 3));
|
|
static assert(isSphere3D!T == (type == sphere && dim == 3));
|
|
static assert(isRay3D!T == (type == ray && dim == 3));
|
|
}
|
|
}
|
|
|
|
with (ShapeType)
|
|
{
|
|
// test case type #dimensions
|
|
test!(seg2f , segment , 2);
|
|
test!(seg3d , segment , 3);
|
|
test!(triangle2d , triangle, 2);
|
|
test!(triangle3f , triangle, 3);
|
|
test!(sphere2d , sphere , 2);
|
|
test!(Sphere!(uint, 3), sphere , 3);
|
|
test!(ray2f , ray , 2);
|
|
test!(Ray!(real, 3) , ray , 3);
|
|
test!(planed , plane , 0); // ignore dimension (always 3D)
|
|
test!(Plane!float , plane , 0);
|
|
test!(Frustum!double , frustum , 0);
|
|
}
|
|
}
|
|
|
|
/// Get the numeric type used to measure a shape's dimensions.
|
|
alias DimensionType(T : Segment!U, U...) = U[0];
|
|
/// ditto
|
|
alias DimensionType(T : Triangle!U, U...) = U[0];
|
|
/// ditto
|
|
alias DimensionType(T : Sphere!U, U...) = U[0];
|
|
/// ditto
|
|
alias DimensionType(T : Ray!U, U...) = U[0];
|
|
/// ditto
|
|
alias DimensionType(T : Plane!U, U) = U;
|
|
/// ditto
|
|
alias DimensionType(T : Frustum!U, U) = U;
|
|
|
|
///
|
|
unittest
|
|
{
|
|
static assert(is(DimensionType!seg2i == int));
|
|
static assert(is(DimensionType!triangle3d == double));
|
|
static assert(is(DimensionType!sphere2d == double));
|
|
static assert(is(DimensionType!ray3f == float));
|
|
static assert(is(DimensionType!planed == double));
|
|
static assert(is(DimensionType!(Frustum!real) == real));
|
|
}
|