Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add Support For Constant Basis In Splines #1298

Merged
merged 6 commits into from
Sep 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion include/IECore/CubicBasis.h
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,8 @@ enum class StandardCubicBasis
Linear,
Bezier,
BSpline,
CatmullRom
CatmullRom,
Constant
};

/// Provides a basis matrix class for use in constructing cubic curves.
Expand Down Expand Up @@ -142,6 +143,7 @@ class IECORE_EXPORT CubicBasis
static const CubicBasis &bezier();
static const CubicBasis &bSpline();
static const CubicBasis &catmullRom();
static const CubicBasis &constant();

StandardCubicBasis standardBasis() const;
};
Expand Down
22 changes: 22 additions & 0 deletions include/IECore/CubicBasis.inl
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,9 @@ CubicBasis<T>::CubicBasis( StandardCubicBasis standardBasis )
case StandardCubicBasis::CatmullRom:
*this = CubicBasis<T>::catmullRom();
break;
case StandardCubicBasis::Constant:
*this = CubicBasis<T>::constant();
break;
case StandardCubicBasis::Unknown:
throw IECore::Exception( "CubicBasis::CubicBasis - Invalid basis");
}
Expand Down Expand Up @@ -352,6 +355,21 @@ const CubicBasis<T> &CubicBasis<T>::catmullRom()
return m;
}

template<typename T>
const CubicBasis<T> &CubicBasis<T>::constant()
{
static CubicBasis<T> m(
MatrixType(
0, 0, 0, 0,
0, 0, 0, 0,
0, 0, 0, 0,
1, 0, 0, 0
),
1
);
return m;
}

