// Geometric Tools, LLC // Copyright (c) 1998-2012 // 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: 5.0.1 (2010/10/01) #include "NURBSCurve.h" #include "LineSegment.h" namespace NURBS { //---------------------------------------------------------------------------- template NURBSCurve::NURBSCurve ( const Array1D_Vector3 & ctrlPoint, const Array1D_Real & ctrlWeight, int degree, bool loop, bool open) : SingleCurve((Real)0, (Real)1), mLoop(loop) { int numCtrlPoints = ctrlPoint.size(); assertion(numCtrlPoints >= 2, "Invalid input\n"); assertion(1 <= degree && degree <= numCtrlPoints-1, "Invalid input\n"); mNumCtrlPoints = numCtrlPoints; mReplicate = (loop ? (open ? 1 : degree) : 0); CreateControl(ctrlPoint, ctrlWeight); mBasis.Create(mNumCtrlPoints + mReplicate, degree, open); } //---------------------------------------------------------------------------- template NURBSCurve::NURBSCurve (int numCtrlPoints, const Array1D_Vector3 & ctrlPoint, const Array1D_Real & ctrlWeight, int degree, bool loop, const Real* knot) : SingleCurve((Real)0, (Real)1), mLoop(loop) { assertion(numCtrlPoints >= 2, "Invalid input\n"); assertion(1 <= degree && degree <= numCtrlPoints-1, "Invalid input\n"); mNumCtrlPoints = numCtrlPoints; mReplicate = (loop ? 1 : 0); CreateControl(ctrlPoint, ctrlWeight); mBasis.Create(mNumCtrlPoints + mReplicate, degree, knot); } //---------------------------------------------------------------------------- template NURBSCurve NURBSCurve::createCurve( Vector3 from, Vector3 to, int steps ) { std::vector ctrlPoint; int degree = 3; Vector3 delta = (to - from) / steps; for(int i = 0; i <= steps; i++) ctrlPoint.push_back(from + (delta * i)); std::vector ctrlWeight(ctrlPoint.size(), 1.0); return NURBSCurve(ctrlPoint, ctrlWeight, degree, false, true); } //---------------------------------------------------------------------------- template void NURBSCurve::CreateControl (const Array1D_Vector3 & ctrlPoint, const Array1D_Real & ctrlWeight) { int newNumCtrlPoints = mNumCtrlPoints + mReplicate; newNumCtrlPoints = newNumCtrlPoints; mCtrlPoint = ctrlPoint; mCtrlWeight = ctrlWeight; for (int i = 0; i < mReplicate; ++i) { mCtrlPoint[mNumCtrlPoints+i] = ctrlPoint[i]; mCtrlWeight[mNumCtrlPoints+i] = ctrlWeight[i]; } } //---------------------------------------------------------------------------- template int NURBSCurve::GetNumCtrlPoints () const { return mNumCtrlPoints; } //---------------------------------------------------------------------------- template int NURBSCurve::GetDegree () const { return mBasis.GetDegree(); } //---------------------------------------------------------------------------- template bool NURBSCurve::IsOpen () const { return mBasis.IsOpen(); } //---------------------------------------------------------------------------- template bool NURBSCurve::IsUniform () const { return mBasis.IsUniform(); } //---------------------------------------------------------------------------- template bool NURBSCurve::IsLoop () const { return mLoop; } //---------------------------------------------------------------------------- template void NURBSCurve::SetControlPoint (int i, const Vector3& ctrl) { if (0 <= i && i < mNumCtrlPoints) { // Set the control point. mCtrlPoint[i] = ctrl; // Set the replicated control point. if (i < mReplicate) { mCtrlPoint[mNumCtrlPoints+i] = ctrl; } } } //---------------------------------------------------------------------------- template Vector3 NURBSCurve::GetControlPoint (int i) const { if (0 <= i && i < mNumCtrlPoints) { return mCtrlPoint[i]; } return Vector3(std::numeric_limits::max(), std::numeric_limits::max(), std::numeric_limits::max()); } //---------------------------------------------------------------------------- template void NURBSCurve::SetControlWeight (int i, Real weight) { if (0 <= i && i < mNumCtrlPoints) { // Set the control weight. mCtrlWeight[i] = weight; // Set the replicated control weight. if (i < mReplicate) { mCtrlWeight[mNumCtrlPoints+i] = weight; } } } //---------------------------------------------------------------------------- template Real NURBSCurve::GetControlWeight (int i) const { if (0 <= i && i < mNumCtrlPoints) { return mCtrlWeight[i]; } return std::numeric_limits::max(); } //---------------------------------------------------------------------------- template void NURBSCurve::SetKnot (int i, Real knot) { mBasis.SetKnot(i, knot); } //---------------------------------------------------------------------------- template Real NURBSCurve::GetKnot (int i) const { return mBasis.GetKnot(i); } //---------------------------------------------------------------------------- template void NURBSCurve::Get (Real t, Vector3* pos, Vector3* der1, Vector3* der2, Vector3* der3) { int i, imin, imax; if (der3) { mBasis.Compute(t, 0, imin, imax); mBasis.Compute(t, 1, imin, imax); mBasis.Compute(t, 2, imin, imax); mBasis.Compute(t, 3, imin, imax); } else if (der2) { mBasis.Compute(t, 0, imin, imax); mBasis.Compute(t, 1, imin, imax); mBasis.Compute(t, 2, imin, imax); } else if (der1) { mBasis.Compute(t, 0, imin, imax); mBasis.Compute(t, 1, imin, imax); } else // pos { mBasis.Compute(t, 0, imin, imax); } Real tmp; // Compute position. Vector3 X = Vector3_ZERO; Real w = (Real)0; for (i = imin; i <= imax; ++i) { tmp = mBasis.GetD0(i)*mCtrlWeight[i]; X += tmp*mCtrlPoint[i]; w += tmp; } Real invW = ((Real)1)/w; Vector3 P = invW*X; if (pos) { *pos = P; } if (!der1 && !der2 && !der3) { return; } // Compute first derivative. Vector3 XDer1 = Vector3_ZERO; Real wDer1 = (Real)0; for (i = imin; i <= imax; ++i) { tmp = mBasis.GetD1(i)*mCtrlWeight[i]; XDer1 += tmp*mCtrlPoint[i]; wDer1 += tmp; } Vector3 PDer1 = invW*(XDer1 - wDer1*P); if (der1) { *der1 = PDer1; } if (!der2 && !der3) { return; } // Compute second derivative. Vector3 XDer2 = Vector3_ZERO; Real wDer2 = (Real)0; for (i = imin; i <= imax; ++i) { tmp = mBasis.GetD2(i)*mCtrlWeight[i]; XDer2 += tmp*mCtrlPoint[i]; wDer2 += tmp; } Vector3 PDer2 = invW*(XDer2 - ((Real)2)*wDer1*PDer1 - wDer2*P); if (der2) { *der2 = PDer2; } if (!der3) { return; } // Compute third derivative. Vector3 XDer3 = Vector3_ZERO; Real wDer3 = (Real)0; for (i = imin; i <= imax; ++i) { tmp = mBasis.GetD3(i)*mCtrlWeight[i]; XDer3 += tmp*mCtrlPoint[i]; wDer3 += tmp; } if (der3) { *der3 = invW*(XDer3 - ((Real)3)*wDer1*PDer2 - ((Real)3)*wDer2*PDer1 - wDer3*P); } } //---------------------------------------------------------------------------- template BSplineBasis& NURBSCurve::GetBasis () { return mBasis; } //---------------------------------------------------------------------------- template Vector3 NURBSCurve::GetPosition (Real t) { Vector3 pos; Get(t, &pos, 0, 0, 0); return pos; } //---------------------------------------------------------------------------- template Vector3 NURBSCurve::GetFirstDerivative (Real t) { Vector3 der1; Get(t, 0, &der1, 0, 0); return der1; } //---------------------------------------------------------------------------- template Vector3 NURBSCurve::GetSecondDerivative (Real t) { Vector3 der2; Get(t, 0, 0, &der2, 0); return der2; } //---------------------------------------------------------------------------- template Vector3 NURBSCurve::GetThirdDerivative (Real t) { Vector3 der3; Get(t, 0, 0, 0, &der3); return der3; } //---------------------------------------------------------------------------- template std::vector NURBSCurve::getControlPoints() { return mCtrlPoint; } template Real NURBSCurve::timeAt( const Vector3 & pos ) { int segmentCount = 100; std::vector times; this->SubdivideByLengthTime(segmentCount, times); int minIdx = 0; double t = 0.0; Vector3 d(0,0,0); Scalar minDist = std::numeric_limits::max(); for(int i = 0; i < ((int)times.size()) - 1; i++) { Line segment(this->GetPosition(times[i]), this->GetPosition(times[i+1])); segment.ClosestPoint(pos, t, d); Scalar dist = (pos - d).norm(); if(dist < minDist){ minDist = dist; minIdx = i; } } Line closestSegment(this->GetPosition(times[minIdx]), this->GetPosition(times[minIdx+1])); closestSegment.ClosestPoint(pos, t, d); return qRanged(0.0, ((1-t) * times[minIdx]) + (t * times[minIdx+1]), 1.0); } template Real NURBSCurve::fastTimeAt( const Vector3 & pos ) { int segmentCount = 6; std::vector times; this->SubdivideByLengthTime(segmentCount, times); int minIdx = 0; double t = 0.0; Vector3 d(0,0,0); Scalar minDist = std::numeric_limits::max(); for(int i = 0; i < ((int)times.size()) - 1; i++){ Line segment(this->GetPosition(times[i]), this->GetPosition(times[i+1])); segment.ClosestPoint(pos, t, d); Scalar dist = (pos - d).norm(); if(dist < minDist){ minDist = dist; minIdx = i; } } Line closestSegment(this->GetPosition(times[minIdx]), this->GetPosition(times[minIdx+1])); closestSegment.ClosestPoint(pos, t, d); return qRanged(0.0, ((1-t) * times[minIdx]) + (t * times[minIdx+1]), 1.0); } template void NURBSCurve::translate( const Vector3 & delta ) { for(int i = 0; i < (int)mCtrlPoint.size(); i++) mCtrlPoint[i] += delta; } template void NURBSCurve::scale( Scalar scaleFactor ) { for(int i = 0; i < (int)mCtrlPoint.size(); i++) mCtrlPoint[i] *= scaleFactor; } template void NURBSCurve::scaleInPlace( Scalar scaleFactor, int placeCtrlPoint ) { Vector3 delta = mCtrlPoint[placeCtrlPoint]; translate( -delta ); scale(scaleFactor); translate( delta ); } template void NURBSCurve::translateTo( const Vector3 & newPos, int cpIDX ) { Vector3d cp = mCtrlPoint[cpIDX]; Vector3d delta = newPos - cp; for(int i = 0; i < GetNumCtrlPoints(); i++) mCtrlPoint[i] += delta; } template std::vector < std::vector > NURBSCurve::toSegments( Scalar resolution ) { std::vector< std::vector > segments; double firstTwoDist = (mCtrlPoint[0] - mCtrlPoint[1]).norm(); if(firstTwoDist < resolution * 0.0001){ segments.push_back(this->mCtrlPoint); return segments; } Scalar curveLength = this->GetLength(0,1); // For singular cases if(curveLength < resolution){ segments.push_back(this->mCtrlPoint); return segments; } // Get a curve polyline for given resolution int np = 1 + (curveLength / resolution); std::vector pts; this->SubdivideByLength(np, pts); for(int i = 0; i < np - 1; i++) { std::vector segment; segment.push_back(pts[i]); segment.push_back(pts[i+1]); segments.push_back(segment); } return segments; } template Array1D_Real NURBSCurve::GetKnotVector(bool isInnerOnly) { if(isInnerOnly) { int d = this->GetDegree(); Array1D_Real result(mBasis.mKnot.begin() + d, mBasis.mKnot.end() - d); return result; } else return this->mBasis.mKnot; } template int NURBSCurve::findSpan(int n, int p, Real u, Array1D_Real U) { int low, high, mid; if (u == U[n+1]) return(n); low = p; high = n+1; mid = (int)(low+high)/2; while (u < U[mid] || u >= U[mid+1]) { if (u < U[mid]) high = mid; else low = mid; mid = (int)(low+high)/2; } return mid; } template int NURBSCurve::findSpan(Real u) { return findSpan(this->mCtrlPoint.size() - 1, GetDegree(), u, GetKnotVector() ); } template Array1D_Vector3 NURBS::NURBSCurve::midPointRefined() { Array1D_Vector3 newCtrlPoints; Array1D_Real Ubar; computeMidPointRefine(newCtrlPoints, Ubar); return newCtrlPoints; } template void NURBSCurve::computeMidPointRefine( Array1D_Vector3 & Qw, Array1D_Real & Ubar ) { std::vector insknts; Array1D_Real oldKnotVec = GetKnotVector(true); for(int i = 0; i < (int)oldKnotVec.size() - 1; i++){ double range = (oldKnotVec[i+1] - oldKnotVec[i]); insknts.push_back( (0.5 * range) + oldKnotVec[i] ); } refine(insknts, Qw, Ubar); } template void NURBSCurve::refine(Array1D_Real & insknts, Array1D_Vector3 & Qw, Array1D_Real & Ubar ) { // Input Array1D_Real X = insknts; Array1D_Vector3 Pw = this->mCtrlPoint; Array1D_Real U = GetKnotVector(); int s = insknts.size(); int order = this->GetDegree() + 1; int p = order - 1; int n = Pw.size() - 1; int r = X.size() - 1; int m = n+p+1; // Output Qw.resize ( n+s+1, Vector3(0,0,0) ); Ubar.resize ( m+s+1, 0 ); int a = findSpan(n,p,X[0],U); int b = findSpan(n,p,X[r],U) + 1; int j = 0; for(j=0 ; j<=a-p; j++) Qw[j] = Pw[j]; for(j=b-1; j<=n ; j++) Qw[j+r+1] = Pw[j]; for(j=0 ; j<=a ; j++) Ubar[j] = U[j]; for(j=b+p; j<=m ; j++) Ubar[j+r+1] = U[j]; int i = b+p-1; int k = b+p+r; Real alfa = 0; for (int j=r; j>=0; j--) { while (X[j] <= U[i] && i > a) { Qw[k-p-1] = Pw[i-p-1]; Ubar[k] = U[i]; k = k - 1; i = i - 1; } Qw[k-p-1] = Qw[k-p]; for (int l=1; l<=p; l++) { int ind = k-p+l; alfa = Ubar[k+l] - X[j]; if (fabs(alfa) == 0.0) { Qw[ind-1] = Qw[ind]; } else { alfa = alfa / (Ubar[k+l] - U[i-p+l]); Qw[ind-1] = alfa * Qw[ind-1] + (1.0 - alfa) * Qw[ind]; } } Ubar[k] = X[j]; k = k - 1; } } /* @param u Parametric coordinate of breaking point @param k Knot index of insertion point @param s Knot multiplicity at k @param r Number of times a knot should be inserted */ template Array1D_Real NURBSCurve::insertKnot(Real u, int k, int s, int r, Array1D_Vector3 & Qw, int * uq) { Array1D_Real UP = GetKnotVector(); Array1D_Vector3 Pw = mCtrlPoint; Array1D_Real UQ; Array1D_Vector3 Rw; Scalar alpha; int L = 0; int order = this->GetDegree() + 1; int p = order - 1; int np = Pw.size() - 1; int mp = np+p+1; int nq = np+r; // Define array UQ.resize(mp+r+1); Qw.resize(nq+1); Rw.resize(p+1); // Load new knot vector for (int i=0; i<=k; i++) UQ[i] = UP[i]; for (int i=1; i<=r; i++) UQ[k+i] = u; for (int i=k+1; i<=mp; i++) UQ[i+r] = UP[i]; // Save unaltered control points for (int i=0; i<=k-p; i++) Qw[i] = Pw[i]; for (int i=k-s; i<=np; i++) Qw[i+r] = Pw[i]; for (int i=0; i<=p-s; i++) Rw[i] = Pw[k-p+i]; // Insert the knot r times for (int j=1; j<=r; j++) { L = k-p+j; for (int i=0; i<=p-j-s; i++) { alpha = (u-UP[L+i])/(UP[i+k+1]-UP[L+i]); Rw[i] = alpha*Rw[i+1] + (1.0 - alpha) * Rw[i]; } Qw[L] = Rw[0]; Qw[k+r-j-s] = Rw[p-j-s]; } // Load remaining control points for (int i=L+1; i Array1D_Vector3 NURBSCurve::simpleRefine( int k ) { if(k <= 0) return this->mCtrlPoint; NURBSCurve curCurve = *this; for(int i = 0; i < k ; i++) { int segment = 0; double maxDist = -DBL_MAX; // Find longest segment for(int j = 0; j < (int)(curCurve.mCtrlPoint.size() - 1); j++) { double dist = (curCurve.mCtrlPoint[j+1] - curCurve.mCtrlPoint[j]).norm(); if(dist > maxDist){ maxDist = dist; segment = j; } } // Insert knot at middle of longest segment Array1D_Real U = curCurve.GetKnotVector(true); double t = double(segment) / (curCurve.mCtrlPoint.size()-2); int idx = t * (U.size()-2); double range = U[idx+1] - U[idx]; double u = (0.5 * range) + U[idx]; Array1D_Vector3 newPnts; Array1D_Real Ubar; Array1D_Real insk = Array1D_Real(1,u); curCurve.refine(insk, newPnts, Ubar); // Update control points curCurve = NURBSCurve::createCurveFromPoints(newPnts); } return Array1D_Vector3(curCurve.mCtrlPoint.begin(),curCurve.mCtrlPoint.end()); } template Real NURBSCurve::KnotRemovalError( int r, int s ) { Array1D_Real U = GetKnotVector(); Array1D_Vector3 P = mCtrlPoint; Array1D_Vector3 temp(P.size(), Vector3(0,0,0)) ; int deg_ = GetDegree(); int ord = deg_+1 ; int last = r-s ; int first = r-deg_ ; int off ; int i,j,ii,jj ; Real alfi,alfj ; Real u ; u = U[r] ; off = first-1; temp[0] = P[off] ; temp[last+1-off] = P[last+1] ; i=first ; j=last ; ii=1 ; jj=last-off ; while(j-i>0){ alfi = (u-U[i])/(U[i+ord]-U[i]) ; alfj = (u-U[j])/(U[j+ord]-U[j]) ; temp[ii] = (P[i]-(1.0-alfi)*temp[ii-1])/alfi ; temp[jj] = (P[j]-alfj*temp[jj+1])/(1.0-alfj) ; ++i ; ++ii ; --j ; --jj ; } if(j-i<0){ return (temp[ii-1]-temp[jj+1]).norm() ; } else{ alfi=(u-U[i])/(U[i+ord]-U[i]) ; return (P[i]-alfi*temp[ii+1]+(1.0-alfi)*temp[ii-1]).norm() ; } } template Array1D_Vector3 NURBSCurve::removeKnots(int iterations) { NURBSCurve curCurve = *this; for(int itr = 0; itr < iterations; itr++) { Array1D_Real U = curCurve.GetKnotVector(); Array1D_Vector3 P = curCurve.mCtrlPoint; int p = curCurve.GetDegree(); QMap errors; for(int i = p + 1; i < (int)U.size() - 1; i++) { if(U[i] < U[i+1]) errors[ curCurve.KnotRemovalError(i, 1) ] = i; } int r = errors.values().at(itr); // Knot to remove int num = 1; int s = 1; int deg_ = curCurve.GetDegree(); int m = U.size() ; int ord = deg_+1 ; int fout = (2*r-s-deg_)/2 ; int last = r-s ; int first = r-deg_ ; Real alfi, alfj ; int i,j,k,ii,jj,off ; Real u ; Array1D_Vector3 temp( 2*deg_+1, Vector3(0,0,0) ) ; u = U[r] ; int t; for(t=0;t t){ alfi = (u-U[i])/(U[i+ord+t]-U[i]) ; alfj = (u-U[j-t])/(U[j+ord]-U[j-t]) ; temp[ii] = (P[i]-(1.0-alfi)*temp[ii-1])/alfi ; temp[jj] = (P[j]-alfj*temp[jj+1])/(1.0-alfj) ; ++i ; ++ii ; --j ; --jj ; } i = first ; j = last ; while(j-i>t){ P[i] = temp[i-off] ; P[j] = temp[j-off] ; ++i; --j ; } --first ; ++last ; } if(t == 0) return P; for(k=r+1; k; template class NURBSCurve; //---------------------------------------------------------------------------- }