Пример #1
0
void Renderer::Cull( XNA::Frustum* frustum, Transform* pTransform )
{
	if (m_Mesh)
	{
		XNA::OrientedBox objectBox;
		for( int i=0; i<m_Mesh->GetNumberOfSubmeshes(); i++ )
		{
			Submesh* submesh = m_Mesh->GetSubmesh( i );

			XMVECTOR extents = XMLoadFloat3( &submesh->GetGeometryChunk()->GetAABB()->Extents );
			XMVECTOR scale = XMLoadFloat3( &pTransform->GetScale().intoXMFLOAT3() );
			XMVECTOR offset = XMLoadFloat3( &submesh->GetGeometryChunk()->GetAABB()->Center )*scale;
			XMVECTOR position = XMVector3Rotate( offset, XMLoadFloat4(&pTransform->GetOrientation().intoXMFLOAT4()) );
			position += XMLoadFloat3( &pTransform->GetPosition().intoXMFLOAT3() );
			XMStoreFloat3( &objectBox.Center, position );
			XMStoreFloat3( &objectBox.Extents, extents*scale );
			objectBox.Orientation = pTransform->GetOrientation().intoXMFLOAT4();
	
			if( XNA::IntersectOrientedBoxFrustum( &objectBox, frustum ) > 0 )
				m_SubmeshRenderData[i].bVisible = true;
			else
				m_SubmeshRenderData[i].bVisible = false;
		}
	}
}
Пример #2
0
void Renderer::CullLight( SpotLight* light, Transform* pTransform )
{
	if (m_Mesh)
	{
		XNA::OrientedBox objectBox;
		XNA::Frustum lightFrustum = light->GetFrustum();
		for( int i=0; i<m_Mesh->GetNumberOfSubmeshes(); i++ )
		{
			if (m_SubmeshRenderData[i].bVisible)
			{
				Submesh* submesh = m_Mesh->GetSubmesh( i );

				XMVECTOR extents = XMLoadFloat3( &submesh->GetGeometryChunk()->GetAABB()->Extents );
				XMVECTOR scale = XMLoadFloat3( &pTransform->GetScale().intoXMFLOAT3() );
				XMVECTOR offset = XMLoadFloat3( &submesh->GetGeometryChunk()->GetAABB()->Center )*scale;
				XMVECTOR position = XMVector3Rotate( offset, XMLoadFloat4(&pTransform->GetOrientation().intoXMFLOAT4()) );
				position += XMLoadFloat3( &pTransform->GetPosition().intoXMFLOAT3() );
				XMStoreFloat3( &objectBox.Center, position );
				XMStoreFloat3( &objectBox.Extents, extents*scale );
				objectBox.Orientation = pTransform->GetOrientation().intoXMFLOAT4();

				if( XNA::IntersectOrientedBoxFrustum( &objectBox, &lightFrustum ) > 0 )
					m_SubmeshRenderData[i].AffectingSpotLights.push_back( light );
			}
		}
	}
}
Пример #3
0
Submesh*
CmodLoader::loadSubmesh()
{
    VertexSpec* vertexSpec = loadVertexSpec();
    if (!vertexSpec)
    {
        return NULL;
    }

    VertexArray* vertexArray = loadVertexArray(*vertexSpec);
    if (!vertexArray)
    {
        delete vertexSpec;
        return NULL;
    }

    Submesh* submesh = new Submesh(vertexArray);

    bool done = false;
    while (!done && !error())
    {
        quint16 token = 0;
        *m_inputStream >> token;

        if (token == CmodEndMesh)
        {
            done = true;
        }
        else
        {
            unsigned int materialIndex = 0;
            PrimitiveBatch* primitives = loadPrimitiveBatch(token, vertexArray->count(), &materialIndex);
            if (primitives)
            {
                submesh->addPrimitiveBatch(primitives, materialIndex);
            }
        }
    }

    if (error())
    {
        delete submesh;
        submesh = NULL;
    }

    return submesh;
}
Пример #4
0
	bool BinaryWriter< Submesh >::doWrite( Submesh const & obj )
	{
		bool result = true;

		if ( result )
		{
			VertexBuffer const & buffer = obj.getVertexBuffer();
			size_t size = buffer.getSize();
			uint32_t stride = buffer.getDeclaration().stride();
			auto count = uint32_t( size / stride );
			result = doWriteChunk( count, ChunkType::eSubmeshVertexCount, m_chunk );

			if ( result )
			{
				auto const * srcbuf = reinterpret_cast< InterleavedVertex const * >( buffer.getData() );
				std::vector< InterleavedVertexT< double > > dstbuf( count );
				doCopyVertices( count, srcbuf, dstbuf.data() );
				result = doWriteChunk( dstbuf, ChunkType::eSubmeshVertex, m_chunk );
			}
		}

		if ( result )
		{
			IndexBuffer const & buffer = obj.getIndexBuffer();
			uint32_t count = buffer.getSize() / 3;
			result = doWriteChunk( count, ChunkType::eSubmeshFaceCount, m_chunk );

			if ( result )
			{
				auto const * srcbuf = reinterpret_cast< FaceIndices const * >( buffer.getData() );
				result = doWriteChunk( srcbuf, buffer.getSize() / 3, ChunkType::eSubmeshFaces, m_chunk );
			}
		}

		if ( result )
		{
			auto it = obj.m_components.find( BonesComponent::Name );

			if ( it != obj.m_components.end() )
			{
				BinaryWriter< BonesComponent >{}.write( *std::static_pointer_cast< BonesComponent >( it->second ), m_chunk );
			}
		}

		return result;
	}
