Skip to content

Commit 17ed048

Browse files
authored
Merge pull request #1298 from danieldresser-ie/stepBasis
Add Support For Constant Basis In Splines
2 parents ef1433f + bb0adc7 commit 17ed048

File tree

8 files changed

+145
-44
lines changed

8 files changed

+145
-44
lines changed

include/IECore/CubicBasis.h

+3-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,8 @@ enum class StandardCubicBasis
5050
Linear,
5151
Bezier,
5252
BSpline,
53-
CatmullRom
53+
CatmullRom,
54+
Constant
5455
};
5556

5657
/// Provides a basis matrix class for use in constructing cubic curves.
@@ -142,6 +143,7 @@ class IECORE_EXPORT CubicBasis
142143
static const CubicBasis &bezier();
143144
static const CubicBasis &bSpline();
144145
static const CubicBasis &catmullRom();
146+
static const CubicBasis &constant();
145147

146148
StandardCubicBasis standardBasis() const;
147149
};

include/IECore/CubicBasis.inl

+22
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ CubicBasis<T>::CubicBasis( StandardCubicBasis standardBasis )
6464
case StandardCubicBasis::CatmullRom:
6565
*this = CubicBasis<T>::catmullRom();
6666
break;
67+
case StandardCubicBasis::Constant:
68+
*this = CubicBasis<T>::constant();
69+
break;
6770
case StandardCubicBasis::Unknown:
6871
throw IECore::Exception( "CubicBasis::CubicBasis - Invalid basis");
6972
}
@@ -352,6 +355,21 @@ const CubicBasis<T> &CubicBasis<T>::catmullRom()
352355
return m;
353356
}
354357

358+
template<typename T>
359+
const CubicBasis<T> &CubicBasis<T>::constant()
360+
{
361+
static CubicBasis<T> m(
362+
MatrixType(
363+
0, 0, 0, 0,
364+
0, 0, 0, 0,
365+
0, 0, 0, 0,
366+
1, 0, 0, 0
367+
),
368+
1
369+
);
370+
return m;
371+
}
372+
355373
template<typename T>
356374
StandardCubicBasis CubicBasis<T>::standardBasis() const
357375
{
@@ -371,6 +389,10 @@ StandardCubicBasis CubicBasis<T>::standardBasis() const
371389
{
372390
return StandardCubicBasis::CatmullRom;
373391
}
392+
else if( *this == CubicBasis<T>::constant() )
393+
{
394+
return StandardCubicBasis::Constant;
395+
}
374396

375397
return StandardCubicBasis::Unknown;
376398
}

include/IECore/Spline.inl

