/*---------------------------------------------------------------------------*\
========= |
\\ / 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