// Create buffers required for drawing the mesh on hardware. This must be called
// before rendering whenever the mesh has changed.
//
// return true if all hardware buffers could be created.
bool
MeshGeometry::realize() const
{
    // Mark hardware buffers as up-to-date. Even if vertex buffer allocation fails,
    // we don't want to keep retrying every time a frame is rendered.
    m_hwBuffersCurrent = true;

    // Only create vertex buffers for submeshes for which the size of the vertex
    // data exceeds the limit below.
    const unsigned int VertexBufferSizeThreshold = 4096;

    // Don't create anything if hardware/driver doesn't support vertex buffer objects,
    // but report success anyhow.
    if (!GLVertexBuffer::supported())
    {
        return true;
    }

    bool ok = true;
    freeSubmeshBuffers();

    for (vector< counted_ptr<Submesh> >::const_iterator iter = m_submeshes.begin();
         iter != m_submeshes.end(); ++iter)
    {
        Submesh* submesh = iter->ptr();
        GLVertexBuffer* vertexBuffer = NULL;

        unsigned int size = submesh->vertices()->stride() * submesh->vertices()->count();
        if (size > VertexBufferSizeThreshold)
        {
            vertexBuffer = new GLVertexBuffer(size, GL_STATIC_DRAW_ARB, submesh->vertices()->data());
            if (!vertexBuffer->isValid())
            {
                delete vertexBuffer;
                ok = false;
            }
        }

        // A null vertex pointer is legal and indicates that the vertex data is
        // stored in system memory instead of graphics memory.
        m_submeshBuffers.push_back(vertexBuffer);
    }

    return ok;
}
Пример #6
0
	void Geometry::setMaterial( Submesh & submesh
		, MaterialSPtr material
		, bool updateSubmesh )
	{
		MeshSPtr mesh = getMesh();

		if ( mesh )
		{
			auto it = std::find_if( mesh->begin()
				, mesh->end()
				, [&submesh]( SubmeshSPtr lookup )
				{
					return lookup.get() == &submesh;
				} );
			REQUIRE( it != mesh->end() );

			bool changed = false;
			MaterialSPtr oldMaterial;
			auto itSubMat = m_submeshesMaterials.find( &submesh );

			if ( itSubMat != m_submeshesMaterials.end() )
			{
				MaterialSPtr oldMaterial = itSubMat->second.lock();

				if ( oldMaterial != material )
				{
					itSubMat->second = material;
					changed = true;
				}
			}
			else if ( material )
			{
				m_submeshesMaterials.emplace( &submesh, material );
				changed = true;
			}

			if ( changed )
			{
				submesh.setMaterial( oldMaterial, material, updateSubmesh );

				if ( material->hasEnvironmentMapping() )
				{
					getScene()->createEnvironmentMap( *getParent() );
				}
			}
		}
		else
		{
			CASTOR_EXCEPTION( "No mesh" );
		}
	}
