// // AABox.cpp // libraries/octree/src // // Created by Brad Hefta-Gaub on 04/11/13. // Copyright 2013 High Fidelity, Inc. // // Distributed under the Apache License, Version 2.0. // See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html // #include "AABox.h" #include "AACube.h" #include "Transform.h" #include "Extents.h" #include "GeometryUtil.h" #include "NumericalConstants.h" const glm::vec3 AABox::INFINITY_VECTOR(std::numeric_limits::infinity()); AABox::AABox(const AACube& other) : _corner(other.getCorner()), _scale(other.getScale(), other.getScale(), other.getScale()) { } AABox::AABox(const Extents& other) : _corner(other.minimum), _scale(other.maximum - other.minimum) { } AABox::AABox(const glm::vec3& corner, float size) : _corner(corner), _scale(size, size, size) { }; AABox::AABox(const glm::vec3& corner, const glm::vec3& dimensions) : _corner(corner), _scale(dimensions) { }; AABox::AABox() : _corner(INFINITY_VECTOR), _scale(0.0f) { }; glm::vec3 AABox::calcCenter() const { glm::vec3 center(_corner); center += (_scale * 0.5f); return center; } glm::vec3 AABox::getVertex(BoxVertex vertex) const { switch (vertex) { case BOTTOM_LEFT_NEAR: return _corner + glm::vec3(_scale.x, 0, 0); case BOTTOM_RIGHT_NEAR: return _corner; case TOP_RIGHT_NEAR: return _corner + glm::vec3(0, _scale.y, 0); case TOP_LEFT_NEAR: return _corner + glm::vec3(_scale.x, _scale.y, 0); case BOTTOM_LEFT_FAR: return _corner + glm::vec3(_scale.x, 0, _scale.z); case BOTTOM_RIGHT_FAR: return _corner + glm::vec3(0, 0, _scale.z); case TOP_RIGHT_FAR: return _corner + glm::vec3(0, _scale.y, _scale.z); default: //quiet windows warnings case TOP_LEFT_FAR: return _corner + _scale; } } void AABox::setBox(const glm::vec3& corner, float scale) { _corner = corner; _scale = glm::vec3(scale, scale, scale); } void AABox::setBox(const glm::vec3& corner, const glm::vec3& scale) { _corner = corner; _scale = scale; } glm::vec3 AABox::getFarthestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; // This is a branchless version of: //if (normal.x > 0.0f) { // result.x += _scale.x; //} //if (normal.y > 0.0f) { // result.y += _scale.y; //} //if (normal.z > 0.0f) { // result.z += _scale.z; //} float blend = (float)(normal.x > 0.0f); result.x += blend * _scale.x + (1.0f - blend) * 0.0f; blend = (float)(normal.y > 0.0f); result.y += blend * _scale.y + (1.0f - blend) * 0.0f; blend = (float)(normal.z > 0.0f); result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } glm::vec3 AABox::getNearestVertex(const glm::vec3& normal) const { glm::vec3 result = _corner; // This is a branchless version of: //if (normal.x < 0.0f) { // result.x += _scale.x; //} //if (normal.y < 0.0f) { // result.y += _scale.y; //} //if (normal.z < 0.0f) { // result.z += _scale.z; //} float blend = (float)(normal.x < 0.0f); result.x += blend * _scale.x + (1.0f - blend) * 0.0f; blend = (float)(normal.y < 0.0f); result.y += blend * _scale.y + (1.0f - blend) * 0.0f; blend = (float)(normal.z < 0.0f); result.z += blend * _scale.z + (1.0f - blend) * 0.0f; return result; } bool AABox::contains(const Triangle& triangle) const { return contains(triangle.v0) && contains(triangle.v1) && contains(triangle.v2); } bool AABox::contains(const glm::vec3& point) const { return aaBoxContains(point, _corner, _scale); } bool AABox::contains(const AABox& otherBox) const { for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) { glm::vec3 vertex = otherBox.getVertex((BoxVertex)v); if (!contains(vertex)) { return false; } } return true; } bool AABox::touches(const AABox& otherBox) const { glm::vec3 relativeCenter = _corner - otherBox._corner + ((_scale - otherBox._scale) * 0.5f); glm::vec3 totalHalfScale = (_scale + otherBox._scale) * 0.5f; return fabsf(relativeCenter.x) <= totalHalfScale.x && fabsf(relativeCenter.y) <= totalHalfScale.y && fabsf(relativeCenter.z) <= totalHalfScale.z; } bool AABox::contains(const AACube& otherCube) const { for (int v = BOTTOM_LEFT_NEAR; v < TOP_LEFT_FAR; v++) { glm::vec3 vertex = otherCube.getVertex((BoxVertex)v); if (!contains(vertex)) { return false; } } return true; } bool AABox::touches(const AACube& otherCube) const { glm::vec3 relativeCenter = _corner - otherCube.getCorner() + ((_scale - otherCube.getDimensions()) * 0.5f); glm::vec3 totalHalfScale = (_scale + otherCube.getDimensions()) * 0.5f; return fabsf(relativeCenter.x) <= totalHalfScale.x && fabsf(relativeCenter.y) <= totalHalfScale.y && fabsf(relativeCenter.z) <= totalHalfScale.z; } // determines whether a value is within the expanded extents static bool isWithinExpanded(float value, float corner, float size, float expansion) { return value >= corner - expansion && value <= corner + size + expansion; } bool AABox::expandedContains(const glm::vec3& point, float expansion) const { return isWithinExpanded(point.x, _corner.x, _scale.x, expansion) && isWithinExpanded(point.y, _corner.y, _scale.y, expansion) && isWithinExpanded(point.z, _corner.z, _scale.z, expansion); } bool AABox::expandedIntersectsSegment(const glm::vec3& start, const glm::vec3& end, float expansion) const { // handle the trivial cases where the expanded box contains the start or end if (expandedContains(start, expansion) || expandedContains(end, expansion)) { return true; } // check each axis glm::vec3 expandedCorner = _corner - glm::vec3(expansion, expansion, expansion); glm::vec3 expandedSize = _scale + glm::vec3(expansion, expansion, expansion) * 2.0f; glm::vec3 direction = end - start; float axisDistance; return (findIntersection(start.x, direction.x, expandedCorner.x, expandedSize.x, axisDistance) && axisDistance >= 0.0f && axisDistance <= 1.0f && isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) && isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) || (findIntersection(start.y, direction.y, expandedCorner.y, expandedSize.y, axisDistance) && axisDistance >= 0.0f && axisDistance <= 1.0f && isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x) && isWithin(start.z + axisDistance*direction.z, expandedCorner.z, expandedSize.z)) || (findIntersection(start.z, direction.z, expandedCorner.z, expandedSize.z, axisDistance) && axisDistance >= 0.0f && axisDistance <= 1.0f && isWithin(start.y + axisDistance*direction.y, expandedCorner.y, expandedSize.y) && isWithin(start.x + axisDistance*direction.x, expandedCorner.x, expandedSize.x)); } bool AABox::findRayIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, float& distance, BoxFace& face, glm::vec3& surfaceNormal) const { return findRayAABoxIntersection(origin, direction, invDirection, _corner, _scale, distance, face, surfaceNormal); } bool AABox::findParabolaIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) const { return findParabolaAABoxIntersection(origin, velocity, acceleration, _corner, _scale, parabolicDistance, face, surfaceNormal); } bool AABox::rayHitsBoundingSphere(const glm::vec3& origin, const glm::vec3& direction) const { glm::vec3 localCenter = calcCenter() - origin; float distance = glm::dot(localCenter, direction); const float ONE_OVER_TWO_SQUARED = 0.25f; float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); return (glm::length2(localCenter) < radiusSquared || (glm::abs(distance) > 0.0f && glm::distance2(distance * direction, localCenter) < radiusSquared)); } bool AABox::parabolaPlaneIntersectsBoundingSphere(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& normal) const { glm::vec3 localCenter = calcCenter() - origin; const float ONE_OVER_TWO_SQUARED = 0.25f; float radiusSquared = ONE_OVER_TWO_SQUARED * glm::length2(_scale); // origin is inside the sphere if (glm::length2(localCenter) < radiusSquared) { return true; } if (glm::length2(acceleration) < EPSILON) { // Handle the degenerate case where acceleration == (0, 0, 0) return rayHitsBoundingSphere(origin, glm::normalize(velocity)); } else { // Project vector from plane to sphere center onto the normal float distance = glm::dot(localCenter, normal); if (distance * distance < radiusSquared) { return true; } } return false; } bool AABox::touchesSphere(const glm::vec3& center, float radius) const { // Avro's algorithm from this paper: http://www.mrtc.mdh.se/projects/3Dgraphics/paperF.pdf glm::vec3 e = glm::max(_corner - center, Vectors::ZERO) + glm::max(center - _corner - _scale, Vectors::ZERO); return glm::length2(e) <= radius * radius; } bool AABox::findSpherePenetration(const glm::vec3& center, float radius, glm::vec3& penetration) const { glm::vec4 center4 = glm::vec4(center, 1.0f); float minPenetrationLength = FLT_MAX; for (int i = 0; i < FACE_COUNT; i++) { glm::vec4 facePlane = getPlane((BoxFace)i); glm::vec3 vector = getClosestPointOnFace(center, (BoxFace)i) - center; if (glm::dot(center4, getPlane((BoxFace)i)) >= 0.0f) { // outside this face, so use vector to closest point to determine penetration return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration); } float vectorLength = glm::length(vector); if (vectorLength < minPenetrationLength) { // remember the smallest penetration vector; if we're inside all faces, we'll use that penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius : vector * ((vectorLength + radius) / -vectorLength); minPenetrationLength = vectorLength; } } return true; } bool AABox::findCapsulePenetration(const glm::vec3& start, const glm::vec3& end, float radius, glm::vec3& penetration) const { glm::vec4 start4 = glm::vec4(start, 1.0f); glm::vec4 end4 = glm::vec4(end, 1.0f); glm::vec4 startToEnd = glm::vec4(end - start, 0.0f); float minPenetrationLength = FLT_MAX; for (int i = 0; i < FACE_COUNT; i++) { // find the vector from the segment to the closest point on the face (starting from deeper end) glm::vec4 facePlane = getPlane((BoxFace)i); glm::vec3 closest = (glm::dot(start4, facePlane) <= glm::dot(end4, facePlane)) ? getClosestPointOnFace(start4, startToEnd, (BoxFace)i) : getClosestPointOnFace(end4, -startToEnd, (BoxFace)i); glm::vec3 vector = -computeVectorFromPointToSegment(closest, start, end); if (glm::dot(vector, glm::vec3(facePlane)) < 0.0f) { // outside this face, so use vector to closest point to determine penetration return ::findSpherePenetration(vector, glm::vec3(-facePlane), radius, penetration); } float vectorLength = glm::length(vector); if (vectorLength < minPenetrationLength) { // remember the smallest penetration vector; if we're inside all faces, we'll use that penetration = (vectorLength < EPSILON) ? glm::vec3(-facePlane) * radius : vector * ((vectorLength + radius) / -vectorLength); minPenetrationLength = vectorLength; } } return true; } glm::vec3 AABox::getClosestPointOnFace(const glm::vec3& point, BoxFace face) const { switch (face) { case MIN_X_FACE: return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), glm::vec3(_corner.x, _corner.y + _scale.y, _corner.z + _scale.z)); case MAX_X_FACE: return glm::clamp(point, glm::vec3(_corner.x + _scale.x, _corner.y, _corner.z), glm::vec3(_corner.x + _scale.x, _corner.y + _scale.y, _corner.z + _scale.z)); case MIN_Y_FACE: return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), glm::vec3(_corner.x + _scale.x, _corner.y, _corner.z + _scale.z)); case MAX_Y_FACE: return glm::clamp(point, glm::vec3(_corner.x, _corner.y + _scale.y, _corner.z), glm::vec3(_corner.x + _scale.x, _corner.y + _scale.y, _corner.z + _scale.z)); case MIN_Z_FACE: return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z), glm::vec3(_corner.x + _scale.x, _corner.y + _scale.y, _corner.z)); default: //quiet windows warnings case MAX_Z_FACE: return glm::clamp(point, glm::vec3(_corner.x, _corner.y, _corner.z + _scale.z), glm::vec3(_corner.x + _scale.x, _corner.y + _scale.y, _corner.z + _scale.z)); } } glm::vec3 AABox::getClosestPointOnFace(const glm::vec4& origin, const glm::vec4& direction, BoxFace face) const { // check against the four planes that border the face BoxFace oppositeFace = getOppositeFace(face); bool anyOutside = false; for (int i = 0; i < FACE_COUNT; i++) { if (i == face || i == oppositeFace) { continue; } glm::vec4 iPlane = getPlane((BoxFace)i); float originDistance = glm::dot(origin, iPlane); if (originDistance < 0.0f) { continue; // inside the border } anyOutside = true; float divisor = glm::dot(direction, iPlane); if (fabsf(divisor) < EPSILON) { continue; // segment is parallel to plane } // find intersection and see if it lies within face bounds float directionalDistance = -originDistance / divisor; glm::vec4 intersection = origin + direction * directionalDistance; BoxFace iOppositeFace = getOppositeFace((BoxFace)i); for (int j = 0; j < FACE_COUNT; j++) { if (j == face || j == oppositeFace || j == i || j == iOppositeFace) { continue; } if (glm::dot(intersection, getPlane((BoxFace)j)) > 0.0f) { goto outerContinue; // intersection is out of bounds } } return getClosestPointOnFace(glm::vec3(intersection), face); outerContinue: ; } // if we were outside any of the sides, we must check against the diagonals if (anyOutside) { int faceAxis = face / 2; int secondAxis = (faceAxis + 1) % 3; int thirdAxis = (faceAxis + 2) % 3; glm::vec4 secondAxisMinPlane = getPlane((BoxFace)(secondAxis * 2)); glm::vec4 secondAxisMaxPlane = getPlane((BoxFace)(secondAxis * 2 + 1)); glm::vec4 thirdAxisMaxPlane = getPlane((BoxFace)(thirdAxis * 2 + 1)); glm::vec4 offset = glm::vec4(0.0f, 0.0f, 0.0f, glm::dot(glm::vec3(secondAxisMaxPlane + thirdAxisMaxPlane), _scale) * 0.5f); glm::vec4 diagonals[] = { secondAxisMinPlane + thirdAxisMaxPlane + offset, secondAxisMaxPlane + thirdAxisMaxPlane + offset }; float minDistance = FLT_MAX; for (size_t i = 0; i < sizeof(diagonals) / sizeof(diagonals[0]); i++) { float divisor = glm::dot(direction, diagonals[i]); if (fabsf(divisor) < EPSILON) { continue; // segment is parallel to diagonal plane } minDistance = glm::min(-glm::dot(origin, diagonals[i]) / divisor, minDistance); } if (minDistance != FLT_MAX) { return getClosestPointOnFace(glm::vec3(origin + direction * minDistance), face); } } // last resort or all inside: clamp origin to face return getClosestPointOnFace(glm::vec3(origin), face); } bool AABox::touchesAAEllipsoid(const glm::vec3& center, const glm::vec3& radials) const { // handle case where ellipsoid's alix-aligned box doesn't touch this AABox if (_corner.x - radials.x > center.x || _corner.y - radials.y > center.y || _corner.z - radials.z > center.z || _corner.x + _scale.x + radials.x < center.x || _corner.y + _scale.y + radials.y < center.y || _corner.z + _scale.z + radials.z < center.z) { return false; } // handle case where ellipsoid is entirely inside this AABox if (contains(center)) { return true; } for (int i = 0; i < FACE_COUNT; i++) { glm::vec3 closest = getClosestPointOnFace(center, (BoxFace)i) - center; float x = closest.x; float y = closest.y; float z = closest.z; float a = radials.x; float b = radials.y; float c = radials.z; if (x*x/(a*a) + y*y/(b*b) + z*z/(c*c) < 1.0f) { return true; } } return false; } glm::vec4 AABox::getPlane(BoxFace face) const { switch (face) { case MIN_X_FACE: return glm::vec4(-1.0f, 0.0f, 0.0f, _corner.x); case MAX_X_FACE: return glm::vec4(1.0f, 0.0f, 0.0f, -_corner.x - _scale.x); case MIN_Y_FACE: return glm::vec4(0.0f, -1.0f, 0.0f, _corner.y); case MAX_Y_FACE: return glm::vec4(0.0f, 1.0f, 0.0f, -_corner.y - _scale.y); case MIN_Z_FACE: return glm::vec4(0.0f, 0.0f, -1.0f, _corner.z); default: //quiet windows warnings case MAX_Z_FACE: return glm::vec4(0.0f, 0.0f, 1.0f, -_corner.z - _scale.z); } } BoxFace AABox::getOppositeFace(BoxFace face) { switch (face) { case MIN_X_FACE: return MAX_X_FACE; case MAX_X_FACE: return MIN_X_FACE; case MIN_Y_FACE: return MAX_Y_FACE; case MAX_Y_FACE: return MIN_Y_FACE; case MIN_Z_FACE: return MAX_Z_FACE; default: //quiet windows warnings case MAX_Z_FACE: return MIN_Z_FACE; } } AABox AABox::clamp(const glm::vec3& min, const glm::vec3& max) const { glm::vec3 clampedCorner = glm::clamp(_corner, min, max); glm::vec3 clampedTopFarLeft = glm::clamp(calcTopFarLeft(), min, max); glm::vec3 clampedScale = clampedTopFarLeft - clampedCorner; return AABox(clampedCorner, clampedScale); } AABox AABox::clamp(float min, float max) const { glm::vec3 clampedCorner = glm::clamp(_corner, min, max); glm::vec3 clampedTopFarLeft = glm::clamp(calcTopFarLeft(), min, max); glm::vec3 clampedScale = clampedTopFarLeft - clampedCorner; return AABox(clampedCorner, clampedScale); } void AABox::embiggen(float scale) { _corner += scale * (-0.5f * _scale); _scale *= scale; } void AABox::embiggen(const glm::vec3& scale) { _corner += scale * (-0.5f * _scale); _scale *= scale; } void AABox::setScaleStayCentered(const glm::vec3& scale) { _corner += -0.5f * scale; _scale = scale; } void AABox::scale(float scale) { _corner *= scale; _scale *= scale; } void AABox::scale(const glm::vec3& scale) { _corner *= scale; _scale *= scale; } void AABox::rotate(const glm::quat& rotation) { auto minimum = _corner; auto maximum = _corner + _scale; glm::vec3 bottomLeftNear(minimum.x, minimum.y, minimum.z); glm::vec3 bottomRightNear(maximum.x, minimum.y, minimum.z); glm::vec3 bottomLeftFar(minimum.x, minimum.y, maximum.z); glm::vec3 bottomRightFar(maximum.x, minimum.y, maximum.z); glm::vec3 topLeftNear(minimum.x, maximum.y, minimum.z); glm::vec3 topRightNear(maximum.x, maximum.y, minimum.z); glm::vec3 topLeftFar(minimum.x, maximum.y, maximum.z); glm::vec3 topRightFar(maximum.x, maximum.y, maximum.z); glm::vec3 bottomLeftNearRotated = rotation * bottomLeftNear; glm::vec3 bottomRightNearRotated = rotation * bottomRightNear; glm::vec3 bottomLeftFarRotated = rotation * bottomLeftFar; glm::vec3 bottomRightFarRotated = rotation * bottomRightFar; glm::vec3 topLeftNearRotated = rotation * topLeftNear; glm::vec3 topRightNearRotated = rotation * topRightNear; glm::vec3 topLeftFarRotated = rotation * topLeftFar; glm::vec3 topRightFarRotated = rotation * topRightFar; minimum = glm::min(bottomLeftNearRotated, glm::min(bottomRightNearRotated, glm::min(bottomLeftFarRotated, glm::min(bottomRightFarRotated, glm::min(topLeftNearRotated, glm::min(topRightNearRotated, glm::min(topLeftFarRotated, topRightFarRotated))))))); maximum = glm::max(bottomLeftNearRotated, glm::max(bottomRightNearRotated, glm::max(bottomLeftFarRotated, glm::max(bottomRightFarRotated, glm::max(topLeftNearRotated, glm::max(topRightNearRotated, glm::max(topLeftFarRotated, topRightFarRotated))))))); _corner = minimum; _scale = maximum - minimum; } void AABox::transform(const Transform& transform) { scale(transform.getScale()); rotate(transform.getRotation()); translate(transform.getTranslation()); } // Logic based on http://clb.demon.fi/MathGeoLib/nightly/docs/AABB.cpp_code.html#471 void AABox::transform(const glm::mat4& matrix) { // FIXME use simd operations auto halfSize = _scale * 0.5f; auto center = _corner + halfSize; halfSize = abs(halfSize); auto mm = glm::transpose(glm::mat3(matrix)); vec3 newDir = vec3( glm::dot(glm::abs(mm[0]), halfSize), glm::dot(glm::abs(mm[1]), halfSize), glm::dot(glm::abs(mm[2]), halfSize) ); auto newCenter = transformPoint(matrix, center); _corner = newCenter - newDir; _scale = newDir * 2.0f; } AABox AABox::getOctreeChild(OctreeChild child) const { AABox result(*this); // self switch (child) { case topLeftNear: result._corner.y += _scale.y / 2.0f; break; case topLeftFar: result._corner.y += _scale.y / 2.0f; result._corner.z += _scale.z / 2.0f; break; case topRightNear: result._corner.y += _scale.y / 2.0f; result._corner.x += _scale.x / 2.0f; break; case topRightFar: result._corner.y += _scale.y / 2.0f; result._corner.x += _scale.x / 2.0f; result._corner.z += _scale.z / 2.0f; break; case bottomLeftNear: // _corner = same as parent break; case bottomLeftFar: result._corner.z += _scale.z / 2.0f; break; case bottomRightNear: result._corner.x += _scale.x / 2.0f; break; case bottomRightFar: result._corner.x += _scale.x / 2.0f; result._corner.z += _scale.z / 2.0f; break; } result._scale /= 2.0f; // everything is half the scale return result; }