+19-3
Original file line numberDiff line numberDiff line change
@@ -128,11 +128,14 @@ inline X Spline<X,Y>::solve( X x, typename PointContainer::const_iterator &segme
128128
// If we hit the end of the points while searching, return the end point of the last valid segment
129129
// this is just a linear search right now - it should be possible to optimise this using points.lower_bound
130130
// to quickly find a better start point for the search.
131-
X co[4];
132-
basis.coefficients( X( 1 ), co );
131+
X endCo[4];
132+
basis.coefficients( X( 1 ), endCo );
133+
X startCo[4];
134+
basis.coefficients( X( 0 ), startCo );
133135
X xp[4] = { X(0), X(0), X(0), X(0) };
134136

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

145-
if( xp[0] * co[0] + xp[1] * co[1] + xp[2] * co[2] + xp[3] * co[3] > x )
148+
if( xp[0] * endCo[0] + xp[1] * endCo[1] + xp[2] * endCo[2] + xp[3] * endCo[3] > x )
146149
{
150+
// We've crossed the target value, we can stop the search
151+
152+
if( segment != points.begin() && xp[0] * startCo[0] + xp[1] * startCo[1] + xp[2] * startCo[2] + xp[3] * startCo[3] > x )
153+
{
154+
// We've not only crossed the target, we'd already jumped past it at the start of
155+
// this segment... this could happen when there is a discontinuity between segments
156+
// ( ie. for the "constant" basis ). In this case, we want to take the end of the
157+
// previous segment
158+
segment = prevSegment;
159+
return X( 1 );
160+
}
161+
147162
break;
148163
}
149164

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

172+
prevSegment = segment;
157173
for( unsigned i=0; i<basis.step; i++ )
158174
{
159175
segment++;

src/IECorePython/CubicBasisBinding.cpp

+3
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ void bindCubicBasis( const char *name )
9898
.def_readwrite( "matrix", &T::matrix )
9999
.def_readwrite( "step", &T::step )
100100
.def( "coefficients", &coefficients<T> )
101+
.def( "numCoefficients", &T::numCoefficients )
101102
.def( "derivativeCoefficients", &derivativeCoefficients<T> )
102103
.def( "integralCoefficients", &integralCoefficients<T> )
103104
.def( "__call__", (BaseType (T::*) ( BaseType, BaseType, BaseType, BaseType, BaseType )const)&T::template operator()<BaseType> )
@@ -121,6 +122,7 @@ void bindCubicBasis( const char *name )
121122
.def( "bezier", &T::bezier, return_value_policy<copy_const_reference>() ).staticmethod( "bezier" )
122123
.def( "bSpline", &T::bSpline, return_value_policy<copy_const_reference>() ).staticmethod( "bSpline" )
123124
.def( "catmullRom", &T::catmullRom, return_value_policy<copy_const_reference>() ).staticmethod( "catmullRom" )
125+
.def( "constant", &T::constant, return_value_policy<copy_const_reference>() ).staticmethod( "constant" )
124126
.def( "standardBasis", &T::standardBasis )
125127
.def( "__repr__", &repr<T> )
126128
;
@@ -135,6 +137,7 @@ void bindCubicBasis()
135137
.value("Bezier", IECore::StandardCubicBasis::Bezier )
136138
.value("BSpline", IECore::StandardCubicBasis::BSpline )
137139
.value("CatmullRom", IECore::StandardCubicBasis::CatmullRom )
140+
.value("Constant", IECore::StandardCubicBasis::Constant )
138141
.export_values()
139142
;
140143

src/IECoreScene/CurvesAlgoUpdateEndpointMultiplicity.cpp

+4
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,10 @@ int requiredMultiplicity( const IECore::CubicBasisf &cubicBasis )
107107
{
108108
return 1;
109109
}
110+
else if( cubicBasis == IECore::CubicBasisf::constant() )
111+
{
112+
return 1;
113+
}
110114
else
111115
{
112116
throw IECore::Exception( "updateEndPointMultiplicity : Unsupported curve basis" );

src/IECoreScene/ShaderNetworkAlgo.cpp

+37-12
Original file line numberDiff line numberDiff line change
@@ -564,7 +564,8 @@ template<typename Spline>
564564
void expandSpline( const InternedString &name, const Spline &spline, CompoundDataMap &newParameters )
565565
{
566566
const char *basis = "catmull-rom";
567-
bool duplicateEndPoints = false;
567+
size_t duplicateStartPoints = 0;
568+
size_t duplicateEndPoints = 0;
568569
if( spline.basis == Spline::Basis::bezier() )
569570
{
570571
basis = "bezier";
@@ -578,9 +579,18 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat
578579
// OSL discards the first and last segment of linear curves
579580
// "To maintain consistency with the other spline types"
580581
// so we need to duplicate the end points to preserve all provided segments
581-
duplicateEndPoints = true;
582+
duplicateStartPoints = 1;
583+
duplicateEndPoints = 1;
582584
basis = "linear";
583585
}
586+
else if( spline.basis == Spline::Basis::constant() )
587+
{
588+
// Also, "To maintain consistency", "constant splines ignore the first and the two last
589+
// data values."
590+
duplicateStartPoints = 1;
591+
duplicateEndPoints = 2;
592+
basis = "constant";
593+
}
584594

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

594-
if( duplicateEndPoints && spline.points.size() )
604+
if( spline.points.size() )
595605
{
596-
positions.push_back( spline.points.begin()->first );
597-
values.push_back( spline.points.begin()->second );
606+
for( size_t i = 0; i < duplicateStartPoints; i++ )
607+
{
608+
positions.push_back( spline.points.begin()->first );
609+
values.push_back( spline.points.begin()->second );
610+
}
598611
}
599612
for( typename Spline::PointContainer::const_iterator it = spline.points.begin(), eIt = spline.points.end(); it != eIt; ++it )
600613
{
601614
positions.push_back( it->first );
602615
values.push_back( it->second );
603616
}
604-
if( duplicateEndPoints && spline.points.size() )
617+
if( spline.points.size() )
605618
{
606-
positions.push_back( spline.points.rbegin()->first );
607-
values.push_back( spline.points.rbegin()->second );
619+
for( size_t i = 0; i < duplicateEndPoints; i++ )
620+
{
621+
positions.push_back( spline.points.rbegin()->first );
622+
values.push_back( spline.points.rbegin()->second );
623+
}
608624
}
609625

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

625-
bool unduplicateEndPoints = false;
641+
size_t unduplicateStartPoints = 0;
642+
size_t unduplicateEndPoints = 0;
626643

627644
const std::string &basis = basisData->readable();
628645
if( basis == "bezier" )
@@ -636,9 +653,17 @@ IECore::DataPtr loadSpline(
636653
else if( basis == "linear" )
637654
{
638655
// Reverse the duplication we do when expanding splines
639-
unduplicateEndPoints = true;
656+
unduplicateStartPoints = 1;
657+
unduplicateEndPoints = 1;
640658
result.basis = SplineData::ValueType::Basis::linear();
641659
}
660+
else if( basis == "constant" )
661+
{
662+
// Reverse the duplication we do when expanding splines
663+
unduplicateStartPoints = 1;
664+
unduplicateEndPoints = 2;
665+
result.basis = SplineData::ValueType::Basis::constant();
666+
}
642667
else
643668
{
644669
result.basis = SplineData::ValueType::Basis::catmullRom();
@@ -650,7 +675,7 @@ IECore::DataPtr loadSpline(
650675
size_t n = std::min( positions.size(), values.size() );
651676
for( size_t i = 0; i < n; ++i )
652677
{
653-
if( unduplicateEndPoints && ( i == 0 || i == n - 1 ) )
678+
if( i < unduplicateStartPoints || i >= n - unduplicateEndPoints )
654679
{
655680
continue;
656681
}

test/IECore/SplineTest.py

+45-28
Original file line numberDiff line numberDiff line change
@@ -130,35 +130,52 @@ def testPointMultiplicity( self ) :
130130
def testSolveAndCall( self ) :
131131

132132
random.seed( 0 )
133-
for i in range( 0, 100 ) :
134133

135-
s = IECore.Splineff()
136-
x = 0
137-
138-
for i in range( 0, 40 ) :
139-
140-
s[x] = random.uniform( 0, 10 )
141-
x += 1 + random.uniform( 0, 1 )
142-
143-
xv = s.keys()
144-
yv = s.values()
145-
146-
for i in range( 0, 1000 ) :
147-
148-
# select a segment
149-
seg = int(random.uniform( 0, int(len(xv) / 4) ))
150-
seg -= seg % s.basis.step
151-
# evaluate an x,y point on the curve directly
152-
# ourselves
153-
t = i / 1000.0
154-
c = s.basis.coefficients( t )
155-
x = xv[seg+0] * c[0] + xv[seg+1] * c[1] + xv[seg+2] * c[2] + xv[seg+3] * c[3]
156-
y = yv[seg+0] * c[0] + yv[seg+1] * c[1] + yv[seg+2] * c[2] + yv[seg+3] * c[3]
157-
158-
# then check that solving for x gives y
159-
yy = s( x )
160-
161-
self.assertAlmostEqual( yy, y, 3 )
134+
for b in [
135+
IECore.StandardCubicBasis.Linear,
136+
IECore.StandardCubicBasis.Bezier,
137+
IECore.StandardCubicBasis.BSpline,
138+
IECore.StandardCubicBasis.CatmullRom,
139+
IECore.StandardCubicBasis.Constant
140+
]:
141+
for i in range( 0, 100 ) :
142+
143+
s = IECore.Splineff( IECore.CubicBasisf( b ) )
144+
numCoeffs = s.basis.numCoefficients()
145+
x = 0
146+
147+
numCVs = 40
148+
for i in range( 0, numCVs ) :
149+
150+
s[x] = random.uniform( 0, 10 )
151+
x += 1 + random.uniform( 0, 1 )
152+
153+
# Pad with zeroes so we can evaluate the last segment for basis that doesn't use all 4 coefficients
154+
xv = s.keys() + ( 0.0, ) * ( 4 - numCoeffs )
155+
yv = s.values() + ( 0.0, ) * ( 4 - numCoeffs )
156+
157+
for i in range( 0, 1000 ) :
158+
159+
# select a segment
160+
seg = int(random.uniform( 0, 1 + numCVs - numCoeffs ) )
161+
seg -= seg % s.basis.step
162+
163+
# evaluate an x,y point on the curve directly
164+
# ourselves
165+
t = i / 1000.0
166+
c = s.basis.coefficients( t )
167+
if b == IECore.StandardCubicBasis.Constant and seg + 1 < numCVs:
168+
# For the constant basis, we can test any of the positions in X within this span,
169+
# not just the beginning point, which is the only point the basis will give us
170+
x = xv[seg+0] * ( 1 - t ) + xv[seg+1] * t
171+
else:
172+
x = xv[seg+0] * c[0] + xv[seg+1] * c[1] + xv[seg+2] * c[2] + xv[seg+3] * c[3]
173+
y = yv[seg+0] * c[0] + yv[seg+1] * c[1] + yv[seg+2] * c[2] + yv[seg+3] * c[3]
174+
175+
# then check that solving for x gives y
176+
yy = s( x )
177+
178+
self.assertAlmostEqual( yy, y, 3 )
162179

163180
def testRepr( self ) :
164181

test/IECoreScene/ShaderNetworkAlgoTest.py

+12
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,8 @@ def testSplineConversion( self ):
397397
( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ), ( 30, imath.Color3f(5) ), ( 40, imath.Color3f(2) ), ( 50, imath.Color3f(6) ) ) ) )
398398
parms["testfColor3flinear"] = IECore.SplinefColor3fData( IECore.SplinefColor3f( IECore.CubicBasisf.linear(),
399399
( ( 0, imath.Color3f(1) ), ( 10, imath.Color3f(2) ), ( 20, imath.Color3f(0) ) ) ) )
400+
parms["testffconstant"] = IECore.SplineffData( IECore.Splineff( IECore.CubicBasisf.constant(),
401+
( ( 0, 1 ), ( 0.2, 6 ), ( 0.3, 7 ) ) ) )
400402

401403
parmsExpanded = IECoreScene.ShaderNetworkAlgo.expandSplineParameters( parms )
402404

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

411+
for name, extra in [
412+
( "testffbSpline", 0 ),
413+
( "testffbezier", 0 ),
414+
( "testfColor3fcatmullRom", 0 ),
415+
( "testfColor3flinear", 2 ),
416+
( "testffconstant", 3 )
417+
]:
418+
self.assertEqual( len( parms[name].value.keys() ) + extra, len( parmsExpanded[name + "Positions"] ) )
419+
409420
parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded )
410421

411422
self.assertEqual( parmsCollapsed, parms )
@@ -414,6 +425,7 @@ def testSplineConversion( self ):
414425
del parmsExpanded["testffbezierValues"]
415426
del parmsExpanded["testfColor3fcatmullRomPositions"]
416427
del parmsExpanded["testfColor3flinearBasis"]
428+
del parmsExpanded["testffconstantPositions"]
417429

418430
parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded )
419431

0 commit comments

Comments
 (0)