Пример #7
0
RenderingMesh* COLRenderWidget::createRenderingMesh(Mesh* mesh)
{
	Matrix4 fModelMat = Matrix4::Identity;

	MeshFrame* frame = mesh->getFrame();

	if (frame) {
		fModelMat = fModelMat * frame->getAbsoluteModelMatrix();
	}

	RenderingPrimitiveFormat rpf;

	switch (mesh->getVertexFormat()) {
	case VertexFormatPoints:
		rpf = RenderingPrimitivePoints;
		break;
	case VertexFormatLines:
		rpf = RenderingPrimitiveLines;
		break;
	case VertexFormatTriangles:
		rpf = RenderingPrimitiveTriangles;
		break;
	case VertexFormatTriangleStrips:
		rpf = RenderingPrimitiveTriangleStrip;
		break;
	}

	//uint32_t flags = RenderingMesh::EnableShaderPluginUniformBuffers | RenderingMesh::HasTransparency;
	uint32_t flags = RenderingMesh::EnableShaderPluginUniformBuffers;

	DefaultRenderingMesh* rm = new DefaultRenderingMesh (
			rpf,
			flags,
			mesh->getVertexCount(), 0, NULL, 0,
			mesh->getDataBuffer(), mesh->getIndexBuffer(),
			mesh->getVertexOffset(), mesh->getVertexStride(),
			mesh->getSubmeshIDOffset(), mesh->getSubmeshIDStride(),
			mesh->getNormalOffset(), mesh->getNormalStride(),
			mesh->getTexCoordOffset(), mesh->getTexCoordStride(),
			mesh->getVertexColorOffset(), mesh->getVertexColorStride(),
			-1, 0,
			-1, 0
			);

	rm->setModelMatrix(fModelMat);


	for (Mesh::SubmeshIterator sit = mesh->getSubmeshBegin() ; sit != mesh->getSubmeshEnd() ; sit++) {
		Submesh* submesh = *sit;

		Material* mat = submesh->getMaterial();

		GLuint oglTex = 0;

		uint8_t r = 255;
		uint8_t g = 255;
		uint8_t b = 255;
		uint8_t a = 255;

		if (mat) {
			mat->getColor(r, g, b, a);
		}

		RenderingSubmesh* sm = new RenderingSubmesh(rm, submesh->getIndexCount(), submesh->getIndexOffset(), oglTex);

		sm->setMaterialColor(r, g, b, a);
	}

	return rm;
}
Пример #8
0
/** Merge a list of submeshes to create a single submesh. All submeshes must share
  * the same vertex spec. There must be at least one submesh in the list to merge.
  *
  * \return the new submesh, or null if there was an error creating the submesh.
  */
