// 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 "Wm4ContBox2.h" #include "Wm4ApprGaussPointsFit2.h" #include "Wm4ConvexHull2.h" namespace Wm4 { //---------------------------------------------------------------------------- template Box2 ContAlignedBox (int iQuantity, const Vector2* akPoint) { Vector2 kMin, kMax; Vector2::ComputeExtremes(iQuantity,akPoint,kMin,kMax); Box2 kBox; kBox.Center = ((Real)0.5)*(kMin + kMax); kBox.Axis[0] = Vector2::UNIT_X; kBox.Axis[1] = Vector2::UNIT_Y; Vector2 kHalfDiagonal = ((Real)0.5)*(kMax - kMin); for (int i = 0; i < 2; i++) { kBox.Extent[i] = kHalfDiagonal[i]; } return kBox; } //---------------------------------------------------------------------------- template Box2 ContOrientedBox (int iQuantity, const Vector2* akPoint) { Box2 kBox = GaussPointsFit2(iQuantity,akPoint); // Let C be the box center and let U0 and U1 be the box axes. Each // input point is of the form X = C + y0*U0 + y1*U1. 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 Vector2 kDiff = akPoint[0] - kBox.Center; Vector2 kMin(kDiff.Dot(kBox.Axis[0]),kDiff.Dot(kBox.Axis[1])); Vector2 kMax = kMin; for (int i = 1; i < iQuantity; i++) { kDiff = akPoint[i] - kBox.Center; for (int j = 0; j < 2; 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]; kBox.Extent[0] = ((Real)0.5)*(kMax[0] - kMin[0]); kBox.Extent[1] = ((Real)0.5)*(kMax[1] - kMin[1]); return kBox; } //---------------------------------------------------------------------------- template static void UpdateBox (const Vector2& rkLPoint, const Vector2& rkRPoint, const Vector2& rkBPoint, const Vector2& rkTPoint, const Vector2& rkU, const Vector2& rkV, Real& rfMinAreaDiv4, Box2& rkBox) { Vector2 kRLDiff = rkRPoint - rkLPoint; Vector2 kTBDiff = rkTPoint - rkBPoint; Real fExtent0 = ((Real)0.5)*(rkU.Dot(kRLDiff)); Real fExtent1 = ((Real)0.5)*(rkV.Dot(kTBDiff)); Real fAreaDiv4 = fExtent0*fExtent1; if (fAreaDiv4 < rfMinAreaDiv4) { rfMinAreaDiv4 = fAreaDiv4; rkBox.Axis[0] = rkU; rkBox.Axis[1] = rkV; rkBox.Extent[0] = fExtent0; rkBox.Extent[1] = fExtent1; Vector2 kLBDiff = rkLPoint - rkBPoint; rkBox.Center = rkLPoint + rkU*fExtent0 + rkV*(fExtent1 - rkV.Dot(kLBDiff)); } } //---------------------------------------------------------------------------- template Box2 ContMinBox (int iQuantity, const Vector2* akPoint, Real fEpsilon, Query::Type eQueryType, bool bIsConvexPolygon) { Box2 kBox; // get the convex hull of the points Vector2* akHPoint = nullptr; if (bIsConvexPolygon) { akHPoint = (Vector2*)akPoint; } else { ConvexHull2 kHull(iQuantity,(Vector2*)akPoint,fEpsilon, false,eQueryType); int iHDim = kHull.GetDimension(); int iHQuantity = kHull.GetSimplexQuantity(); const int* aiHIndex = kHull.GetIndices(); if (iHDim == 0) { kBox.Center = akPoint[0]; kBox.Axis[0] = Vector2::UNIT_X; kBox.Axis[1] = Vector2::UNIT_Y; kBox.Extent[0] = (Real)0.0; kBox.Extent[1] = (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]]); Vector2 kDiff = akPoint[aiHIndex[1]] - akPoint[aiHIndex[0]]; kBox.Extent[0] = ((Real)0.5)*kDiff.Normalize(); kBox.Extent[1] = (Real)0.0; kBox.Axis[0] = kDiff; kBox.Axis[1] = -kBox.Axis[0].Perp(); WM4_DELETE pkHull1; return kBox; } iQuantity = iHQuantity; akHPoint = WM4_NEW Vector2[iQuantity]; for (int i = 0; i < iQuantity; i++) { akHPoint[i] = akPoint[aiHIndex[i]]; } } // The input points are V[0] through V[N-1] and are assumed to be the // vertices of a convex polygon that are counterclockwise ordered. The // input points must not contain three consecutive collinear points. // Unit-length edge directions of convex polygon. These could be // precomputed and passed to this routine if the application requires it. int iQuantityM1 = iQuantity -1; Vector2* akEdge = WM4_NEW Vector2[iQuantity]; bool* abVisited = WM4_NEW bool[iQuantity]; int i; for (i = 0; i < iQuantityM1; i++) { akEdge[i] = akHPoint[i+1] - akHPoint[i]; akEdge[i].Normalize(); abVisited[i] = false; } akEdge[iQuantityM1] = akHPoint[0] - akHPoint[iQuantityM1]; akEdge[iQuantityM1].Normalize(); abVisited[iQuantityM1] = false; // Find the smallest axis-aligned box containing the points. Keep track // of the extremum indices, L (left), R (right), B (bottom), and T (top) // so that the following constraints are met: // V[L].X() <= V[i].X() for all i and V[(L+1)%N].X() > V[L].X() // V[R].X() >= V[i].X() for all i and V[(R+1)%N].X() < V[R].X() // V[B].Y() <= V[i].Y() for all i and V[(B+1)%N].Y() > V[B].Y() // V[T].Y() >= V[i].Y() for all i and V[(T+1)%N].Y() < V[T].Y() Real fXMin = akHPoint[0].X(), fXMax = fXMin; Real fYMin = akHPoint[0].Y(), fYMax = fYMin; int iLIndex = 0, iRIndex = 0, iBIndex = 0, iTIndex = 0; for (i = 1; i < iQuantity; i++) { if (akHPoint[i].X() <= fXMin) { fXMin = akHPoint[i].X(); iLIndex = i; } if (akHPoint[i].X() >= fXMax) { fXMax = akHPoint[i].X(); iRIndex = i; } if (akHPoint[i].Y() <= fYMin) { fYMin = akHPoint[i].Y(); iBIndex = i; } if (akHPoint[i].Y() >= fYMax) { fYMax = akHPoint[i].Y(); iTIndex = i; } } // wrap-around tests to ensure the constraints mentioned above if (iLIndex == iQuantityM1) { if (akHPoint[0].X() <= fXMin) { fXMin = akHPoint[0].X(); iLIndex = 0; } } if (iRIndex == iQuantityM1) { if (akHPoint[0].X() >= fXMax) { fXMax = akHPoint[0].X(); iRIndex = 0; } } if (iBIndex == iQuantityM1) { if (akHPoint[0].Y() <= fYMin) { fYMin = akHPoint[0].Y(); iBIndex = 0; } } if (iTIndex == iQuantityM1) { if (akHPoint[0].Y() >= fYMax) { fYMax = akHPoint[0].Y(); iTIndex = 0; } } // dimensions of axis-aligned box (extents store width and height for now) kBox.Center.X() = ((Real)0.5)*(fXMin + fXMax); kBox.Center.Y() = ((Real)0.5)*(fYMin + fYMax); kBox.Axis[0] = Vector2::UNIT_X; kBox.Axis[1] = Vector2::UNIT_Y; kBox.Extent[0] = ((Real)0.5)*(fXMax - fXMin); kBox.Extent[1] = ((Real)0.5)*(fYMax - fYMin); Real fMinAreaDiv4 = kBox.Extent[0]*kBox.Extent[1]; // rotating calipers algorithm enum { F_NONE, F_LEFT, F_RIGHT, F_BOTTOM, F_TOP }; Vector2 kU = Vector2::UNIT_X, kV = Vector2::UNIT_Y; bool bDone = false; while (!bDone) { // determine edge that forms smallest angle with current box edges int iFlag = F_NONE; Real fMaxDot = (Real)0.0; Real fDot = kU.Dot(akEdge[iBIndex]); if (fDot > fMaxDot) { fMaxDot = fDot; iFlag = F_BOTTOM; } fDot = kV.Dot(akEdge[iRIndex]); if (fDot > fMaxDot) { fMaxDot = fDot; iFlag = F_RIGHT; } fDot = -kU.Dot(akEdge[iTIndex]); if (fDot > fMaxDot) { fMaxDot = fDot; iFlag = F_TOP; } fDot = -kV.Dot(akEdge[iLIndex]); if (fDot > fMaxDot) { fMaxDot = fDot; iFlag = F_LEFT; } switch (iFlag) { case F_BOTTOM: if (abVisited[iBIndex]) { bDone = true; } else { // compute box axes with E[B] as an edge kU = akEdge[iBIndex]; kV = -kU.Perp(); UpdateBox(akHPoint[iLIndex],akHPoint[iRIndex], akHPoint[iBIndex],akHPoint[iTIndex],kU,kV,fMinAreaDiv4, kBox); // mark edge visited and rotate the calipers abVisited[iBIndex] = true; if (++iBIndex == iQuantity) { iBIndex = 0; } } break; case F_RIGHT: if (abVisited[iRIndex]) { bDone = true; } else { // compute dimensions of box with E[R] as an edge kV = akEdge[iRIndex]; kU = kV.Perp(); UpdateBox(akHPoint[iLIndex],akHPoint[iRIndex], akHPoint[iBIndex],akHPoint[iTIndex],kU,kV,fMinAreaDiv4, kBox); // mark edge visited and rotate the calipers abVisited[iRIndex] = true; if (++iRIndex == iQuantity) { iRIndex = 0; } } break; case F_TOP: if (abVisited[iTIndex]) { bDone = true; } else { // compute dimensions of box with E[T] as an edge kU = -akEdge[iTIndex]; kV = -kU.Perp(); UpdateBox(akHPoint[iLIndex],akHPoint[iRIndex], akHPoint[iBIndex],akHPoint[iTIndex],kU,kV,fMinAreaDiv4, kBox); // mark edge visited and rotate the calipers abVisited[iTIndex] = true; if (++iTIndex == iQuantity) { iTIndex = 0; } } break; case F_LEFT: if (abVisited[iLIndex]) { bDone = true; } else { // compute dimensions of box with E[L] as an edge kV = -akEdge[iLIndex]; kU = kV.Perp(); UpdateBox(akHPoint[iLIndex],akHPoint[iRIndex], akHPoint[iBIndex],akHPoint[iTIndex],kU,kV,fMinAreaDiv4, kBox); // mark edge visited and rotate the calipers abVisited[iLIndex] = true; if (++iLIndex == iQuantity) { iLIndex = 0; } } break; case F_NONE: // polygon is a rectangle bDone = true; break; } } WM4_DELETE[] abVisited; WM4_DELETE[] akEdge; if (!bIsConvexPolygon) { WM4_DELETE[] akHPoint; } return kBox; } //---------------------------------------------------------------------------- template bool InBox (const Vector2& rkPoint, const Box2& rkBox) { Vector2 kDiff = rkPoint - rkBox.Center; for (int i = 0; i < 2; i++) { Real fCoeff = kDiff.Dot(rkBox.Axis[i]); if (Math::FAbs(fCoeff) > rkBox.Extent[i]) { return false; } } return true; } //---------------------------------------------------------------------------- template Box2 MergeBoxes (const Box2& rkBox0, const Box2& rkBox1) { // construct a box that contains the input boxes Box2 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); // The merged box axes are the averages of the input box axes. The // axes of the second box are negated, if necessary, so they form acute // angles with the axes of the first box. if (rkBox0.Axis[0].Dot(rkBox1.Axis[0]) >= (Real)0.0) { kBox.Axis[0] = ((Real)0.5)*(rkBox0.Axis[0] + rkBox1.Axis[0]); kBox.Axis[0].Normalize(); } else { kBox.Axis[0] = ((Real)0.5)*(rkBox0.Axis[0] - rkBox1.Axis[0]); kBox.Axis[0].Normalize(); } kBox.Axis[1] = -kBox.Axis[0].Perp(); // 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}^1 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; Vector2 akVertex[4], kDiff; Vector2 kMin = Vector2::ZERO; Vector2 kMax = Vector2::ZERO; rkBox0.ComputeVertices(akVertex); for (i = 0; i < 4; i++) { kDiff = akVertex[i] - kBox.Center; for (j = 0; j < 2; 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 < 4; i++) { kDiff = akVertex[i] - kBox.Center; for (j = 0; j < 2; 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 < 2; j++) { kBox.Center += kBox.Axis[j]*(((Real)0.5)*(kMax[j]+kMin[j])); kBox.Extent[j] = ((Real)0.5)*(kMax[j]-kMin[j]); } return kBox; } //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // explicit instantiation //---------------------------------------------------------------------------- template WM4_FOUNDATION_ITEM Box2 ContAlignedBox (int, const Vector2*); template WM4_FOUNDATION_ITEM Box2 ContOrientedBox (int, const Vector2*); template WM4_FOUNDATION_ITEM Box2 ContMinBox (int, const Vector2*, float, Query::Type, bool); template WM4_FOUNDATION_ITEM bool InBox (const Vector2&, const Box2&); template WM4_FOUNDATION_ITEM Box2 MergeBoxes (const Box2&, const Box2&); template WM4_FOUNDATION_ITEM Box2 ContAlignedBox (int, const Vector2*); template WM4_FOUNDATION_ITEM Box2 ContOrientedBox (int, const Vector2*); template WM4_FOUNDATION_ITEM Box2 ContMinBox (int, const Vector2*, double, Query::Type, bool); template WM4_FOUNDATION_ITEM bool InBox (const Vector2&, const Box2&); template WM4_FOUNDATION_ITEM Box2 MergeBoxes (const Box2&, const Box2&); //---------------------------------------------------------------------------- }