Loading files
3d097a032This file is pinned to the selected release. Download the original to open it in your design tool.
// Copyright (c) 2014 OPEN CASCADE SAS
//
// This file is part of Open CASCADE Technology software library.
//
// This library is free software; you can redistribute it and/or modify it under
// the terms of the GNU Lesser General Public License version 2.1 as published
// by the Free Software Foundation, with special exception defined in the file
// OCCT_LGPL_EXCEPTION.txt. Consult the file LICENSE_LGPL_21.txt included in OCCT
// distribution for complete text of the license and disclaimer of any warranty.
//
// Alternatively, this file may be used under the terms of Open CASCADE
// commercial license or contractual agreement.
#include <BSplSLib_Cache.hxx>
#include <BSplSLib.hxx>
#include <NCollection_LocalArray.hxx>
#include <gp_Pnt.hxx>
#include <NCollection_Array2.hxx>
#include <NCollection_HArray2.hxx>
#include <utility>
IMPLEMENT_STANDARD_RTTIEXT(BSplSLib_Cache, Standard_Transient)
namespace
{
//! Converts handle of array of double into the pointer to double
double* ConvertArray(const occ::handle<NCollection_HArray2<double>>& theHArray)
{
const NCollection_Array2<double>& anArray = theHArray->Array2();
return (double*)&(anArray(anArray.LowerRow(), anArray.LowerCol()));
}
//=================================================================================================
//! Computes local UV parameters for D0 evaluation (no derivative scaling needed).
//! BSplSLib uses different convention for span parameters than BSplCLib
//! (Start is in the middle of the span and length is half-span).
std::pair<double, double> toLocalParamsD0(double theU,
double theV,
const BSplCLib_CacheParams& theParamsU,
const BSplCLib_CacheParams& theParamsV)
{
const double aNewU = theParamsU.PeriodicNormalization(theU);
const double aNewV = theParamsV.PeriodicNormalization(theV);
const double aSpanLengthU = 0.5 * theParamsU.SpanLength;
const double aSpanStartU = theParamsU.SpanStart + aSpanLengthU;
const double aSpanLengthV = 0.5 * theParamsV.SpanLength;
const double aSpanStartV = theParamsV.SpanStart + aSpanLengthV;
return {(aNewU - aSpanStartU) / aSpanLengthU, (aNewV - aSpanStartV) / aSpanLengthV};
}
//! Computes local UV parameters and inverse span lengths for derivative evaluation.
//! The same inverse values are used for both parameter transformation and derivative scaling
//! to maintain numerical consistency with the original implementation.
std::pair<double, double> toLocalParams(double theU,
double theV,
const BSplCLib_CacheParams& theParamsU,
const BSplCLib_CacheParams& theParamsV,
double& theInvU,
double& theInvV)
{
const double aNewU = theParamsU.PeriodicNormalization(theU);
const double aNewV = theParamsV.PeriodicNormalization(theV);
const double aSpanLengthU = 0.5 * theParamsU.SpanLength;
const double aSpanStartU = theParamsU.SpanStart + aSpanLengthU;
const double aSpanLengthV = 0.5 * theParamsV.SpanLength;
const double aSpanStartV = theParamsV.SpanStart + aSpanLengthV;
// Compute inverses once and reuse for both parameter transformation and derivative scaling
// to maintain numerical consistency with the original implementation
theInvU = 1.0 / aSpanLengthU;
theInvV = 1.0 / aSpanLengthV;
return {(aNewU - aSpanStartU) * theInvU, (aNewV - aSpanStartV) * theInvV};
}
//=================================================================================================
//! Evaluates the polynomials and their derivatives.
//! @param[in] thePolesWeights handle to the array of poles and weights
//! @param[in] theParamsU parameters for U direction
//! @param[in] theParamsV parameters for V direction
//! @param[in] theIsRational flag indicating if the surface is rational
//! @param[in] theLocalU local U parameter
//! @param[in] theLocalV local V parameter
//! @param[in] theUDerivMax maximum U derivative
//! @param[in] theVDerivMax maximum V derivative
//! @param[out] theResultArray array to store the results
void EvaluatePolynomials(const occ::handle<NCollection_HArray2<double>>& thePolesWeights,
const BSplCLib_CacheParams& theParamsU,
const BSplCLib_CacheParams& theParamsV,
const bool theIsRational,
double theLocalU,
double theLocalV,
int theUDerivMax,
int theVDerivMax,
double* theResultArray)
{
double* const aPolesArray = ConvertArray(thePolesWeights);
const int aDimension = theIsRational ? 4 : 3;
const int aCacheCols = thePolesWeights->RowLength();
const bool isMaxU = (theParamsU.Degree > theParamsV.Degree);
const auto [aMinParam, aMaxParam] =
isMaxU ? std::make_pair(theLocalV, theLocalU) : std::make_pair(theLocalU, theLocalV);
// Determine derivatives to calculate along each direction
const int aMaxDeriv = isMaxU ? theUDerivMax : theVDerivMax;
const int aMinDeriv = isMaxU ? theVDerivMax : theUDerivMax;
// Stride between rows in the result array (corresponds to one step in MaxParam direction)
// For full grid (required by RationalDerivative), stride is (aMinDeriv + 1) points.
const int aRowStride = (aMinDeriv + 1) * aDimension;
// clang-format off
// Transient coefficients array size:
// (aMaxDeriv + 1) * CacheCols for the first evaluation (along max degree parameter)
// (aMinDeriv + 1) * Dimension for the second evaluation (along min degree parameter)
NCollection_LocalArray<double> aTransientCoeffs(std::max((aMaxDeriv + 1) * aCacheCols, (aMinDeriv + 1) * aDimension));
// clang-format on
// Calculate intermediate values and derivatives of bivariate polynomial along variable with
// maximal degree
PLib::EvalPolynomial(aMaxParam,
aMaxDeriv,
isMaxU ? theParamsU.Degree : theParamsV.Degree,
aCacheCols,
aPolesArray[0],
aTransientCoeffs[0]);
// Block 0: Evaluate derivatives along variable with minimal degree for D0_max
// Produces (0,0), (0,1)...(0, aMinDeriv)
// Writes to offset 0
PLib::EvalPolynomial(aMinParam,
aMinDeriv,
isMaxU ? theParamsV.Degree : theParamsU.Degree,
aDimension,
aTransientCoeffs[0],
theResultArray[0]);
if (aMaxDeriv > 0)
{
// Block 1: Evaluate derivatives along variable with minimal degree for D1_max
// Writes to offset aRowStride (start of second row)
// If Rational, we need full row (up to aMinDeriv).
// If Not Rational, we can optimize: we strictly need (1,0) and (1,1).
// D1Local calls with (1,1) -> aMinDeriv=1. We need up to 1.
// D2Local calls with (2,2) -> aMinDeriv=2. We need up to 1 for mixed D2.
// So usually min(aMinDeriv, 1) is sufficient for non-rational.
const int aDeriv = theIsRational ? aMinDeriv : std::min(aMinDeriv, 1);
PLib::EvalPolynomial(aMinParam,
aDeriv,
isMaxU ? theParamsV.Degree : theParamsU.Degree,
aDimension,
aTransientCoeffs[aCacheCols],
theResultArray[aRowStride]);
if (aMaxDeriv > 1)
{
// Block 2: Evaluate derivatives along variable with minimal degree for D2_max
// Writes to offset 2 * aRowStride (start of third row)
// If Rational, full row.
// If Not Rational, we only need (2,0) for standard D2.
const int aDeriv2 = theIsRational ? aMinDeriv : 0;
if (aDeriv2 == 0)
{
PLib::NoDerivativeEvalPolynomial(aMinParam,
isMaxU ? theParamsV.Degree : theParamsU.Degree,
aDimension,
(isMaxU ? theParamsV.Degree : theParamsU.Degree)
* aDimension,
aTransientCoeffs[aCacheCols << 1],
theResultArray[aRowStride << 1]);
}
else
{
PLib::EvalPolynomial(aMinParam,
aDeriv2,
isMaxU ? theParamsV.Degree : theParamsU.Degree,
aDimension,
aTransientCoeffs[aCacheCols << 1],
theResultArray[aRowStride << 1]);
}
}
}
if (theIsRational)
{
// RationalDerivative is NOT safe for in-place operation because it reads 4-component data
// and writes 3-component data to potentially overlapping memory locations.
// We need a separate temporary storage for the output.
const int aResultSize = (theUDerivMax + 1) * (theVDerivMax + 1) * 3;
NCollection_LocalArray<double> aTempStorage(aResultSize);
if (isMaxU)
{
BSplSLib::RationalDerivative(theUDerivMax,
theVDerivMax,
theUDerivMax,
theVDerivMax,
theResultArray[0],
aTempStorage[0]);
}
else
{
// If V is max degree, our result array is V-major (transposed relative to what
// RationalDerivative expects) We swap U/V arguments to trick RationalDerivative into
// processing it correctly.
BSplSLib::RationalDerivative(theVDerivMax,
theUDerivMax,
theVDerivMax,
theUDerivMax,
theResultArray[0],
aTempStorage[0]);
}
// Copy results back to the output array
for (int i = 0; i < aResultSize; ++i)
{
theResultArray[i] = aTempStorage[i];
}
}
}
} // namespace
//=================================================================================================
BSplSLib_Cache::BSplSLib_Cache(const int& theDegreeU,
const bool& thePeriodicU,
const NCollection_Array1<double>& theFlatKnotsU,
const int& theDegreeV,
const bool& thePeriodicV,
const NCollection_Array1<double>& theFlatKnotsV,
const NCollection_Array2<double>* theWeights)
: myIsRational(theWeights != nullptr),
myParamsU(theDegreeU, thePeriodicU, theFlatKnotsU),
myParamsV(theDegreeV, thePeriodicV, theFlatKnotsV)
{
int aMinDegree = std::min(theDegreeU, theDegreeV);
int aMaxDegree = std::max(theDegreeU, theDegreeV);
int aPWColNumber = (myIsRational ? 4 : 3);
myPolesWeights =
new NCollection_HArray2<double>(1, aMaxDegree + 1, 1, aPWColNumber * (aMinDegree + 1));
}
//=================================================================================================
bool BSplSLib_Cache::IsCacheValid(double theParameterU, double theParameterV) const
{
return myParamsU.IsCacheValid(theParameterU) && myParamsV.IsCacheValid(theParameterV);
}
//=================================================================================================
void BSplSLib_Cache::BuildCache(const double& theParameterU,
const double& theParameterV,
const NCollection_Array1<double>& theFlatKnotsU,
const NCollection_Array1<double>& theFlatKnotsV,
const NCollection_Array2<gp_Pnt>& thePoles,
const NCollection_Array2<double>* theWeights)
{
// Normalize the parameters for periodical B-splines
double aNewParamU = myParamsU.PeriodicNormalization(theParameterU);
double aNewParamV = myParamsV.PeriodicNormalization(theParameterV);
myParamsU.LocateParameter(aNewParamU, theFlatKnotsU);
myParamsV.LocateParameter(aNewParamV, theFlatKnotsV);
// BSplSLib uses different convention for span parameters than BSplCLib
// (Start is in the middle of the span and length is half-span),
// thus we need to amend them here
double aSpanLengthU = 0.5 * myParamsU.SpanLength;
double aSpanStartU = myParamsU.SpanStart + aSpanLengthU;
double aSpanLengthV = 0.5 * myParamsV.SpanLength;
double aSpanStartV = myParamsV.SpanStart + aSpanLengthV;
// Calculate new cache data
BSplSLib::BuildCache(aSpanStartU,
aSpanStartV,
aSpanLengthU,
aSpanLengthV,
myParamsU.IsPeriodic,
myParamsV.IsPeriodic,
myParamsU.Degree,
myParamsV.Degree,
myParamsU.SpanIndex,
myParamsV.SpanIndex,
theFlatKnotsU,
theFlatKnotsV,
thePoles,
theWeights,
myPolesWeights->ChangeArray2());
}
//=================================================================================================
void BSplSLib_Cache::D0(const double& theU, const double& theV, gp_Pnt& thePoint) const
{
const auto [aLocalU, aLocalV] = toLocalParamsD0(theU, theV, myParamsU, myParamsV);
D0Local(aLocalU, aLocalV, thePoint);
}
//=================================================================================================
void BSplSLib_Cache::D0Local(double theLocalU, double theLocalV, gp_Pnt& thePoint) const
{
double* aPolesArray = ConvertArray(myPolesWeights);
double aPoint[4] = {};
const int aDimension = myIsRational ? 4 : 3;
const int aCacheCols = myPolesWeights->RowLength();
const bool isMaxU = (myParamsU.Degree > myParamsV.Degree);
const auto [aMinDegree, aMaxDegree] = std::minmax(myParamsU.Degree, myParamsV.Degree);
const auto [aMinParam, aMaxParam] =
isMaxU ? std::make_pair(theLocalV, theLocalU) : std::make_pair(theLocalU, theLocalV);
// Array for intermediate results
NCollection_LocalArray<double> aTransientCoeffs(aCacheCols);
// Calculate intermediate value of cached polynomial along variable with maximal degree
PLib::NoDerivativeEvalPolynomial(aMaxParam,
aMaxDegree,
aCacheCols,
aMaxDegree * aCacheCols,
aPolesArray[0],
aTransientCoeffs[0]);
// Calculate total value along variable with minimal degree
PLib::NoDerivativeEvalPolynomial(aMinParam,
aMinDegree,
aDimension,
aDimension * aMinDegree,
aTransientCoeffs[0],
aPoint[0]);
thePoint.SetCoord(aPoint[0], aPoint[1], aPoint[2]);
if (myIsRational)
{
thePoint.ChangeCoord().Divide(aPoint[3]);
}
}
//=================================================================================================
void BSplSLib_Cache::D1Local(double theLocalU,
double theLocalV,
gp_Pnt& thePoint,
gp_Vec& theTangentU,
gp_Vec& theTangentV) const
{
double aPntDeriv[16] = {}; // Result storage for D1, zero-initialized
EvaluatePolynomials(myPolesWeights,
myParamsU,
myParamsV,
myIsRational,
theLocalU,
theLocalV,
1,
1,
aPntDeriv);
// After RationalDerivative (for rational surfaces), the output dimension is 3 (not 4)
// because weights have been processed out
const int aDimension = 3;
thePoint.SetCoord(aPntDeriv[0], aPntDeriv[1], aPntDeriv[2]);
// Tangents are stored after the point coordinates
// Order depends on which parameter has higher degree
// If U degree > V degree: layout is [P, DV, DU, ...]
// If V degree >= U degree: layout is [P, DU, DV, ...]
if (myParamsU.Degree > myParamsV.Degree)
{
theTangentV.SetCoord(aPntDeriv[aDimension],
aPntDeriv[aDimension + 1],
aPntDeriv[aDimension + 2]);
theTangentU.SetCoord(aPntDeriv[aDimension << 1],
aPntDeriv[(aDimension << 1) + 1],
aPntDeriv[(aDimension << 1) + 2]);
}
else
{
theTangentU.SetCoord(aPntDeriv[aDimension],
aPntDeriv[aDimension + 1],
aPntDeriv[aDimension + 2]);
theTangentV.SetCoord(aPntDeriv[aDimension << 1],
aPntDeriv[(aDimension << 1) + 1],
aPntDeriv[(aDimension << 1) + 2]);
}
// Use direct division for better numerical stability with very small span lengths
const double aSpanLengthU = 0.5 * myParamsU.SpanLength;
const double aSpanLengthV = 0.5 * myParamsV.SpanLength;
theTangentU.Divide(aSpanLengthU);
theTangentV.Divide(aSpanLengthV);
}
//=================================================================================================
void BSplSLib_Cache::D2Local(double theLocalU,
double theLocalV,
gp_Pnt& thePoint,
gp_Vec& theTangentU,
gp_Vec& theTangentV,
gp_Vec& theCurvatureU,
gp_Vec& theCurvatureV,
gp_Vec& theCurvatureUV) const
{
double aPntDeriv[36] = {}; // Result storage for D2, zero-initialized
EvaluatePolynomials(myPolesWeights,
myParamsU,
myParamsV,
myIsRational,
theLocalU,
theLocalV,
2,
2,
aPntDeriv);
// After RationalDerivative (for rational surfaces), the output dimension is 3 (not 4)
// because weights have been processed out
const int aDimension = 3;
const int aShift = aDimension; // Shift for first derivatives
const int aShift2 = aDimension << 1;
const int aShift3 = aShift2 + aDimension;
const int aShift4 = aShift3 + aDimension;
const int aShift6 = 6 * aDimension;
thePoint.SetCoord(aPntDeriv[0], aPntDeriv[1], aPntDeriv[2]);
// Derivatives are stored consecutively
// If Max=U (U degree > V degree):
// [0]=P, [Dim]=DV, [2Dim]=DVV
// [3Dim]=DU, [4Dim]=DUV
// [6Dim]=DUU
if (myParamsU.Degree > myParamsV.Degree)
{
theTangentV.SetCoord(aPntDeriv[aShift], aPntDeriv[aShift + 1], aPntDeriv[aShift + 2]);
theCurvatureV.SetCoord(aPntDeriv[aShift2], aPntDeriv[aShift2 + 1], aPntDeriv[aShift2 + 2]);
theTangentU.SetCoord(aPntDeriv[aShift3], aPntDeriv[aShift3 + 1], aPntDeriv[aShift3 + 2]);
theCurvatureUV.SetCoord(aPntDeriv[aShift4], aPntDeriv[aShift4 + 1], aPntDeriv[aShift4 + 2]);
theCurvatureU.SetCoord(aPntDeriv[aShift6], aPntDeriv[aShift6 + 1], aPntDeriv[aShift6 + 2]);
}
else
{
// If Max=V (V degree >= U degree):
// [0]=P, [Dim]=DU, [2Dim]=DUU
// [3Dim]=DV, [4Dim]=DUV (DVU is symmetric)
// [6Dim]=DVV
theTangentU.SetCoord(aPntDeriv[aShift], aPntDeriv[aShift + 1], aPntDeriv[aShift + 2]);
theCurvatureU.SetCoord(aPntDeriv[aShift2], aPntDeriv[aShift2 + 1], aPntDeriv[aShift2 + 2]);
theTangentV.SetCoord(aPntDeriv[aShift3], aPntDeriv[aShift3 + 1], aPntDeriv[aShift3 + 2]);
theCurvatureUV.SetCoord(aPntDeriv[aShift4], aPntDeriv[aShift4 + 1], aPntDeriv[aShift4 + 2]);
theCurvatureV.SetCoord(aPntDeriv[aShift6], aPntDeriv[aShift6 + 1], aPntDeriv[aShift6 + 2]);
}
// Use direct division for better numerical stability with very small span lengths
const double aSpanLengthU = 0.5 * myParamsU.SpanLength;
const double aSpanLengthV = 0.5 * myParamsV.SpanLength;
const double aSpanLengthU2 = aSpanLengthU * aSpanLengthU;
const double aSpanLengthV2 = aSpanLengthV * aSpanLengthV;
theTangentU.Divide(aSpanLengthU);
theTangentV.Divide(aSpanLengthV);
theCurvatureU.Divide(aSpanLengthU2);
theCurvatureV.Divide(aSpanLengthV2);
theCurvatureUV.Divide(aSpanLengthU * aSpanLengthV);
}
//=================================================================================================
void BSplSLib_Cache::D1(const double& theU,
const double& theV,
gp_Pnt& thePoint,
gp_Vec& theTangentU,
gp_Vec& theTangentV) const
{
// Use the same inverse values for both parameter transformation and derivative scaling
// to maintain numerical consistency with the original implementation
double anInvU = 0.0, anInvV = 0.0;
const auto [aLocalU, aLocalV] = toLocalParams(theU, theV, myParamsU, myParamsV, anInvU, anInvV);
double aPntDeriv[16] = {};
EvaluatePolynomials(myPolesWeights,
myParamsU,
myParamsV,
myIsRational,
aLocalU,
aLocalV,
1,
1,
aPntDeriv);
const int aDimension = 3;
thePoint.SetCoord(aPntDeriv[0], aPntDeriv[1], aPntDeriv[2]);
if (myParamsU.Degree > myParamsV.Degree)
{
theTangentV.SetCoord(aPntDeriv[aDimension],
aPntDeriv[aDimension + 1],
aPntDeriv[aDimension + 2]);
theTangentU.SetCoord(aPntDeriv[aDimension << 1],
aPntDeriv[(aDimension << 1) + 1],
aPntDeriv[(aDimension << 1) + 2]);
}
else
{
theTangentU.SetCoord(aPntDeriv[aDimension],
aPntDeriv[aDimension + 1],
aPntDeriv[aDimension + 2]);
theTangentV.SetCoord(aPntDeriv[aDimension << 1],
aPntDeriv[(aDimension << 1) + 1],
aPntDeriv[(aDimension << 1) + 2]);
}
// Scale derivatives using the same inverse values used for parameter transformation
theTangentU.Multiply(anInvU);
theTangentV.Multiply(anInvV);
}
//=================================================================================================
void BSplSLib_Cache::D2(const double& theU,
const double& theV,
gp_Pnt& thePoint,
gp_Vec& theTangentU,
gp_Vec& theTangentV,
gp_Vec& theCurvatureU,
gp_Vec& theCurvatureV,
gp_Vec& theCurvatureUV) const
{
// Use the same inverse values for both parameter transformation and derivative scaling
// to maintain numerical consistency with the original implementation
double anInvU = 0.0, anInvV = 0.0;
const auto [aLocalU, aLocalV] = toLocalParams(theU, theV, myParamsU, myParamsV, anInvU, anInvV);
double aPntDeriv[36] = {};
EvaluatePolynomials(myPolesWeights,
myParamsU,
myParamsV,
myIsRational,
aLocalU,
aLocalV,
2,
2,
aPntDeriv);
const int aDimension = 3;
const int aShift = aDimension;
const int aShift2 = aDimension << 1;
const int aShift3 = aShift2 + aDimension;
const int aShift4 = aShift3 + aDimension;
const int aShift6 = 6 * aDimension;
thePoint.SetCoord(aPntDeriv[0], aPntDeriv[1], aPntDeriv[2]);
if (myParamsU.Degree > myParamsV.Degree)
{
theTangentV.SetCoord(aPntDeriv[aShift], aPntDeriv[aShift + 1], aPntDeriv[aShift + 2]);
theCurvatureV.SetCoord(aPntDeriv[aShift2], aPntDeriv[aShift2 + 1], aPntDeriv[aShift2 + 2]);
theTangentU.SetCoord(aPntDeriv[aShift3], aPntDeriv[aShift3 + 1], aPntDeriv[aShift3 + 2]);
theCurvatureUV.SetCoord(aPntDeriv[aShift4], aPntDeriv[aShift4 + 1], aPntDeriv[aShift4 + 2]);
theCurvatureU.SetCoord(aPntDeriv[aShift6], aPntDeriv[aShift6 + 1], aPntDeriv[aShift6 + 2]);
}
else
{
theTangentU.SetCoord(aPntDeriv[aShift], aPntDeriv[aShift + 1], aPntDeriv[aShift + 2]);
theCurvatureU.SetCoord(aPntDeriv[aShift2], aPntDeriv[aShift2 + 1], aPntDeriv[aShift2 + 2]);
theTangentV.SetCoord(aPntDeriv[aShift3], aPntDeriv[aShift3 + 1], aPntDeriv[aShift3 + 2]);
theCurvatureUV.SetCoord(aPntDeriv[aShift4], aPntDeriv[aShift4 + 1], aPntDeriv[aShift4 + 2]);
theCurvatureV.SetCoord(aPntDeriv[aShift6], aPntDeriv[aShift6 + 1], aPntDeriv[aShift6 + 2]);
}
// Scale derivatives using the same inverse values used for parameter transformation
theTangentU.Multiply(anInvU);
theTangentV.Multiply(anInvV);
theCurvatureU.Multiply(anInvU * anInvU);
theCurvatureV.Multiply(anInvV * anInvV);
theCurvatureUV.Multiply(anInvU * anInvV);
}