Submesh*
Submesh::mergeSubmeshes(const std::vector<Submesh*>& submeshes)
{
    if (submeshes.empty())
    {
        return NULL;
    }

    const VertexSpec& vertexSpec = submeshes.front()->vertices()->vertexSpec();
    const unsigned int vertexStride = submeshes.front()->vertices()->stride();

    // Verify that the strides and vertex specs of all submeshes match
    unsigned int vertexCount = 0;
    for (vector<Submesh*>::const_iterator iter = submeshes.begin(); iter != submeshes.end(); ++iter)
    {
        Submesh* s = *iter;
        if (s->vertices()->vertexSpec() != vertexSpec || s->vertices()->stride() != vertexStride)
        {
            VESTA_WARNING("MergeSubmeshes attempted on incompatible submeshes.");
            return false;
        }

        vertexCount += s->vertices()->count();
    }

    // Create a new vertex array large enough to contain all of the submeshes
    unsigned int vertexDataSize = vertexCount * vertexStride;

    Submesh* submesh = NULL;
    char* vertexData = NULL;
    VertexArray* vertexArray = NULL;
    try
    {
        vertexData = new char[vertexDataSize];
        vertexArray = new VertexArray(vertexData, vertexCount, vertexSpec, vertexStride);
        submesh = new Submesh(vertexArray);
    }
    catch (bad_alloc&)
    {
        VESTA_WARNING("Out of memory during submesh merge.");
        if (vertexArray)
        {
            // Deleting the vertex array takes care of the vertexData too
            delete vertexArray;
        }
        else if (vertexData)
        {
            delete[] vertexData;
        }
        return NULL;
    }

    unsigned int vertexDataOffset = 0;

    // Copy vertices from the submeshes in the merge list to the new vertex array
    for (vector<Submesh*>::const_iterator iter = submeshes.begin(); iter != submeshes.end(); ++iter)
    {
        Submesh* s = *iter;
        unsigned int submeshVertexDataSize = vertexStride * s->vertices()->count();
        assert(vertexDataOffset + submeshVertexDataSize <= vertexDataSize);

        copy(reinterpret_cast<const char*>(s->vertices()->data()),
             reinterpret_cast<const char*>(s->vertices()->data()) + submeshVertexDataSize,
             vertexData + vertexDataOffset);
        vertexDataOffset += submeshVertexDataSize;
    }

    // Copy materials and primitive batches from submeshes in the merge list
    try
    {
        unsigned int vertexOffset = 0;
        for (vector<Submesh*>::const_iterator iter = submeshes.begin(); iter != submeshes.end() && submesh != NULL; ++iter)
        {
            Submesh* s = *iter;

            assert(s->materials().size() == s->primitiveBatches().size());
            for (unsigned int i = 0; i < s->primitiveBatchCount(); ++i)
            {
                const PrimitiveBatch* prims = s->primitiveBatches().at(i);
                PrimitiveBatch* newPrims = new PrimitiveBatch(*prims);
                if (vertexOffset != 0)
                {
                    if (!newPrims->offsetIndices(vertexOffset))
                    {
                        delete submesh;
                        submesh = NULL;
                        break;
                    }
                }

                submesh->addPrimitiveBatch(newPrims, s->materials().at(i));
            }

            vertexOffset += s->vertices()->count();
        }
    }
    catch (bad_alloc&)
    {
        VESTA_WARNING("Out of memory during submesh merge.");
        delete submesh;
        submesh = NULL;
    }

    return submesh;
}
Пример #9
0
void Mesh::link()
{
	if (isLinked)
		return;


	// Build the data buffer

	size_t numSubmeshes = submeshes.size();

	glBindBuffer(GL_ARRAY_BUFFER, dataBuffer);
	char* bufData = (char*) glMapBuffer(GL_ARRAY_BUFFER, GL_READ_ONLY);

	char* newData = new char[dataBufferSingleSize * numSubmeshes];

	for (uint8_t i = 0 ; i < numSubmeshes ; i++) {
		memcpy(newData + i*dataBufferSingleSize, bufData, dataBufferSingleSize);

		if (submeshIDOffs != -1) {
			for (size_t j = 0 ; j < vertexCount ; j++) {
				newData[i*dataBufferSingleSize + submeshIDOffs + j*submeshIDStride] = i;
			}
		}
	}

	glUnmapBuffer(GL_ARRAY_BUFFER);

	glBufferData(GL_ARRAY_BUFFER, dataBufferSingleSize * numSubmeshes, newData, GL_STATIC_DRAW);

	delete[] newData;


	// Build the index buffer

	if (indexBuffer == 0)
		glGenBuffers(1, &indexBuffer);

	bool hasCompiledSubmeshes = false;
	//GLuint bufSize = 0;
	size_t numIndices = 0;

	for (SubmeshIterator it = submeshes.begin() ; it != submeshes.end() ; it++) {
		Submesh* submesh = *it;

		if (submesh->isLinked())
			hasCompiledSubmeshes = true;

		numIndices += submesh->getIndexCount();
	}

	uint32_t* newIndices = new uint32_t[numIndices];

	glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, indexBuffer);

	uint32_t* oldIndices;

	if (hasCompiledSubmeshes)
		oldIndices = (uint32_t*) glMapBuffer(GL_ELEMENT_ARRAY_BUFFER, GL_READ_ONLY);

	size_t offset = 0;
	for (SubmeshIterator it = submeshes.begin() ; it != submeshes.end() ; it++) {
		Submesh* submesh = *it;

		int ic = submesh->getIndexCount();

		if (submesh->isLinked()) {
			memcpy(newIndices + offset, oldIndices + submesh->getIndexOffset(), ic*4);
		} else {
			memcpy(newIndices + offset, submesh->indices, ic*4);
		}

		submesh->setLinked(offset);

		offset += ic;
	}

	if (hasCompiledSubmeshes)
		glUnmapBuffer(GL_ELEMENT_ARRAY_BUFFER);

	glBufferData(GL_ELEMENT_ARRAY_BUFFER, numIndices*4, newIndices, GL_STATIC_DRAW);

	delete[] newIndices;

	isLinked = true;
}
Пример #10
0
		/**
		 *\~english
		 *\return		The submesh buffer matching given submesh.
		 *\~french
		 *\return		Le tampon de sous-maillage correspondant au sous-maillage donné.
		 */
		inline SubmeshAnimationBufferMap::const_iterator find( Submesh const & submesh )const
		{
			return m_submeshesBuffers.find( submesh.getId() );
		}
