// SPDX-License-Identifier: BSL-1.0 // Geometric Tools, LLC // Copyright (c) 1998-2010 // Distributed under the Boost Software License, Version 1.0. // http://www.boost.org/LICENSE_1_0.txt // http://www.geometrictools.com/License/Boost/LICENSE_1_0.txt // // File Version: 4.10.0 (2009/11/18) #include "Wm4FoundationPCH.h" #include "Wm4ContBox3.h" #include "Wm4ApprGaussPointsFit3.h" #include "Wm4ContBox2.h" #include "Wm4ConvexHull3.h" #include "Wm4EdgeKey.h" #include "Wm4Quaternion.h" namespace Wm4 { //---------------------------------------------------------------------------- template Box3 ContAlignedBox (int iQuantity, const Vector3* akPoint) { Vector3 kMin, kMax; Vector3::ComputeExtremes(iQuantity,akPoint,kMin,kMax); Box3 kBox; kBox.Center = ((Real)0.5)*(kMin + kMax); kBox.Axis[0] = Vector3::UNIT_X; kBox.Axis[1] = Vector3::UNIT_Y; kBox.Axis[2] = Vector3::UNIT_Z; Vector3 kHalfDiagonal = ((Real)0.5)*(kMax - kMin); for (int i = 0; i < 3; i++) { kBox.Extent[i] = kHalfDiagonal[i]; } return kBox; } //---------------------------------------------------------------------------- template Box3 ContOrientedBox (int iQuantity, const Vector3* akPoint) { Box3 kBox = GaussPointsFit3(iQuantity,akPoint); // Let C be the box center and let U0, U1, and U2 be the box axes. Each // input point is of the form X = C + y0*U0 + y1*U1 + y2*U2. The // following code computes min(y0), max(y0), min(y1), max(y1), min(y2), // and max(y2). The box center is then adjusted to be // C' = C + 0.5*(min(y0)+max(y0))*U0 + 0.5*(min(y1)+max(y1))*U1 + // 0.5*(min(y2)+max(y2))*U2 Vector3 kDiff = akPoint[0] - kBox.Center; Vector3 kMin(kDiff.Dot(kBox.Axis[0]),kDiff.Dot(kBox.Axis[1]), kDiff.Dot(kBox.Axis[2])); Vector3 kMax = kMin; for (int i = 1; i < iQuantity; i++) { kDiff = akPoint[i] - kBox.Center; for (int j = 0; j < 3; j++) { Real fDot = kDiff.Dot(kBox.Axis[j]); if (fDot < kMin[j]) { kMin[j] = fDot; } else if (fDot > kMax[j]) { kMax[j] = fDot; } } } kBox.Center += (((Real)0.5)*(kMin[0]+kMax[0]))*kBox.Axis[0] + (((Real)0.5)*(kMin[1]+kMax[1]))*kBox.Axis[1] + (((Real)0.5)*(kMin[2]+kMax[2]))*kBox.Axis[2]; kBox.Extent[0] = ((Real)0.5)*(kMax[0] - kMin[0]); kBox.Extent[1] = ((Real)0.5)*(kMax[1] - kMin[1]); kBox.Extent[2] = ((Real)0.5)*(kMax[2] - kMin[2]); return kBox; } //---------------------------------------------------------------------------- template Box3 ContMinBox (int iQuantity, const Vector3* akPoint, Real fEpsilon, Query::Type eQueryType) { Box3 kBox; // Get the convex hull of the points. ConvexHull3 kHull(iQuantity,(Vector3*)akPoint,fEpsilon,false, eQueryType); int iHDim = kHull.GetDimension(); int iHQuantity; const int* aiHIndex; if (iHDim == 0) { kBox.Center = akPoint[0]; kBox.Axis[0] = Vector3::UNIT_X; kBox.Axis[1] = Vector3::UNIT_Y; kBox.Axis[2] = Vector3::UNIT_Z; kBox.Extent[0] = (Real)0.0; kBox.Extent[1] = (Real)0.0; kBox.Extent[2] = (Real)0.0; return kBox; } if (iHDim == 1) { ConvexHull1* pkHull1 = kHull.GetConvexHull1(); aiHIndex = pkHull1->GetIndices(); kBox.Center = ((Real)0.5)*(akPoint[aiHIndex[0]]+akPoint[aiHIndex[1]]); Vector3 kDiff = akPoint[aiHIndex[1]] - akPoint[aiHIndex[0]]; kBox.Extent[0] = ((Real)0.5)*kDiff.Normalize(); kBox.Extent[1] = (Real)0.0; kBox.Extent[2] = (Real)0.0; kBox.Axis[0] = kDiff; Vector3::GenerateComplementBasis(kBox.Axis[1],kBox.Axis[2], kBox.Axis[0]); WM4_DELETE pkHull1; return kBox; } int i, j; Vector3 kOrigin, kDiff, kU, kV, kW; Vector2* akPoint2; Box2 kBox2; if (iHDim == 2) { // When ConvexHull3 reports that the point set is 2-dimensional, the // caller is responsible for projecting the points onto a plane and // calling ConvexHull2. ConvexHull3 does provide information about // the plane of the points. In this application, we need only // project the input points onto that plane and call ContMinBox in // two dimensions. // Get a coordinate system relative to the plane of the points. kOrigin = kHull.GetPlaneOrigin(); kW = kHull.GetPlaneDirection(0).Cross(kHull.GetPlaneDirection(1)); Vector3::GenerateComplementBasis(kU,kV,kW); // Project the input points onto the plane. akPoint2 = WM4_NEW Vector2[iQuantity]; for (i = 0; i < iQuantity; i++) { kDiff = akPoint[i] - kOrigin; akPoint2[i].X() = kU.Dot(kDiff); akPoint2[i].Y() = kV.Dot(kDiff); } // Compute the minimum area box in 2D. kBox2 = ContMinBox(iQuantity,akPoint2,fEpsilon,eQueryType, false); WM4_DELETE[] akPoint2; // Lift the values into 3D. kBox.Center = kOrigin + kBox2.Center.X()*kU + kBox2.Center.Y()*kV; kBox.Axis[0] = kBox2.Axis[0].X()*kU + kBox2.Axis[0].Y()*kV; kBox.Axis[1] = kBox2.Axis[1].X()*kU + kBox2.Axis[1].Y()*kV; kBox.Axis[2] = kW; kBox.Extent[0] = kBox2.Extent[0]; kBox.Extent[1] = kBox2.Extent[1]; kBox.Extent[2] = (Real)0.0; return kBox; } iHQuantity = kHull.GetSimplexQuantity(); aiHIndex = kHull.GetIndices(); Real fVolume, fMinVolume = Math::MAX_REAL; // Create the unique set of hull vertices to minimize the time spent // projecting vertices onto planes of the hull faces. std::set kUniqueIndices; for (i = 0; i < 3*iHQuantity; i++) { kUniqueIndices.insert(aiHIndex[i]); } // Use the rotating calipers method on the projection of the hull onto // the plane of each face. Also project the hull onto the normal line // of each face. The minimum area box in the plane and the height on // the line produce a containing box. If its volume is smaller than the // current volume, this box is the new candidate for the minimum volume // box. The unique edges are accumulated into a set for use by a later // step in the algorithm. const int* piIndex = aiHIndex; Real fHeight, fMinHeight, fMaxHeight; std::set kEdges; akPoint2 = WM4_NEW Vector2[kUniqueIndices.size()]; for (i = 0; i < iHQuantity; i++) { // get triangle int iV0 = *piIndex++; int iV1 = *piIndex++; int iV2 = *piIndex++; // save the edges for later use kEdges.insert(EdgeKey(iV0,iV1)); kEdges.insert(EdgeKey(iV1,iV2)); kEdges.insert(EdgeKey(iV2,iV0)); // get 3D coordinate system relative to plane of triangle kOrigin = (akPoint[iV0] + akPoint[iV1] + akPoint[iV2])/(Real)3.0; Vector3 kEdge1 = akPoint[iV1] - akPoint[iV0]; Vector3 kEdge2 = akPoint[iV2] - akPoint[iV0]; kW = kEdge2.UnitCross(kEdge1); // inner-pointing normal if (kW == Vector3::ZERO) { // The triangle is needle-like, so skip it. continue; } Vector3::GenerateComplementBasis(kU,kV,kW); // Project points onto plane of triangle, onto normal line of plane. // TO DO. In theory, minHeight should be zero since W points to the // interior of the hull. However, the snap rounding used in the 3D // convex hull finder involves loss of precision, which in turn can // cause a hull facet to have the wrong ordering (clockwise instead // of counterclockwise when viewed from outside the hull). The // height calculations here trap that problem (the incorrectly ordered // face will not affect the minimum volume box calculations). fMinHeight = (Real)0.0; fMaxHeight = (Real)0.0; j = 0; std::set::const_iterator pkUI = kUniqueIndices.begin(); while (pkUI != kUniqueIndices.end()) { int index = *pkUI++; kDiff = akPoint[index] - kOrigin; akPoint2[j].X() = kU.Dot(kDiff); akPoint2[j].Y() = kV.Dot(kDiff); fHeight = kW.Dot(kDiff); if (fHeight > fMaxHeight) { fMaxHeight = fHeight; } else if (fHeight < fMinHeight) { fMinHeight = fHeight; } j++; } if (-fMinHeight > fMaxHeight) { fMaxHeight = -fMinHeight; } // compute minimum area box in 2D kBox2 = ContMinBox((int)kUniqueIndices.size(),akPoint2,fEpsilon, eQueryType,false); // update current minimum-volume box (if necessary) fVolume = fMaxHeight*kBox2.Extent[0]*kBox2.Extent[1]; if (fVolume < fMinVolume) { fMinVolume = fVolume; // lift the values into 3D kBox.Extent[0] = kBox2.Extent[0]; kBox.Extent[1] = kBox2.Extent[1]; kBox.Extent[2] = ((Real)0.5)*fMaxHeight; kBox.Axis[0] = kBox2.Axis[0].X()*kU + kBox2.Axis[0].Y()*kV; kBox.Axis[1] = kBox2.Axis[1].X()*kU + kBox2.Axis[1].Y()*kV; kBox.Axis[2] = kW; kBox.Center = kOrigin + kBox2.Center.X()*kU + kBox2.Center.Y()*kV + kBox.Extent[2]*kW; } } // The minimum-volume box can also be supported by three mutually // orthogonal edges of the convex hull. For each triple of orthogonal // edges, compute the minimum-volume box for that coordinate frame by // projecting the points onto the axes of the frame. std::set::const_iterator pkE2; for (pkE2 = kEdges.begin(); pkE2 != kEdges.end(); pkE2++) { kW = akPoint[pkE2->V[1]] - akPoint[pkE2->V[0]]; kW.Normalize(); std::set::const_iterator pkE1 = pkE2; for (++pkE1; pkE1 != kEdges.end(); pkE1++) { kV = akPoint[pkE1->V[1]] - akPoint[pkE1->V[0]]; kV.Normalize(); Real fDot = kV.Dot(kW); if (Math::FAbs(fDot) > Math::ZERO_TOLERANCE) { continue; } std::set::const_iterator pkE0 = pkE1; for (++pkE0; pkE0 != kEdges.end(); pkE0++) { kU = akPoint[pkE0->V[1]] - akPoint[pkE0->V[0]]; kU.Normalize(); fDot = kU.Dot(kV); if (Math::FAbs(fDot) > Math::ZERO_TOLERANCE) { continue; } fDot = kU.Dot(kW); if (Math::FAbs(fDot) > Math::ZERO_TOLERANCE) { continue; } // The three edges are mutually orthogonal. Project the // hull points onto the lines containing the edges. Use // hull point zero as the origin. Real fUMin = (Real)0.0, fUMax = (Real)0.0; Real fVMin = (Real)0.0, fVMax = (Real)0.0; Real fWMin = (Real)0.0, fWMax = (Real)0.0; kOrigin = akPoint[aiHIndex[0]]; std::set::const_iterator pkUI = kUniqueIndices.begin(); while (pkUI != kUniqueIndices.end()) { int index = *pkUI++; kDiff = akPoint[index] - kOrigin; Real fU = kU.Dot(kDiff); if (fU < fUMin) { fUMin = fU; } else if (fU > fUMax) { fUMax = fU; } Real fV = kV.Dot(kDiff); if (fV < fVMin) { fVMin = fV; } else if (fV > fVMax) { fVMax = fV; } Real fW = kW.Dot(kDiff); if (fW < fWMin) { fWMin = fW; } else if (fW > fWMax) { fWMax = fW; } } Real fUExtent = ((Real)0.5)*(fUMax - fUMin); Real fVExtent = ((Real)0.5)*(fVMax - fVMin); Real fWExtent = ((Real)0.5)*(fWMax - fWMin); // update current minimum-volume box (if necessary) fVolume = fUExtent*fVExtent*fWExtent; if (fVolume < fMinVolume) { fMinVolume = fVolume; kBox.Extent[0] = fUExtent; kBox.Extent[1] = fVExtent; kBox.Extent[2] = fWExtent; kBox.Axis[0] = kU; kBox.Axis[1] = kV; kBox.Axis[2] = kW; kBox.Center = kOrigin + ((Real)0.5)*(fUMin+fUMax)*kU + ((Real)0.5)*(fVMin+fVMax)*kV + ((Real)0.5)*(fWMin+fWMax)*kW; } } } } WM4_DELETE[] akPoint2; return kBox; } //---------------------------------------------------------------------------- template bool InBox (const Vector3& rkPoint, const Box3& rkBox) { Vector3 kDiff = rkPoint - rkBox.Center; for (int i = 0; i < 3; i++) { Real fCoeff = kDiff.Dot(rkBox.Axis[i]); if (Math::FAbs(fCoeff) > rkBox.Extent[i]) { return false; } } return true; } //---------------------------------------------------------------------------- template Box3 MergeBoxes (const Box3& rkBox0, const Box3& rkBox1) { // construct a box that contains the input boxes Box3 kBox; // The first guess at the box center. This value will be updated later // after the input box vertices are projected onto axes determined by an // average of box axes. kBox.Center = ((Real)0.5)*(rkBox0.Center + rkBox1.Center); // A box's axes, when viewed as the columns of a matrix, form a rotation // matrix. The input box axes are converted to quaternions. The average // quaternion is computed, then normalized to unit length. The result is // the slerp of the two input quaternions with t-value of 1/2. The result // is converted back to a rotation matrix and its columns are selected as // the merged box axes. Quaternion kQ0, kQ1; kQ0.FromRotationMatrix(rkBox0.Axis); kQ1.FromRotationMatrix(rkBox1.Axis); if (kQ0.Dot(kQ1) < (Real)0.0) { kQ1 = -kQ1; } Quaternion kQ = kQ0 + kQ1; Real fInvLength = Math::InvSqrt(kQ.Dot(kQ)); kQ = fInvLength*kQ; kQ.ToRotationMatrix(kBox.Axis); // Project the input box vertices onto the merged-box axes. Each axis // D[i] containing the current center C has a minimum projected value // pmin[i] and a maximum projected value pmax[i]. The corresponding end // points on the axes are C+pmin[i]*D[i] and C+pmax[i]*D[i]. The point C // is not necessarily the midpoint for any of the intervals. The actual // box center will be adjusted from C to a point C' that is the midpoint // of each interval, // C' = C + sum_{i=0}^2 0.5*(pmin[i]+pmax[i])*D[i] // The box extents are // e[i] = 0.5*(pmax[i]-pmin[i]) int i, j; Real fDot; Vector3 akVertex[8], kDiff; Vector3 kMin = Vector3::ZERO; Vector3 kMax = Vector3::ZERO; rkBox0.ComputeVertices(akVertex); for (i = 0; i < 8; i++) { kDiff = akVertex[i] - kBox.Center; for (j = 0; j < 3; j++) { fDot = kDiff.Dot(kBox.Axis[j]); if (fDot > kMax[j]) { kMax[j] = fDot; } else if (fDot < kMin[j]) { kMin[j] = fDot; } } } rkBox1.ComputeVertices(akVertex); for (i = 0; i < 8; i++) { kDiff = akVertex[i] - kBox.Center; for (j = 0; j < 3; j++) { fDot = kDiff.Dot(kBox.Axis[j]); if (fDot > kMax[j]) { kMax[j] = fDot; } else if (fDot < kMin[j]) { kMin[j] = fDot; } } } // [kMin,kMax] is the axis-aligned box in the coordinate system of the // merged box axes. Update the current box center to be the center of // the new box. Compute the extents based on the new center. for (j = 0; j < 3; j++) { kBox.Center += (((Real)0.5)*(kMax[j]+kMin[j]))*kBox.Axis[j]; kBox.Extent[j] = ((Real)0.5)*(kMax[j]-kMin[j]); } return kBox; } //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // explicit instantiation //---------------------------------------------------------------------------- template WM4_FOUNDATION_ITEM Box3 ContAlignedBox (int, const Vector3*); template WM4_FOUNDATION_ITEM Box3 ContOrientedBox (int, const Vector3*); template WM4_FOUNDATION_ITEM Box3 ContMinBox (int, const Vector3*, float, Query::Type); template WM4_FOUNDATION_ITEM bool InBox (const Vector3&, const Box3&); template WM4_FOUNDATION_ITEM Box3 MergeBoxes (const Box3&, const Box3&); template WM4_FOUNDATION_ITEM Box3 ContAlignedBox (int, const Vector3*); template WM4_FOUNDATION_ITEM Box3 ContOrientedBox (int, const Vector3*); template WM4_FOUNDATION_ITEM Box3 ContMinBox (int, const Vector3*, double, Query::Type); template WM4_FOUNDATION_ITEM bool InBox (const Vector3&, const Box3&); template WM4_FOUNDATION_ITEM Box3 MergeBoxes (const Box3&, const Box3&); //---------------------------------------------------------------------------- }