diff --git a/include/IECore/CubicBasis.h b/include/IECore/CubicBasis.h index 5b56043220..06b999b141 100644 --- a/include/IECore/CubicBasis.h +++ b/include/IECore/CubicBasis.h @@ -50,7 +50,8 @@ enum class StandardCubicBasis Linear, Bezier, BSpline, - CatmullRom + CatmullRom, + Constant }; /// Provides a basis matrix class for use in constructing cubic curves. @@ -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; }; diff --git a/include/IECore/CubicBasis.inl b/include/IECore/CubicBasis.inl index 876dcec020..5dffbc1903 100644 --- a/include/IECore/CubicBasis.inl +++ b/include/IECore/CubicBasis.inl @@ -64,6 +64,9 @@ CubicBasis::CubicBasis( StandardCubicBasis standardBasis ) case StandardCubicBasis::CatmullRom: *this = CubicBasis::catmullRom(); break; + case StandardCubicBasis::Constant: + *this = CubicBasis::constant(); + break; case StandardCubicBasis::Unknown: throw IECore::Exception( "CubicBasis::CubicBasis - Invalid basis"); } @@ -352,6 +355,21 @@ const CubicBasis &CubicBasis::catmullRom() return m; } +template +const CubicBasis &CubicBasis::constant() +{ + static CubicBasis m( + MatrixType( + 0, 0, 0, 0, + 0, 0, 0, 0, + 0, 0, 0, 0, + 1, 0, 0, 0 + ), + 1 + ); + return m; +} + template StandardCubicBasis CubicBasis::standardBasis() const { @@ -371,6 +389,10 @@ StandardCubicBasis CubicBasis::standardBasis() const { return StandardCubicBasis::CatmullRom; } + else if( *this == CubicBasis::constant() ) + { + return StandardCubicBasis::Constant; + } return StandardCubicBasis::Unknown; } diff --git a/include/IECore/Spline.inl b/include/IECore/Spline.inl index 1f9d107db2..ca0b026e01 100644 --- a/include/IECore/Spline.inl +++ b/include/IECore/Spline.inl @@ -128,11 +128,14 @@ inline X Spline::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 ); @@ -142,8 +145,20 @@ inline X Spline::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; } @@ -154,6 +169,7 @@ inline X Spline::solve( X x, typename PointContainer::const_iterator &segme return X( 1 ); } + prevSegment = segment; for( unsigned i=0; i ) + .def( "numCoefficients", &T::numCoefficients ) .def( "derivativeCoefficients", &derivativeCoefficients ) .def( "integralCoefficients", &integralCoefficients ) .def( "__call__", (BaseType (T::*) ( BaseType, BaseType, BaseType, BaseType, BaseType )const)&T::template operator() ) @@ -121,6 +122,7 @@ void bindCubicBasis( const char *name ) .def( "bezier", &T::bezier, return_value_policy() ).staticmethod( "bezier" ) .def( "bSpline", &T::bSpline, return_value_policy() ).staticmethod( "bSpline" ) .def( "catmullRom", &T::catmullRom, return_value_policy() ).staticmethod( "catmullRom" ) + .def( "constant", &T::constant, return_value_policy() ).staticmethod( "constant" ) .def( "standardBasis", &T::standardBasis ) .def( "__repr__", &repr ) ; @@ -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() ; diff --git a/src/IECoreScene/CurvesAlgoUpdateEndpointMultiplicity.cpp b/src/IECoreScene/CurvesAlgoUpdateEndpointMultiplicity.cpp index 771697a581..61d0078710 100644 --- a/src/IECoreScene/CurvesAlgoUpdateEndpointMultiplicity.cpp +++ b/src/IECoreScene/CurvesAlgoUpdateEndpointMultiplicity.cpp @@ -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" ); diff --git a/src/IECoreScene/ShaderNetworkAlgo.cpp b/src/IECoreScene/ShaderNetworkAlgo.cpp index e23c18de84..286b7f00b0 100644 --- a/src/IECoreScene/ShaderNetworkAlgo.cpp +++ b/src/IECoreScene/ShaderNetworkAlgo.cpp @@ -564,7 +564,8 @@ template 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"; @@ -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 > XTypedVectorData; typename XTypedVectorData::Ptr positionsData = new XTypedVectorData(); @@ -589,22 +599,28 @@ void expandSpline( const InternedString &name, const Spline &spline, CompoundDat typedef TypedData< vector > 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; @@ -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" ) @@ -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(); @@ -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; } diff --git a/test/IECore/SplineTest.py b/test/IECore/SplineTest.py index b88df71b02..4f01e967db 100644 --- a/test/IECore/SplineTest.py +++ b/test/IECore/SplineTest.py @@ -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 ) : diff --git a/test/IECoreScene/ShaderNetworkAlgoTest.py b/test/IECoreScene/ShaderNetworkAlgoTest.py index e654e05a7e..60fe84a1dd 100644 --- a/test/IECoreScene/ShaderNetworkAlgoTest.py +++ b/test/IECoreScene/ShaderNetworkAlgoTest.py @@ -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 ) @@ -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 ) @@ -414,6 +425,7 @@ def testSplineConversion( self ): del parmsExpanded["testffbezierValues"] del parmsExpanded["testfColor3fcatmullRomPositions"] del parmsExpanded["testfColor3flinearBasis"] + del parmsExpanded["testffconstantPositions"] parmsCollapsed = IECoreScene.ShaderNetworkAlgo.collapseSplineParameters( parmsExpanded )