Пример #11
0
	bool BinaryParser< Submesh >::doParse( Submesh & obj )
	{
		bool result = true;
		String name;
		std::vector< FaceIndices > faces;
		std::vector< VertexBoneData > bones;
		std::vector< InterleavedVertexT< double > > srcbuf;
		uint32_t count{ 0u };
		uint32_t faceCount{ 0u };
		uint32_t boneCount{ 0u };
		BinaryChunk chunk;
		std::shared_ptr< BonesComponent > bonesComponent;

		while ( result && doGetSubChunk( chunk ) )
		{
			switch ( chunk.getChunkType() )
			{
			case ChunkType::eSubmeshVertexCount:
				result = doParseChunk( count, chunk );

				if ( result )
				{
					srcbuf.resize( count );
				}

				break;

			case ChunkType::eSubmeshVertex:
				result = doParseChunk( srcbuf, chunk );

				if ( result && !srcbuf.empty() )
				{
					std::vector< InterleavedVertex > dstbuf( srcbuf.size() );
					doCopyVertices( uint32_t( srcbuf.size() ), srcbuf.data(), dstbuf.data() );
					obj.addPoints( dstbuf );
				}

				break;

			case ChunkType::eSubmeshBoneCount:
				if ( !bonesComponent )
				{
					bonesComponent = std::make_shared< BonesComponent >( obj );
					obj.addComponent( bonesComponent );
				}

				result = doParseChunk( count, chunk );

				if ( result )
				{
					boneCount = count;
					bones.resize( count );
				}

				break;

			case ChunkType::eSubmeshBones:
				result = doParseChunk( bones, chunk );

				if ( result && boneCount > 0 )
				{
					bonesComponent->addBoneDatas( bones );
				}

				boneCount = 0u;
				break;

			case ChunkType::eBonesComponent:
				bonesComponent = std::make_shared< BonesComponent >( obj );
				result = BinaryParser< BonesComponent >{}.parse( *bonesComponent, chunk );

				if ( result )
				{
					obj.addComponent( bonesComponent );
				}

				break;

			case ChunkType::eSubmeshFaceCount:
				result = doParseChunk( count, chunk );

				if ( result )
				{
					faceCount = count;
					faces.resize( count );
				}

				break;

			case ChunkType::eSubmeshFaces:
				result = doParseChunk( faces, chunk );

				if ( result && faceCount > 0 )
				{
					auto indexMapping = std::make_shared< TriFaceMapping >( obj );
					indexMapping->addFaceGroup( faces );
					obj.setIndexMapping( indexMapping );
				}

				faceCount = 0u;
				break;

			default:
				result = false;
				break;
			}
		}

		return result;
	}
