ObjectPtr FromHoudiniPolygonsConverter::doDetailConversion( const GU_Detail *geo, const CompoundObject *operands ) const
{
	const GA_PrimitiveList &primitives = geo->getPrimitiveList();

	MeshPrimitivePtr result = new MeshPrimitive();

	size_t numEdges = 0;
	std::vector<int> vertIds;
	std::vector<int> vertsPerFace;

	GA_Offset start, end;
	for( GA_Iterator it( geo->getPrimitiveRange() ); it.blockAdvance( start, end ); )
	{
		for( GA_Offset offset = start; offset < end; ++offset )
		{
			const GA_Primitive *prim = primitives.get( offset );
			if( prim->getTypeId() != GEO_PRIMPOLY )
			{
				throw std::runtime_error( "FromHoudiniPolygonsConverter: Geometry contains non-polygon primitives" );
			}

			size_t numPrimVerts = prim->getVertexCount();
			vertsPerFace.push_back( numPrimVerts );
			numEdges += numPrimVerts;
			std::vector<int> ids( numPrimVerts );
			for( size_t j = 0; j < numPrimVerts; j++ )
			{
				vertIds.push_back( geo->pointIndex( prim->getPointOffset( numPrimVerts - 1 - j ) ) );
			}
		}
	}

	result->setTopology( new IntVectorData( vertsPerFace ), new IntVectorData( vertIds ) );

	CompoundObjectPtr modifiedOperands = transferMeshInterpolation( geo, operands, result.get() );

	if( geo->getNumVertices() )
	{
		transferAttribs( geo, result.get(), modifiedOperands ? modifiedOperands.get() : operands );
	}

	// check for corners and creases, which would have been extracted via transferAttribs()
	// as they are no different to standard attribs in Houdini.
	convertCorners( result.get() );
	convertCreases( result.get(), vertIds, numEdges );

	return result;
}
PrimitivePtr FromHoudiniPolygonsConverter::doPrimitiveConversion( const GU_Detail *geo, const CompoundObject *operands ) const
{
	const GA_PrimitiveList &primitives = geo->getPrimitiveList();
	
	MeshPrimitivePtr result = new MeshPrimitive();
	
	GA_Iterator firstPrim = geo->getPrimitiveRange().begin();
	for ( GA_Iterator it=firstPrim; !it.atEnd(); ++it )
	{
		const GA_Primitive *prim = primitives.get( it.getOffset() );
		if ( prim->getTypeId() != GEO_PRIMPOLY )
		{
			throw std::runtime_error( "FromHoudiniPolygonsConverter: Geometry contains non-polygon primitives" );
		}
	}
	
	// loop over primitives gathering mesh data
	std::vector<int> vertIds;
	std::vector<int> vertsPerFace;
	for ( GA_Iterator it=firstPrim; !it.atEnd(); ++it )
	{
		const GA_Primitive *prim = primitives.get( it.getOffset() );
		size_t numPrimVerts = prim->getVertexCount();
		vertsPerFace.push_back( numPrimVerts );
		std::vector<int> ids( numPrimVerts );
		for ( size_t j=0; j < numPrimVerts; j++ )
		{
			vertIds.push_back( geo->pointIndex( prim->getPointOffset( numPrimVerts - 1 - j ) ) );
		}
	}
	
	result->setTopology( new IntVectorData( vertsPerFace ), new IntVectorData( vertIds ) );
	
	if ( geo->getNumVertices() )
	{
		transferAttribs( geo, result, operands );
	}
	
	return result;
}
Example #3
0
ObjectPtr BINMeshReader::doOperation( const CompoundObject *operands )
{
	const std::string &fileName = m_fileNameParameter->getTypedValue();
	ifstream f( fileName.c_str() );

	f.seekg( 0, ios_base::beg );
	uint32_t magic = 0;
	readLittleEndian( f, magic );

	uint32_t version = 0;
	readLittleEndian( f, version );
	if ( version <= 3 )
	{
		throw IOException(( boost::format( "BINMeshReader: '%s' is of an unsupported version" ) % fileName ).str() );
	}

	MeshPrimitivePtr mesh = new MeshPrimitive();

	uint32_t numVertices = 0;

	bool foundGeometryChunk = false;
	bool done = false;

	uint32_t chunkId = 0;

	while ( !done && !f.fail() )
	{
		readLittleEndian( f, chunkId );
		if ( f.fail() )
		{
			throw IOException(( boost::format( "BINMeshReader: Error encountered while reading '%s'" ) % fileName ).str() );
		}

		if ( chunkId == 0xDEDEDEDE ) /// EOF marker
		{
			if ( !foundGeometryChunk )
			{
				throw IOException(( boost::format( "BINMeshReader: No geometry chunk encountered while reading '%s'" ) % fileName ).str() );
			}
			done = true;
		}
		else if ( chunkId == 0xCCCCCCCC ) /// geometry chunk
		{
			if ( foundGeometryChunk )
			{
				throw IOException(( boost::format( "BINMeshReader: Duplicate geometry chunk encountered while reading '%s'" ) % fileName ).str() );
			}
			foundGeometryChunk = true;

			V3fVectorDataPtr pData = new V3fVectorData();

			readLittleEndian( f, numVertices );

			pData->writable().resize( numVertices );
			for ( uint32_t i = 0; i < numVertices; i ++ )
			{
				V3f p;
				readLittleEndian( f, p.x );
				readLittleEndian( f, p.y );
				readLittleEndian( f, p.z );

				pData->writable()[i] = p;
			}


			uint32_t numFaces = 0;
			readLittleEndian( f, numFaces );

			IntVectorDataPtr vertsPerFaceData = new IntVectorData();

			/// All faces are triangles
			vertsPerFaceData->writable().resize( numFaces, 3 );

			IntVectorDataPtr vertIdsData = new IntVectorData();
			vertIdsData->writable().reserve( numFaces * 3 );

			for ( uint32_t i = 0; i < numFaces; i ++ )
			{
				uint32_t v0 = 0, v1 = 0, v2 = 0;
				readLittleEndian( f, v0 );
				readLittleEndian( f, v1 );
				readLittleEndian( f, v2 );

				vertIdsData->writable().push_back( v0 );
				vertIdsData->writable().push_back( v1 );
				vertIdsData->writable().push_back( v2 );
			}

			mesh->variables[ "P" ] = PrimitiveVariable( PrimitiveVariable::Vertex, pData );
			mesh->setTopology( vertsPerFaceData, vertIdsData, "linear" );

		}
		else if ( chunkId == 0xCCCCCC00 ) /// texture chunk
		{
			if ( !foundGeometryChunk )
			{
				throw IOException(( boost::format( "BINMeshReader: No geometry chunk encountered while reading '%s'" ) % fileName ).str() );
			}

			uint32_t numFluids = 0;
			readLittleEndian( f, numFluids );

			V3fVectorDataPtr uvwData = new V3fVectorData();
			uvwData->writable().resize( numVertices );

			for ( uint32_t v = 0; v < numVertices; v ++ )
			{
				for ( uint32_t fl = 0; fl < numFluids - 1; fl ++ )
				{
					/// Just skip over there for now
					/// \todo Work out what to do with them
					float textureWeight = 0.0f;
					readLittleEndian( f, textureWeight );
				}

				V3f uvw;
				readLittleEndian( f, uvw.x );
				readLittleEndian( f, uvw.y );
				readLittleEndian( f, uvw.z );

				uvwData->writable()[v] = uvw;
			}

			mesh->variables[ "uvw" ] = PrimitiveVariable( PrimitiveVariable::Vertex, uvwData );
		}
		else if ( chunkId == 0xCCCCCC11 ) /// velocity chunk
		{
			if ( !foundGeometryChunk )
			{
				throw IOException(( boost::format( "BINMeshReader: No geometry chunk encountered while reading '%s'" ) % fileName ).str() );
			}

			V3fVectorDataPtr velocityData = new V3fVectorData();
			velocityData->writable().resize( numVertices );
			for ( uint32_t i = 0; i < numVertices; i ++ )
			{
				V3f vel;
				readLittleEndian( f, vel.x );
				readLittleEndian( f, vel.y );
				readLittleEndian( f, vel.z );

				velocityData->writable()[i] = vel;
			}
			mesh->variables[ "velocity" ] = PrimitiveVariable( PrimitiveVariable::Vertex, velocityData );
		}
		else
		{
			throw IOException(( boost::format( "BINMeshReader: Invalid chunk encountered while reading '%s'" ) % fileName ).str() );
		}
	}

	if ( chunkId != 0xDEDEDEDE )
	{
		throw IOException(( boost::format( "BINMeshReader: No end of file chunk encountered while reading '%s'" ) % fileName ).str() );
	}

	assert( mesh );
	return mesh;
}
Example #4
0
IECore::RunTimeTypedPtr ToGLMeshConverter::doConversion( IECore::ConstObjectPtr src, IECore::ConstCompoundObjectPtr operands ) const
{
	IECore::MeshPrimitivePtr mesh = IECore::staticPointerCast<IECore::MeshPrimitive>( src->copy() ); // safe because the parameter validated it for us
	
	if( !mesh->variableData<IECore::V3fVectorData>( "P", IECore::PrimitiveVariable::Vertex ) )
	{
		throw IECore::Exception( "Must specify primitive variable \"P\", of type V3fVectorData and interpolation type Vertex." );
	}

	if( mesh->variables.find( "N" )==mesh->variables.end() )
	{
		// 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();

	IECore::FaceVaryingPromotionOpPtr faceVaryingOp = new IECore::FaceVaryingPromotionOp;
	faceVaryingOp->inputParameter()->setValue( mesh );
	faceVaryingOp->copyParameter()->setTypedValue( false );
	faceVaryingOp->operate();

	MeshPrimitivePtr glMesh = new MeshPrimitive( mesh->vertexIds() );

	for ( IECore::PrimitiveVariableMap::iterator pIt = mesh->variables.begin(); pIt != mesh->variables.end(); ++pIt )
	{
		if ( pIt->second.data )
		{
			glMesh->addPrimitiveVariable( pIt->first, pIt->second );
		}
		else
		{
			IECore::msg( IECore::Msg::Warning, "ToGLMeshConverter", boost::format( "No data given for primvar \"%s\"" ) % pIt->first );
		}
	}

	IECore::PrimitiveVariableMap::const_iterator sIt = mesh->variables.find( "s" );
	IECore::PrimitiveVariableMap::const_iterator tIt = mesh->variables.find( "t" );
	if ( sIt != mesh->variables.end() && tIt != mesh->variables.end() )
	{
		if ( sIt->second.interpolation != IECore::PrimitiveVariable::Constant  
			&&  tIt->second.interpolation != IECore::PrimitiveVariable::Constant
			&& sIt->second.interpolation == tIt->second.interpolation )
		{
			IECore::ConstFloatVectorDataPtr s = IECore::runTimeCast< const IECore::FloatVectorData >( sIt->second.data );
			IECore::ConstFloatVectorDataPtr t = IECore::runTimeCast< const IECore::FloatVectorData >( tIt->second.data );

			if ( s && t )
			{
				/// Should hold true if primvarsAreValid
				assert( s->readable().size() == t->readable().size() );

				IECore::V2fVectorDataPtr stData = new IECore::V2fVectorData();
				stData->writable().resize( s->readable().size() );

				for ( unsigned i = 0; i < s->readable().size(); i++ )
				{
					stData->writable()[i] = Imath::V2f( s->readable()[i], t->readable()[i] );
				}
				glMesh->addPrimitiveVariable( "st", IECore::PrimitiveVariable( sIt->second.interpolation, stData ) );
			}
			else
			{
				IECore::msg( IECore::Msg::Warning, "ToGLMeshConverter", "If specified, primitive variables \"s\" and \"t\" must be of type FloatVectorData and interpolation type FaceVarying." );
			}
		}
		else
		{
			IECore::msg( IECore::Msg::Warning, "ToGLMeshConverter", "If specified, primitive variables \"s\" and \"t\" must be of type FloatVectorData and non-Constant interpolation type." );
		}
	}
	else if ( sIt != mesh->variables.end() || tIt != mesh->variables.end() )
	{
		IECore::msg( IECore::Msg::Warning, "ToGLMeshConverter", "Primitive variable \"s\" or \"t\" found, but not both." );
	}

	return glMesh;
}
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 IECoreArnold::RendererImplementation::mesh( IECore::ConstIntVectorDataPtr vertsPerFace, IECore::ConstIntVectorDataPtr vertIds, const std::string &interpolation, const IECore::PrimitiveVariableMap &primVars )
{
	MeshPrimitivePtr mesh = new IECore::MeshPrimitive( vertsPerFace, vertIds, interpolation );
	mesh->variables = primVars;
	addPrimitive( mesh.get(), "ai:polymesh:" );
}
Example #7
0
renderer::MeshObject *convert( const IECore::Object *primitive )
{
	assert( primitive->typeId() == IECore::MeshPrimitiveTypeId );
	const IECore::MeshPrimitive *mesh = static_cast<const IECore::MeshPrimitive *>( primitive );

	const V3fVectorData *p = mesh->variableData<V3fVectorData>( "P", PrimitiveVariable::Vertex );
	if( !p )
	{
		throw Exception( "MeshPrimitive does not have \"P\" primitive variable of interpolation type Vertex." );
	}

	asf::auto_release_ptr<asr::MeshObject> meshEntity = asr::MeshObjectFactory::create( "mesh", asr::ParamArray() );
	const size_t materialSlot = meshEntity->push_material_slot( "default" );

	// vertices
	{
		size_t numVertices = p->readable().size();
		meshEntity->reserve_vertices( numVertices );
		const std::vector<V3f> &points = p->readable();
		for( size_t i = 0; i < numVertices; ++i )
		{
			meshEntity->push_vertex( asr::GVector3( points[i].x, points[i].y, points[i].z ) );
		}
	}

	// triangulate primitive (this should be in appleseed at some point)
	MeshPrimitivePtr triangulatedMeshPrimPtr = mesh->copy();
	{
		TriangulateOpPtr op = new TriangulateOp();
		op->inputParameter()->setValue( triangulatedMeshPrimPtr );
		op->throwExceptionsParameter()->setTypedValue( false ); // it's better to see something than nothing
		op->copyParameter()->setTypedValue( false );
		op->operate();
	}

	// triangles
	size_t numTriangles = triangulatedMeshPrimPtr->numFaces();
	std::vector<asr::Triangle> triangles;
	triangles.reserve( numTriangles );
	const std::vector<int> &vidx = triangulatedMeshPrimPtr->vertexIds()->readable();
	for( size_t i = 0; i < vidx.size(); i += 3 )
	{
		triangles.push_back( asr::Triangle( vidx[i], vidx[i+1], vidx[i+2], materialSlot ) );
	}

	// texture coords
	{
		const FloatVectorData *s = triangulatedMeshPrimPtr->variableData<FloatVectorData>( "s" );
		const FloatVectorData *t = triangulatedMeshPrimPtr->variableData<FloatVectorData>( "t" );
		if( s && t )
		{
			PrimitiveVariable::Interpolation sInterpolation = triangulatedMeshPrimPtr->variables.find( "s" )->second.interpolation;
			PrimitiveVariable::Interpolation tInterpolation = triangulatedMeshPrimPtr->variables.find( "t" )->second.interpolation;
			if( sInterpolation == tInterpolation )
			{
				if( sInterpolation == PrimitiveVariable::Varying || sInterpolation == PrimitiveVariable::Vertex || sInterpolation == PrimitiveVariable::FaceVarying )
				{
					size_t numSTs = s->readable().size();
					meshEntity->reserve_tex_coords( numSTs );
					const std::vector<float> &svec = s->readable();
					const std::vector<float> &tvec = t->readable();

					for( size_t i = 0; i < numSTs; ++i)
					{
						meshEntity->push_tex_coords( asr::GVector2( svec[i], 1.0f - tvec[i] ) );
					}

					if( sInterpolation == PrimitiveVariable::FaceVarying )
					{
						for( size_t i = 0, j = 0; i < numTriangles; ++i)
						{
							asr::Triangle& tri = triangles[i];
							tri.m_a0 = j++;
							tri.m_a1 = j++;
							tri.m_a2 = j++;
						}
					}
					else
					{
						for( size_t i = 0; i < vidx.size(); i += 3)
						{
							asr::Triangle& tri = triangles[i / 3];
							tri.m_a0 = vidx[i];
							tri.m_a1 = vidx[i+1];
							tri.m_a2 = vidx[i+2];
						}
					}
				}
				else
				{
					msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", "Variables s and t have unsupported interpolation type - not generating uvs." );
				}
			}
			else
			{
				msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", "Variables s and t have different interpolation - not generating uvs." );
			}
		}
		else if( s || t )
		{
			msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", "Only one of s and t available - not generating uvs." );
		}
	}

	// normals
	{
		PrimitiveVariableMap::const_iterator nIt = triangulatedMeshPrimPtr->variables.find( "N" );
		if( nIt != triangulatedMeshPrimPtr->variables.end() )
		{
			const V3fVectorData *n = runTimeCast<const V3fVectorData>( nIt->second.data.get() );
			if( n )
			{
				PrimitiveVariable::Interpolation nInterpolation = nIt->second.interpolation;
				if( nInterpolation == PrimitiveVariable::Varying || nInterpolation == PrimitiveVariable::Vertex || nInterpolation == PrimitiveVariable::FaceVarying )
				{
					size_t numNormals = n->readable().size();
					meshEntity->reserve_vertex_normals( numNormals );
					const std::vector<V3f> &normals = n->readable();
					for( size_t i = 0; i < numNormals; ++i)
					{
						asr::GVector3 n( normals[i].x, normals[i].y, normals[i].z );
						meshEntity->push_vertex_normal( asf::normalize( n ) );
					}

					if( nInterpolation == PrimitiveVariable::FaceVarying )
					{
						for( size_t i = 0, j = 0; i < numTriangles; ++i)
						{
							asr::Triangle& tri = triangles[i];
							tri.m_n0 = j++;
							tri.m_n1 = j++;
							tri.m_n2 = j++;
						}
					}
					else
					{
						for( size_t i = 0; i < vidx.size(); i += 3)
						{
							asr::Triangle& tri = triangles[i / 3];
							tri.m_n0 = vidx[i];
							tri.m_n1 = vidx[i+1];
							tri.m_n2 = vidx[i+2];
						}
					}
				}
				else
				{
					msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", "Variable \"N\" has unsupported interpolation type - not generating normals." );
				}
			}
			else
			{
				msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", boost::format( "Variable \"N\" has unsupported type \"%s\" (expected V3fVectorData)." ) % nIt->second.data->typeName() );
			}
		}
	}

	// tangents
	{
		PrimitiveVariableMap::const_iterator tIt = triangulatedMeshPrimPtr->variables.find( "uTangent" );
		if( tIt != triangulatedMeshPrimPtr->variables.end() )
		{
			const V3fVectorData *t = runTimeCast<const V3fVectorData>( tIt->second.data.get() );
			if( t )
			{
				PrimitiveVariable::Interpolation tInterpolation = tIt->second.interpolation;
				if( tInterpolation == PrimitiveVariable::Varying || tInterpolation == PrimitiveVariable::Vertex )
				{
					size_t numTangents = t->readable().size();
					meshEntity->reserve_vertex_tangents( numTangents );
					const std::vector<V3f> &tangents = t->readable();
					for( size_t i = 0; i < numTangents; ++i)
					{
						asr::GVector3 t( tangents[i].x, tangents[i].y, tangents[i].z );
						meshEntity->push_vertex_tangent( asf::normalize( t ) );
					}
				}
				else
				{
					msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", "Variable \"uTangent\" has unsupported interpolation type - not generating tangents." );
				}
			}
			else
			{
				msg( Msg::Warning, "ToAppleseedMeshConverter::doConversion", boost::format( "Variable \"uTangent\" has unsupported type \"%s\" (expected V3fVectorData)." ) % tIt->second.data->typeName() );
			}
		}
	}

	// copy triangles to mesh entity
	{
		meshEntity->reserve_triangles( numTriangles );

		for( size_t i = 0; i < triangles.size(); ++i)
		{
			meshEntity->push_triangle( triangles[i] );
		}
	}

	return meshEntity.release();
}