/*---------------------------------------------------------------------------*\ ========= | \\ / F ield | OpenFOAM: The Open Source CFD Toolbox \\ / O peration | \\ / A nd | www.openfoam.com \\/ M anipulation | ------------------------------------------------------------------------------- Copyright (C) 2007-2019 PCOpt/NTUA Copyright (C) 2013-2019 FOSS GP Copyright (C) 2019-2022 OpenCFD Ltd. ------------------------------------------------------------------------------- License This file is part of OpenFOAM. OpenFOAM is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. OpenFOAM is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenFOAM. If not, see . \*---------------------------------------------------------------------------*/ #include "NURBS3DSurface.H" #include "vtkSurfaceWriter.H" #include "OFstream.H" // * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * // namespace Foam { // * * * * * * * * * * * * * Private Member Functions * * * * * * * * * * * // label NURBS3DSurface::sgn(const scalar val) const { return val >= scalar(0) ? 1 : -1; } scalar NURBS3DSurface::abs(const scalar val) const { return (sgn(val) == 1)? val: -val; } label NURBS3DSurface::mod(const label x, const label interval) const { label ratio(x%interval); return ratio < 0 ? ratio+interval : ratio; } void NURBS3DSurface::setCPUVLinking() { const label uNCPs(uBasis_.nCPs()); const label vNCPs(vBasis_.nCPs()); CPsUCPIs_.setSize(uNCPs*vNCPs, -1); CPsVCPIs_.setSize(uNCPs*vNCPs, -1); for (label vCPI = 0; vCPI scalar(1)) { u = maxVal; boundPoint = true; } return boundPoint; } void NURBS3DSurface::setEquidistantR ( scalarList& R, const scalar SHeld, const label paramR, const label lenAcc = 25, const label maxIter = 10, const label spacingCorrInterval = -1, const scalar tolerance = 1.e-5 ) const { const label nPts(R.size()); scalar SNull(SHeld); scalar xLength(Zero); const scalar rLength(scalar(1) / scalar(nPts - 1)); if (paramR == PARAMU) { xLength = lengthU(SHeld) / (nPts - 1); } else { xLength = lengthV(SHeld) / (nPts - 1); } R[0] = Zero; R[nPts-1] = scalar(1); for (label ptI=1; ptI<(nPts - 1); ptI++) { const scalar& RPrev(R[ptI - 1]); scalar& RCurr(R[ptI]); scalar direc(1); scalar xDiff(0); scalar delta(0); bool overReached(false); RCurr = RPrev + rLength; // Find the starting U value to ensure target is within 1 rLength. while (true) { bool bounded(false); if (paramR == PARAMU) { bounded = bound(RCurr, SNull); delta = lengthU(SHeld, RPrev, RCurr, lenAcc); } else { bounded = bound(SNull, RCurr); delta = lengthV(SHeld, RPrev, RCurr, lenAcc); } xDiff = xLength - delta; // Found the point. if (abs(xDiff) < tolerance) { break; } else { direc = sgn(xDiff); if (bounded && (direc == 1)) { // rLength addition makes U exceed 1 so it becomes bounded. // However, the desired x location still exceeds how far the // bounded rLength can move (~e-5 error). // Must force U to be u=0.999999. overReached = true; break; } else if (direc == scalar(1)) { RCurr += rLength; } else { break; } } } if (!overReached) { label iter(0); while (iter < maxIter) { // Set the new search length to ensure convergence and next v. direc /= scalar(2); RCurr += direc * rLength; if (paramR == PARAMU) { bound(RCurr, SNull); } else { bound(SNull, RCurr); } // Can employ an occasional tolerance check from beg of curve. if ( (spacingCorrInterval != -1) && (mod(ptI, spacingCorrInterval) == 0) ) { if (paramR == PARAMU) { delta = lengthU(SHeld, Zero, RCurr, ptI*lenAcc); } else { delta = lengthV(SHeld, Zero, RCurr, ptI*lenAcc); } xDiff = (ptI * xLength) - delta; } else { if (paramR == PARAMU) { delta = lengthU(SHeld, RPrev, RCurr, lenAcc); } else { delta = lengthV(SHeld, RPrev, RCurr, lenAcc); } xDiff = xLength - delta; } // Break if found point or find the new search direction. if (abs(xDiff) < tolerance) { break; } else { const scalar oldDirec(direc); direc = sgn(xDiff) * abs(oldDirec); } ++iter; } } } } // * * * * * * * * * * * * * * * * Constructors * * * * * * * * * * * * * * // NURBS3DSurface::NURBS3DSurface ( const List& CPs, const label nUPts, const label nVPts, const NURBSbasis& uBasis, const NURBSbasis& vBasis, const word name ) : vectorField (nUPts*nVPts, Zero), CPs_(CPs), u_(nUPts*nVPts, Zero), v_(nUPts*nVPts, Zero), weights_(CPs.size(), scalar(1)), nUPts_(nUPts), nVPts_(nVPts), name_(name), uBasis_(uBasis), vBasis_(vBasis), givenInitNrm_(Zero), CPsUCPIs_(0), CPsVCPIs_(0), nrmOrientation_(ALIGNED), boundaryCPIDs_(nullptr) { setUniformUV(); buildSurface(); setCPUVLinking(); } NURBS3DSurface::NURBS3DSurface ( const List& CPs, const List& weights, const label nUPts, const label nVPts, const NURBSbasis& uBasis, const NURBSbasis& vBasis, const word name ) : vectorField(nUPts*nVPts, Zero), CPs_(CPs), u_(nUPts*nVPts, Zero), v_(nUPts*nVPts, Zero), weights_(weights), nUPts_(nUPts), nVPts_(nVPts), name_ (name), uBasis_(uBasis), vBasis_(vBasis), givenInitNrm_(Zero), CPsUCPIs_(0), CPsVCPIs_(0), nrmOrientation_(ALIGNED), boundaryCPIDs_(nullptr) { setUniformUV(); buildSurface(); setCPUVLinking(); } NURBS3DSurface::NURBS3DSurface ( const List& CPs, const label nUPts, const label nVPts, const label uDegree, const label vDegree, const label nCPsU, const label nCPsV, const word name ) : vectorField(nUPts*nVPts, Zero), CPs_(CPs), u_(nUPts*nVPts, Zero), v_(nUPts*nVPts, Zero), weights_(CPs.size(), scalar(1)), nUPts_(nUPts), nVPts_(nVPts), name_(name), uBasis_(nCPsU, uDegree), vBasis_(nCPsV, vDegree), givenInitNrm_(Zero), CPsUCPIs_(0), CPsVCPIs_(0), nrmOrientation_(ALIGNED), boundaryCPIDs_(nullptr) { // Sanity checks if (nCPsU*nCPsV != CPs_.size()) { FatalErrorInFunction << "nCPsU*nCPsV " << nCPsU*nCPsV << " not equal to size of CPs " << CPs_.size() << exit(FatalError); } // Initialize surface setUniformUV(); buildSurface(); setCPUVLinking(); } NURBS3DSurface::NURBS3DSurface ( const List& CPs, const label nUPts, const label nVPts, const label uDegree, const label vDegree, const label nCPsU, const label nCPsV, const scalarField& knotsU, const scalarField& knotsV, const word name ) : vectorField(nUPts*nVPts, Zero), CPs_(CPs), u_(nUPts*nVPts, Zero), v_(nUPts*nVPts, Zero), weights_(CPs.size(), scalar(1)), nUPts_(nUPts), nVPts_(nVPts), name_(name), uBasis_(nCPsU, uDegree, knotsU), vBasis_(nCPsV, vDegree, knotsV), givenInitNrm_(Zero), CPsUCPIs_(0), CPsVCPIs_(0), nrmOrientation_(ALIGNED), boundaryCPIDs_(nullptr) { // Sanity checks if (nCPsU*nCPsV != CPs_.size()) { FatalErrorInFunction << "nCPsU*nCPsV " << nCPsU*nCPsV << " not equal to size of CPs " << CPs_.size() << exit(FatalError); } // initialize surface setUniformUV(); buildSurface(); setCPUVLinking(); } NURBS3DSurface::NURBS3DSurface ( const List& CPs, const List& weights, const label nUPts, const label nVPts, const label uDegree, const label vDegree, const label nCPsU, const label nCPsV, const word name ) : vectorField(nUPts*nVPts, Zero), CPs_(CPs), u_(nUPts*nVPts, Zero), v_(nUPts*nVPts, Zero), weights_(weights), nUPts_(nUPts), nVPts_(nVPts), name_(name), uBasis_(nCPsU, uDegree), vBasis_(nCPsV, vDegree), givenInitNrm_(Zero), CPsUCPIs_(0), CPsVCPIs_(0), nrmOrientation_(ALIGNED), boundaryCPIDs_(nullptr) { // Sanity checks if (nCPsU*nCPsV != CPs_.size()) { FatalErrorInFunction << "nCPsU*nCPsV " << nCPsU*nCPsV << " not equal to size of CPs " << CPs_.size() << exit(FatalError); } // Initialize surface setUniformUV(); buildSurface(); setCPUVLinking(); } NURBS3DSurface::NURBS3DSurface ( const List& CPs, const List& weights, const label nUPts, const label nVPts, const label uDegree, const label vDegree, const label nCPsU, const label nCPsV, const scalarField& knotsU, const scalarField& knotsV, const word name ) : vectorField(nUPts*nVPts, Zero), CPs_(CPs), u_(nUPts*nVPts, Zero), v_(nUPts*nVPts, Zero), weights_(weights), nUPts_(nUPts), nVPts_(nVPts), name_(name), uBasis_(nCPsU, uDegree, knotsU), vBasis_(nCPsV, vDegree, knotsV), givenInitNrm_(Zero), CPsUCPIs_(0), CPsVCPIs_(0), nrmOrientation_(ALIGNED), boundaryCPIDs_(nullptr) { // Sanity checks if (nCPsU*nCPsV != CPs_.size()) { FatalErrorInFunction << "nCPsU*nCPsV " << nCPsU*nCPsV << " not equal to size of CPs " << CPs_.size() << exit(FatalError); } // Initialize surface setUniformUV(); buildSurface(); setCPUVLinking(); } // * * * * * * * * * * * * * * * Member Functions * * * * * * * * * * * * * // // * * * * * * * * * * * * * * * * Set Functions * * * * * * * * * * * * * * // void NURBS3DSurface::setNrmOrientation ( const vector& givenNrm, const scalar u, const scalar v ) { vector surfaceNrm(surfaceDerivativeU(u, v) ^ surfaceDerivativeV(u, v)); givenInitNrm_ = givenNrm; surfaceNrm /= mag(surfaceNrm); const scalar relation(givenNrm & surfaceNrm); if (relation >= 0) { nrmOrientation_ = ALIGNED; } else { nrmOrientation_ = OPPOSED; } Info<< "Initial nrmOrientation after comparison to NURBS u=" << u << ",v=" << v << " nrm: " << nrmOrientation_ << endl; } void NURBS3DSurface::flipNrmOrientation() { if (nrmOrientation_ == ALIGNED) { nrmOrientation_ = OPPOSED; } else { nrmOrientation_ = ALIGNED; } } void NURBS3DSurface::setCPs(const List& CPs) { CPs_ = CPs; } void NURBS3DSurface::setWeights(const scalarList& weights) { weights_ = weights; } void NURBS3DSurface::setName(const word& name) { name_ = name; } void NURBS3DSurface::buildSurface() { const label uDegree(uBasis_.degree()); const label vDegree(vBasis_.degree()); const label uNCPs(uBasis_.nCPs()); const label vNCPs(vBasis_.nCPs()); /* Info<< "\nuDegree: " << uDegree << "\nvDegree: " << vDegree << "\nnUPts: " << nUPts_ << "\nnVPts: " << nVPts_ << "\nuNCPs: " << uNCPs << "\nvNCPs: " << vNCPs << "\nNURBSSurface:\nCPs: " << CPs << endl; */ vectorField& field = *this; field = vector::zero; for (label uI = 0; uIoperator[](ptI) += CPs_[CPI] * uBasis_.basisValue(uCPI, uDegree, u) * vBasis_.basisValue(vCPI, vDegree, v) * weights_[CPI]/NMW; } } } } } void NURBS3DSurface::invertU() { Info<< "Inverting NURBS surface " << name_ << " in u." << endl; const label uNCPs(uBasis_.nCPs()); const label vNCPs(vBasis_.nCPs()); List invertedCPs(CPs_.size(), Zero); List invertedWeights(CPs_.size(), Zero); for (label vCPI = 0; vCPI invertedCPs(CPs_.size(), Zero); List invertedWeights(CPs_.size(), Zero); for (label vCPI = 0; vCPI invertedCPs(CPs_.size(), Zero); List invertedWeights(CPs_.size(), Zero); for (label vCPI = 0; vCPI= 5) { res = mag((xuv-targetPoint) & surfaceDerivativeV(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; // Info<< targetPoint << " " << res << endl; } // If manual assignment in v is required, deal only with the u eqn else if (nBoundsV >= 5) { res = mag((xuv-targetPoint) & surfaceDerivativeU(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; // Info<< targetPoint << " " << res << endl; } else if (nBoundsU <= 5 && nBoundsV <= 5) { res = mag((xuv-targetPoint) & surfaceDerivativeU(u, v)) + mag((xuv-targetPoint) & surfaceDerivativeV(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; } else { WarningInFunction << "More than 5 bounds in both the u and v directions!" << "Something seems weird" << nBoundsU << " " << nBoundsV << endl; res = mag((xuv-targetPoint) & surfaceDerivativeU(u, v)) + mag((xuv-targetPoint) & surfaceDerivativeV(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; } } while ((iter++ < maxIter) && (res > tolerance)); scalarList closestParameters(2, u); closestParameters[1] = v; // warning if method did not reach threshold if (iter > maxIter) { WarningInFunction << "Finding surface point closest to " << targetPoint << " for surface " << name_ << " failed \n" << " Number of bounding operations in u,v " << nBoundsU << " " << nBoundsV << endl << " Residual value and derivative " << res << " " << resDeriv << endl << endl; closestParameters = -1; } return closestParameters; } tmp NURBS3DSurface::findClosestSurfacePoint ( const vectorField& targetPoints, const label maxIter, const scalar tolerance ) { auto tparamCoors = tmp::New(targetPoints.size(), Zero); auto& paramCoors = tparamCoors.ref(); const vectorField& surface(*this); label nBoundedPoints(0); scalar maxResidual(0); scalar maxResidualDeriv(0); forAll(paramCoors, pI) { const vector& targetPoint(targetPoints[pI]); // Loop through surface points to find the closest one for // initialization. The initialization could possibly be done with a // geodesical Laplace. Potentially faster? scalar dist(GREAT); label closePtI(-1); forAll(surface, ptI) { const scalar distLoc(mag(targetPoint - surface[ptI])); if (distLoc < dist) { dist = distLoc; closePtI = ptI; } } label iter(0); scalar u(u_[closePtI]); scalar v(v_[closePtI]); vector xuv(surfacePoint(u, v)); scalar res(GREAT); scalar resOld(GREAT); scalar resDeriv(GREAT); label nBoundsU(0); label nBoundsV(0); do { const vector dxdu(surfaceDerivativeU(u, v)); const vector dxdv(surfaceDerivativeV(u, v)); const vector d2xdu2(surfaceDerivativeUU(u, v)); const vector d2xdv2(surfaceDerivativeVV(u, v)); const vector d2xduv(surfaceDerivativeUV(u, v)); const scalar a((dxdu&dxdu) + ((xuv-targetPoint) & d2xdu2)); const scalar b((dxdu&dxdv) + ((xuv-targetPoint) & d2xduv)); const scalar c=b; const scalar d((dxdv&dxdv) + ((xuv-targetPoint) & d2xdv2)); const scalar invDenom = 1./(a*d-b*c); const scalar uRHS(-((xuv-targetPoint) & dxdu)); const scalar vRHS(-((xuv-targetPoint) & dxdv)); // Update parametric coordinate and compute new point and // bound param coordinates if needed. // Compute residual. u += ( d*uRHS-b*vRHS)*invDenom; v += (-c*uRHS+a*vRHS)*invDenom; if (boundDirection(u)) { nBoundsU++; } if (boundDirection(v)) { nBoundsV++; } xuv = surfacePoint(u, v); // If manual assignment in u is required, deal only with the v eqn if (nBoundsU >= 5) { res = mag((xuv-targetPoint) & surfaceDerivativeV(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; } // If manual assignment in b is required, deal only with the u eqn else if (nBoundsV >= 5) { res = mag((xuv-targetPoint) & surfaceDerivativeU(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; } else if (nBoundsU<=5 && nBoundsV<=5) { res = mag((xuv-targetPoint) & surfaceDerivativeU(u, v)) + mag((xuv-targetPoint) & surfaceDerivativeV(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; } else { WarningInFunction << "More than 5 bounding operations in both the u and v directions!" << "u direction " << nBoundsU << endl << "v direction " << nBoundsV << endl << endl; res = mag((xuv-targetPoint) & surfaceDerivativeU(u, v)) + mag((xuv-targetPoint) & surfaceDerivativeV(u, v)); resDeriv = mag(res-resOld)/resOld; resOld = res; } } while ((iter++ < maxIter) && (res > tolerance)); // warning if method did not reach threshold if (iter > maxIter) { nBoundedPoints++; maxResidual = max(maxResidual, res); maxResidualDeriv = max(maxResidualDeriv, resDeriv); } paramCoors[pI].x() = u; paramCoors[pI].y() = v; } Info<< "Points that couldn't reach the residual limit:: " << returnReduce(nBoundedPoints, sumOp