static MeshGeometry*
Convert3DSMesh(Lib3dsFile* meshfile, TextureMapLoader* textureLoader)
{
    MeshGeometry* meshGeometry = new MeshGeometry();

    for (int materialIndex = 0; materialIndex < meshfile->nmaterials; ++materialIndex)
    {
        Lib3dsMaterial* material = meshfile->materials[materialIndex];
        Material* vmaterial = new Material();

        // Convert a 3ds material to VESTA material
        vmaterial->setOpacity(1.0f - material->transparency);
        vmaterial->setDiffuse(Spectrum(material->diffuse));

        if (material->shininess != 0.0f)
        {
            vmaterial->setSpecular(Spectrum(material->specular));
            vmaterial->setPhongExponent(std::pow(2.0f, 1.0f + 10.0f * material->shininess));
        }

        if (material->self_illum_flag)
        {
            vmaterial->setEmission(vmaterial->diffuse() * material->self_illum);
        }

        const string baseTextureName(material->texture1_map.name);
        if (!baseTextureName.empty())
        {
            TextureProperties texProperties;
            if ((material->texture1_map.flags & LIB3DS_TEXTURE_NO_TILE) != 0)
            {
                texProperties.addressS = TextureProperties::Clamp;
                texProperties.addressT = TextureProperties::Clamp;
            }

            if (textureLoader)
            {
                TextureMap* baseTexture = textureLoader->loadTexture(baseTextureName, texProperties);
                vmaterial->setBaseTexture(baseTexture);
            }
        }

        meshGeometry->addMaterial(vmaterial);
    }

    for (int meshIndex = 0; meshIndex < meshfile->nmeshes; ++meshIndex)
    {
        Lib3dsMesh* mesh = meshfile->meshes[meshIndex];
        if (mesh->nfaces > 0)
        {
            bool hasTextureCoords = mesh->texcos != 0;

            // Generate normals for the mesh
            float* normals = new float[mesh->nfaces * 9];
            lib3ds_mesh_calculate_vertex_normals(mesh, (float(*)[3]) normals);

            VertexPool vertexPool;

            for (int faceIndex = 0; faceIndex < mesh->nfaces; ++faceIndex)
            {
                for (int i = 0; i < 3; i++)
                {
                    int vertexIndex = mesh->faces[faceIndex].index[i];
                    vertexPool.addVec3(mesh->vertices[vertexIndex]);
                    vertexPool.addVec3(&normals[(faceIndex * 3 + i) * 3]);

                    if (hasTextureCoords)
                    {
                        // Invert the v texture coordinate, since 3ds uses a texture
                        // coordinate system that is flipped with respect to OpenGL's
                        vertexPool.addVec2(mesh->texcos[vertexIndex][0], 1.0f - mesh->texcos[vertexIndex][1]);
                    }
                }
            }

            delete[] normals;

            const VertexSpec* vertexSpec = hasTextureCoords ? &VertexSpec::PositionNormalTex : &VertexSpec::PositionNormal;
            VertexArray* vertexArray = vertexPool.createVertexArray(mesh->nfaces * 3, *vertexSpec);
            PrimitiveBatch* batch = new PrimitiveBatch(PrimitiveBatch::Triangles, mesh->nfaces);

            // Get the material for the primitive batch
            // TODO: This assumes that a single material is applied to the whole mesh; however,
            // materials can be assigned per-face (but rarely are in most 3ds files.)
            unsigned int materialIndex = Submesh::DefaultMaterialIndex;
            if (mesh->nfaces > 0)
            {
                // Use the material of the first face
                materialIndex = mesh->faces[0].material;
            }

            Submesh* submesh = new Submesh(vertexArray);
            submesh->addPrimitiveBatch(batch, materialIndex);

            meshGeometry->addSubmesh(submesh);
        }
    }

    return meshGeometry;
}
/** Optimize the mesh by merging submeshes that share the same vertex spec.
  * This reduces the number of separate vertex buffers required.
  */
bool
MeshGeometry::mergeSubmeshes()
{
    if (m_submeshes.size() <= 1)
    {
        return true;
    }

    // At the beginning, all submeshes are unmerged, none are merged
    vector<counted_ptr<Submesh> > merged;
    vector<Submesh*> unmerged;
    for (vector<counted_ptr<Submesh> >::const_iterator iter = m_submeshes.begin(); iter != m_submeshes.end(); ++iter)
    {
        unmerged.push_back(iter->ptr());
    }

    // Iterate over submeshes repeatedly, removing groups that are merged.
    while (!unmerged.empty())
    {
        const VertexArray* vertexArray = unmerged.front()->vertices();

        vector<Submesh*> matches;
        vector<Submesh*> nonmatches;
        for (vector<Submesh*>::const_iterator iter = unmerged.begin(); iter != unmerged.end(); ++iter)
        {
            Submesh* s = *iter;
            if (vertexArray->stride() == s->vertices()->stride() &&
                vertexArray->vertexSpec() == s->vertices()->vertexSpec())
            {
                matches.push_back(s);
            }
            else
            {
                nonmatches.push_back(s);
            }
        }

        if (matches.size() == 1)
        {
            // Avoid the expense of merging when there's just a single
            // mesh.
            merged.push_back(counted_ptr<Submesh>(matches.front()));
        }
        else
        {
            Submesh* mergedSubmesh = Submesh::mergeSubmeshes(matches);
            if (!mergedSubmesh)
            {
                return false;
            }

            merged.push_back(counted_ptr<Submesh>(mergedSubmesh));
        }

        // Set submeshes to the list of unmerged submeshes
        unmerged = nonmatches;
    }

    VESTA_LOG("Merged %d submeshes into %d", m_submeshes.size(), merged.size());

    m_submeshes = merged;
    setMeshChanged();

    return true;
}