template<typename T>
StandardCubicBasis CubicBasis<T>::standardBasis() const
{
Expand All @@ -371,6 +389,10 @@ StandardCubicBasis CubicBasis<T>::standardBasis() const
{
return StandardCubicBasis::CatmullRom;
}
else if( *this == CubicBasis<T>::constant() )
{
return StandardCubicBasis::Constant;
}

return StandardCubicBasis::Unknown;
}
Expand Down
22 changes: 19 additions & 3 deletions include/IECore/Spline.inl
Original file line number Diff line number Diff line change
Expand Up @@ -128,11 +128,14 @@ inline X Spline<X,Y>::solve( X x, typename PointContainer::const_iterator &segme
// If we hit the end of the points while searching, return the end point of the last valid segment
// this is just a linear search right now - it should be possible to optimise this using points.lower_bound
// to quickly find a better start point for the search.
X co[4];
basis.coefficients( X( 1 ), co );
X endCo[4];
basis.coefficients( X( 1 ), endCo );
X startCo[4];
basis.coefficients( X( 0 ), startCo );
X xp[4] = { X(0), X(0), X(0), X(0) };

segment = points.begin();
It prevSegment = segment;
for( int pointNum = 0;; pointNum += basis.step )
{
It xIt( segment );
Expand All @@ -142,8 +145,20 @@ inline X Spline<X,Y>::solve( X x, typename PointContainer::const_iterator &segme
xIt++;
}

if( xp[0] * co[0] + xp[1] * co[1] + xp[2] * co[2] + xp[3] * co[3] > x )
if( xp[0] * endCo[0] + xp[1] * endCo[1] + xp[2] * endCo[2] + xp[3] * endCo[3] > x )
{
// We've crossed the target value, we can stop the search

if( segment != points.begin() && xp[0] * startCo[0] + xp[1] * startCo[1] + xp[2] * startCo[2] + xp[3] * startCo[3] > x )
{
// We've not only crossed the target, we'd already jumped past it at the start of
// this segment... this could happen when there is a discontinuity between segments
// ( ie. for the "constant" basis ). In this case, we want to take the end of the
// previous segment
segment = prevSegment;
return X( 1 );
}

break;
}

Expand All @@ -154,6 +169,7 @@ inline X Spline<X,Y>::solve( X x, typename PointContainer::const_iterator &segme
return X( 1 );
}

prevSegment = segment;
for( unsigned i=0; i<basis.step; i++ )
{
segment++;
Expand Down
3 changes: 3 additions & 0 deletions src/IECorePython/CubicBasisBinding.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,7 @@ void bindCubicBasis( const char *name )
.def_readwrite( "matrix", &T::matrix )
.def_readwrite( "step", &T::step )
.def( "coefficients", &coefficients<T> )
.def( "numCoefficients", &T::numCoefficients )
.def( "derivativeCoefficients", &derivativeCoefficients<T> )
.def( "integralCoefficients", &integralCoefficients<T> )
.def( "__call__", (BaseType (T::*) ( BaseType, BaseType, BaseType, BaseType, BaseType )const)&T::template operator()<BaseType> )
Expand All @@ -121,6 +122,7 @@ void bindCubicBasis( const char *name )
.def( "bezier", &T::bezier, return_value_policy<copy_const_reference>() ).staticmethod( "bezier" )
.def( "bSpline", &T::bSpline, return_value_policy<copy_const_reference>() ).staticmethod( "bSpline" )
.def( "catmullRom", &T::catmullRom, return_value_policy<copy_const_reference>() ).staticmethod( "catmullRom" )
.def( "constant", &T::constant, return_value_policy<copy_const_reference>() ).staticmethod( "constant" )
.def( "standardBasis", &T::standardBasis )
.def( "__repr__", &repr<T> )
;
Expand All @@ -135,6 +137,7 @@ void bindCubicBasis()
.value("Bezier", IECore::StandardCubicBasis::Bezier )
.value("BSpline", IECore::StandardCubicBasis::BSpline )
.value("CatmullRom", IECore::StandardCubicBasis::CatmullRom )
.value("Constant", IECore::StandardCubicBasis::Constant )
.export_values()
;

Expand Down
4 changes: 4 additions & 0 deletions src/IECoreScene/CurvesAlgoUpdateEndpointMultiplicity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,10 @@ int requiredMultiplicity( const IECore::CubicBasisf &cubicBasis )
{
return 1;
}
else if( cubicBasis == IECore::CubicBasisf::constant() )
{
return 1;
}
else
{
throw IECore::Exception( "updateEndPointMultiplicity : Unsupported curve basis" );
Expand Down
49 changes: 37 additions & 12 deletions src/IECoreScene/ShaderNetworkAlgo.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -564,7 +564,8 @@ template<typename Spline>
void expandSpline( const InternedString &name, const Spline &spline, CompoundDataMap &newParameters )
{
const char *basis = "catmull-rom";
bool duplicateEndPoints = false;
size_t duplicateStartPoints = 0;
size_t duplicateEndPoints = 0;
if( spline.basis == Spline::Basis::bezier() )
{
basis = "bezier";
Expand All @@ -578,9 +579,18 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat
// OSL discards the first and last segment of linear curves
// "To maintain consistency with the other spline types"
// so we need to duplicate the end points to preserve all provided segments
duplicateEndPoints = true;
duplicateStartPoints = 1;
duplicateEndPoints = 1;
basis = "linear";
}
else if( spline.basis == Spline::Basis::constant() )
{
// Also, "To maintain consistency", "constant splines ignore the first and the two last
// data values."
duplicateStartPoints = 1;
duplicateEndPoints = 2;
basis = "constant";
}

typedef TypedData< vector<typename Spline::XType> > XTypedVectorData;
typename XTypedVectorData::Ptr positionsData = new XTypedVectorData();
Expand All @@ -589,22 +599,28 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat
typedef TypedData< vector<typename Spline::YType> > YTypedVectorData;
typename YTypedVectorData::Ptr valuesData = new YTypedVectorData();
auto &values = valuesData->writable();
values.reserve( spline.points.size() + 2 * duplicateEndPoints );
values.reserve( spline.points.size() + duplicateStartPoints + duplicateEndPoints );

if( duplicateEndPoints && spline.points.size() )
if( spline.points.size() )
{
positions.push_back( spline.points.begin()->first );
values.push_back( spline.points.begin()->second );
for( size_t i = 0; i < duplicateStartPoints; i++ )
{
positions.push_back( spline.points.begin()->first );
values.push_back( spline.points.begin()->second );
}
}
for( typename Spline::PointContainer::const_iterator it = spline.points.begin(), eIt = spline.points.end(); it != eIt; ++it )
{
positions.push_back( it->first );
values.push_back( it->second );
}
if( duplicateEndPoints && spline.points.size() )
if( spline.points.size() )
{
positions.push_back( spline.points.rbegin()->first );
values.push_back( spline.points.rbegin()->second );
for( size_t i = 0; i < duplicateEndPoints; i++ )
{
positions.push_back( spline.points.rbegin()->first );
values.push_back( spline.points.rbegin()->second );
}
}

newParameters[ name.string() + "Positions" ] = positionsData;
Expand All @@ -622,7 +638,8 @@ IECore::DataPtr loadSpline(
typename SplineData::Ptr resultData = new SplineData();
auto &result = resultData->writable();

bool unduplicateEndPoints = false;
size_t unduplicateStartPoints = 0;
size_t unduplicateEndPoints = 0;

const std::string &basis = basisData->readable();
if( basis == "bezier" )
Expand All @@ -636,9 +653,17 @@ IECore::DataPtr loadSpline(
else if( basis == "linear" )
{
// Reverse the duplication we do when expanding splines
unduplicateEndPoints = true;
unduplicateStartPoints = 1;
unduplicateEndPoints = 1;
result.basis = SplineData::ValueType::Basis::linear();
}
else if( basis == "constant" )
{
// Reverse the duplication we do when expanding splines
unduplicateStartPoints = 1;
unduplicateEndPoints = 2;
result.basis = SplineData::ValueType::Basis::constant();
}
else
{
result.basis = SplineData::ValueType::Basis::catmullRom();
Expand All @@ -650,7 +675,7 @@ IECore::DataPtr loadSpline(
size_t n = std::min( positions.size(), values.size() );
for( size_t i = 0; i < n; ++i )
{
if( unduplicateEndPoints && ( i == 0 || i == n - 1 ) )
if( i < unduplicateStartPoints || i >= n - unduplicateEndPoints )
{
continue;
}
Expand Down
73 changes: 45 additions & 28 deletions test/IECore/SplineTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -130,35 +130,52 @@ def testPointMultiplicity( self ) :
def testSolveAndCall( self ) :

random.seed( 0 )
for i in range( 0, 100 ) :

s = IECore.Splineff()
x = 0

for i in range( 0, 40 ) :

s[x] = random.uniform( 0, 10 )
x += 1 + random.uniform( 0, 1 )

xv = s.keys()
yv = s.values()

for i in range( 0, 1000 ) :

# select a segment
seg = int(random.uniform( 0, int(len(xv) / 4) ))
seg -= seg % s.basis.step
# evaluate an x,y point on the curve directly
# ourselves
t = i / 1000.0
c = s.basis.coefficients( t )
x = xv[seg+0] * c[0] + xv[seg+1] * c[1] + xv[seg+2] * c[2] + xv[seg+3] * c[3]
y = yv[seg+0] * c[0] + yv[seg+1] * c[1] + yv[seg+2] * c[2] + yv[seg+3] * c[3]

# then check that solving for x gives y
yy = s( x )

self.assertAlmostEqual( yy, y, 3 )
for b in [
IECore.StandardCubicBasis.Linear,
IECore.StandardCubicBasis.Bezier,
IECore.StandardCubicBasis.BSpline,
IECore.StandardCubicBasis.CatmullRom,
IECore.StandardCubicBasis.Constant
]:
for i in range( 0, 100 ) :

s = IECore.Splineff( IECore.CubicBasisf( b ) )
numCoeffs = s.basis.numCoefficients()
x = 0

numCVs = 40
for i in range( 0, numCVs ) :

s[x] = random.uniform( 0, 10 )
x += 1 + random.uniform( 0, 1 )

# Pad with zeroes so we can evaluate the last segment for basis that doesn't use all 4 coefficients
xv = s.keys() + ( 0.0, ) * ( 4 - numCoeffs )
yv = s.values() + ( 0.0, ) * ( 4 - numCoeffs )

for i in range( 0, 1000 ) :

# select a segment
seg = int(random.uniform( 0, 1 + numCVs - numCoeffs ) )
seg -= seg % s.basis.step

# evaluate an x,y point on the curve directly
# ourselves
t = i / 1000.0
c = s.basis.coefficients( t )
if b == IECore.StandardCubicBasis.Constant and seg + 1 < numCVs:
# For the constant basis, we can test any of the positions in X within this span,
# not just the beginning point, which is the only point the basis will give us
x = xv[seg+0] * ( 1 - t ) + xv[seg+1] * t
else:
x = xv[seg+0] * c[0] + xv[seg+1] * c[1] + xv[seg+2] * c[2] + xv[seg+3] * c[3]
y = yv[seg+0] * c[0] + yv[seg+1] * c[1] + yv[seg+2] * c[2] + yv[seg+3] * c[3]

# then check that solving for x gives y
yy = s( x )

self.assertAlmostEqual( yy, y, 3 )

def testRepr( self ) :

Expand Down
12 changes: 12 additions & 0 deletions test/IECoreScene/ShaderNetworkAlgoTest.py
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,8 @@ def testSplineConversion( self ):
( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ), ( 50, imath.Color3f(6) ) ) ) )
parms["testfColor3flinear"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(),
( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) ) )
parms["testffconstant"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.constant(),
( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) ) )

parmsExpanded = IECoreScene.ShaderNetworkAlgo.expandSplineParameters( parms )

Expand All @@ -406,6 +408,15 @@ def testSplineConversion( self ):
self.assertEqual( type( parmsExpanded["testffbSplineValues"] ), IECore.FloatVectorData )
self.assertEqual( type( parmsExpanded["testfColor3fcatmullRomValues"] ), IECore.Color3fVectorData )

for name, extra in [
( "testffbSpline", 0 ),
( "testffbezier", 0 ),
( "testfColor3fcatmullRom", 0 ),
( "testfColor3flinear", 2 ),
( "testffconstant", 3 )
]:
self.assertEqual( len( parms[name].value.keys() ) + extra, len( parmsExpanded[name + "Positions"] ) )

parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded )

self.assertEqual( parmsCollapsed, parms )
Expand All @@ -414,6 +425,7 @@ def testSplineConversion( self ):
del parmsExpanded["testffbezierValues"]
del parmsExpanded["testfColor3fcatmullRomPositions"]
del parmsExpanded["testfColor3flinearBasis"]
del parmsExpanded["testffconstantPositions"]

parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded )

Expand Down