IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { std::string name = namePlug()->getValue(); if( !name.size() ) { return outPlug()->childNamesPlug()->defaultValue(); } ConstV3fVectorDataPtr p = sourcePoints( parentPath ); if( !p || !p->readable().size() ) { return outPlug()->childNamesPlug()->defaultValue(); } InternedStringVectorDataPtr result = new InternedStringVectorData(); for( size_t i=0; i<p->readable().size(); i++ ) { result->writable().push_back( boost::lexical_cast<string>( i ) ); } return result; } else { ContextPtr ic = instanceContext( context, branchPath ); Context::Scope scopedContext( ic ); return instancePlug()->childNamesPlug()->getValue(); } }
Imath::Box3f Instancer::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const { ContextPtr ic = instanceContext( context, branchPath ); if( ic ) { Context::Scope scopedContext( ic ); return instancePlug()->boundPlug()->getValue(); } // branchPath == "/" Box3f result; ConstV3fVectorDataPtr p = sourcePoints( parentPath ); if( p ) { ScenePath branchChildPath( branchPath ); branchChildPath.push_back( InternedString() ); // where we'll place the instance index for( size_t i=0; i<p->readable().size(); i++ ) { /// \todo We could have a very fast InternedString( int ) constructor rather than all this lexical cast nonsense branchChildPath[branchChildPath.size()-1] = boost::lexical_cast<string>( i ); Box3f branchChildBound = computeBranchBound( parentPath, branchChildPath, context ); branchChildBound = transform( branchChildBound, computeBranchTransform( parentPath, branchChildPath, context ) ); result.extendBy( branchChildBound ); } } return result; }
void Instancer::hashBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context, IECore::MurmurHash &h ) const { if( branchPath.size() <= 1 ) { // "/" or "/name" BranchCreator::hashBranchBound( parentPath, branchPath, context, h ); ConstV3fVectorDataPtr p = sourcePoints( parentPath ); if( p ) { p->hash( h ); ScenePath branchChildPath( branchPath ); if( branchChildPath.size() == 0 ) { branchChildPath.push_back( namePlug()->getValue() ); } BoundHash hasher( this, branchChildPath, context ); parallel_deterministic_reduce( blocked_range<size_t>( 0, p->readable().size(), 100 ), hasher ); h.append( hasher.result() ); } } else { InstanceScope instanceScope( context, branchPath ); h = instancePlug()->boundPlug()->hash(); } }
Imath::Box3f Instancer::computeBranchBound( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() <= 1 ) { // "/" or "/name" Box3f result; ConstV3fVectorDataPtr p = sourcePoints( parentPath ); if( p ) { ScenePath branchChildPath( branchPath ); if( branchChildPath.size() == 0 ) { branchChildPath.push_back( namePlug()->getValue() ); } BoundUnion unioner( this, branchChildPath, context, p.get() ); parallel_reduce( blocked_range<size_t>( 0, p->readable().size() ), unioner ); result = unioner.result(); } return result; } else { InstanceScope instanceScope( context, branchPath ); return instancePlug()->boundPlug()->getValue(); } }
Imath::Box3f Primitive::bound() const { Box3f result; PrimitiveVariableMap::const_iterator it = variables.find( "P" ); if( it!=variables.end() ) { ConstV3fVectorDataPtr p = runTimeCast<const V3fVectorData>( it->second.data ); if( p ) { const vector<V3f> &pp = p->readable(); for( size_t i=0; i<pp.size(); i++ ) { result.extendBy( pp[i] ); } } } return result; }
Imath::M44f Instancer::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() < 2 ) { // "/" or "/name" return M44f(); } else if( branchPath.size() == 2 ) { // "/name/instanceNumber" int index = instanceIndex( branchPath ); ConstV3fVectorDataPtr p = sourcePoints( parentPath ); return instanceTransform( p.get(), index ); } else { InstanceScope instanceScope( context, branchPath ); return instancePlug()->transformPlug()->getValue(); } }
NURBSPrimitive::NURBSPrimitive( int uOrder, ConstFloatVectorDataPtr uKnot, float uMin, float uMax, int vOrder, ConstFloatVectorDataPtr vKnot, float vMin, float vMax, ConstV3fVectorDataPtr p ) { setTopology( uOrder, uKnot, uMin, uMax, vOrder, vKnot, vMin, vMax ); if( p ) { V3fVectorDataPtr pData = p->copy(); pData->setInterpretation( GeometricData::Point ); variables.insert( PrimitiveVariableMap::value_type( "P", PrimitiveVariable( PrimitiveVariable::Vertex, pData ) ) ); } }
CurvesPrimitive::CurvesPrimitive( ConstIntVectorDataPtr vertsPerCurve, const CubicBasisf &basis, bool periodic, ConstV3fVectorDataPtr p ) : m_basis( CubicBasisf::linear() ) { setTopology( vertsPerCurve, basis, periodic ); if( p ) { V3fVectorDataPtr pData = p->copy(); pData->setInterpretation( GeometricData::Point ); variables["P"] = PrimitiveVariable( PrimitiveVariable::Vertex, pData ); } }
IECore::ConstInternedStringVectorDataPtr Instancer::computeBranchChildNames( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const { if( branchPath.size() == 0 ) { // "/" std::string name = namePlug()->getValue(); if( name.empty() ) { return outPlug()->childNamesPlug()->defaultValue(); } InternedStringVectorDataPtr result = new InternedStringVectorData(); result->writable().push_back( name ); return result; } else if( branchPath.size() == 1 ) { ConstV3fVectorDataPtr p = sourcePoints( parentPath ); if( !p || !p->readable().size() ) { return outPlug()->childNamesPlug()->defaultValue(); } const size_t s = p->readable().size(); InternedStringVectorDataPtr resultData = new InternedStringVectorData(); vector<InternedString> &result = resultData->writable(); result.resize( s ); for( size_t i = 0; i < s ; ++i ) { result[i] = InternedString( i ); } return resultData; } else { InstanceScope instanceScope( context, branchPath ); return instancePlug()->childNamesPlug()->getValue(); } }
Imath::M44f Instancer::computeBranchTransform( const ScenePath &parentPath, const ScenePath &branchPath, const Gaffer::Context *context ) const { M44f result; ContextPtr ic = instanceContext( context, branchPath ); if( ic ) { Context::Scope scopedContext( ic ); result = instancePlug()->transformPlug()->getValue(); } if( branchPath.size() == 1 ) { int index = instanceIndex( branchPath ); ConstV3fVectorDataPtr p = sourcePoints( parentPath ); if( p && (size_t)index < p->readable().size() ) { M44f t; t.translate( p->readable()[index] ); result *= t; } } return result; }
SimpleSubsurface::SimpleSubsurface( ConstV3fVectorDataPtr p, ConstColor3fVectorDataPtr c, ConstFloatVectorDataPtr a, const SplinefColor3f &falloff ) { m_privateData = boost::shared_ptr<PrivateData>( new PrivateData ); m_privateData->points = p->copy(); m_privateData->colors = c->copy(); const vector<float> &areas = a->readable(); vector<Color3f> &colors = m_privateData->colors->writable(); for( size_t i=0; i<colors.size(); i++ ) { colors[i] *= areas[i]; } m_privateData->tree.init( m_privateData->points->readable().begin(), m_privateData->points->readable().end() ); m_privateData->falloff.init( SplineRemapper( falloff ), 0, 1, 100 ); m_privateData->nodeCentroids.resize( m_privateData->tree.numNodes() ); m_privateData->nodeColors.resize( m_privateData->tree.numNodes() ); m_privateData->nodeBounds.resize( m_privateData->tree.numNodes() ); buildWalk( m_privateData->tree.rootIndex() ); }
void MeshPrimitiveImplicitSurfaceOp::modifyTypedPrimitive( MeshPrimitive * typedPrimitive, const CompoundObject * operands ) { const float threshold = m_thresholdParameter->getNumericValue(); bool automaticBound = static_cast<const BoolData *>( m_automaticBoundParameter->getValue() )->readable(); Box3f bound; if (automaticBound) { bound.makeEmpty(); PrimitiveVariableMap::const_iterator it = typedPrimitive->variables.find("P"); if (it != typedPrimitive->variables.end()) { const DataPtr &verticesData = it->second.data; /// \todo Use depatchTypedData if (runTimeCast<V3fVectorData>(verticesData)) { ConstV3fVectorDataPtr p = runTimeCast<V3fVectorData>(verticesData); for ( V3fVectorData::ValueType::const_iterator it = p->readable().begin(); it != p->readable().end(); ++it) { bound.extendBy( *it ); } } else if (runTimeCast<V3dVectorData>(verticesData)) { ConstV3dVectorDataPtr p = runTimeCast<V3dVectorData>(verticesData); for ( V3dVectorData::ValueType::const_iterator it = p->readable().begin(); it != p->readable().end(); ++it) { bound.extendBy( *it ); } } else { throw InvalidArgumentException("MeshPrimitive has no primitive variable \"P\" of type V3fVectorData/V3dVectorData in MeshPrimitiveImplicitSurfaceOp"); } } else { throw InvalidArgumentException("MeshPrimitive has no primitive variable \"P\" in MeshPrimitiveImplicitSurfaceOp"); } } else { bound = static_cast<const Box3fData *>( m_boundParameter->getValue() )->readable(); } float boundExtend = m_boundExtendParameter->getNumericValue(); bound.min -= V3f( boundExtend, boundExtend, boundExtend ); bound.max += V3f( boundExtend, boundExtend, boundExtend ); V3i resolution; int gridMethod = m_gridMethodParameter->getNumericValue(); if ( gridMethod == Resolution ) { resolution = static_cast<const V3iData *>( m_resolutionParameter->getValue() )->readable(); } else if ( gridMethod == DivisionSize ) { V3f divisionSize = static_cast<const V3fData *>( m_divisionSizeParameter->getValue() )->readable(); resolution.x = (int)((bound.max.x - bound.min.x) / divisionSize.x); resolution.y = (int)((bound.max.y - bound.min.y) / divisionSize.y); resolution.z = (int)((bound.max.z - bound.min.z) / divisionSize.z); } else { assert( false ); } resolution.x = std::max( 1, resolution.x ); resolution.y = std::max( 1, resolution.y ); resolution.z = std::max( 1, resolution.z ); /// Calculate a tolerance which is half the size of the smallest grid division double cacheTolerance = ((bound.max.x - bound.min.x) / (double)resolution.x) / 2.0; cacheTolerance = std::min(cacheTolerance, ((bound.max.y - bound.min.y) / (double)resolution.y) / 2.0 ); cacheTolerance = std::min(cacheTolerance, ((bound.max.z - bound.min.z) / (double)resolution.z) / 2.0 ); MeshPrimitiveBuilderPtr builder = new MeshPrimitiveBuilder(); typedef MarchingCubes< CachedImplicitSurfaceFunction< V3f, float > > Marcher ; MeshPrimitiveImplicitSurfaceFunctionPtr fn = new MeshPrimitiveImplicitSurfaceFunction( typedPrimitive ); Marcher::Ptr m = new Marcher ( new CachedImplicitSurfaceFunction< V3f, float >( fn, cacheTolerance ), builder ); m->march( Box3f( bound.min, bound.max ), resolution, threshold ); MeshPrimitivePtr resultMesh = builder->mesh(); typedPrimitive->variables.clear(); typedPrimitive->setTopology( resultMesh->verticesPerFace(), resultMesh->vertexIds() ); typedPrimitive->variables["P"] = PrimitiveVariable( resultMesh->variables["P"].interpolation, resultMesh->variables["P"].data->copy() ); typedPrimitive->variables["N"] = PrimitiveVariable( resultMesh->variables["N"].interpolation, resultMesh->variables["N"].data->copy() ); }
void FaceAreaOp::modifyTypedPrimitive( MeshPrimitive * mesh, const CompoundObject * operands ) { string areaPrimVarName = parameters()->parameter<StringParameter>( "areaPrimVar" )->getTypedValue(); if( areaPrimVarName!="" ) { const string &pName = parameters()->parameter<StringParameter>( "pointPrimVar" )->getTypedValue(); ConstV3fVectorDataPtr pData = mesh->variableData<V3fVectorData>( pName, PrimitiveVariable::Vertex ); if( !pData ) { throw InvalidArgumentException( boost::str( boost::format( "FaceAreaOp : MeshPrimitive has no \"%s\" primitive variable." ) % pName ) ); } const vector<V3f> &p = pData->readable(); FloatVectorDataPtr areasData = new FloatVectorData; vector<float> &areas = areasData->writable(); areas.reserve( mesh->variableSize( PrimitiveVariable::Uniform ) ); PolygonIterator faceEnd = mesh->faceEnd(); for( PolygonIterator pIt = mesh->faceBegin(); pIt!=faceEnd; pIt++ ) { typedef vector<V3f> PointVector; areas.push_back( polygonArea( pIt.vertexBegin( p.begin() ), pIt.vertexEnd( p.begin() ) ) ); } mesh->variables[areaPrimVarName] = PrimitiveVariable( PrimitiveVariable::Uniform, areasData ); } string textureAreaPrimVarName = parameters()->parameter<StringParameter>( "textureAreaPrimVar" )->getTypedValue(); if( textureAreaPrimVarName!="" ) { const string &sName = parameters()->parameter<StringParameter>( "sPrimVar" )->getTypedValue(); PrimitiveVariable::Interpolation sInterpolation = PrimitiveVariable::Vertex; ConstFloatVectorDataPtr sData = mesh->variableData<FloatVectorData>( sName, PrimitiveVariable::Vertex ); if( !sData ) { sData = mesh->variableData<FloatVectorData>( sName, PrimitiveVariable::FaceVarying ); if( !sData ) { throw InvalidArgumentException( boost::str( boost::format( "FaceAreaOp : MeshPrimitive has no suitable \"%s\" primitive variable." ) % sName ) ); } sInterpolation = PrimitiveVariable::FaceVarying; } const vector<float> &s = sData->readable(); const string &tName = parameters()->parameter<StringParameter>( "tPrimVar" )->getTypedValue(); PrimitiveVariable::Interpolation tInterpolation = PrimitiveVariable::Vertex; ConstFloatVectorDataPtr tData = mesh->variableData<FloatVectorData>( tName, PrimitiveVariable::Vertex ); if( !tData ) { tData = mesh->variableData<FloatVectorData>( tName, PrimitiveVariable::FaceVarying ); if( !tData ) { throw InvalidArgumentException( boost::str( boost::format( "FaceAreaOp : MeshPrimitive has no suitable \"%s\" primitive variable." ) % tName ) ); } tInterpolation = PrimitiveVariable::FaceVarying; } const vector<float> &t = tData->readable(); if( sInterpolation!=tInterpolation ) { throw InvalidArgumentException( boost::str( boost::format( "FaceAreaOp : interpolation for \"%s\" and \"%s\" primitive variables don't match." ) % sName % tName ) ); } FloatVectorDataPtr textureAreasData = new FloatVectorData; vector<float> &textureAreas = textureAreasData->writable(); textureAreas.reserve( mesh->variableSize( PrimitiveVariable::Uniform ) ); PolygonIterator faceEnd = mesh->faceEnd(); for( PolygonIterator pIt = mesh->faceBegin(); pIt!=faceEnd; pIt++ ) { if( sInterpolation==PrimitiveVariable::Vertex ) { typedef PolygonVertexIterator<vector<float>::const_iterator> VertexIterator; typedef boost::tuple<VertexIterator, VertexIterator> IteratorTuple; typedef boost::zip_iterator<IteratorTuple> ZipIterator; typedef boost::transform_iterator<STTupleToV3f, ZipIterator> STIterator; STIterator begin( ZipIterator( IteratorTuple( pIt.vertexBegin( s.begin() ), pIt.vertexBegin( t.begin() ) ) ) ); STIterator end( ZipIterator( IteratorTuple( pIt.vertexEnd( s.begin() ), pIt.vertexEnd( t.begin() ) ) ) ); textureAreas.push_back( polygonArea( begin, end ) ); } else { assert( sInterpolation==PrimitiveVariable::FaceVarying ); typedef boost::tuple<vector<float>::const_iterator, vector<float>::const_iterator> IteratorTuple; typedef boost::zip_iterator<IteratorTuple> ZipIterator; typedef boost::transform_iterator<STTupleToV3f, ZipIterator> STIterator; STIterator begin( ZipIterator( IteratorTuple( pIt.faceVaryingBegin( s.begin() ), pIt.faceVaryingBegin( t.begin() ) ) ) ); STIterator end( ZipIterator( IteratorTuple( pIt.faceVaryingEnd( s.begin() ), pIt.faceVaryingEnd( t.begin() ) ) ) ); textureAreas.push_back( polygonArea( begin, end ) ); } } mesh->variables[textureAreaPrimVarName] = PrimitiveVariable( PrimitiveVariable::Uniform, textureAreasData ); } }
PatchMeshPrimitivePtr CurveExtrudeOp::buildPatchMesh( const CurvesPrimitive * curves, unsigned curveIndex, unsigned vertexOffset, unsigned varyingOffset ) const { if ( curves->periodic() ) { throw InvalidArgumentException( "CurveExtrudeOp: Cannot convert periodic curves" ); } PrimitiveVariableMap::const_iterator it = curves->variables.find( "P" ); if ( it == curves->variables.end() ) { throw InvalidArgumentException( "CurveExtrudeOp: Input curve has no 'P' primvar" ); } ConstV3fVectorDataPtr pData = runTimeCast< const V3fVectorData >( it->second.data ); if ( !pData ) { throw InvalidArgumentException( "CurveExtrudeOp: Input curve has no 'P' primvar of type V3fVectorData" ); } float width = 1.0f; it = curves->variables.find( "constantwidth" ); if ( it != curves->variables.end() ) { ConstFloatDataPtr widthData = 0; if ( it->second.interpolation == PrimitiveVariable::Constant ) { widthData = runTimeCast< const FloatData >( it->second.data ); } if ( widthData ) { width = widthData->readable(); } else { msg( Msg::Warning, "CurveExtrudeOp", "Ignoring malformed primvar 'constantwidth'" ); } } ConstFloatVectorDataPtr varyingWidthData = 0; ConstFloatVectorDataPtr vertexWidthData = 0; it = curves->variables.find( "width" ); if ( it != curves->variables.end() ) { if ( it->second.interpolation == PrimitiveVariable::Varying ) { varyingWidthData = runTimeCast< const FloatVectorData >( it->second.data ); } else if ( it->second.interpolation == PrimitiveVariable::Vertex ) { vertexWidthData = runTimeCast< const FloatVectorData >( it->second.data ); } if ( !varyingWidthData && !vertexWidthData) { msg( Msg::Warning, "CurveExtrudeOp", "Ignoring malformed primvar 'width'" ); } } const V2i &resolution = m_resolutionParameter->getTypedValue(); const unsigned int vPoints = resolution.y; const unsigned int uPoints = resolution.x; PatchMeshPrimitivePtr patchMesh = new PatchMeshPrimitive( uPoints, vPoints + 2, // End points are duplicated CubicBasisf::catmullRom(), CubicBasisf::catmullRom(), true, false ); for ( PrimitiveVariableMap::const_iterator it = curves->variables.begin(); it != curves->variables.end(); ++it ) { if ( it->second.interpolation == PrimitiveVariable::FaceVarying || it->second.interpolation == PrimitiveVariable::Varying ) { VaryingFn varyingFn( it->first, curves, curveIndex, varyingOffset, resolution ); assert( it->second.data ); patchMesh->variables[ it->first ] = PrimitiveVariable( it->second.interpolation, despatchTypedData<VaryingFn, TypeTraits::IsStrictlyInterpolableVectorTypedData>( it->second.data, varyingFn ) ); } else if ( it->second.interpolation == PrimitiveVariable::Vertex ) { VertexFn vertexFn( it->first, curves, curveIndex, vertexOffset, resolution ); assert( it->second.data ); patchMesh->variables[ it->first ] = PrimitiveVariable( it->second.interpolation, despatchTypedData<VertexFn, TypeTraits::IsStrictlyInterpolableVectorTypedData>( it->second.data, vertexFn ) ); } else if ( it->second.interpolation == PrimitiveVariable::Constant ) { patchMesh->variables[ it->first ] = PrimitiveVariable( it->second.interpolation, it->second.data->copy() ); } else if ( it->second.interpolation == PrimitiveVariable::Uniform ) { UniformFn uniformFn( it->first, curves, curveIndex ); patchMesh->variables[ it->first ] = PrimitiveVariable( PrimitiveVariable::Constant, despatchTypedData<UniformFn, TypeTraits::IsVectorTypedData>( it->second.data, uniformFn ) ); } } if ( varyingWidthData ) { assert( !vertexWidthData ); PrimitiveVariableMap::const_iterator it = patchMesh->variables.find( "width" ); assert( it != patchMesh->variables.end() ); varyingWidthData = runTimeCast< const FloatVectorData >( it->second.data ); assert( varyingWidthData ); } else if ( vertexWidthData ) { PrimitiveVariableMap::const_iterator it = patchMesh->variables.find( "width" ); assert( it != patchMesh->variables.end() ); vertexWidthData = runTimeCast< const FloatVectorData >( it->second.data ); assert( vertexWidthData ); } const V3fVectorData::ValueType &p = pData->readable(); V3fVectorData::ValueType resampledPoints; resampledPoints.reserve( vPoints ); V3fVectorData::ValueType resampledTangents; resampledPoints.reserve( vPoints ); /// \todo Make adaptive for ( unsigned v = 0; v < vPoints; v ++) { size_t iSeg; float fSeg; /// Make sure we don't fall off the end of the curve if ( v == vPoints - 1 ) { iSeg = curves->numSegments( curveIndex ) - 1; fSeg = 1.0f - std::numeric_limits<float>::epsilon(); } else { float curveParam = float(v) / ( vPoints - 1 ); fSeg = curveParam * curves->numSegments( curveIndex ); iSeg = (size_t)floor( fSeg ); fSeg = fSeg - iSeg; } size_t segmentStart = iSeg; size_t i0 = std::min( segmentStart + 0, curves->variableSize( PrimitiveVariable::Vertex, curveIndex ) ); size_t i1 = std::min( segmentStart + 1, curves->variableSize( PrimitiveVariable::Vertex, curveIndex ) ); size_t i2 = std::min( segmentStart + 2, curves->variableSize( PrimitiveVariable::Vertex, curveIndex ) ); size_t i3 = std::min( segmentStart + 3, curves->variableSize( PrimitiveVariable::Vertex, curveIndex ) ); const Imath::V3f &p0 = p[ vertexOffset + i0 ]; const Imath::V3f &p1 = p[ vertexOffset + i1 ]; const Imath::V3f &p2 = p[ vertexOffset + i2 ]; const Imath::V3f &p3 = p[ vertexOffset + i3 ]; Imath::V3f pt = curves->basis()( fSeg, p0, p1, p2, p3 ); resampledPoints.push_back( pt ); resampledTangents.push_back( curves->basis().derivative( fSeg, p0, p1, p2, p3 ).normalized() ); } assert( resampledPoints.size() == vPoints ); assert( resampledTangents.size() == vPoints ); std::vector< M44f > frames; buildReferenceFrames( resampledPoints, resampledTangents, frames ); assert( frames.size() == vPoints ); std::vector< V3f > patchP; patchP.reserve( uPoints * ( vPoints + 2 ) ); for ( unsigned int v = 0; v < vPoints; v++ ) { if ( varyingWidthData ) { assert( !vertexWidthData ); assert( v * uPoints < varyingWidthData->readable().size() ); width = varyingWidthData->readable()[v * uPoints]; } else if ( vertexWidthData ) { assert( (v+1) * uPoints < vertexWidthData->readable().size() ); width = vertexWidthData->readable()[(v+1) * uPoints]; } const float radius = width / 2.0f; /// Double up end points const int num = v == 0 || v == vPoints - 1 ? 2 : 1; for ( int x = 0; x < num; x++) { for( unsigned int u = 0; u < uPoints; u++ ) { /// We're periodic in 'u', so no need to close the curve. /// Go from -PI to PI, in order to make the periodicity work, and to give the /// surface the correct orientation. float theta = -2.0 * M_PI * float(u) / float(uPoints) - M_PI; V3f circlePoint( 0.0, radius * cos( theta ), radius * sin( theta ) ); circlePoint = circlePoint * frames[v]; patchP.push_back( circlePoint ); } } } patchMesh->variables["P"] = PrimitiveVariable( PrimitiveVariable::Vertex, new V3fVectorData( patchP ) ); assert( patchMesh->arePrimitiveVariablesValid() ); return patchMesh; }