/** \file G3D.lib/source/AABox.cpp \maintainer Morgan McGuire, http://graphics.cs.williams.edu \created 2004-01-10 \edited 2013-06-11 Copyright 2000-2013, Morgan McGuire. All rights reserved. */ #include "G3D/platform.h" #include "G3D/AABox.h" #include "G3D/Box.h" #include "G3D/Plane.h" #include "G3D/Sphere.h" #include "G3D/BinaryInput.h" #include "G3D/BinaryOutput.h" #include "G3D/Any.h" namespace G3D { AABox::AABox(const Any& a) { if (a.name() == "AABox::empty") { *this = AABox::empty(); } else if (a.name() == "AABox::inf") { *this = AABox::inf(); } else { a.verifyName("AABox"); a.verifyType(Any::ARRAY); if (a.size() == 1) { *this = AABox(Point3(a[0])); } else if (a.size() == 2) { set(Point3(a[0]), Point3(a[1])); } else { a.verify(false, "AABox must recieve exactly 1 or two arguments."); } } } Any AABox::toAny() const { if (isEmpty()) { return Any(Any::ARRAY, "AABox::empty"); } else if (! isFinite()) { return Any(Any::ARRAY, "AABox::inf"); } else { Any a(Any::ARRAY, "AABox"); if (lo == hi) { a.append(lo); } else { a.append(lo, hi); } return a; } } const AABox& AABox::empty() { static const AABox b; return b; } const AABox& AABox::maxFinite() { static const AABox b = AABox(Vector3::minFinite(), Vector3::maxFinite()); return b; } const AABox& AABox::large() { static const AABox b = AABox(Vector3::minFinite() * 0.5f, Vector3::maxFinite() * 0.5f); return b; } const AABox& AABox::inf() { static const AABox b = AABox(-Vector3::inf(), Vector3::inf()); return b; } const AABox& AABox::zero() { static const AABox b = AABox(Vector3::zero(), Vector3::zero()); return b; } void AABox::serialize(class BinaryOutput& b) const { b.writeVector3(lo); b.writeVector3(hi); } void AABox::deserialize(class BinaryInput& b) { lo = b.readVector3(); hi = b.readVector3(); } void AABox::merge(const Box& b) { AABox aab; b.getBounds(aab); merge(aab); } void AABox::split(const Vector3::Axis& axis, float location, AABox& low, AABox& high) const { // Low, medium, and high along the chosen axis float L = G3D::min(location, lo[axis]); float M = G3D::min(G3D::max(location, lo[axis]), hi[axis]); float H = G3D::max(location, hi[axis]); // Copy over this box. high = low = *this; // Now move the split points along the special axis low.lo[axis] = L; low.hi[axis] = M; high.lo[axis] = M; high.hi[axis] = H; } Vector3 AABox::randomSurfacePoint() const { Vector3 extent = hi - lo; float aXY = extent.x * extent.y; float aYZ = extent.y * extent.z; float aZX = extent.z * extent.x; float r = (float)uniformRandom(0.0f, aXY + aYZ + aZX); // Choose evenly between positive and negative face planes float d = ((float)uniformRandom(0, 1) < 0.5f) ? 0.0f : 1.0f; // The probability of choosing a given face is proportional to // its area. if (r < aXY) { return lo + Vector3( (float)uniformRandom(0.0f, extent.x), (float)uniformRandom(0.0f, extent.y), d * extent.z); } else if (r < aYZ) { return lo + Vector3( d * extent.x, (float)uniformRandom(0, extent.y), (float)uniformRandom(0, extent.z)); } else { return lo + Vector3( (float)uniformRandom(0, extent.x), d * extent.y, (float)uniformRandom(0, extent.z)); } } Vector3 AABox::randomInteriorPoint() const { return Vector3( (float)uniformRandom(lo.x, hi.x), (float)uniformRandom(lo.y, hi.y), (float)uniformRandom(lo.z, hi.z)); } bool AABox::intersects(const AABox& other) const { // Must be overlap along all three axes. // Try to find a separating axis. for (int a = 0; a < 3; ++a) { // |--------| // |------| if ((lo[a] > other.hi[a]) || (hi[a] < other.lo[a])) { return false; } } return true; } int AABox::dummy = 0; bool AABox::culledBy (const Array& plane, int& cullingPlane, const uint32 _inMask, uint32& childMask) const { uint32 inMask = _inMask; assert(plane.size() < 31); childMask = 0; const bool finite = (abs(lo.x) < G3D::finf()) && (abs(hi.x) < G3D::finf()) && (abs(lo.y) < G3D::finf()) && (abs(hi.y) < G3D::finf()) && (abs(lo.z) < G3D::finf()) && (abs(hi.z) < G3D::finf()); // See if there is one plane for which all of the // vertices are in the negative half space. for (int p = 0; p < plane.size(); ++p) { // Only test planes that are not masked if ((inMask & 1) != 0) { Vector3 corner; int numContained = 0; int v = 0; // We can early-out only if we have found one point on each // side of the plane (i.e. if we are straddling). That // occurs when (numContained < v) && (numContained > 0) for (v = 0; (v < 8) && ((numContained == v) || (numContained == 0)); ++v) { // Unrolling these 3 if's into a switch decreases performance // by about 2x corner.x = (v & 1) ? hi.x : lo.x; corner.y = (v & 2) ? hi.y : lo.y; corner.z = (v & 4) ? hi.z : lo.z; if (finite) { // this branch is highly predictable if (plane[p].halfSpaceContainsFinite(corner)) { ++numContained; } } else { if (plane[p].halfSpaceContains(corner)) { ++numContained; } } } if (numContained == 0) { // Plane p culled the box cullingPlane = p; // The caller should not recurse into the children, // since the parent is culled. If they do recurse, // make them only test against this one plane, which // will immediately cull the volume. childMask = 1 << p; return true; } else if (numContained < v) { // The bounding volume straddled the plane; we have // to keep testing against this plane childMask |= (1 << p); } } // Move on to the next bit. inMask = inMask >> 1; } // None of the planes could cull this box cullingPlane = -1; return false; } bool AABox::culledBy( const Array& plane, int& cullingPlane, const uint32 _inMask) const { uint32 inMask = _inMask; assert(plane.size() < 31); const bool finite = (abs(lo.x) < G3D::finf()) && (abs(hi.x) < G3D::finf()) && (abs(lo.y) < G3D::finf()) && (abs(hi.y) < G3D::finf()) && (abs(lo.z) < G3D::finf()) && (abs(hi.z) < G3D::finf()); // See if there is one plane for which all of the // vertices are in the negative half space. for (int p = 0; p < plane.size(); ++p) { // Only test planes that are not masked if ((inMask & 1) != 0) { bool culled = true; Vector3 corner; int v; // Assume this plane culls all points. See if there is a point // not culled by the plane... early out when at least one point // is in the positive half space. for (v = 0; (v < 8) && culled; ++v) { // Unrolling these 3 if's into a switch decreases performance // by about 2x corner.x = (v & 1) ? hi.x : lo.x; corner.y = (v & 2) ? hi.y : lo.y; corner.z = (v & 4) ? hi.z : lo.z; if (finite) { // this branch is highly predictable culled = ! plane[p].halfSpaceContainsFinite(corner); } else { culled = ! plane[p].halfSpaceContains(corner); } } if (culled) { // Plane p culled the box cullingPlane = p; return true; } } // Move on to the next bit. inMask = inMask >> 1; } // None of the planes could cull this box cullingPlane = -1; return false; } void AABox::getBounds(Sphere& s) const { s.center = center(); s.radius = extent().length() / 2; } bool AABox::intersects(const Sphere& sphere) const { double d = 0; //find the square of the distance //from the sphere to the box for (int i = 0; i < 3; ++i) { if (sphere.center[i] < lo[i]) { d += square(sphere.center[i] - lo[i]); } else if (sphere.center[i] > hi[i]) { d += square(sphere.center[i] - hi[i]); } } return d <= square(sphere.radius); } Vector3 AABox::corner(int index) const { // default constructor inits all components to 0 Vector3 v; switch (index) { case 0: v.x = lo.x; v.y = lo.y; v.z = hi.z; break; case 1: v.x = hi.x; v.y = lo.y; v.z = hi.z; break; case 2: v.x = hi.x; v.y = hi.y; v.z = hi.z; break; case 3: v.x = lo.x; v.y = hi.y; v.z = hi.z; break; case 4: v.x = lo.x; v.y = lo.y; v.z = lo.z; break; case 5: v.x = hi.x; v.y = lo.y; v.z = lo.z; break; case 6: v.x = hi.x; v.y = hi.y; v.z = lo.z; break; case 7: v.x = lo.x; v.y = hi.y; v.z = lo.z; break; default: debugAssertM(false, "Invalid corner index"); break; } return v; } }