// SPDX-License-Identifier: LGPL-2.1-or-later // Wild Magic Source Code // David Eberly // http://www.geometrictools.com // Copyright (c) 1998-2007 // // This library is free software; you can redistribute it and/or modify it // under the terms of the GNU Lesser General Public License as published by // the Free Software Foundation; either version 2.1 of the License, or (at // your option) any later version. The license is available for reading at // either of the locations: // http://www.gnu.org/copyleft/lgpl.html // http://www.geometrictools.com/License/WildMagicLicense.pdf // The license applies to versions 0 through 4 of Wild Magic. // // Version: 4.0.0 (2006/06/28) #include "Wm4FoundationPCH.h" #include "Wm4ApprCylinderFit3.h" #include "Wm4ApprLineFit3.h" #include "Wm4PolynomialRoots.h" namespace Wm4 { //---------------------------------------------------------------------------- template CylinderFit3::CylinderFit3 (int iQuantity, const Vector3* akPoint, Vector3& rkC, Vector3& rkU, Real& rfR, Real& rfH, bool bInputsAreInitialGuess) { Real fInvRSqr; if (!bInputsAreInitialGuess) { // Find the least-squares line that fits the data and use it as an // initial guess for the cylinder axis. Line3 kLine = OrthogonalLineFit3(iQuantity,akPoint); rkC = kLine.Origin; rkU = kLine.Direction; } m_fError = Math::MAX_REAL; const int iMax = 8; int i; for (i = 0; i < iMax; i++) { m_fError = UpdateInvRSqr(iQuantity,akPoint,rkC,rkU,fInvRSqr); m_fError = UpdateDirection(iQuantity,akPoint,rkC,rkU,fInvRSqr); m_fError = UpdateCenter(iQuantity,akPoint,rkC,rkU,fInvRSqr); } // Compute the radius. rfR = Math::InvSqrt(fInvRSqr); // Project points onto fitted axis to determine extent of cylinder along // the axis. Real fTMin = rkU.Dot(akPoint[0]-rkC), fTMax = fTMin; for (i = 1; i < iQuantity; i++) { Real fT = rkU.Dot(akPoint[i]-rkC); if (fT < fTMin) { fTMin = fT; } else if (fT > fTMax) { fTMax = fT; } } // Compute the height. Adjust the center to point that projects to // midpoint of extent. rfH = fTMax - fTMin; rkC += ((Real)0.5)*(fTMin+fTMax)*rkU; } //---------------------------------------------------------------------------- template CylinderFit3::operator Real () { return m_fError; } //---------------------------------------------------------------------------- template Real CylinderFit3::UpdateInvRSqr (int iQuantity, const Vector3* akPoint, const Vector3& rkC, const Vector3& rkU, Real& rfInvRSqr) { Real fASum = (Real)0.0, fAASum = (Real)0.0; for (int i = 0; i < iQuantity; i++) { Vector3 kDelta = akPoint[i] - rkC; Vector3 kDxU = kDelta.Cross(rkU); Real fL2 = kDxU.SquaredLength(); fASum += fL2; fAASum += fL2*fL2; } rfInvRSqr = fASum/fAASum; Real fMin = (Real)1.0 - rfInvRSqr*fASum/(Real)iQuantity; return fMin; } //---------------------------------------------------------------------------- template Real CylinderFit3::UpdateDirection (int iQuantity, const Vector3* akPoint, const Vector3& rkC, Vector3& rkU, Real& rfInvRSqr) { Real fInvQuantity = ((Real)1.0)/(Real)iQuantity; int i; Vector3 kDelta, kDxU, kDxVDir; Real fA, fB, fC; // compute direction of steepest descent Vector3 kVDir = Vector3::ZERO; Real fAMean = (Real)0.0, fAAMean = (Real)0.0; for (i = 0; i < iQuantity; i++) { kDelta = akPoint[i] - rkC; kDxU = kDelta.Cross(rkU); fA = rfInvRSqr*kDxU.SquaredLength() - (Real)1.0; fAMean += fA; fAAMean += fA*fA; kVDir.X() += fA*(rkU.X()*(kDelta.Y()*kDelta.Y() + kDelta.Z()*kDelta.Z()) - kDelta.X()*(rkU.Y()*kDelta.Y() + rkU.Z()*kDelta.Z())); kVDir.Y() += fA*(rkU.Y()*(kDelta.X()*kDelta.X() + kDelta.Z()*kDelta.Z()) - kDelta.Y()*(rkU.X()*kDelta.X() + rkU.Z()*kDelta.Z())); kVDir.Z() += fA*(rkU.Z()*(kDelta.X()*kDelta.X() + kDelta.Y()*kDelta.Y()) - kDelta.Z()*(rkU.X()*kDelta.X() + rkU.Y()*kDelta.Y())); } fAMean *= fInvQuantity; fAAMean *= fInvQuantity; if (kVDir.Normalize() < Math::ZERO_TOLERANCE) { return fAAMean; } // compute 4th-degree polynomial for line of steepest descent Real fABMean = (Real)0.0, fACMean = (Real)0.0; Real fBBMean = (Real)0.0, fBCMean = (Real)0.0, fCCMean = (Real)0.0; for (i = 0; i < iQuantity; i++) { kDelta = akPoint[i] - rkC; kDxU = kDelta.Cross(rkU); kDxVDir = kDelta.Cross(kVDir); fA = rfInvRSqr*kDxU.SquaredLength() - (Real)1.0; fB = rfInvRSqr*(kDxU.Dot(kDxVDir)); fC = rfInvRSqr*kDxVDir.SquaredLength(); fABMean += fA*fB; fACMean += fA*fC; fBBMean += fB*fB; fBCMean += fB*fC; fCCMean += fC*fC; } fABMean *= fInvQuantity; fACMean *= fInvQuantity; fBBMean *= fInvQuantity; fBCMean *= fInvQuantity; fCCMean *= fInvQuantity; Polynomial1 kPoly(4); kPoly[0] = fAAMean; kPoly[1] = -((Real)4.0)*fABMean; kPoly[2] = ((Real)2.0)*fACMean + ((Real)4.0)*fBBMean; kPoly[3] = -((Real)4.0)*fBCMean; kPoly[4] = fCCMean; Polynomial1 kDPoly = kPoly.GetDerivative(); PolynomialRoots kPR(Math::ZERO_TOLERANCE); kPR.FindA(kDPoly[0],kDPoly[1],kDPoly[2],kDPoly[3]); int iCount = kPR.GetCount(); const Real* afRoot = kPR.GetRoots(); Real fMin = kPoly((Real)0.0); int iMin = -1; for (i = 0; i < iCount; i++) { Real fValue = kPoly(afRoot[i]); if (fValue < fMin) { fMin = fValue; iMin = i; } } if (iMin >= 0) { rkU -= afRoot[iMin]*kVDir; Real fLength = rkU.Normalize(); rfInvRSqr *= fLength*fLength; } return fMin; } //---------------------------------------------------------------------------- template Real CylinderFit3::UpdateCenter (int iQuantity, const Vector3* akPoint, Vector3& rkC, const Vector3& rkU, const Real& rfInvRSqr) { Real fInvQuantity = ((Real)1.0)/(Real)iQuantity; int i; Vector3 kDelta, kDxU, kDxCDir; Real fA, fB, fC; // compute direction of steepest descent Vector3 kCDir = Vector3::ZERO; Real fAMean = (Real)0.0, fAAMean = (Real)0.0; for (i = 0; i < iQuantity; i++) { kDelta = akPoint[i] - rkC; kDxU = kDelta.Cross(rkU); fA = rfInvRSqr*kDxU.SquaredLength() - (Real)1.0; fAMean += fA; fAAMean += fA*fA; kCDir += fA*(kDelta-rkU.Dot(kDelta)*rkU); // |U|=1 assumed } fAMean *= fInvQuantity; fAAMean *= fInvQuantity; if (kCDir.Normalize() < Math::ZERO_TOLERANCE) { return fAAMean; } // compute 4th-degree polynomial for line of steepest descent kDxCDir = kCDir.Cross(rkU); fC = kDxCDir.SquaredLength()*fInvQuantity*rfInvRSqr; Real fBMean = (Real)0.0, fABMean = (Real)0.0, fBBMean = (Real)0.0; for (i = 0; i < iQuantity; i++) { kDelta = akPoint[i] - rkC; kDxU = kDelta.Cross(rkU); fA = rfInvRSqr*kDxU.SquaredLength() - (Real)1.0; fB = rfInvRSqr*(kDxU.Dot(kDxCDir)); fBMean += fB; fABMean += fA*fB; fBBMean += fB*fB; } fBMean *= fInvQuantity; fABMean *= fInvQuantity; fBBMean *= fInvQuantity; Polynomial1 kPoly(4); kPoly[0] = fAAMean; kPoly[1] = ((Real)4.0)*fABMean; kPoly[2] = ((Real)2.0)*fC*fAMean + ((Real)4.0)*fBBMean; kPoly[3] = ((Real)4.0)*fC*fBMean; kPoly[4] = fC*fC; Polynomial1 kDPoly = kPoly.GetDerivative(); PolynomialRoots kPR(Math::ZERO_TOLERANCE); kPR.FindA(kDPoly[0],kDPoly[1],kDPoly[2],kDPoly[3]); int iCount = kPR.GetCount(); const Real* afRoot = kPR.GetRoots(); Real fMin = kPoly((Real)0.0); int iMin = -1; for (i = 0; i < iCount; i++) { Real fValue = kPoly(afRoot[i]); if (fValue < fMin) { fMin = fValue; iMin = i; } } if (iMin >= 0) { rkC -= afRoot[iMin]*kCDir; } return fMin; } //---------------------------------------------------------------------------- //---------------------------------------------------------------------------- // explicit instantiation //---------------------------------------------------------------------------- template WM4_FOUNDATION_ITEM class CylinderFit3; template WM4_FOUNDATION_ITEM class CylinderFit3; //---------------------------------------------------------------------------- }