// // GeometryUtil.cpp // libraries/shared/src // // Created by Andrzej Kapolka on 5/21/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 "GeometryUtil.h" #include #include #include #include #include #include #include #include "glm/gtc/matrix_transform.hpp" #include "NumericalConstants.h" #include "GLMHelpers.h" #include "Plane.h" glm::vec3 computeVectorFromPointToSegment(const glm::vec3& point, const glm::vec3& start, const glm::vec3& end) { // compute the projection of the point vector onto the segment vector glm::vec3 segmentVector = end - start; float lengthSquared = glm::dot(segmentVector, segmentVector); if (lengthSquared < EPSILON) { return start - point; // start and end the same } float proj = glm::dot(point - start, segmentVector) / lengthSquared; if (proj <= 0.0f) { // closest to the start return start - point; } else if (proj >= 1.0f) { // closest to the end return end - point; } else { // closest to the middle return start + segmentVector*proj - point; } } // Computes the penetration between a point and a sphere (centered at the origin) // if point is inside sphere: returns true and stores the result in 'penetration' // (the vector that would move the point outside the sphere) // otherwise returns false bool findSpherePenetration(const glm::vec3& point, const glm::vec3& defaultDirection, float sphereRadius, glm::vec3& penetration) { float vectorLength = glm::length(point); if (vectorLength < EPSILON) { penetration = defaultDirection * sphereRadius; return true; } float distance = vectorLength - sphereRadius; if (distance < 0.0f) { penetration = point * (-distance / vectorLength); return true; } return false; } bool findSpherePointPenetration(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& point, glm::vec3& penetration) { return findSpherePenetration(point - sphereCenter, glm::vec3(0.0f, -1.0f, 0.0f), sphereRadius, penetration); } bool findPointSpherePenetration(const glm::vec3& point, const glm::vec3& sphereCenter, float sphereRadius, glm::vec3& penetration) { return findSpherePenetration(sphereCenter - point, glm::vec3(0.0f, -1.0f, 0.0f), sphereRadius, penetration); } bool findSphereSpherePenetration(const glm::vec3& firstCenter, float firstRadius, const glm::vec3& secondCenter, float secondRadius, glm::vec3& penetration) { return findSpherePointPenetration(firstCenter, firstRadius + secondRadius, secondCenter, penetration); } bool findSphereSegmentPenetration(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& segmentStart, const glm::vec3& segmentEnd, glm::vec3& penetration) { return findSpherePenetration(computeVectorFromPointToSegment(sphereCenter, segmentStart, segmentEnd), glm::vec3(0.0f, -1.0f, 0.0f), sphereRadius, penetration); } bool findSphereCapsulePenetration(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius, glm::vec3& penetration) { return findSphereSegmentPenetration(sphereCenter, sphereRadius + capsuleRadius, capsuleStart, capsuleEnd, penetration); } bool findPointCapsuleConePenetration(const glm::vec3& point, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float startRadius, float endRadius, glm::vec3& penetration) { // compute the projection of the point vector onto the segment vector glm::vec3 segmentVector = capsuleEnd - capsuleStart; float lengthSquared = glm::dot(segmentVector, segmentVector); if (lengthSquared < EPSILON) { // start and end the same return findPointSpherePenetration(point, capsuleStart, glm::max(startRadius, endRadius), penetration); } float proj = glm::dot(point - capsuleStart, segmentVector) / lengthSquared; if (proj <= 0.0f) { // closest to the start return findPointSpherePenetration(point, capsuleStart, startRadius, penetration); } else if (proj >= 1.0f) { // closest to the end return findPointSpherePenetration(point, capsuleEnd, endRadius, penetration); } else { // closest to the middle return findPointSpherePenetration(point, capsuleStart + segmentVector * proj, glm::mix(startRadius, endRadius, proj), penetration); } } bool findSphereCapsuleConePenetration(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float startRadius, float endRadius, glm::vec3& penetration) { return findPointCapsuleConePenetration(sphereCenter, capsuleStart, capsuleEnd, startRadius + sphereRadius, endRadius + sphereRadius, penetration); } bool findSpherePlanePenetration(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec4& plane, glm::vec3& penetration) { float distance = glm::dot(plane, glm::vec4(sphereCenter, 1.0f)) - sphereRadius; if (distance < 0.0f) { penetration = glm::vec3(plane) * distance; return true; } return false; } bool findSphereDiskPenetration(const glm::vec3& sphereCenter, float sphereRadius, const glm::vec3& diskCenter, float diskRadius, float diskThickness, const glm::vec3& diskNormal, glm::vec3& penetration) { glm::vec3 localCenter = sphereCenter - diskCenter; float axialDistance = glm::dot(localCenter, diskNormal); if (std::fabs(axialDistance) < (sphereRadius + 0.5f * diskThickness)) { // sphere hit the plane, but does it hit the disk? // Note: this algorithm ignores edge hits. glm::vec3 axialOffset = axialDistance * diskNormal; if (glm::length(localCenter - axialOffset) < diskRadius) { // yes, hit the disk penetration = (std::fabs(axialDistance) - (sphereRadius + 0.5f * diskThickness) ) * diskNormal; if (axialDistance < 0.0f) { // hit the backside of the disk, so negate penetration vector penetration *= -1.0f; } return true; } } return false; } bool findCapsuleSpherePenetration(const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius, const glm::vec3& sphereCenter, float sphereRadius, glm::vec3& penetration) { if (findSphereCapsulePenetration(sphereCenter, sphereRadius, capsuleStart, capsuleEnd, capsuleRadius, penetration)) { penetration = -penetration; return true; } return false; } bool findCapsulePlanePenetration(const glm::vec3& capsuleStart, const glm::vec3& capsuleEnd, float capsuleRadius, const glm::vec4& plane, glm::vec3& penetration) { float distance = glm::min(glm::dot(plane, glm::vec4(capsuleStart, 1.0f)), glm::dot(plane, glm::vec4(capsuleEnd, 1.0f))) - capsuleRadius; if (distance < 0.0f) { penetration = glm::vec3(plane) * distance; return true; } return false; } glm::vec3 addPenetrations(const glm::vec3& currentPenetration, const glm::vec3& newPenetration) { // find the component of the new penetration in the direction of the current float currentLength = glm::length(currentPenetration); if (currentLength == 0.0f) { return newPenetration; } glm::vec3 currentDirection = currentPenetration / currentLength; float directionalComponent = glm::dot(newPenetration, currentDirection); // if orthogonal or in the opposite direction, we can simply add if (directionalComponent <= 0.0f) { return currentPenetration + newPenetration; } // otherwise, we need to take the maximum component of current and new return currentDirection * glm::max(directionalComponent, currentLength) + newPenetration - (currentDirection * directionalComponent); } // finds the intersection between a ray and the facing plane on one axis bool findIntersection(float origin, float direction, float corner, float size, float& distance) { if (direction > EPSILON) { distance = (corner - origin) / direction; return true; } else if (direction < -EPSILON) { distance = (corner + size - origin) / direction; return true; } return false; } // finds the intersection between a ray and the inside facing plane on one axis bool findInsideOutIntersection(float origin, float direction, float corner, float size, float& distance) { if (direction > EPSILON) { distance = -1.0f * (origin - (corner + size)) / direction; return true; } else if (direction < -EPSILON) { distance = -1.0f * (origin - corner) / direction; return true; } return false; } // https://tavianator.com/fast-branchless-raybounding-box-intersections/ bool findRayAABoxIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& invDirection, const glm::vec3& corner, const glm::vec3& scale, float& distance, BoxFace& face, glm::vec3& surfaceNormal) { float t1, t2, newTmin, newTmax, tmin = -INFINITY, tmax = INFINITY; int minAxis = -1, maxAxis = -1; for (int i = 0; i < 3; ++i) { t1 = (corner[i] - origin[i]) * invDirection[i]; t2 = (corner[i] + scale[i] - origin[i]) * invDirection[i]; newTmin = glm::min(t1, t2); newTmax = glm::max(t1, t2); minAxis = newTmin > tmin ? i : minAxis; tmin = glm::max(tmin, newTmin); maxAxis = newTmax < tmax ? i : maxAxis; tmax = glm::min(tmax, newTmax); } if (tmax >= glm::max(tmin, 0.0f)) { if (tmin < 0.0f) { distance = tmax; bool positiveDirection = direction[maxAxis] > 0.0f; surfaceNormal = glm::vec3(0.0f); surfaceNormal[maxAxis] = positiveDirection ? -1.0f : 1.0f; face = positiveDirection ? BoxFace(2 * maxAxis + 1) : BoxFace(2 * maxAxis); } else { distance = tmin; bool positiveDirection = direction[minAxis] > 0.0f; surfaceNormal = glm::vec3(0.0f); surfaceNormal[minAxis] = positiveDirection ? -1.0f : 1.0f; face = positiveDirection ? BoxFace(2 * minAxis) : BoxFace(2 * minAxis + 1); } return true; } return false; } bool findRaySphereIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& center, float radius, float& distance) { glm::vec3 relativeOrigin = origin - center; float c = glm::dot(relativeOrigin, relativeOrigin) - radius * radius; if (c < 0.0f) { distance = 0.0f; return true; // starts inside the sphere } float b = 2.0f * glm::dot(direction, relativeOrigin); float a = glm::dot(direction, direction); float radicand = b * b - 4.0f * a * c; if (radicand < 0.0f) { return false; // doesn't hit the sphere } float t = 0.5f * (-b - sqrtf(radicand)) / a; if (t < 0.0f) { return false; // doesn't hit the sphere } distance = t; return true; } bool pointInSphere(const glm::vec3& origin, const glm::vec3& center, float radius) { glm::vec3 relativeOrigin = origin - center; float c = glm::dot(relativeOrigin, relativeOrigin) - radius * radius; return c <= 0.0f; } bool pointInCapsule(const glm::vec3& origin, const glm::vec3& start, const glm::vec3& end, float radius) { glm::vec3 relativeOrigin = origin - start; glm::vec3 relativeEnd = end - start; float capsuleLength = glm::length(relativeEnd); relativeEnd /= capsuleLength; float originProjection = glm::dot(relativeEnd, relativeOrigin); glm::vec3 constant = relativeOrigin - relativeEnd * originProjection; float c = glm::dot(constant, constant) - radius * radius; if (c < 0.0f) { // starts inside cylinder if (originProjection < 0.0f) { // below start return pointInSphere(origin, start, radius); } else if (originProjection > capsuleLength) { // above end return pointInSphere(origin, end, radius); } else { // between start and end return true; } } return false; } bool findRayCapsuleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& start, const glm::vec3& end, float radius, float& distance) { if (start == end) { return findRaySphereIntersection(origin, direction, start, radius, distance); // handle degenerate case } glm::vec3 relativeOrigin = origin - start; glm::vec3 relativeEnd = end - start; float capsuleLength = glm::length(relativeEnd); relativeEnd /= capsuleLength; float originProjection = glm::dot(relativeEnd, relativeOrigin); glm::vec3 constant = relativeOrigin - relativeEnd * originProjection; float c = glm::dot(constant, constant) - radius * radius; if (c < 0.0f) { // starts inside cylinder if (originProjection < 0.0f) { // below start return findRaySphereIntersection(origin, direction, start, radius, distance); } else if (originProjection > capsuleLength) { // above end return findRaySphereIntersection(origin, direction, end, radius, distance); } else { // between start and end distance = 0.0f; return true; } } glm::vec3 coefficient = direction - relativeEnd * glm::dot(relativeEnd, direction); float a = glm::dot(coefficient, coefficient); if (a == 0.0f) { return false; // parallel to enclosing cylinder } float b = 2.0f * glm::dot(constant, coefficient); float radicand = b * b - 4.0f * a * c; if (radicand < 0.0f) { return false; // doesn't hit the enclosing cylinder } float t = (-b - sqrtf(radicand)) / (2.0f * a); if (t < 0.0f) { return false; // doesn't hit the enclosing cylinder } glm::vec3 intersection = relativeOrigin + direction * t; float intersectionProjection = glm::dot(relativeEnd, intersection); if (intersectionProjection < 0.0f) { // below start return findRaySphereIntersection(origin, direction, start, radius, distance); } else if (intersectionProjection > capsuleLength) { // above end return findRaySphereIntersection(origin, direction, end, radius, distance); } distance = t; // between start and end return true; } // reference https://www.opengl.org/wiki/Calculating_a_Surface_Normal glm::vec3 Triangle::getNormal() const { glm::vec3 edge1 = v1 - v0; glm::vec3 edge2 = v2 - v0; return glm::normalize(glm::cross(edge1, edge2)); } float Triangle::getArea() const { glm::vec3 edge1 = v1 - v0; glm::vec3 edge2 = v2 - v0; return 0.5f * glm::length(glm::cross(edge1, edge2)); } Triangle Triangle::operator*(const glm::mat4& transform) const { return { glm::vec3(transform * glm::vec4(v0, 1.0f)), glm::vec3(transform * glm::vec4(v1, 1.0f)), glm::vec3(transform * glm::vec4(v2, 1.0f)) }; } // https://en.wikipedia.org/wiki/M%C3%B6ller%E2%80%93Trumbore_intersection_algorithm bool findRayTriangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& distance, bool allowBackface) { glm::vec3 firstSide = v1 - v0; glm::vec3 secondSide = v2 - v0; glm::vec3 P = glm::cross(direction, secondSide); float det = glm::dot(firstSide, P); if (!allowBackface && det < EPSILON) { return false; } else if (fabsf(det) < EPSILON) { return false; } float invDet = 1.0f / det; glm::vec3 T = origin - v0; float u = glm::dot(T, P) * invDet; if (u < 0.0f || u > 1.0f) { return false; } glm::vec3 Q = glm::cross(T, firstSide); float v = glm::dot(direction, Q) * invDet; if (v < 0.0f || u + v > 1.0f) { return false; } float t = glm::dot(secondSide, Q) * invDet; if (t > EPSILON) { distance = t; return true; } return false; } static void getTrianglePlaneIntersectionPoints(const glm::vec3 trianglePoints[3], const float pointPlaneDistances[3], const int clippedPointIndex, const int keptPointIndices[2], glm::vec3 points[2]) { assert(clippedPointIndex >= 0 && clippedPointIndex < 3); const auto& clippedPoint = trianglePoints[clippedPointIndex]; const float clippedPointPlaneDistance = pointPlaneDistances[clippedPointIndex]; for (auto i = 0; i < 2; i++) { assert(keptPointIndices[i] >= 0 && keptPointIndices[i] < 3); const auto& keptPoint = trianglePoints[keptPointIndices[i]]; const float keptPointPlaneDistance = pointPlaneDistances[keptPointIndices[i]]; auto intersectionEdgeRatio = clippedPointPlaneDistance / (clippedPointPlaneDistance - keptPointPlaneDistance); points[i] = clippedPoint + (keptPoint - clippedPoint) * intersectionEdgeRatio; } } int clipTriangleWithPlane(const Triangle& triangle, const Plane& plane, Triangle* clippedTriangles, int maxClippedTriangleCount) { float pointDistanceToPlane[3]; std::bitset<3> arePointsClipped; glm::vec3 triangleVertices[3] = { triangle.v0, triangle.v1, triangle.v2 }; int clippedTriangleCount = 0; int i; for (i = 0; i < 3; i++) { pointDistanceToPlane[i] = plane.distance(triangleVertices[i]); arePointsClipped.set(i, pointDistanceToPlane[i] < 0.0f); } switch (arePointsClipped.count()) { case 0: // Easy, the entire triangle is kept as is. *clippedTriangles = triangle; clippedTriangleCount = 1; break; case 1: { int clippedPointIndex = 2; int keptPointIndices[2] = { 0, 1 }; glm::vec3 newVertices[2]; // Determine which point was clipped. if (arePointsClipped.test(0)) { clippedPointIndex = 0; keptPointIndices[0] = 2; } else if (arePointsClipped.test(1)) { clippedPointIndex = 1; keptPointIndices[1] = 2; } // We have a quad now, so we need to create two triangles. getTrianglePlaneIntersectionPoints(triangleVertices, pointDistanceToPlane, clippedPointIndex, keptPointIndices, newVertices); clippedTriangles->v0 = triangleVertices[keptPointIndices[0]]; clippedTriangles->v1 = triangleVertices[keptPointIndices[1]]; clippedTriangles->v2 = newVertices[1]; clippedTriangles++; clippedTriangleCount++; if (clippedTriangleCount < maxClippedTriangleCount) { clippedTriangles->v0 = triangleVertices[keptPointIndices[0]]; clippedTriangles->v1 = newVertices[0]; clippedTriangles->v2 = newVertices[1]; clippedTriangles++; clippedTriangleCount++; } } break; case 2: { int keptPointIndex = 2; int clippedPointIndices[2] = { 0, 1 }; glm::vec3 newVertices[2]; // Determine which point was NOT clipped. if (!arePointsClipped.test(0)) { keptPointIndex = 0; clippedPointIndices[0] = 2; } else if (!arePointsClipped.test(1)) { keptPointIndex = 1; clippedPointIndices[1] = 2; } // We have a single triangle getTrianglePlaneIntersectionPoints(triangleVertices, pointDistanceToPlane, keptPointIndex, clippedPointIndices, newVertices); clippedTriangles->v0 = triangleVertices[keptPointIndex]; clippedTriangles->v1 = newVertices[0]; clippedTriangles->v2 = newVertices[1]; clippedTriangleCount = 1; } break; default: // Entire triangle is clipped. break; } return clippedTriangleCount; } int clipTriangleWithPlanes(const Triangle& triangle, const Plane* planes, int planeCount, Triangle* clippedTriangles, int maxClippedTriangleCount) { auto planesEnd = planes + planeCount; int triangleCount = 1; std::vector trianglesToTest; assert(maxClippedTriangleCount > 0); *clippedTriangles = triangle; while (planes < planesEnd && triangleCount) { int clippedSubTriangleCount; trianglesToTest.clear(); trianglesToTest.insert(trianglesToTest.begin(), clippedTriangles, clippedTriangles + triangleCount); triangleCount = 0; for (const auto& triangleToTest : trianglesToTest) { clippedSubTriangleCount = clipTriangleWithPlane(triangleToTest, *planes, clippedTriangles + triangleCount, maxClippedTriangleCount - triangleCount); triangleCount += clippedSubTriangleCount; if (triangleCount >= maxClippedTriangleCount) { return triangleCount; } } ++planes; } return triangleCount; } // Do line segments (r1p1.x, r1p1.y)--(r1p2.x, r1p2.y) and (r2p1.x, r2p1.y)--(r2p2.x, r2p2.y) intersect? // from: http://ptspts.blogspot.com/2010/06/how-to-determine-if-two-line-segments.html bool doLineSegmentsIntersect(glm::vec2 r1p1, glm::vec2 r1p2, glm::vec2 r2p1, glm::vec2 r2p2) { int d1 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y); int d2 = computeDirection(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y); int d3 = computeDirection(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p1.x, r2p1.y); int d4 = computeDirection(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y); return (((d1 > 0 && d2 < 0) || (d1 < 0 && d2 > 0)) && ((d3 > 0 && d4 < 0) || (d3 < 0 && d4 > 0))) || (d1 == 0 && isOnSegment(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p1.x, r1p1.y)) || (d2 == 0 && isOnSegment(r2p1.x, r2p1.y, r2p2.x, r2p2.y, r1p2.x, r1p2.y)) || (d3 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p1.x, r2p1.y)) || (d4 == 0 && isOnSegment(r1p1.x, r1p1.y, r1p2.x, r1p2.y, r2p2.x, r2p2.y)); } bool findClosestApproachOfLines(glm::vec3 p1, glm::vec3 d1, glm::vec3 p2, glm::vec3 d2, // return values... float& t1, float& t2) { // https://math.stackexchange.com/questions/1993953/closest-points-between-two-lines/1993990#1993990 // https://en.wikipedia.org/wiki/Skew_lines#Nearest_Points glm::vec3 n1 = glm::cross(d1, glm::cross(d2, d1)); glm::vec3 n2 = glm::cross(d2, glm::cross(d1, d2)); float denom1 = glm::dot(d1, n2); float denom2 = glm::dot(d2, n1); if (denom1 != 0.0f && denom2 != 0.0f) { t1 = glm::dot((p2 - p1), n2) / denom1; t2 = glm::dot((p1 - p2), n1) / denom2; return true; } else { t1 = 0.0f; t2 = 0.0f; return false; } } bool isOnSegment(float xi, float yi, float xj, float yj, float xk, float yk) { return (xi <= xk || xj <= xk) && (xk <= xi || xk <= xj) && (yi <= yk || yj <= yk) && (yk <= yi || yk <= yj); } int computeDirection(float xi, float yi, float xj, float yj, float xk, float yk) { float a = (xk - xi) * (yj - yi); float b = (xj - xi) * (yk - yi); return a < b ? -1 : a > b ? 1 : 0; } // // Polygon Clipping routines inspired by, pseudo code found here: http://www.cs.rit.edu/~icss571/clipTrans/PolyClipBack.html // // Coverage Map's polygon coordinates are from -1 to 1 in the following mapping to screen space. // // (0,0) (windowWidth, 0) // -1,1 1,1 // +-----------------------+ // | | | // | | | // | -1,0 | | // |-----------+-----------| // | 0,0 | // | | | // | | | // | | | // +-----------------------+ // -1,-1 1,-1 // (0,windowHeight) (windowWidth,windowHeight) // const float PolygonClip::TOP_OF_CLIPPING_WINDOW = 1.0f; const float PolygonClip::BOTTOM_OF_CLIPPING_WINDOW = -1.0f; const float PolygonClip::LEFT_OF_CLIPPING_WINDOW = -1.0f; const float PolygonClip::RIGHT_OF_CLIPPING_WINDOW = 1.0f; const glm::vec2 PolygonClip::TOP_LEFT_CLIPPING_WINDOW ( LEFT_OF_CLIPPING_WINDOW , TOP_OF_CLIPPING_WINDOW ); const glm::vec2 PolygonClip::TOP_RIGHT_CLIPPING_WINDOW ( RIGHT_OF_CLIPPING_WINDOW, TOP_OF_CLIPPING_WINDOW ); const glm::vec2 PolygonClip::BOTTOM_LEFT_CLIPPING_WINDOW ( LEFT_OF_CLIPPING_WINDOW , BOTTOM_OF_CLIPPING_WINDOW ); const glm::vec2 PolygonClip::BOTTOM_RIGHT_CLIPPING_WINDOW ( RIGHT_OF_CLIPPING_WINDOW, BOTTOM_OF_CLIPPING_WINDOW ); void PolygonClip::clipToScreen(const glm::vec2* inputVertexArray, int inLength, glm::vec2*& outputVertexArray, int& outLength) { int tempLengthA = inLength; int tempLengthB; int maxLength = inLength * 2; glm::vec2* tempVertexArrayA = new glm::vec2[maxLength]; glm::vec2* tempVertexArrayB = new glm::vec2[maxLength]; // set up our temporary arrays for (int i=0; i clipBoundary[0].x) { if (testVertex.y >= clipBoundary[0].y) { return true; } } // top edge if (clipBoundary[1].x < clipBoundary[0].x) { if (testVertex.y <= clipBoundary[0].y) { return true; } } // right edge if (clipBoundary[1].y > clipBoundary[0].y) { if (testVertex.x <= clipBoundary[1].x) { return true; } } // left edge if (clipBoundary[1].y < clipBoundary[0].y) { if (testVertex.x >= clipBoundary[1].x) { return true; } } return false; } void PolygonClip::segmentIntersectsBoundary(const glm::vec2& first, const glm::vec2& second, const LineSegment2& clipBoundary, glm::vec2& intersection) { // horizontal if (clipBoundary[0].y==clipBoundary[1].y) { intersection.y = clipBoundary[0].y; intersection.x = first.x + (clipBoundary[0].y - first.y) * (second.x - first.x) / (second.y - first.y); } else { // Vertical intersection.x = clipBoundary[0].x; intersection.y = first.y + (clipBoundary[0].x - first.x) * (second.y - first.y) / (second.x - first.x); } } void PolygonClip::appendPoint(glm::vec2 newVertex, int& outLength, glm::vec2* outVertexArray) { outVertexArray[outLength].x = newVertex.x; outVertexArray[outLength].y = newVertex.y; outLength++; } // The copyCleanArray() function sets the resulting polygon of the previous step up to be the input polygon for next step of the // clipping algorithm. As the Sutherland-Hodgman algorithm is a polygon clipping algorithm, it does not handle line // clipping very well. The modification so that lines may be clipped as well as polygons is included in this function. // when completed vertexArrayA will be ready for output and/or next step of clipping void PolygonClip::copyCleanArray(int& lengthA, glm::vec2* vertexArrayA, int& lengthB, glm::vec2* vertexArrayB) { // Fix lines: they will come back with a length of 3, from an original of length of 2 if ((lengthA == 2) && (lengthB == 3)) { // The first vertex should be copied as is. vertexArrayA[0] = vertexArrayB[0]; // If the first two vertices of the "B" array are same, then collapse them down to be the 2nd vertex if (vertexArrayB[0].x == vertexArrayB[1].x) { vertexArrayA[1] = vertexArrayB[2]; } else { // Otherwise the first vertex should be the same as third vertex vertexArrayA[1] = vertexArrayB[1]; } lengthA=2; } else { // for all other polygons, then just copy the vertexArrayB to vertextArrayA for next step lengthA = lengthB; for (int i = 0; i < lengthB; i++) { vertexArrayA[i] = vertexArrayB[i]; } } } bool findRayRectangleIntersection(const glm::vec3& origin, const glm::vec3& direction, const glm::quat& rotation, const glm::vec3& position, const glm::vec2& dimensions, float& distance) { const glm::vec3 UNROTATED_NORMAL(0.0f, 0.0f, -1.0f); glm::vec3 normal = rotation * UNROTATED_NORMAL; bool maybeIntersects = false; float denominator = glm::dot(normal, direction); glm::vec3 offset = origin - position; float normDotOffset = glm::dot(offset, normal); float d = 0.0f; if (fabsf(denominator) < EPSILON) { // line is perpendicular to plane if (fabsf(normDotOffset) < EPSILON) { // ray starts on the plane maybeIntersects = true; // compute distance to closest approach d = - glm::dot(offset, direction); // distance to closest approach of center of rectangle if (d < 0.0f) { // ray points away from center of rectangle, so ray's start is the closest approach d = 0.0f; } } } else { d = - normDotOffset / denominator; if (d > 0.0f) { // ray points toward plane maybeIntersects = true; } } if (maybeIntersects) { glm::vec3 hitPosition = origin + (d * direction); glm::vec3 localHitPosition = glm::inverse(rotation) * (hitPosition - position); glm::vec2 halfDimensions = 0.5f * dimensions; if (fabsf(localHitPosition.x) < halfDimensions.x && fabsf(localHitPosition.y) < halfDimensions.y) { // only update distance on intersection distance = d; return true; } } return false; } // determines whether a value is within the extents bool isWithin(float value, float corner, float size) { return value >= corner && value <= corner + size; } bool aaBoxContains(const glm::vec3& point, const glm::vec3& corner, const glm::vec3& scale) { return isWithin(point.x, corner.x, scale.x) && isWithin(point.y, corner.y, scale.y) && isWithin(point.z, corner.z, scale.z); } void checkPossibleParabolicIntersectionWithZPlane(float t, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& corner, const glm::vec2& scale) { if (t < minDistance && t > 0.0f && isWithin(origin.x + velocity.x * t + 0.5f * acceleration.x * t * t, corner.x, scale.x) && isWithin(origin.y + velocity.y * t + 0.5f * acceleration.y * t * t, corner.y, scale.y)) { minDistance = t; } } // Intersect with the plane z = 0 and make sure the intersection is within dimensions bool findParabolaRectangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec2& dimensions, float& parabolicDistance) { glm::vec2 localCorner = -0.5f * dimensions; float minDistance = FLT_MAX; if (fabsf(acceleration.z) < EPSILON) { if (fabsf(velocity.z) > EPSILON) { // Handle the degenerate case where we only have a line in the z-axis float possibleDistance = -origin.z / velocity.z; checkPossibleParabolicIntersectionWithZPlane(possibleDistance, minDistance, origin, velocity, acceleration, localCorner, dimensions); } } else { float a = 0.5f * acceleration.z; float b = velocity.z; float c = origin.z; glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int i = 0; i < 2; i++) { checkPossibleParabolicIntersectionWithZPlane(possibleDistances[i], minDistance, origin, velocity, acceleration, localCorner, dimensions); } } } if (minDistance < FLT_MAX) { parabolicDistance = minDistance; return true; } return false; } bool findParabolaSphereIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& center, float radius, float& parabolicDistance) { glm::vec3 localCenter = center - origin; float radiusSquared = radius * radius; float accelerationLength = glm::length(acceleration); float minDistance = FLT_MAX; if (accelerationLength < EPSILON) { // Handle the degenerate case where acceleration == (0, 0, 0) glm::vec3 offset = origin - center; float a = glm::dot(velocity, velocity); float b = 2.0f * glm::dot(velocity, offset); float c = glm::dot(offset, offset) - radius * radius; glm::vec2 possibleDistances(FLT_MAX); if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int i = 0; i < 2; i++) { if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { minDistance = possibleDistances[i]; } } } } else { glm::vec3 vectorOnPlane = velocity; if (fabsf(glm::dot(glm::normalize(velocity), glm::normalize(acceleration))) > 1.0f - EPSILON) { // Handle the degenerate case where velocity is parallel to acceleration // We pick t = 1 and calculate a second point on the plane vectorOnPlane = velocity + 0.5f * acceleration; } // Get the normal of the plane, the cross product of two vectors on the plane glm::vec3 normal = glm::normalize(glm::cross(vectorOnPlane, acceleration)); // Project vector from plane to sphere center onto the normal float distance = glm::dot(localCenter, normal); // Exit early if the sphere doesn't intersect the plane defined by the parabola if (fabsf(distance) > radius) { return false; } glm::vec3 circleCenter = center - distance * normal; float circleRadius = sqrtf(radiusSquared - distance * distance); glm::vec3 q = glm::normalize(acceleration); glm::vec3 p = glm::cross(normal, q); float a1 = accelerationLength * 0.5f; float b1 = glm::dot(velocity, q); float c1 = glm::dot(origin - circleCenter, q); float a2 = glm::dot(velocity, p); float b2 = glm::dot(origin - circleCenter, p); float a = a1 * a1; float b = 2.0f * a1 * b1; float c = 2.0f * a1 * c1 + b1 * b1 + a2 * a2; float d = 2.0f * b1 * c1 + 2.0f * a2 * b2; float e = c1 * c1 + b2 * b2 - circleRadius * circleRadius; glm::vec4 possibleDistances(FLT_MAX); if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { for (int i = 0; i < 4; i++) { if (possibleDistances[i] < minDistance && possibleDistances[i] > 0.0f) { minDistance = possibleDistances[i]; } } } } if (minDistance < FLT_MAX) { parabolicDistance = minDistance; return true; } return false; } void checkPossibleParabolicIntersectionWithTriangle(float t, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& localVelocity, const glm::vec3& localAcceleration, const glm::vec3& normal, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, bool allowBackface) { // Check if we're hitting the backface in the rotated coordinate space float localIntersectionVelocityZ = localVelocity.z + localAcceleration.z * t; if (!allowBackface && localIntersectionVelocityZ < 0.0f) { return; } // Check that the point is within all three sides glm::vec3 point = origin + velocity * t + 0.5f * acceleration * t * t; if (t < minDistance && t > 0.0f && glm::dot(normal, glm::cross(point - v1, v0 - v1)) > 0.0f && glm::dot(normal, glm::cross(v2 - v1, point - v1)) > 0.0f && glm::dot(normal, glm::cross(point - v0, v2 - v0)) > 0.0f) { minDistance = t; } } bool findParabolaTriangleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& v0, const glm::vec3& v1, const glm::vec3& v2, float& parabolicDistance, bool allowBackface) { glm::vec3 normal = glm::normalize(glm::cross(v2 - v1, v0 - v1)); // We transform the parabola and triangle so that the triangle is in the plane z = 0, with v0 at the origin glm::quat inverseRot; // Note: OpenGL view matrix is already the inverse of our camera matrix // if the direction is nearly aligned with the Y axis, then use the X axis for 'up' const float MAX_ABS_Y_COMPONENT = 0.9999991f; if (fabsf(normal.y) > MAX_ABS_Y_COMPONENT) { inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_X)); } else { inverseRot = glm::quat_cast(glm::lookAt(glm::vec3(0.0f), normal, Vectors::UNIT_Y)); } glm::vec3 localOrigin = inverseRot * (origin - v0); glm::vec3 localVelocity = inverseRot * velocity; glm::vec3 localAcceleration = inverseRot * acceleration; float minDistance = FLT_MAX; if (fabsf(localAcceleration.z) < EPSILON) { if (fabsf(localVelocity.z) > EPSILON) { float possibleDistance = -localOrigin.z / localVelocity.z; checkPossibleParabolicIntersectionWithTriangle(possibleDistance, minDistance, origin, velocity, acceleration, localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); } } else { float a = 0.5f * localAcceleration.z; float b = localVelocity.z; float c = localOrigin.z; glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int i = 0; i < 2; i++) { checkPossibleParabolicIntersectionWithTriangle(possibleDistances[i], minDistance, origin, velocity, acceleration, localVelocity, localAcceleration, normal, v0, v1, v2, allowBackface); } } } if (minDistance < FLT_MAX) { parabolicDistance = minDistance; return true; } return false; } bool findParabolaCapsuleIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& start, const glm::vec3& end, float radius, const glm::quat& rotation, float& parabolicDistance) { if (start == end) { return findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, parabolicDistance); // handle degenerate case } if (glm::distance2(origin, start) < radius * radius) { // inside start sphere float startDistance; bool intersectsStart = findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, startDistance); if (glm::distance2(origin, end) < radius * radius) { // also inside end sphere float endDistance; bool intersectsEnd = findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, endDistance); if (endDistance < startDistance) { parabolicDistance = endDistance; return intersectsEnd; } } parabolicDistance = startDistance; return intersectsStart; } else if (glm::distance2(origin, end) < radius * radius) { // inside end sphere (and not start sphere) return findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, parabolicDistance); } // We are either inside the middle of the capsule or outside it completely // Either way, we need to check all three parts of the capsule and find the closest intersection glm::vec3 results(FLT_MAX); findParabolaSphereIntersection(origin, velocity, acceleration, start, radius, results[0]); findParabolaSphereIntersection(origin, velocity, acceleration, end, radius, results[1]); // We rotate the infinite cylinder to be aligned with the y-axis and then cap the values at the end glm::quat inverseRot = glm::inverse(rotation); glm::vec3 localOrigin = inverseRot * (origin - start); glm::vec3 localVelocity = inverseRot * velocity; glm::vec3 localAcceleration = inverseRot * acceleration; float capsuleLength = glm::length(end - start); const float MIN_ACCELERATION_PRODUCT = 0.00001f; if (fabsf(localAcceleration.x * localAcceleration.z) < MIN_ACCELERATION_PRODUCT) { // Handle the degenerate case where we only have a line in the XZ plane float a = localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; float b = 2.0f * (localVelocity.x * localOrigin.x + localVelocity.z * localOrigin.z); float c = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; glm::vec2 possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int i = 0; i < 2; i++) { if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; if (y > 0.0f && y < capsuleLength) { results[2] = possibleDistances[i]; } } } } } else { float a = 0.25f * (localAcceleration.x * localAcceleration.x + localAcceleration.z * localAcceleration.z); float b = localVelocity.x * localAcceleration.x + localVelocity.z * localAcceleration.z; float c = localOrigin.x * localAcceleration.x + localOrigin.z * localAcceleration.z + localVelocity.x * localVelocity.x + localVelocity.z * localVelocity.z; float d = 2.0f * (localOrigin.x * localVelocity.x + localOrigin.z * localVelocity.z); float e = localOrigin.x * localOrigin.x + localOrigin.z * localOrigin.z - radius * radius; glm::vec4 possibleDistances(FLT_MAX); if (computeRealQuarticRoots(a, b, c, d, e, possibleDistances)) { for (int i = 0; i < 4; i++) { if (possibleDistances[i] < results[2] && possibleDistances[i] > 0.0f) { float y = localOrigin.y + localVelocity.y * possibleDistances[i] + 0.5f * localAcceleration.y * possibleDistances[i] * possibleDistances[i]; if (y > 0.0f && y < capsuleLength) { results[2] = possibleDistances[i]; } } } } } float minDistance = FLT_MAX; for (int i = 0; i < 3; i++) { minDistance = glm::min(minDistance, results[i]); } parabolicDistance = minDistance; return minDistance != FLT_MAX; } void checkPossibleParabolicIntersection(float t, int i, float& minDistance, const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& corner, const glm::vec3& scale, bool& hit) { if (t < minDistance && t > 0.0f && isWithin(origin[(i + 1) % 3] + velocity[(i + 1) % 3] * t + 0.5f * acceleration[(i + 1) % 3] * t * t, corner[(i + 1) % 3], scale[(i + 1) % 3]) && isWithin(origin[(i + 2) % 3] + velocity[(i + 2) % 3] * t + 0.5f * acceleration[(i + 2) % 3] * t * t, corner[(i + 2) % 3], scale[(i + 2) % 3])) { minDistance = t; hit = true; } } inline float parabolaVelocityAtT(float velocity, float acceleration, float t) { return velocity + acceleration * t; } bool findParabolaAABoxIntersection(const glm::vec3& origin, const glm::vec3& velocity, const glm::vec3& acceleration, const glm::vec3& corner, const glm::vec3& scale, float& parabolicDistance, BoxFace& face, glm::vec3& surfaceNormal) { float minDistance = FLT_MAX; BoxFace minFace = UNKNOWN_FACE; glm::vec3 minNormal; glm::vec2 possibleDistances; float a, b, c; // Solve the intersection for each face of the cube. As we go, keep track of the smallest, positive, real distance // that is within the bounds of the other two dimensions for (int i = 0; i < 3; i++) { if (fabsf(acceleration[i]) < EPSILON) { // Handle the degenerate case where we only have a line in this axis if (origin[i] < corner[i]) { { // min if (velocity[i] > 0.0f) { float possibleDistance = (corner[i] - origin[i]) / velocity[i]; bool hit = false; checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } else if (origin[i] > corner[i] + scale[i]) { { // max if (velocity[i] < 0.0f) { float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i]; bool hit = false; checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } } else { { // min if (velocity[i] < 0.0f) { float possibleDistance = (corner[i] - origin[i]) / velocity[i]; bool hit = false; checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } { // max if (velocity[i] > 0.0f) { float possibleDistance = (corner[i] + scale[i] - origin[i]) / velocity[i]; bool hit = false; checkPossibleParabolicIntersection(possibleDistance, i, minDistance, origin, velocity, acceleration, corner, scale, hit); if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } } else { a = 0.5f * acceleration[i]; b = velocity[i]; if (origin[i] < corner[i]) { // If we're below corner, we have the following cases: // - within bounds on other axes // - if +velocity or +acceleration // - can only hit MIN_FACE with -normal // - else // - if +acceleration // - can only hit MIN_FACE with -normal // - else if +velocity // - can hit MIN_FACE with -normal iff velocity at intersection is + // - else can hit MAX_FACE with +normal iff velocity at intersection is - if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { if (velocity[i] > 0.0f || acceleration[i] > 0.0f) { { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } } else { if (acceleration[i] > 0.0f) { { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } else if (velocity[i] > 0.0f) { bool hit = false; { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } if (!hit) { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } } } } else if (origin[i] > corner[i] + scale[i]) { // If we're above corner + scale, we have the following cases: // - within bounds on other axes // - if -velocity or -acceleration // - can only hit MAX_FACE with +normal // - else // - if -acceleration // - can only hit MAX_FACE with +normal // - else if -velocity // - can hit MAX_FACE with +normal iff velocity at intersection is - // - else can hit MIN_FACE with -normal iff velocity at intersection is + if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { if (velocity[i] < 0.0f || acceleration[i] < 0.0f) { { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } } } else { if (acceleration[i] < 0.0f) { { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } } else if (velocity[i] < 0.0f) { bool hit = false; { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } if (!hit) { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } } } else { // If we're between corner and corner + scale, we have the following cases: // - within bounds on other axes // - if -velocity and -acceleration // - can only hit MIN_FACE with +normal // - else if +velocity and +acceleration // - can only hit MAX_FACE with -normal // - else // - can hit MIN_FACE with +normal iff velocity at intersection is - // - can hit MAX_FACE with -normal iff velocity at intersection is + // - else // - if -velocity and +acceleration // - can hit MIN_FACE with -normal iff velocity at intersection is + // - else if +velocity and -acceleration // - can hit MAX_FACE with +normal iff velocity at intersection is - if (origin[(i + 1) % 3] > corner[(i + 1) % 3] && origin[(i + 1) % 3] < corner[(i + 1) % 3] + scale[(i + 1) % 3] && origin[(i + 2) % 3] > corner[(i + 2) % 3] && origin[(i + 2) % 3] < corner[(i + 2) % 3] + scale[(i + 2) % 3]) { if (velocity[i] < 0.0f && acceleration[i] < 0.0f) { { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } } else if (velocity[i] > 0.0f && acceleration[i] > 0.0f) { { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } else { { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } } else { if (velocity[i] < 0.0f && acceleration[i] > 0.0f) { { // min c = origin[i] - corner[i]; possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) > 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i); minNormal = glm::vec3(0.0f); minNormal[i] = -1.0f; } } } } else if (velocity[i] > 0.0f && acceleration[i] < 0.0f) { { // max c = origin[i] - (corner[i] + scale[i]); possibleDistances = { FLT_MAX, FLT_MAX }; if (computeRealQuadraticRoots(a, b, c, possibleDistances)) { bool hit = false; for (int j = 0; j < 2; j++) { if (parabolaVelocityAtT(velocity[i], acceleration[i], possibleDistances[j]) < 0.0f) { checkPossibleParabolicIntersection(possibleDistances[j], i, minDistance, origin, velocity, acceleration, corner, scale, hit); } } if (hit) { minFace = BoxFace(2 * i + 1); minNormal = glm::vec3(0.0f); minNormal[i] = 1.0f; } } } } } } } } if (minDistance < FLT_MAX) { parabolicDistance = minDistance; face = minFace; surfaceNormal = minNormal; return true; } return false; } void swingTwistDecomposition(const glm::quat& rotation, const glm::vec3& direction, glm::quat& swing, glm::quat& twist) { // direction MUST be normalized else the decomposition will be inaccurate assert(fabsf(glm::length2(direction) - 1.0f) < 1.0e-4f); // the twist part has an axis (imaginary component) that is parallel to direction argument glm::vec3 axisOfRotation(rotation.x, rotation.y, rotation.z); glm::vec3 twistImaginaryPart = glm::dot(direction, axisOfRotation) * direction; // and a real component that is relatively proportional to rotation's real component twist = glm::normalize(glm::quat(rotation.w, twistImaginaryPart.x, twistImaginaryPart.y, twistImaginaryPart.z)); // once twist is known we can solve for swing: // rotation = swing * twist --> swing = rotation * invTwist swing = rotation * glm::inverse(twist); } // calculate the minimum angle between a point and a sphere. float coneSphereAngle(const glm::vec3& coneCenter, const glm::vec3& coneDirection, const glm::vec3& sphereCenter, float sphereRadius) { glm::vec3 d = sphereCenter - coneCenter; float dLen = glm::length(d); // theta is the angle between the coneDirection normal and the center of the sphere. float theta = acosf(glm::dot(d, coneDirection) / dLen); // phi is the deflection angle from the center of the sphere to a point tangent to the sphere. float phi = atanf(sphereRadius / dLen); return glm::max(0.0f, theta - phi); } // given a set of points, compute a best fit plane that passes as close as possible through all the points. // http://www.ilikebigbits.com/blog/2015/3/2/plane-from-points bool findPlaneFromPoints(const glm::vec3* points, size_t numPoints, glm::vec3& planeNormalOut, glm::vec3& pointOnPlaneOut) { if (numPoints < 3) { return false; } glm::vec3 sum; for (size_t i = 0; i < numPoints; i++) { sum += points[i]; } glm::vec3 centroid = sum * (1.0f / (float)numPoints); float xx = 0.0f, xy = 0.0f, xz = 0.0f; float yy = 0.0f, yz = 0.0f, zz = 0.0f; for (size_t i = 0; i < numPoints; i++) { glm::vec3 r = points[i] - centroid; xx += r.x * r.x; xy += r.x * r.y; xz += r.x * r.z; yy += r.y * r.y; yz += r.y * r.z; zz += r.z * r.z; } float det_x = yy * zz - yz * yz; float det_y = xx * zz - xz * xz; float det_z = xx * yy - xy * xy; float det_max = std::max(std::max(det_x, det_y), det_z); if (det_max == 0.0f) { return false; // The points don't span a plane } glm::vec3 dir; if (det_max == det_x) { float a = (xz * yz - xy * zz) / det_x; float b = (xy * yz - xz * yy) / det_x; dir = glm::vec3(1.0f, a, b); } else if (det_max == det_y) { float a = (yz * xz - xy * zz) / det_y; float b = (xy * xz - yz * xx) / det_y; dir = glm::vec3(a, 1.0f, b); } else { float a = (yz * xy - xz * yy) / det_z; float b = (xz * xy - yz * xx) / det_z; dir = glm::vec3(a, b, 1.0f); } pointOnPlaneOut = centroid; planeNormalOut = glm::normalize(dir); return true; } bool findIntersectionOfThreePlanes(const glm::vec4& planeA, const glm::vec4& planeB, const glm::vec4& planeC, glm::vec3& intersectionPointOut) { glm::vec3 normalA(planeA); glm::vec3 normalB(planeB); glm::vec3 normalC(planeC); glm::vec3 u = glm::cross(normalB, normalC); float denom = glm::dot(normalA, u); if (fabsf(denom) < EPSILON) { return false; // planes do not intersect in a point. } else { intersectionPointOut = (planeA.w * u + glm::cross(normalA, planeC.w * normalB - planeB.w * normalC)) / denom; return true; } } const float INV_SQRT_3 = 1.0f / sqrtf(3.0f); const int DOP14_COUNT = 14; const glm::vec3 DOP14_NORMALS[DOP14_COUNT] = { Vectors::UNIT_X, -Vectors::UNIT_X, Vectors::UNIT_Y, -Vectors::UNIT_Y, Vectors::UNIT_Z, -Vectors::UNIT_Z, glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), -glm::vec3(INV_SQRT_3, INV_SQRT_3, INV_SQRT_3), glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), -glm::vec3(INV_SQRT_3, -INV_SQRT_3, INV_SQRT_3), glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), -glm::vec3(INV_SQRT_3, INV_SQRT_3, -INV_SQRT_3), glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3), -glm::vec3(INV_SQRT_3, -INV_SQRT_3, -INV_SQRT_3) }; typedef std::tuple Int3Tuple; const std::tuple DOP14_PLANE_COMBINATIONS[] = { Int3Tuple(0, 2, 4), Int3Tuple(0, 2, 5), Int3Tuple(0, 2, 6), Int3Tuple(0, 2, 7), Int3Tuple(0, 2, 8), Int3Tuple(0, 2, 9), Int3Tuple(0, 2, 10), Int3Tuple(0, 2, 11), Int3Tuple(0, 2, 12), Int3Tuple(0, 2, 13), Int3Tuple(0, 3, 4), Int3Tuple(0, 3, 5), Int3Tuple(0, 3, 6), Int3Tuple(0, 3, 7), Int3Tuple(0, 3, 8), Int3Tuple(0, 3, 9), Int3Tuple(0, 3, 10), Int3Tuple(0, 3, 11), Int3Tuple(0, 3, 12), Int3Tuple(0, 3, 13), Int3Tuple(0, 4, 6), Int3Tuple(0, 4, 7), Int3Tuple(0, 4, 8), Int3Tuple(0, 4, 9), Int3Tuple(0, 4, 10), Int3Tuple(0, 4, 11), Int3Tuple(0, 4, 12), Int3Tuple(0, 4, 13), Int3Tuple(0, 5, 6), Int3Tuple(0, 5, 7), Int3Tuple(0, 5, 8), Int3Tuple(0, 5, 9), Int3Tuple(0, 5, 10), Int3Tuple(0, 5, 11), Int3Tuple(0, 5, 12), Int3Tuple(0, 5, 13), Int3Tuple(0, 6, 8), Int3Tuple(0, 6, 9), Int3Tuple(0, 6, 10), Int3Tuple(0, 6, 11), Int3Tuple(0, 6, 12), Int3Tuple(0, 6, 13), Int3Tuple(0, 7, 8), Int3Tuple(0, 7, 9), Int3Tuple(0, 7, 10), Int3Tuple(0, 7, 11), Int3Tuple(0, 7, 12), Int3Tuple(0, 7, 13), Int3Tuple(0, 8, 10), Int3Tuple(0, 8, 11), Int3Tuple(0, 8, 12), Int3Tuple(0, 8, 13), Int3Tuple(0, 9, 10), Int3Tuple(0, 9, 11), Int3Tuple(0, 9, 12), Int3Tuple(0, 9, 13), Int3Tuple(0, 10, 12), Int3Tuple(0, 10, 13), Int3Tuple(0, 11, 12), Int3Tuple(0, 11, 13), Int3Tuple(1, 2, 4), Int3Tuple(1, 2, 5), Int3Tuple(1, 2, 6), Int3Tuple(1, 2, 7), Int3Tuple(1, 2, 8), Int3Tuple(1, 2, 9), Int3Tuple(1, 2, 10), Int3Tuple(1, 2, 11), Int3Tuple(1, 2, 12), Int3Tuple(1, 2, 13), Int3Tuple(1, 3, 4), Int3Tuple(1, 3, 5), Int3Tuple(1, 3, 6), Int3Tuple(1, 3, 7), Int3Tuple(1, 3, 8), Int3Tuple(1, 3, 9), Int3Tuple(1, 3, 10), Int3Tuple(1, 3, 11), Int3Tuple(1, 3, 12), Int3Tuple(1, 3, 13), Int3Tuple(1, 4, 6), Int3Tuple(1, 4, 7), Int3Tuple(1, 4, 8), Int3Tuple(1, 4, 9), Int3Tuple(1, 4, 10), Int3Tuple(1, 4, 11), Int3Tuple(1, 4, 12), Int3Tuple(1, 4, 13), Int3Tuple(1, 5, 6), Int3Tuple(1, 5, 7), Int3Tuple(1, 5, 8), Int3Tuple(1, 5, 9), Int3Tuple(1, 5, 10), Int3Tuple(1, 5, 11), Int3Tuple(1, 5, 12), Int3Tuple(1, 5, 13), Int3Tuple(1, 6, 8), Int3Tuple(1, 6, 9), Int3Tuple(1, 6, 10), Int3Tuple(1, 6, 11), Int3Tuple(1, 6, 12), Int3Tuple(1, 6, 13), Int3Tuple(1, 7, 8), Int3Tuple(1, 7, 9), Int3Tuple(1, 7, 10), Int3Tuple(1, 7, 11), Int3Tuple(1, 7, 12), Int3Tuple(1, 7, 13), Int3Tuple(1, 8, 10), Int3Tuple(1, 8, 11), Int3Tuple(1, 8, 12), Int3Tuple(1, 8, 13), Int3Tuple(1, 9, 10), Int3Tuple(1, 9, 11), Int3Tuple(1, 9, 12), Int3Tuple(1, 9, 13), Int3Tuple(1, 10, 12), Int3Tuple(1, 10, 13), Int3Tuple(1, 11, 12), Int3Tuple(1, 11, 13), Int3Tuple(2, 4, 6), Int3Tuple(2, 4, 7), Int3Tuple(2, 4, 8), Int3Tuple(2, 4, 9), Int3Tuple(2, 4, 10), Int3Tuple(2, 4, 11), Int3Tuple(2, 4, 12), Int3Tuple(2, 4, 13), Int3Tuple(2, 5, 6), Int3Tuple(2, 5, 7), Int3Tuple(2, 5, 8), Int3Tuple(2, 5, 9), Int3Tuple(2, 5, 10), Int3Tuple(2, 5, 11), Int3Tuple(2, 5, 12), Int3Tuple(2, 5, 13), Int3Tuple(2, 6, 8), Int3Tuple(2, 6, 9), Int3Tuple(2, 6, 10), Int3Tuple(2, 6, 11), Int3Tuple(2, 6, 12), Int3Tuple(2, 6, 13), Int3Tuple(2, 7, 8), Int3Tuple(2, 7, 9), Int3Tuple(2, 7, 10), Int3Tuple(2, 7, 11), Int3Tuple(2, 7, 12), Int3Tuple(2, 7, 13), Int3Tuple(2, 8, 10), Int3Tuple(2, 8, 11), Int3Tuple(2, 8, 12), Int3Tuple(2, 8, 13), Int3Tuple(2, 9, 10), Int3Tuple(2, 9, 11), Int3Tuple(2, 9, 12), Int3Tuple(2, 9, 13), Int3Tuple(2, 10, 12), Int3Tuple(2, 10, 13), Int3Tuple(2, 11, 12), Int3Tuple(2, 11, 13), Int3Tuple(3, 4, 6), Int3Tuple(3, 4, 7), Int3Tuple(3, 4, 8), Int3Tuple(3, 4, 9), Int3Tuple(3, 4, 10), Int3Tuple(3, 4, 11), Int3Tuple(3, 4, 12), Int3Tuple(3, 4, 13), Int3Tuple(3, 5, 6), Int3Tuple(3, 5, 7), Int3Tuple(3, 5, 8), Int3Tuple(3, 5, 9), Int3Tuple(3, 5, 10), Int3Tuple(3, 5, 11), Int3Tuple(3, 5, 12), Int3Tuple(3, 5, 13), Int3Tuple(3, 6, 8), Int3Tuple(3, 6, 9), Int3Tuple(3, 6, 10), Int3Tuple(3, 6, 11), Int3Tuple(3, 6, 12), Int3Tuple(3, 6, 13), Int3Tuple(3, 7, 8), Int3Tuple(3, 7, 9), Int3Tuple(3, 7, 10), Int3Tuple(3, 7, 11), Int3Tuple(3, 7, 12), Int3Tuple(3, 7, 13), Int3Tuple(3, 8, 10), Int3Tuple(3, 8, 11), Int3Tuple(3, 8, 12), Int3Tuple(3, 8, 13), Int3Tuple(3, 9, 10), Int3Tuple(3, 9, 11), Int3Tuple(3, 9, 12), Int3Tuple(3, 9, 13), Int3Tuple(3, 10, 12), Int3Tuple(3, 10, 13), Int3Tuple(3, 11, 12), Int3Tuple(3, 11, 13), Int3Tuple(4, 6, 8), Int3Tuple(4, 6, 9), Int3Tuple(4, 6, 10), Int3Tuple(4, 6, 11), Int3Tuple(4, 6, 12), Int3Tuple(4, 6, 13), Int3Tuple(4, 7, 8), Int3Tuple(4, 7, 9), Int3Tuple(4, 7, 10), Int3Tuple(4, 7, 11), Int3Tuple(4, 7, 12), Int3Tuple(4, 7, 13), Int3Tuple(4, 8, 10), Int3Tuple(4, 8, 11), Int3Tuple(4, 8, 12), Int3Tuple(4, 8, 13), Int3Tuple(4, 9, 10), Int3Tuple(4, 9, 11), Int3Tuple(4, 9, 12), Int3Tuple(4, 9, 13), Int3Tuple(4, 10, 12), Int3Tuple(4, 10, 13), Int3Tuple(4, 11, 12), Int3Tuple(4, 11, 13), Int3Tuple(5, 6, 8), Int3Tuple(5, 6, 9), Int3Tuple(5, 6, 10), Int3Tuple(5, 6, 11), Int3Tuple(5, 6, 12), Int3Tuple(5, 6, 13), Int3Tuple(5, 7, 8), Int3Tuple(5, 7, 9), Int3Tuple(5, 7, 10), Int3Tuple(5, 7, 11), Int3Tuple(5, 7, 12), Int3Tuple(5, 7, 13), Int3Tuple(5, 8, 10), Int3Tuple(5, 8, 11), Int3Tuple(5, 8, 12), Int3Tuple(5, 8, 13), Int3Tuple(5, 9, 10), Int3Tuple(5, 9, 11), Int3Tuple(5, 9, 12), Int3Tuple(5, 9, 13), Int3Tuple(5, 10, 12), Int3Tuple(5, 10, 13), Int3Tuple(5, 11, 12), Int3Tuple(5, 11, 13), Int3Tuple(6, 8, 10), Int3Tuple(6, 8, 11), Int3Tuple(6, 8, 12), Int3Tuple(6, 8, 13), Int3Tuple(6, 9, 10), Int3Tuple(6, 9, 11), Int3Tuple(6, 9, 12), Int3Tuple(6, 9, 13), Int3Tuple(6, 10, 12), Int3Tuple(6, 10, 13), Int3Tuple(6, 11, 12), Int3Tuple(6, 11, 13), Int3Tuple(7, 8, 10), Int3Tuple(7, 8, 11), Int3Tuple(7, 8, 12), Int3Tuple(7, 8, 13), Int3Tuple(7, 9, 10), Int3Tuple(7, 9, 11), Int3Tuple(7, 9, 12), Int3Tuple(7, 9, 13), Int3Tuple(7, 10, 12), Int3Tuple(7, 10, 13), Int3Tuple(7, 11, 12), Int3Tuple(7, 11, 13), Int3Tuple(8, 10, 12), Int3Tuple(8, 10, 13), Int3Tuple(8, 11, 12), Int3Tuple(8, 11, 13), Int3Tuple(9, 10, 12), Int3Tuple(9, 10, 13), Int3Tuple(9, 11, 12), Int3Tuple(9, 11, 13) }; void generateBoundryLinesForDop14(const std::vector& dots, const glm::vec3& center, std::vector& linesOut) { if (dots.size() != DOP14_COUNT) { return; } // iterate over all purmutations of non-parallel planes. // find all the vertices that lie on the surface of the k-dop std::vector vertices; for (auto& tuple : DOP14_PLANE_COMBINATIONS) { int i = std::get<0>(tuple); int j = std::get<1>(tuple); int k = std::get<2>(tuple); glm::vec4 planeA(DOP14_NORMALS[i], dots[i]); glm::vec4 planeB(DOP14_NORMALS[j], dots[j]); glm::vec4 planeC(DOP14_NORMALS[k], dots[k]); glm::vec3 intersectionPoint; const float IN_FRONT_MARGIN = 0.01f; if (findIntersectionOfThreePlanes(planeA, planeB, planeC, intersectionPoint)) { bool inFront = false; for (int p = 0; p < DOP14_COUNT; p++) { if (glm::dot(DOP14_NORMALS[p], intersectionPoint) > dots[p] + IN_FRONT_MARGIN) { inFront = true; } } if (!inFront) { vertices.push_back(intersectionPoint); } } } // build a set of lines between these vertices, that also lie on the surface of the k-dop. for (size_t i = 0; i < vertices.size(); i++) { for (size_t j = i; j < vertices.size(); j++) { glm::vec3 midPoint = (vertices[i] + vertices[j]) * 0.5f; int onSurfaceCount = 0; const float SURFACE_MARGIN = 0.01f; for (int p = 0; p < DOP14_COUNT; p++) { float d = glm::dot(DOP14_NORMALS[p], midPoint); if (d > dots[p] - SURFACE_MARGIN && d < dots[p] + SURFACE_MARGIN) { onSurfaceCount++; } } if (onSurfaceCount > 1) { linesOut.push_back(vertices[i] + center); linesOut.push_back(vertices[j] + center); } } } } bool computeRealQuadraticRoots(float a, float b, float c, glm::vec2& roots) { float discriminant = b * b - 4.0f * a * c; if (discriminant < 0.0f) { return false; } else if (discriminant == 0.0f) { roots.x = (-b + sqrtf(discriminant)) / (2.0f * a); } else { float discriminantRoot = sqrtf(discriminant); roots.x = (-b + discriminantRoot) / (2.0f * a); roots.y = (-b - discriminantRoot) / (2.0f * a); } return true; } // The following functions provide an analytical solution to a quartic equation, adapted from the solution here: https://github.com/sasamil/Quartic unsigned int solveP3(float* x, float a, float b, float c) { float a2 = a * a; float q = (a2 - 3.0f * b) / 9.0f; float r = (a * (2.0f * a2 - 9.0f * b) + 27.0f * c) / 54.0f; float r2 = r * r; float q3 = q * q * q; float A, B; if (r2 < q3) { float t = r / sqrtf(q3); t = glm::clamp(t, -1.0f, 1.0f); t = acosf(t); a /= 3.0f; q = -2.0f * sqrtf(q); x[0] = q * cosf(t / 3.0f) - a; x[1] = q * cosf((t + 2.0f * (float)M_PI) / 3.0f) - a; x[2] = q * cosf((t - 2.0f * (float)M_PI) / 3.0f) - a; return 3; } else { A = -powf(fabsf(r) + sqrtf(r2 - q3), 1.0f / 3.0f); if (r < 0) { A = -A; } B = (A == 0.0f ? 0.0f : q / A); a /= 3.0f; x[0] = (A + B) - a; x[1] = -0.5f * (A + B) - a; x[2] = 0.5f * sqrtf(3.0f) * (A - B); if (fabsf(x[2]) < EPSILON) { x[2] = x[1]; return 2; } return 1; } } bool solve_quartic(float a, float b, float c, float d, glm::vec4& roots) { float a3 = -b; float b3 = a * c - 4.0f *d; float c3 = -a * a * d - c * c + 4.0f * b * d; float px3[3]; unsigned int iZeroes = solveP3(px3, a3, b3, c3); float q1, q2, p1, p2, D, sqD, y; y = px3[0]; if (iZeroes != 1) { if (fabsf(px3[1]) > fabsf(y)) { y = px3[1]; } if (fabsf(px3[2]) > fabsf(y)) { y = px3[2]; } } D = y * y - 4.0f * d; if (fabsf(D) < EPSILON) { q1 = q2 = 0.5f * y; D = a * a - 4.0f * (b - y); if (fabsf(D) < EPSILON) { p1 = p2 = 0.5f * a; } else { sqD = sqrtf(D); p1 = 0.5f * (a + sqD); p2 = 0.5f * (a - sqD); } } else { sqD = sqrtf(D); q1 = 0.5f * (y + sqD); q2 = 0.5f * (y - sqD); p1 = (a * q1 - c) / (q1 - q2); p2 = (c - a * q2) / (q1 - q2); } std::complex x1, x2, x3, x4; D = p1 * p1 - 4.0f * q1; if (D < 0.0f) { x1.real(-0.5f * p1); x1.imag(0.5f * sqrtf(-D)); x2 = std::conj(x1); } else { sqD = sqrtf(D); x1.real(0.5f * (-p1 + sqD)); x2.real(0.5f * (-p1 - sqD)); } D = p2 * p2 - 4.0f * q2; if (D < 0.0f) { x3.real(-0.5f * p2); x3.imag(0.5f * sqrtf(-D)); x4 = std::conj(x3); } else { sqD = sqrtf(D); x3.real(0.5f * (-p2 + sqD)); x4.real(0.5f * (-p2 - sqD)); } bool hasRealRoot = false; if (fabsf(x1.imag()) < EPSILON) { roots.x = x1.real(); hasRealRoot = true; } if (fabsf(x2.imag()) < EPSILON) { roots.y = x2.real(); hasRealRoot = true; } if (fabsf(x3.imag()) < EPSILON) { roots.z = x3.real(); hasRealRoot = true; } if (fabsf(x4.imag()) < EPSILON) { roots.w = x4.real(); hasRealRoot = true; } return hasRealRoot; } bool computeRealQuarticRoots(float a, float b, float c, float d, float e, glm::vec4& roots) { return solve_quartic(b / a, c / a, d / a, e / a, roots); }