diff --git a/SConstruct b/SConstruct index 8117f1a7e6..1bbd83ead8 100644 --- a/SConstruct +++ b/SConstruct @@ -1173,6 +1173,7 @@ if env["PLATFORM"] == "posix" : # get the python link flags if pythonEnv["PYTHON_LINK_FLAGS"]=="" : pythonEnv["PYTHON_LINK_FLAGS"] = getPythonConfig( pythonEnv, "--ldflags" ) + pythonEnv["PYTHON_LINK_FLAGS"] = pythonEnv["PYTHON_LINK_FLAGS"].replace( "Python.framework/Versions/" + pythonEnv["PYTHON_VERSION"] + "/Python", "" ) pythonEnv.Append( SHLINKFLAGS = pythonEnv["PYTHON_LINK_FLAGS"].split() ) diff --git a/include/IECore/MeshNormalsOp.h b/include/IECore/MeshNormalsOp.h index c3ec85fc7f..249acc9bd0 100644 --- a/include/IECore/MeshNormalsOp.h +++ b/include/IECore/MeshNormalsOp.h @@ -36,6 +36,7 @@ #define IECORE_MESHNORMALSOP_H #include "IECore/TypedPrimitiveOp.h" +#include "IECore/NumericParameter.h" namespace IECore { @@ -57,6 +58,8 @@ class MeshNormalsOp : public MeshPrimitiveOp StringParameter * nPrimVarNameParameter(); const StringParameter * nPrimVarNameParameter() const; + IntParameter *interpolationParameter(); + const IntParameter *interpolationParameter() const; protected: diff --git a/include/IECore/PolygonAlgo.inl b/include/IECore/PolygonAlgo.inl index 1163ff9fba..758490f098 100644 --- a/include/IECore/PolygonAlgo.inl +++ b/include/IECore/PolygonAlgo.inl @@ -58,21 +58,17 @@ typename std::iterator_traits::value_type polygonNormal( Iterator firs Vec n( 0 ); - CircularIt v0It( first, last, first ); - CircularIt v1It( v0It ); v1It++; + CircularIt vIt( first, last, first ); do { - const Vec &v0 = *v0It; - const Vec &v1 = *v1It; + const Vec &v0 = *vIt; ++vIt; + const Vec &v1 = *vIt; n.x += (v0.y - v1.y) * (v0.z + v1.z); n.y += (v0.z - v1.z) * (v0.x + v1.x); n.z += (v0.x - v1.x) * (v0.y + v1.y); - - v0It++; - v1It++; } - while( v0It != first ); + while( vIt != first ); return normalized ? n.normalized() : n; } @@ -89,19 +85,15 @@ Winding polygonWinding( Iterator first, Iterator last ) Real z( 0 ); - CircularIt v0It( first, last, first ); - CircularIt v1It( v0It ); v1It++; + CircularIt vIt( first, last, first ); do { - const Vec &v0 = *v0It; - const Vec &v1 = *v1It; + const Vec &v0 = *vIt; ++vIt; + const Vec &v1 = *vIt; z += (v0.x - v1.x) * (v0.y + v1.y); - - v0It++; - v1It++; } - while( v0It != first ); + while( vIt != first ); return z < Real( 0 ) ? ClockwiseWinding : CounterClockwiseWinding; } diff --git a/include/IECoreGL/MeshPrimitive.h b/include/IECoreGL/MeshPrimitive.h index 50dcefbc26..5ceb8d7a77 100644 --- a/include/IECoreGL/MeshPrimitive.h +++ b/include/IECoreGL/MeshPrimitive.h @@ -51,6 +51,14 @@ class MeshPrimitive : public Primitive IE_CORE_DECLARERUNTIMETYPEDEXTENSION( IECoreGL::MeshPrimitive, MeshPrimitiveTypeId, Primitive ); /// Copies of all data are taken. + /// \deprecated. This constructor was being used to allow the MeshPrimitive to support + /// Vertex and Varying primitive variables in addPrimitiveVariable(), but it lacks the + /// information necessary to support Uniform primitive variables. In the future this + /// constructor will be removed - for forwards compatibility, use a ToGLMeshConverter + /// to create MeshPrimitives. + /// \todo Replace this with a simple MeshPrimitive( numTriangles ) constructor, remove all + /// the conversions from addPrimitiveVariable, and just rely on the work the ToGLMeshConverter + /// already does. MeshPrimitive( IECore::ConstIntVectorDataPtr vertIds ); virtual ~MeshPrimitive(); diff --git a/include/IECoreGL/bindings/MeshPrimitiveBinding.h b/include/IECoreGL/bindings/MeshPrimitiveBinding.h new file mode 100644 index 0000000000..d6b488c2f2 --- /dev/null +++ b/include/IECoreGL/bindings/MeshPrimitiveBinding.h @@ -0,0 +1,45 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2013, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#ifndef IECOREGL_MESHPRIMITIVEBINDING_H +#define IECOREGL_MESHPRIMITIVEBINDING_H + +namespace IECoreGL +{ + +void bindMeshPrimitive(); + +} + +#endif // IECOREGL_MESHPRIMITIVEBINDING_H diff --git a/src/IECore/MeshNormalsOp.cpp b/src/IECore/MeshNormalsOp.cpp index fc83ba8e87..9680f6ac80 100644 --- a/src/IECore/MeshNormalsOp.cpp +++ b/src/IECore/MeshNormalsOp.cpp @@ -32,12 +32,12 @@ // ////////////////////////////////////////////////////////////////////////// +#include "boost/format.hpp" + #include "IECore/MeshNormalsOp.h" #include "IECore/DespatchTypedData.h" #include "IECore/CompoundParameter.h" -#include "boost/format.hpp" - using namespace IECore; using namespace std; @@ -59,8 +59,19 @@ MeshNormalsOp::MeshNormalsOp() : MeshPrimitiveOp( "Calculates vertex normals for "N" ); + IntParameter::PresetsContainer interpolationPresets; + interpolationPresets.push_back( IntParameter::Preset( "Vertex", PrimitiveVariable::Vertex ) ); + interpolationPresets.push_back( IntParameter::Preset( "Uniform", PrimitiveVariable::Uniform ) ); + IntParameterPtr interpolationParameter = new IntParameter( + "interpolation", + "The primitive variable interpolation type for the calculated normals.", + PrimitiveVariable::Vertex, + interpolationPresets + ); + parameters()->addParameter( pPrimVarNameParameter ); parameters()->addParameter( nPrimVarNameParameter ); + parameters()->addParameter( interpolationParameter ); } MeshNormalsOp::~MeshNormalsOp() @@ -87,12 +98,22 @@ const StringParameter * MeshNormalsOp::nPrimVarNameParameter() const return parameters()->parameter( "nPrimVarName" ); } +IntParameter * MeshNormalsOp::interpolationParameter() +{ + return parameters()->parameter( "interpolation" ); +} + +const IntParameter * MeshNormalsOp::interpolationParameter() const +{ + return parameters()->parameter( "interpolation" ); +} + struct MeshNormalsOp::CalculateNormals { typedef DataPtr ReturnType; - CalculateNormals( const IntVectorData * vertsPerFace, const IntVectorData * vertIds ) - : m_vertsPerFace( vertsPerFace ), m_vertIds( vertIds ) + CalculateNormals( const IntVectorData *vertsPerFace, const IntVectorData *vertIds, PrimitiveVariable::Interpolation interpolation ) + : m_vertsPerFace( vertsPerFace ), m_vertIds( vertIds ), m_interpolation( interpolation ) { } @@ -109,32 +130,55 @@ struct MeshNormalsOp::CalculateNormals typename T::Ptr normalsData = new T; normalsData->setInterpretation( GeometricData::Normal ); VecContainer &normals = normalsData->writable(); - normals.resize( points.size(), Vec( 0 ) ); + if( m_interpolation == PrimitiveVariable::Uniform ) + { + normals.reserve( vertsPerFace.size() ); + } + else + { + normals.resize( points.size(), Vec( 0 ) ); + } - // for each face, calculate its normal, and accumulate that normal onto - // the normal for each of its vertices. + // loop over the faces const int *vertId = &(vertIds[0]); for( vector::const_iterator it = vertsPerFace.begin(); it!=vertsPerFace.end(); it++ ) { + // calculate the face normal. note that this method is very naive, and doesn't + // cope with colinear vertices or concave faces - we could use polygonNormal() from + // PolygonAlgo.h to deal with that, but currently we'd prefer to avoid the overhead. const Vec &p0 = points[*vertId]; const Vec &p1 = points[*(vertId+1)]; const Vec &p2 = points[*(vertId+2)]; Vec normal = (p2-p1).cross(p0-p1); normal.normalize(); - for( int i=0; i<*it; i++ ) + + if( m_interpolation == PrimitiveVariable::Uniform ) { - normals[*vertId] += normal; - vertId++; + normals.push_back( normal ); + vertId += *it; + } + else + { + // accumulate the face normal onto each of the vertices + // for this face. + for( int i=0; i<*it; ++i ) + { + normals[*vertId] += normal; + ++vertId; + } } } // normalize each of the vertex normals - for( typename VecContainer::iterator it=normals.begin(); it!=normals.end(); it++ ) + if( m_interpolation == PrimitiveVariable::Vertex ) { - it->normalize(); + for( typename VecContainer::iterator it=normals.begin(), eIt=normals.end(); it != eIt; ++it ) + { + it->normalize(); + } } - + return normalsData; } @@ -142,6 +186,7 @@ struct MeshNormalsOp::CalculateNormals ConstIntVectorDataPtr m_vertsPerFace; ConstIntVectorDataPtr m_vertIds; + PrimitiveVariable::Interpolation m_interpolation; }; @@ -171,8 +216,10 @@ void MeshNormalsOp::modifyTypedPrimitive( MeshPrimitive * mesh, const CompoundOb throw InvalidArgumentException( e ); } - CalculateNormals f( mesh->verticesPerFace(), mesh->vertexIds() ); + const PrimitiveVariable::Interpolation interpolation = static_cast( operands->member( "interpolation" )->readable() ); + + CalculateNormals f( mesh->verticesPerFace(), mesh->vertexIds(), interpolation ); DataPtr n = despatchTypedData( pvIt->second.data, f ); - mesh->variables[ nPrimVarNameParameter()->getTypedValue() ] = PrimitiveVariable( PrimitiveVariable::Vertex, n ); + mesh->variables[ nPrimVarNameParameter()->getTypedValue() ] = PrimitiveVariable( interpolation, n ); } diff --git a/src/IECoreGL/MeshPrimitive.cpp b/src/IECoreGL/MeshPrimitive.cpp index c9057f6bc8..a77c14a975 100644 --- a/src/IECoreGL/MeshPrimitive.cpp +++ b/src/IECoreGL/MeshPrimitive.cpp @@ -60,8 +60,12 @@ struct MeshPrimitive::MemberData : public IECore::RefCounted IECore::ConstIntVectorDataPtr vertIds; Imath::Box3f bound; - /// \todo This could be removed and the ToGLMeshConverter could use FaceVaryingPromotionOp - /// to convert everything to FaceVarying before being added. + /// \todo This could be removed now the ToGLMeshConverter uses FaceVaryingPromotionOp + /// to convert everything to FaceVarying before being added. The only reason we're even + /// doing this still is in case client code is creating MeshPrimitives directly rather + /// than using the converter. We should actually be able to remove the code here, and + /// instead of accept vertIds in the MeshPrimitive constructor, just accept the number + /// of triangles instead. class ToFaceVaryingConverter { public: @@ -118,23 +122,23 @@ IECore::ConstIntVectorDataPtr MeshPrimitive::vertexIds() const void MeshPrimitive::addPrimitiveVariable( const std::string &name, const IECore::PrimitiveVariable &primVar ) { - if ( primVar.interpolation==IECore::PrimitiveVariable::Vertex || primVar.interpolation==IECore::PrimitiveVariable::Varying ) + if( name == "P" ) { - if ( name == "P" ) + // update the bounding box. + m_memberData->bound.makeEmpty(); + IECore::ConstV3fVectorDataPtr points = IECore::runTimeCast< IECore::V3fVectorData >( primVar.data ); + if( points ) { - // update the bounding box. - m_memberData->bound.makeEmpty(); - IECore::ConstV3fVectorDataPtr points = IECore::runTimeCast< IECore::V3fVectorData >( primVar.data ); - if ( points ) + const std::vector &p = points->readable(); + for( unsigned int i=0; i &p = points->readable(); - for( unsigned int i=0; ibound.extendBy( p[i] ); - } + m_memberData->bound.extendBy( p[i] ); } } - + } + + if ( primVar.interpolation==IECore::PrimitiveVariable::Vertex || primVar.interpolation==IECore::PrimitiveVariable::Varying ) + { MemberData::ToFaceVaryingConverter primVarConverter( m_memberData->vertIds ); // convert to facevarying IECore::DataPtr newData = IECore::despatchTypedData< MemberData::ToFaceVaryingConverter, IECore::TypeTraits::IsVectorTypedData >( primVar.data, primVarConverter ); diff --git a/src/IECoreGL/ToGLMeshConverter.cpp b/src/IECoreGL/ToGLMeshConverter.cpp index 83f2b1b070..f1bd81ce8b 100644 --- a/src/IECoreGL/ToGLMeshConverter.cpp +++ b/src/IECoreGL/ToGLMeshConverter.cpp @@ -36,14 +36,15 @@ #include "boost/format.hpp" -#include "IECoreGL/ToGLMeshConverter.h" -#include "IECoreGL/MeshPrimitive.h" - #include "IECore/MeshPrimitive.h" #include "IECore/TriangulateOp.h" #include "IECore/MeshNormalsOp.h" #include "IECore/DespatchTypedData.h" #include "IECore/MessageHandler.h" +#include "IECore/FaceVaryingPromotionOp.h" + +#include "IECoreGL/ToGLMeshConverter.h" +#include "IECoreGL/MeshPrimitive.h" using namespace IECoreGL; @@ -64,41 +65,36 @@ ToGLMeshConverter::~ToGLMeshConverter() IECore::RunTimeTypedPtr ToGLMeshConverter::doConversion( IECore::ConstObjectPtr src, IECore::ConstCompoundObjectPtr operands ) const { IECore::MeshPrimitivePtr mesh = IECore::staticPointerCast( src->copy() ); // safe because the parameter validated it for us + + if( !mesh->variableData( "P", IECore::PrimitiveVariable::Vertex ) ) + { + throw IECore::Exception( "Must specify primitive variable \"P\", of type V3fVectorData and interpolation type Vertex." ); + } - if( mesh->interpolation() != "linear" ) + if( mesh->variables.find( "N" )==mesh->variables.end() ) { - // it's a subdivision mesh. in the absence of a nice subdivision algorithm to display things with, - // we can at least make things look a bit nicer by calculating some smooth shading normals. - // if interpolation is linear and no normals are provided then we assume the faceted look is intentional. - if( mesh->variables.find( "N" )==mesh->variables.end() ) - { - IECore::MeshNormalsOpPtr normalOp = new IECore::MeshNormalsOp(); - normalOp->inputParameter()->setValue( mesh ); - normalOp->copyParameter()->setTypedValue( false ); - normalOp->operate(); - } + // the mesh has no normals - we need to explicitly add some. if it's a polygon + // mesh (interpolation==linear) then we add per-face normals for a faceted look + // and if it's a subdivision mesh we add smooth per-vertex normals. + IECore::MeshNormalsOpPtr normalOp = new IECore::MeshNormalsOp(); + normalOp->inputParameter()->setValue( mesh ); + normalOp->copyParameter()->setTypedValue( false ); + normalOp->interpolationParameter()->setNumericValue( + mesh->interpolation() == "linear" ? IECore::PrimitiveVariable::Uniform : IECore::PrimitiveVariable::Vertex + ); + normalOp->operate(); } - + IECore::TriangulateOpPtr op = new IECore::TriangulateOp(); op->inputParameter()->setValue( mesh ); op->throwExceptionsParameter()->setTypedValue( false ); // it's better to see something than nothing + op->copyParameter()->setTypedValue( false ); + op->operate(); - mesh = IECore::runTimeCast< IECore::MeshPrimitive > ( op->operate() ); - assert( mesh ); - - IECore::ConstV3fVectorDataPtr p = 0; - IECore::PrimitiveVariableMap::const_iterator pIt = mesh->variables.find( "P" ); - if( pIt!=mesh->variables.end() ) - { - if( pIt->second.interpolation==IECore::PrimitiveVariable::Vertex ) - { - p = IECore::runTimeCast( pIt->second.data ); - } - } - if( !p ) - { - throw IECore::Exception( "Must specify primitive variable \"P\", of type V3fVectorData and interpolation type Vertex." ); - } + IECore::FaceVaryingPromotionOpPtr faceVaryingOp = new IECore::FaceVaryingPromotionOp; + faceVaryingOp->inputParameter()->setValue( mesh ); + faceVaryingOp->copyParameter()->setTypedValue( false ); + faceVaryingOp->operate(); MeshPrimitivePtr glMesh = new MeshPrimitive( mesh->vertexIds() ); diff --git a/src/IECoreGL/bindings/IECoreGLBinding.cpp b/src/IECoreGL/bindings/IECoreGLBinding.cpp index 8a115c51d3..9952d60b51 100644 --- a/src/IECoreGL/bindings/IECoreGLBinding.cpp +++ b/src/IECoreGL/bindings/IECoreGLBinding.cpp @@ -64,6 +64,7 @@ #include "IECoreGL/bindings/ToGLTextureConverterBinding.h" #include "IECoreGL/bindings/PrimitiveBinding.h" #include "IECoreGL/bindings/PointsPrimitiveBinding.h" +#include "IECoreGL/bindings/MeshPrimitiveBinding.h" #include "IECoreGL/bindings/SelectorBinding.h" #include "IECoreGL/bindings/FontBinding.h" #include "IECoreGL/bindings/FontLoaderBinding.h" @@ -107,6 +108,7 @@ BOOST_PYTHON_MODULE( _IECoreGL ) bindToGLTextureConverter(); bindPrimitive(); bindPointsPrimitive(); + bindMeshPrimitive(); bindSelector(); bindToGLMeshConverter(); bindToGLPointsConverter(); diff --git a/src/IECoreGL/bindings/MeshPrimitiveBinding.cpp b/src/IECoreGL/bindings/MeshPrimitiveBinding.cpp new file mode 100644 index 0000000000..d8022a7282 --- /dev/null +++ b/src/IECoreGL/bindings/MeshPrimitiveBinding.cpp @@ -0,0 +1,53 @@ +////////////////////////////////////////////////////////////////////////// +// +// Copyright (c) 2013, Image Engine Design Inc. All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are +// met: +// +// * Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// * Neither the name of Image Engine Design nor the names of any +// other contributors to this software may be used to endorse or +// promote products derived from this software without specific prior +// written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +// IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +// THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +// PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +// LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +// NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +// +////////////////////////////////////////////////////////////////////////// + +#include + +#include "IECorePython/RunTimeTypedBinding.h" + +#include "IECoreGL/MeshPrimitive.h" +#include "IECoreGL/bindings/MeshPrimitiveBinding.h" + +using namespace boost::python; + +namespace IECoreGL +{ + +void bindMeshPrimitive() +{ + IECorePython::RunTimeTypedClass() + ; +} + +} // namespace IECoreGL diff --git a/test/IECore/MeshNormalsOpTest.py b/test/IECore/MeshNormalsOpTest.py index 3dc462c24c..e650ad5624 100644 --- a/test/IECore/MeshNormalsOpTest.py +++ b/test/IECore/MeshNormalsOpTest.py @@ -92,5 +92,17 @@ def testSphere( self ) : self.assert_( normals[i].dot( p ) > 0.99 ) self.assert_( normals[i].dot( p ) < 1.01 ) + def testUniformInterpolation( self ) : + + m = MeshPrimitive.createPlane( Box2f( V2f( -1 ), V2f( 1 ) ), V2i( 10 ) ) + self.assertTrue( "N" not in m ) + + m2 = MeshNormalsOp()( input = m, interpolation = PrimitiveVariable.Interpolation.Uniform ) + self.assertEqual( m2["N"].interpolation, PrimitiveVariable.Interpolation.Uniform ) + self.assertEqual( len( m2["N"].data ), m2.variableSize( PrimitiveVariable.Interpolation.Uniform ) ) + + for n in m2["N"].data : + self.assertEqual( n, V3f( 0, 0, 1 ) ) + if __name__ == "__main__": unittest.main() diff --git a/test/IECoreGL/MeshPrimitiveTest.py b/test/IECoreGL/MeshPrimitiveTest.py index e1215adeda..5e6bbb12d6 100644 --- a/test/IECoreGL/MeshPrimitiveTest.py +++ b/test/IECoreGL/MeshPrimitiveTest.py @@ -106,6 +106,127 @@ def testVertexAttributes( self ) : self.assertEqual( IECore.ImageDiffOp()( imageA = expectedImage, imageB = actualImage, maxError = 0.05 ).value, False ) + def testUniformCs( self ) : + + fragmentSource = """ + #include "IECoreGL/FragmentShader.h" + + IECOREGL_FRAGMENTSHADER_IN vec3 fragmentCs; + + void main() + { + gl_FragColor = vec4( fragmentCs, 1.0 ); + } + """ + + r = IECoreGL.Renderer() + r.setOption( "gl:mode", IECore.StringData( "immediate" ) ) + + r.camera( "main", { + "projection" : IECore.StringData( "orthographic" ), + "resolution" : IECore.V2iData( IECore.V2i( 256 ) ), + "clippingPlanes" : IECore.V2fData( IECore.V2f( 1, 1000 ) ), + "screenWindow" : IECore.Box2fData( IECore.Box2f( IECore.V2f( -1 ), IECore.V2f( 1 ) ) ) + } + ) + r.display( self.outputFileName, "tif", "rgba", {} ) + + with IECore.WorldBlock( r ) : + + r.concatTransform( IECore.M44f.createTranslated( IECore.V3f( 0, 0, -15 ) ) ) + + r.shader( "surface", "test", { "gl:fragmentSource" : IECore.StringData( fragmentSource ) } ) + + m = IECore.MeshPrimitive.createPlane( IECore.Box2f( IECore.V2f( -1 ), IECore.V2f( 1 ) ), IECore.V2i( 2 ) ) + m["Cs"] = IECore.PrimitiveVariable( + IECore.PrimitiveVariable.Interpolation.Uniform, + IECore.Color3fVectorData( [ + IECore.Color3f( 1, 0, 0 ), + IECore.Color3f( 0, 1, 0 ), + IECore.Color3f( 0, 0, 1 ), + IECore.Color3f( 1, 1, 1, ), + ] ) + ) + + m.render( r ) + + image = IECore.Reader.create( self.outputFileName ).read() + e = IECore.ImagePrimitiveEvaluator( image ) + r = e.createResult() + + e.pointAtUV( IECore.V2f( 0.25, 0.75 ), r ) + self.assertEqual( r.floatPrimVar( image["R"] ), 1 ) + self.assertEqual( r.floatPrimVar( image["G"] ), 0 ) + self.assertEqual( r.floatPrimVar( image["B"] ), 0 ) + + e.pointAtUV( IECore.V2f( 0.75, 0.75 ), r ) + self.assertEqual( r.floatPrimVar( image["R"] ), 0 ) + self.assertEqual( r.floatPrimVar( image["G"] ), 1 ) + self.assertEqual( r.floatPrimVar( image["B"] ), 0 ) + + e.pointAtUV( IECore.V2f( 0.75, 0.25 ), r ) + self.assertEqual( r.floatPrimVar( image["R"] ), 1 ) + self.assertEqual( r.floatPrimVar( image["G"] ), 1 ) + self.assertEqual( r.floatPrimVar( image["B"] ), 1 ) + + e.pointAtUV( IECore.V2f( 0.25, 0.25 ), r ) + self.assertEqual( r.floatPrimVar( image["R"] ), 0 ) + self.assertEqual( r.floatPrimVar( image["G"] ), 0 ) + self.assertEqual( r.floatPrimVar( image["B"] ), 1 ) + + def testBound( self ) : + + m = IECore.MeshPrimitive.createPlane( IECore.Box2f( IECore.V2f( -0.5 ), IECore.V2f( 0.5 ) ) ) + m2 = IECoreGL.ToGLMeshConverter( m ).convert() + + self.assertEqual( m.bound(), m2.bound() ) + + def testFaceNormals( self ) : + + # when a polygon mesh has no normals, we must calculate face normals so we can + # shade it in a faceted manner. + + fragmentSource = """ + #include "IECoreGL/FragmentShader.h" + IECOREGL_FRAGMENTSHADER_IN vec3 fragmentN; + + void main() + { + gl_FragColor = vec4( fragmentN, 1.0 ); + } + """ + + r = IECoreGL.Renderer() + r.setOption( "gl:mode", IECore.StringData( "immediate" ) ) + + r.camera( "main", { + "projection" : IECore.StringData( "orthographic" ), + "resolution" : IECore.V2iData( IECore.V2i( 256 ) ), + "clippingPlanes" : IECore.V2fData( IECore.V2f( 1, 1000 ) ), + "screenWindow" : IECore.Box2fData( IECore.Box2f( IECore.V2f( -1 ), IECore.V2f( 1 ) ) ) + } + ) + r.display( self.outputFileName, "tif", "rgba", {} ) + + with IECore.WorldBlock( r ) : + + r.concatTransform( IECore.M44f.createTranslated( IECore.V3f( 0, 0, -15 ) ) ) + + r.shader( "surface", "test", { "gl:fragmentSource" : IECore.StringData( fragmentSource ) } ) + + m = IECore.MeshPrimitive.createPlane( IECore.Box2f( IECore.V2f( -0.5 ), IECore.V2f( 0.5 ) ) ) + self.assertTrue( "N" not in m ) + m.render( r ) + + image = IECore.Reader.create( self.outputFileName ).read() + e = IECore.ImagePrimitiveEvaluator( image ) + r = e.createResult() + + e.pointAtUV( IECore.V2f( 0.5, 0.5 ), r ) + self.assertEqual( r.floatPrimVar( image["R"] ), 0 ) + self.assertEqual( r.floatPrimVar( image["G"] ), 0 ) + self.assertEqual( r.floatPrimVar( image["B"] ), 1 ) + def setUp( self ) : if not os.path.isdir( "test/IECoreGL/output" ) :