Question about 2d mesh

Problems building or running the engine, queries about how to use features etc.
Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Question about 2d mesh

Post by Niubbo »

Ogre Version: 1.11.6
Operating System: Win10
Render System: :?:

I'm studying the tutorials and manuals about the objects, materials, mesh and animations. My question if is possible, after creating a 2d model (with a graphical editor like blender or Maya) with different animations sequence, to export it as mesh for ogre3d so be be imported as entity and animation list attached (like the list attached to Sinbad example). Jus to be clear the type of 2d model animated I think is like this

Thanks

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

you can find the source blend file of Sinbad in Sinbad.zip and use the blender2ogre exporter to re-create the Ogre mesh and skeleton files.

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

Thanks Paroj, but Sinbad is a 3d model, my question is: if I create a 2d model in Blender with a list of animations and I import it in Ogre as mesh file, I can move and animate it in the scene like it was a 3d model, the getAllAnimationStates function will returns in ogre the list of the animations I created in blender?

If this is possible it should be better than use this at place Billboard and sprites sheets in order to animate sprites in 3d scene, i think?

Davide

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

If I understand correctly, you are asking if it is possible to animate, export and import the mesh and animations into Ogre, like the video handles it.

If that is correct, you can just treat it like any other 3D model, even if the character is flat you can still animate it as any other mesh.
Just be sure to see that it is not culled by back-face culling.

If you want a high number of these kind of characters, they might drag down the performance because normal skeleton animation is pretty slow.
I would suggest you to optimize them if you find the performance bad, either by writing a hardware animation vertex shader or by using an instancing technique which does this for you and instancing at the same time (https://ogrecave.github.io/ogre-next/ap ... ncing.html and check the SampleBrowser).

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

Thanks Rpg, you understood correctly my question; thanks for the suggestion about the instancing. Do you think using billboards and sprites sheet coordinates, can be a better solution (also in relation to performance) for sprites animations, in the case of many sprites?

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

Yes, for sure.

If you have a system where you manually do the sprites (using ManualObject for example), you could batch every character (or everything) in the game together in one single batch using triangles or quads.

To do this you need to create an atlas texture containing every character texture (or every texture) in the game, with UV coordinates so you can set them correctly in the ManualObject. Keep in mind that you can only then have one single material with it as well, but if that is a problem you could create special ManualObjects for other materials (as each ManualObject can contain an arbitrary amount of polygons but only with one material).

But, even with this it could be too slow. I rendered a lot of grass using a ManualObject in my game, but it proved to be very slow.
You might have to actually create it by hand using mesh data.
I actually made my own class for this which was around 3-4 times faster (IIRC) than the built-in ManualObject, which made grass rendering in my game and everything else be extremely fast. I no longer use the built-in ManualObject for anything.

If you decide to go this route and succeed to do it using ManualObjects, I could provide you my own code if you want. It looks almost the same as the syntax for a normal ManualObject, only a different class.
It does not require anything in the source to be changed.
Though I do not have that code here right now and I would have to alter it so you could use it which would take a while (many special functions that I would need to replace which are only in my application).

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

After thinking about this some more, if you really do want animations like in the video, I would suggest your/my first post (the vertex animation technique) for ultimate control.
You could probably still have 200+ characters on the screen with 60+ FPS anyway, since there are just a few joints and a very low vertex count.

With this quote in mind: "Premature optimization is the root of all evil":
You could start by not having any hardware animation on them.
If the FPS is high, perfect.
If it is low, implement it in a shader or by using an instancing technique in the link in my first post that supports hardware animation.

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

Thanks Rpg
To do this you need to create an atlas texture containing every character texture (or every texture) in the game, with UV coordinates so you can set them correctly in the ManualObject. Keep in mind that you can only then have one single material with it as well, but if that is a problem you could create special ManualObjects for other materials (as each ManualObject can contain an arbitrary amount of polygons but only with one material).
I saw how to to use the UV coordinates in spritesheet in Billboard Set for change dynamically the texture in a billboard (with TextureCoords) and I saw also how to create a ManualObject starting from a material and polygon for fixed sprites, but is it possible change dynamically the UV texture of a manuaobject too?

Billboardsets do what I want try in the easy way but they seems not very good for manage sprites which have to interacts (like human figures). Thanks.

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

Yes, ManualObjects supports UV per vertex as well.

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

thanks and the UV coordinate for vertex can be changed dinamically every frame so the give the animation effect on the surface? I'll try this way after making some experience with materials and billboards, eventually when i'll more competence I'll be very happy to see how you improve the performance with your class. Thanks you again.

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

Exactly, I do this for all my grass in my game, every frame. It is called on-the-fly rendering if I remember correctly.
Every update of the manual object (in my case each frame) you need to supply everything again: position, normal, uv, color and tangent (for example).
It is the same with the built-in ManualObject as with my class.

Why I do this every frame in my game with the grass is because it waves in the wind as well as being bent by abilities (like a fireball) and such going over it. It could not be done using the GPU since I would still need to supply it to the GPU the same way, every frame (unless I make it very advanced using different shapes and strengths supplied to the shader, which probably would not handle all my special cases anyway).

But for background objects and such, you could have a manual object that only updates once and then it is done, since it does not need to update.

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

ill' try to experiment with this then, thanks you again.

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

I found some time to alter the class for anyone to use.

Big changes compared to the built-in version (ManualObject):
* Only the vertex function for position checks if the data needs to resize the vertex data, the built-in version checks it on all the per-vertex functions. This saves on an if-case for all other functions: uv, normal, tangent and colour.
* All those functions also checks if it needs to create the VertexElementSemantic for it, while mine requires it on creation of the class. This also saves on an if-case for all per-vertex functions.
* There are also other arbitrary if-cases on all per-vertex functions in the built-in version, which mine does not require.
* The intense functions are inline, most of those are not on the built-in version.

In my opinion, this class I made (or a version of it) should be added into the Ogre source. The normal ManualObject is just too slow.

Before making my own version of ManualObject, I considered to not use grass in my game. After I made my own, I pushed out even more grass into the game. The update code is so fast that I barely see an impact when it stops updating, the rendering of the grass is what takes the most performance (which you can not get around of course).

First, a small introduction on how to use it:

CREATION:

Code: Select all

// Create the manual object
m_manualObject = new CManual_Object();

// Add all vertex element semantics you are going to need (always supply position first though!)
m_manualObject->AddVertexElementSemantic(VertexElementSemantic::VES_POSITION);
m_manualObject->AddVertexElementSemantic(VertexElementSemantic::VES_NORMAL);
m_manualObject->AddVertexElementSemantic(VertexElementSemantic::VES_TANGENT);
m_manualObject->AddVertexElementSemantic(VertexElementSemantic::VES_TEXTURE_COORDINATES);
m_manualObject->AddVertexElementSemantic(VertexElementSemantic::VES_DIFFUSE);

// Initialize the manual object
m_manualObject->Initialize(); // You can initialize it with "true" if you want it to create an entity for you

// Create the entity
m_ent = app->m_SceneManager->createEntity(m_manualObject->m_mesh->getName());

// Create the scene node
m_node = app->m_SceneManager->getRootSceneNode()->createChildSceneNode();

// Attach the entity to the scene node
m_node->attachObject(m_ent);

DESTRUCTION:

Code: Select all

// Delete the scene node and entity here first

// Delete the manual object
delete m_manualObject;

UPDATE:

Code: Select all

// Start updating the manual object (this cleans everything it had before)
m_manualObject->begin(tmpMaterialName.m_string, RenderOperation::OT_TRIANGLE_LIST);

// Update the data of the manual object
some for-loop to create some triangles
{
	m_manualObject->position(x); // You do not need all these, just mirror how you created it while initializing the class
	m_manualObject->normal(x);
	m_manualObject->tangent(x);
	m_manualObject->textureCoord(x);
	m_manualObject->colour(x);
}

// End the updating of the manual object
m_manualObject->end();


And here are the source files, though some places you need to replace "app->m_sceneManager" with your own scene manager and "CList" to your list class (it is basically an std::vector):

CManual_Object.cpp:

Code: Select all

#include "StdAfx.h"
using namespace Ogre;



// Functions that I used from outside classes merged into this class:
void Error(CString message)
{
	MessageBox(NULL, (LPCTSTR)message.ToCharArray(), (LPCTSTR)message.ToCharArray(), MB_OK);
	exit(0);
}
static std::string GenerateUniqueName()
{
	static int uniqueIDCreator = 0;
	return "Unique" + std::to_string(uniqueIDCreator++);
}
void Destroy(Entity** resource)
{
	app->m_SceneManager->destroyEntity((*resource));
	(*resource) = NULL;
}
void Destroy(MeshPtr& resource)
{
	String tmpStr = resource->getName();
	MeshManager::getSingleton().unload(tmpStr);
	MeshManager::getSingleton().remove(tmpStr);
	resource.reset();

	if (MeshManager::getSingleton().resourceExists(tmpStr))
		Error("Destroy, The removal of a resource failed, the resource is still in use somewhere else.");
}



// Materials that helped make this class:
// https://ogrecave.github.io/ogre/api/1.11/manual-mesh-creation.html
// https://forums.ogre3d.org/viewtopic.php?t=78388
// https://stackoverflow.com/questions/11171859/creating-manual-mesh-in-ogre3d

// Setup static variables
char* CManual_Object::m_vertexData = NULL;
size_t CManual_Object::m_vertexDataSize = 0;
char* CManual_Object::m_indexData = NULL;
size_t CManual_Object::m_indexDataSize = 0;
int CManual_Object::m_numberOfInstances = 0;

// Constructor for the CManual_Object
CManual_Object::CManual_Object()
{
	// Null necessary pointers
	m_ent = NULL;

	// Set that we should use infinite bounds as default
	m_useInfiniteBounds = true;

	// Set that we should not use indices
	m_useIndices = false;

	// Set that we should not build tangent vectors
	m_buildTangentVectors = false;

	// Add one to the number of instances
	m_numberOfInstances++;

	// Get the render system
	m_renderSystem = Root::getSingleton().getRenderSystem();

	// Reset the number of vertices
	m_numberOfVertices = 0;

	// Reset the number of indices
	m_numberOfIndices = 0;

	// Create the vertex data
	if (!m_vertexData)
	{
		m_vertexDataSize = 2 * 1024 * 1024; // 2 MB
		m_vertexData = new char[m_vertexDataSize];
	}
	m_vertexDataCurrentIndex = 0;

	// Create the index data
	if (!m_indexData)
	{
		m_indexDataSize = 1 * 1024 * 1024; // 1 MB = 87 381 triangles
		m_indexData = new char[m_indexDataSize];
	}
	m_indexDataCurrentIndex = 0;

	// Check for errors
	if (m_renderSystem == NULL)
		Error("CManual_Object, m_renderSystem is NULL");
}

// Destructor for the CManual_Object
CManual_Object::~CManual_Object()
{
	// Clear the list of entities
	m_entities.Clear();

	// Check if we created the entity
	if (m_createdEntity)
		// Destroy the entity
		Destroy(&m_ent);

	// Destroy the mesh
	Destroy(m_mesh);

	// Clear the list of vertex element semantics
	m_vertexElementSemantics.Clear();

	// Remove one from the number of instances
	m_numberOfInstances--;

	// Check if we were the last instance
	if (m_numberOfInstances == 0)
	{
		// Destroy the vertex data
		delete[] m_vertexData;
		m_vertexData = NULL;

		// Destroy the index data
		delete[] m_indexData;
		m_indexData = NULL;
	}
}

void CManual_Object::AddVertexElementSemantic(VertexElementSemantic vertexElementSemantic)
{
	m_vertexElementSemantics.Add(vertexElementSemantic);
}

// Initializes the CManual_Object
void CManual_Object::Initialize(bool createEntity)
{
	// Create the mesh
	m_mesh = MeshManager::getSingleton().createManual("CManual_Object" + GenerateUniqueName(), ResourceGroupManager::DEFAULT_RESOURCE_GROUP_NAME);

	// Create a submesh
	SubMesh* tmpSubMesh = m_mesh->createSubMesh();
	tmpSubMesh->useSharedVertices = false;
	tmpSubMesh->setMaterialName("white");

	// Create the vertex data
	tmpSubMesh->vertexData = new VertexData();
	tmpSubMesh->vertexData->vertexCount = 0;
	VertexDeclaration* tmpVertexDeclaration = tmpSubMesh->vertexData->vertexDeclaration;
	m_vertexBufferNumberOfVertices = 0;

	// Add the vertex element semantics and calculate the vertex size
	m_vertexSize = 0;
	for (int i = 0; i < m_vertexElementSemantics.Size(); i++)
	{
		VertexElementSemantic& tmpVertexElementSemantic = m_vertexElementSemantics[i];
		if (tmpVertexElementSemantic == VES_POSITION)
		{
			tmpVertexDeclaration->addElement(0, m_vertexSize, VET_FLOAT3, tmpVertexElementSemantic);
			m_vertexSize += VertexElement::getTypeSize(VET_FLOAT3);
		}
		else if (tmpVertexElementSemantic == VES_NORMAL)
		{
			tmpVertexDeclaration->addElement(0, m_vertexSize, VET_FLOAT3, tmpVertexElementSemantic);
			m_vertexSize += VertexElement::getTypeSize(VET_FLOAT3);
		}
		else if (tmpVertexElementSemantic == VES_TEXTURE_COORDINATES)
		{
			tmpVertexDeclaration->addElement(0, m_vertexSize, VET_FLOAT2, tmpVertexElementSemantic, 0);
			m_vertexSize += VertexElement::getTypeSize(VET_FLOAT2);
		}
		else if (tmpVertexElementSemantic == VES_TANGENT)
		{
			tmpVertexDeclaration->addElement(0, m_vertexSize, VET_FLOAT3, tmpVertexElementSemantic, 0);
			m_vertexSize += VertexElement::getTypeSize(VET_FLOAT3);
		}
		else if (tmpVertexElementSemantic == VES_DIFFUSE)
		{
			tmpVertexDeclaration->addElement(0, m_vertexSize, VET_COLOUR, tmpVertexElementSemantic, 0);
			m_vertexSize += VertexElement::getTypeSize(VET_COLOUR);
		}
		else
			Error("CManual_Object, invalid VertexElementSemantic: " + std::to_string(tmpVertexElementSemantic));
	}

	// Check for errors
	if (m_vertexElementSemantics.Size() == 0 || m_vertexSize == 0)
		Error("CManual_Object, m_vertexElementSemantics is empty");

	// Check if we should use indices
	if (m_useIndices)
	{
		// Create the index data
		tmpSubMesh->indexData = new IndexData();
		tmpSubMesh->indexData->indexCount = 0;
		m_indexBufferNumberOfIndices = 0;
	}

	// Setup the mesh with correct bounds at startup (otherwise it can in certain situations think to never update it on creation)
	m_mesh->_setBounds(AxisAlignedBox::BOX_INFINITE);
	m_mesh->_setBoundingSphereRadius(CDefine::INVALID_POSITION.x);

	// Check if we should create the entity
	m_createdEntity = createEntity;
	if (m_createdEntity)
	{
		// Create the entity
		m_ent = app->m_SceneManager->createEntity(m_mesh->getName());
		m_ent->setVisibilityFlags(CDefine::VISIBILITY_FLAG_NORMAL);
		m_entities.Add(m_ent);
	}
}

void CManual_Object::begin(CString materialName, RenderOperation::OperationType operationType)
{
	// Reset the bounds
	m_aabb.setNull();
	m_radius = 0.00001f;

	// Set the material name
	m_materialName = materialName;

	// Set the operation type
	m_operationType = operationType;

	// Reset the number of vertices
	m_numberOfVertices = 0;

	// Reset the number of indices
	m_numberOfIndices = 0;

	// Reset vertex data current index
	m_vertexDataCurrentIndex = 0;

	// Reset index data current index
	m_indexDataCurrentIndex = 0;
}

void CManual_Object::end()
{
	// Set the new number of vertices
	SubMesh* tmpSubMesh = m_mesh->getSubMesh(0);
	tmpSubMesh->vertexData->vertexCount = m_numberOfVertices;

	// Check if we do not have enough vertices in our buffer
	size_t tmpBytesToWrite = m_numberOfVertices * m_vertexSize;
	VertexBufferBinding* tmpVertexBufferBinding = tmpSubMesh->vertexData->vertexBufferBinding;
	if (m_numberOfVertices > m_vertexBufferNumberOfVertices)
	{
		// Resize our vertex buffer
		m_vertexBufferNumberOfVertices = size_t(float(m_numberOfVertices) * 1.2f);

		// Create/recreate the vertex buffer (binding it to a new one destroys the last one)
		HardwareVertexBufferSharedPtr tmpVertexBuffer = HardwareBufferManager::getSingleton().createVertexBuffer(m_vertexSize,
			m_vertexBufferNumberOfVertices, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
		tmpVertexBuffer->writeData(0, tmpBytesToWrite, m_vertexData, true);
		tmpVertexBufferBinding->setBinding(0, tmpVertexBuffer);

		//Beep(1000, 100);
	}
	else
	{
		// Write the vertex buffer
		if (m_numberOfVertices > 0)
		{
			HardwareVertexBufferSharedPtr tmpVertexBuffer = tmpVertexBufferBinding->getBindings().begin()->second;
			tmpVertexBuffer->writeData(0, tmpBytesToWrite, m_vertexData, true);
		}
	}

	// Check if we should use indices
	if (m_useIndices)
	{
		// Set the new number of indices
		tmpSubMesh->indexData->indexCount = m_numberOfIndices;

		// Check if we do not have enough indices in our buffer
		tmpBytesToWrite = m_numberOfIndices * sizeof(Ogre::uint32);
		if (m_numberOfIndices > m_indexBufferNumberOfIndices)
		{
			// Resize our index buffer
			m_indexBufferNumberOfIndices = size_t(float(m_numberOfIndices) * 1.2f);

			// Create/recreate the index buffer (binding it to a new one destroys the last one)
			HardwareIndexBufferSharedPtr tmpIndexBuffer = HardwareBufferManager::getSingleton().createIndexBuffer(HardwareIndexBuffer::IT_32BIT,
				m_indexBufferNumberOfIndices, HardwareBuffer::HBU_STATIC_WRITE_ONLY);
			tmpIndexBuffer->writeData(0, tmpBytesToWrite, m_indexData, true);
			tmpSubMesh->indexData->indexBuffer = tmpIndexBuffer;

			//Beep(1000, 100);
		}
		else
		{
			// Write the index buffer
			if (m_numberOfIndices > 0)
			{
				HardwareIndexBufferSharedPtr tmpIndexBuffer = tmpSubMesh->indexData->indexBuffer;
				tmpIndexBuffer->writeData(0, tmpBytesToWrite, m_indexData, true);
			}
		}
	}

	// Check if we should build tangent vectors
	if (m_buildTangentVectors)
		// Build tangent vectors
		BuildTangentVectors();

	// Set the render operation to the submesh
	tmpSubMesh->operationType = m_operationType;

	// Set the material name
	tmpSubMesh->setMaterialName(m_materialName.m_string);
	for(int i = 0; i < m_entities.Size(); i++)
		m_entities[i]->setMaterialName(m_materialName.m_string);

	// Set the bounds of the mesh
	if (m_useInfiniteBounds)
	{
		m_mesh->_setBounds(AxisAlignedBox::BOX_INFINITE);
		m_mesh->_setBoundingSphereRadius(CDefine::INVALID_POSITION.x);
	}
	else
	{
		m_radius = sqrt(m_radius);
		if (m_radius <= 0.0f)
			m_radius = 0.000001f;
		if (m_aabb.isNull())
			m_aabb.merge(CDefine::INVALID_POSITION);
		m_mesh->_setBounds(m_aabb);
		m_mesh->_setBoundingSphereRadius(m_radius);
	}
}
































































































































class CTangentSpaceCalc
{
public:
	CTangentSpaceCalc();

	typedef std::pair<size_t, size_t> VertexSplit;

	/// Information about a remapped index
	struct IndexRemap
	{
		/// Index data set (can be >0 if more than one index data was added)
		size_t indexSet;
		/// The position in the index buffer that's affected
		size_t faceIndex;
		/// The old and new vertex index
		VertexSplit splitVertex;

		IndexRemap() {} // to keep container happy
		IndexRemap(size_t i, size_t f, const VertexSplit& s) : indexSet(i), faceIndex(f), splitVertex(s) {}
	};
	/** List of indexes that were remapped (split vertices).
	*/
	typedef std::list<IndexRemap> IndexRemapList;

	typedef std::list<VertexSplit> VertexSplits;

	/// The result of having built a tangent space basis
	struct Result
	{
		/** A list of vertex indices which were split off into new vertices
		because of mirroring. First item in each pair is the source vertex
		index, the second value is the split vertex index.
		*/
		VertexSplits vertexSplits;
		/** A list of indexes which were affected by splits. You can use this if you have other
		triangle-based data which you will need to alter to match. */
		IndexRemapList indexesRemapped;
	};

	/// Reset the calculation object
	void clear();

	/** Set the incoming vertex data (which will be modified) */
	void setVertexData(VertexData* v_in);

	/** Add a set of index data that references the vertex data.
	This might be modified if there are vertex splits.
	*/
	void addIndexData(IndexData* i_in, RenderOperation::OperationType opType = RenderOperation::OT_TRIANGLE_LIST);

	/** Sets whether to store tangent space parity in the W of a 4-component tangent or not.
	@remarks
	The default element format to use is VET_FLOAT3 which is enough to accurately
	deal with tangents that do not involve any texture coordinate mirroring.
	If you wish to allow UV mirroring in your model, you must enable 4-component
	tangents using this method, and the 'w' co-ordinate will be populated
	with the parity of the triangle (+1 or -1), which will allow you to generate
	the bitangent properly.
	@param enabled true to enable 4-component tangents (default false). If you enable
	this, you will probably also want to enable mirror splitting (see setSplitMirrored),
	and your shader must understand how to deal with the parity.
	*/
	void setStoreParityInW(bool enabled) { mStoreParityInW = enabled; }

	/**  Gets whether to store tangent space parity in the W of a 4-component tangent or not. */
	bool getStoreParityInW() const { return mStoreParityInW; }

	/** Sets whether or not to split vertices when a mirrored tangent space
	transition is detected (matrix parity differs).
	@remarks
	This defaults to 'off' because it's the safest option; tangents will be
	interpolated in all cases even if they don't agree around a vertex, so
	artefacts will be smoothed out. When you're using art assets of
	unknown quality this can avoid extra seams on the visible surface.
	However, if your artists are good, they will be hiding texture seams
	in folds of the model and thus you can turn this option on, which will
	prevent the results of those seams from getting smoothed into other
	areas, which is exactly what you want.
	@note This option is automatically disabled if you provide any strip or
	fan based geometry.
	*/
	void setSplitMirrored(bool split) { mSplitMirrored = split; }

	/** Gets whether or not to split vertices when a mirrored tangent space
	transition is detected.
	*/
	bool getSplitMirrored() const { return mSplitMirrored; }

	/** Sets whether or not to split vertices when tangent space rotates
	more than 90 degrees around a vertex.
	@remarks
	This defaults to 'off' because it's the safest option; tangents will be
	interpolated in all cases even if they don't agree around a vertex, so
	artefacts will be smoothed out. When you're using art assets of
	unknown quality this can avoid extra seams on the visible surface.
	However, if your artists are good, they will be hiding texture inconsistencies
	in folds of the model and thus you can turn this option on, which will
	prevent the results of those seams from getting smoothed into other
	areas, which is exactly what you want.
	@note This option is automatically disabled if you provide any strip or
	fan based geometry.
	*/
	void setSplitRotated(bool split) { mSplitRotated = split; }
	/** Sets whether or not to split vertices when tangent space rotates
	more than 90 degrees around a vertex.
	*/
	bool getSplitRotated() const { return mSplitRotated; }

	/** Build a tangent space basis from the provided data.
	@remarks
	Only indexed triangle lists are allowed. Strips and fans cannot be
	supported because it may be necessary to split the geometry up to
	respect deviances in the tangent space basis better.
	@param targetSemantic The semantic to store the tangents in. Defaults to
	the explicit tangent binding, but note that this is only usable on more
	modern hardware (Shader Model 2), so if you need portability with older
	cards you should change this to a texture coordinate binding instead.
	@param sourceTexCoordSet The texture coordinate index which should be used as the source
	of 2D texture coordinates, with which to calculate the tangents.
	@param index The element index, ie the texture coordinate set which should be used to store the 3D
	coordinates representing a tangent vector per vertex, if targetSemantic is
	VES_TEXTURE_COORDINATES. If this already exists, it will be overwritten.
	@return
	A structure containing the results of the tangent space build. Vertex data
	will always be modified but it's also possible that the index data
	could be adjusted. This happens when mirroring is used on a mesh, which
	causes the tangent space to be inverted on opposite sides of an edge.
	This is discontinuous, therefore the vertices have to be split along
	this edge, resulting in new vertices.
	*/
	Result build(VertexElementSemantic targetSemantic = VES_TANGENT,
		unsigned short sourceTexCoordSet = 0, unsigned short index = 1);


protected:

	VertexData * mVData;
	typedef std::vector<IndexData*> IndexDataList;
	typedef std::vector<RenderOperation::OperationType> OpTypeList;
	IndexDataList mIDataList;
	OpTypeList mOpTypes;
	bool mSplitMirrored;
	bool mSplitRotated;
	bool mStoreParityInW;


	struct VertexInfo
	{
		Vector3 pos;
		Vector3 norm;
		Vector2 uv;
		Vector3 tangent;
		Vector3 binormal;
		// Which way the tangent space is oriented (+1 / -1) (set on first time found)
		int parity;
		// What index the opposite parity vertex copy is at (0 if not created yet)
		size_t oppositeParityIndex;

		VertexInfo() : tangent(Vector3::ZERO), binormal(Vector3::ZERO),
			parity(0), oppositeParityIndex(0) {}
	};
	typedef std::vector<VertexInfo> VertexInfoArray;
	VertexInfoArray mVertexArray;

	void extendBuffers(VertexSplits& splits);
	void insertTangents(Result& res,
		VertexElementSemantic targetSemantic,
		unsigned short sourceTexCoordSet, unsigned short index);

	void populateVertexArray(unsigned short sourceTexCoordSet);
	void processFaces(Result& result);
	/// Calculate face tangent space, U and V are weighted by UV area, N is normalised
	void calculateFaceTangentSpace(const size_t* vertInd, Vector3& tsU, Vector3& tsV, Vector3& tsN);
	Real calculateAngleWeight(size_t v0, size_t v1, size_t v2);
	int calculateParity(const Vector3& u, const Vector3& v, const Vector3& n);
	void addFaceTangentSpaceToVertices(size_t indexSet, size_t faceIndex, size_t *localVertInd,
		const Vector3& faceTsU, const Vector3& faceTsV, const Vector3& faceNorm, Result& result);
	void normaliseVertices();
	void remapIndexes(Result& res);
	template <typename T>
	void remapIndexes(T* ibuf, size_t indexSet, Result& res)
	{
		for (IndexRemapList::iterator i = res.indexesRemapped.begin();
			i != res.indexesRemapped.end(); ++i)
		{
			IndexRemap& remap = *i;

			// Note that because this is a vertex split situation, and vertex
			// split is only for some faces, it's not a case of replacing all
			// instances of vertex index A with vertex index B
			// It actually matters which triangle we're talking about, so drive
			// the update from the face index

			if (remap.indexSet == indexSet)
			{
				T* pBuf;
				pBuf = ibuf + remap.faceIndex * 3;

				for (int v = 0; v < 3; ++v, ++pBuf)
				{
					if (*pBuf == remap.splitVertex.first)
					{
						*pBuf = (T)remap.splitVertex.second;
					}
				}
			}
		}
	}
};


//---------------------------------------------------------------------
CTangentSpaceCalc::CTangentSpaceCalc()
	: mVData(0)
	, mSplitMirrored(false)
	, mSplitRotated(false)
	, mStoreParityInW(false)
{
}

//---------------------------------------------------------------------
void CTangentSpaceCalc::clear()
{
	mIDataList.clear();
	mOpTypes.clear();
	mVData = 0;
}
//---------------------------------------------------------------------
void CTangentSpaceCalc::setVertexData(VertexData* v_in)
{
	mVData = v_in;
}
//---------------------------------------------------------------------
void CTangentSpaceCalc::addIndexData(IndexData* i_in, RenderOperation::OperationType op)
{
	if (op != RenderOperation::OT_TRIANGLE_FAN &&
		op != RenderOperation::OT_TRIANGLE_LIST &&
		op != RenderOperation::OT_TRIANGLE_STRIP)
	{
		OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
			"Only indexed triangle (list, strip, fan) render operations are supported.",
			"CTangentSpaceCalc::addIndexData");

	}
	mIDataList.push_back(i_in);
	mOpTypes.push_back(op);
}
//---------------------------------------------------------------------
CTangentSpaceCalc::Result CTangentSpaceCalc::build(
	VertexElementSemantic targetSemantic,
	unsigned short sourceTexCoordSet, unsigned short index)
{
	Result res;

	// Pull out all the vertex components we'll need
	populateVertexArray(sourceTexCoordSet);

	// Now process the faces and calculate / add their contributions
	processFaces(res);

	// Now normalise & orthogonalise
	normaliseVertices();

	// Create new final geometry
	// First extend existing buffers to cope with new vertices
	extendBuffers(res.vertexSplits);

	// Alter indexes
	remapIndexes(res);

	// Create / identify target & write tangents
	insertTangents(res, targetSemantic, sourceTexCoordSet, index);

	return res;


}
//---------------------------------------------------------------------
void CTangentSpaceCalc::extendBuffers(VertexSplits& vertexSplits)
{
	if (!vertexSplits.empty())
	{
		// ok, need to increase the vertex buffer size, and alter some indexes

		// vertex buffers first
		VertexBufferBinding* newBindings = HardwareBufferManager::getSingleton().createVertexBufferBinding();
		const VertexBufferBinding::VertexBufferBindingMap& bindmap =
			mVData->vertexBufferBinding->getBindings();
		for (VertexBufferBinding::VertexBufferBindingMap::const_iterator i =
			bindmap.begin(); i != bindmap.end(); ++i)
		{
			HardwareVertexBufferSharedPtr srcbuf = i->second;
			// Derive vertex count from buffer not vertex data, in case using
			// the vertexStart option in vertex data
			size_t newVertexCount = srcbuf->getNumVertices() + vertexSplits.size();
			// Create new buffer & bind
			HardwareVertexBufferSharedPtr newBuf =
				HardwareBufferManager::getSingleton().createVertexBuffer(
					srcbuf->getVertexSize(), newVertexCount, srcbuf->getUsage(),
					srcbuf->hasShadowBuffer());
			newBindings->setBinding(i->first, newBuf);

			// Copy existing contents (again, entire buffer, not just elements referenced)
			newBuf->copyData(*(srcbuf.get()), 0, 0, srcbuf->getNumVertices() * srcbuf->getVertexSize(), true);

			// Split vertices, read / write from new buffer
			char* pBase = static_cast<char*>(newBuf->lock(HardwareBuffer::HBL_NORMAL));
			for (VertexSplits::iterator spliti = vertexSplits.begin();
				spliti != vertexSplits.end(); ++spliti)
			{
				const char* pSrcBase = pBase + spliti->first * newBuf->getVertexSize();
				char* pDstBase = pBase + spliti->second * newBuf->getVertexSize();
				memcpy(pDstBase, pSrcBase, newBuf->getVertexSize());
			}
			newBuf->unlock();

		}

		// Update vertex data
		// Increase vertex count according to num splits
		mVData->vertexCount += vertexSplits.size();
		// Flip bindings over to new buffers (old buffers released)
		HardwareBufferManager::getSingleton().destroyVertexBufferBinding(mVData->vertexBufferBinding);
		mVData->vertexBufferBinding = newBindings;

		// If vertex size requires 32bit index buffer
		if (mVData->vertexCount > 65536)
		{
			for (size_t i = 0; i < mIDataList.size(); ++i)
			{
				// check index size
				IndexData* idata = mIDataList[i];
				HardwareIndexBufferSharedPtr srcbuf = idata->indexBuffer;
				if (srcbuf->getType() == HardwareIndexBuffer::IT_16BIT)
				{
					size_t indexCount = srcbuf->getNumIndexes();

					// convert index buffer to 32bit.
					HardwareIndexBufferSharedPtr newBuf =
						HardwareBufferManager::getSingleton().createIndexBuffer(
							HardwareIndexBuffer::IT_32BIT, indexCount,
							srcbuf->getUsage(), srcbuf->hasShadowBuffer());

					uint16* pSrcBase = static_cast<uint16*>(srcbuf->lock(HardwareBuffer::HBL_NORMAL));
					uint32* pBase = static_cast<uint32*>(newBuf->lock(HardwareBuffer::HBL_NORMAL));

					size_t j = 0;
					while (j < indexCount)
					{
						*pBase++ = *pSrcBase++;
						++j;
					}

					srcbuf->unlock();
					newBuf->unlock();

					// assign new index buffer.
					idata->indexBuffer = newBuf;
				}
			}
		}
	}

}
//---------------------------------------------------------------------
void CTangentSpaceCalc::remapIndexes(Result& res)
{
	for (size_t i = 0; i < mIDataList.size(); ++i)
	{

		IndexData* idata = mIDataList[i];
		// Now do index data
		// no new buffer required, same size but some triangles remapped
		if (idata->indexBuffer->getType() == HardwareIndexBuffer::IT_32BIT)
		{
			uint32* p32 = static_cast<uint32*>(idata->indexBuffer->lock(HardwareBuffer::HBL_NORMAL));
			remapIndexes(p32, i, res);
		}
		else
		{
			uint16* p16 = static_cast<uint16*>(idata->indexBuffer->lock(HardwareBuffer::HBL_NORMAL));
			remapIndexes(p16, i, res);
		}
		idata->indexBuffer->unlock();
	}

}
//---------------------------------------------------------------------
void CTangentSpaceCalc::normaliseVertices()
{
	// Just run through our complete (possibly augmented) list of vertices
	// Normalise the tangents & binormals
	for (VertexInfoArray::iterator i = mVertexArray.begin(); i != mVertexArray.end(); ++i)
	{
		VertexInfo& v = *i;

		v.tangent.normalise();
		v.binormal.normalise();

		// Orthogonalise with the vertex normal since it's currently
		// orthogonal with the face normals, but will be close to ortho
		// Apply Gram-Schmidt orthogonalise
		Vector3 temp = v.tangent;
		v.tangent = temp - (v.norm * v.norm.dotProduct(temp));

		temp = v.binormal;
		v.binormal = temp - (v.norm * v.norm.dotProduct(temp));

		// renormalize 
		v.tangent.normalise();
		v.binormal.normalise();

	}
}
//---------------------------------------------------------------------
void CTangentSpaceCalc::processFaces(Result& result)
{
	// Quick pre-check for triangle strips / fans
	for (OpTypeList::iterator ot = mOpTypes.begin(); ot != mOpTypes.end(); ++ot)
	{
		if (*ot != RenderOperation::OT_TRIANGLE_LIST)
		{
			// Can't split strips / fans
			setSplitMirrored(false);
			setSplitRotated(false);
		}
	}

	for (size_t i = 0; i < mIDataList.size(); ++i)
	{
		IndexData* i_in = mIDataList[i];
		RenderOperation::OperationType opType = mOpTypes[i];

		// Read data from buffers
		uint16 *p16 = 0;
		uint32 *p32 = 0;

		HardwareIndexBufferSharedPtr ibuf = i_in->indexBuffer;
		if (ibuf->getType() == HardwareIndexBuffer::IT_32BIT)
		{
			p32 = static_cast<uint32*>(
				ibuf->lock(HardwareBuffer::HBL_READ_ONLY));
			// offset by index start
			p32 += i_in->indexStart;
		}
		else
		{
			p16 = static_cast<uint16*>(
				ibuf->lock(HardwareBuffer::HBL_READ_ONLY));
			// offset by index start
			p16 += i_in->indexStart;
		}
		// current triangle
		size_t vertInd[3] = { 0, 0, 0 };
		// loop through all faces to calculate the tangents and normals
		size_t faceCount = opType == RenderOperation::OT_TRIANGLE_LIST ?
			i_in->indexCount / 3 : i_in->indexCount - 2;
		for (size_t f = 0; f < faceCount; ++f)
		{
			bool invertOrdering = false;
			// Read 1 or 3 indexes depending on type
			if (f == 0 || opType == RenderOperation::OT_TRIANGLE_LIST)
			{
				vertInd[0] = p32 ? *p32++ : *p16++;
				vertInd[1] = p32 ? *p32++ : *p16++;
				vertInd[2] = p32 ? *p32++ : *p16++;
			}
			else if (opType == RenderOperation::OT_TRIANGLE_FAN)
			{
				// Element 0 always remains the same
				// Element 2 becomes element 1
				vertInd[1] = vertInd[2];
				// read new into element 2
				vertInd[2] = p32 ? *p32++ : *p16++;
			}
			else if (opType == RenderOperation::OT_TRIANGLE_STRIP)
			{
				// Shunt everything down one, but also invert the ordering on 
				// odd numbered triangles (== even numbered i's)
				// we interpret front as anticlockwise all the time but strips alternate
				if (f & 0x1)
				{
					// odd tris (index starts at 3, 5, 7)
					invertOrdering = true;
				}
				vertInd[0] = vertInd[1];
				vertInd[1] = vertInd[2];
				vertInd[2] = p32 ? *p32++ : *p16++;
			}

			// deal with strip inversion of winding
			size_t localVertInd[3];
			localVertInd[0] = vertInd[0];
			if (invertOrdering)
			{
				localVertInd[1] = vertInd[2];
				localVertInd[2] = vertInd[1];
			}
			else
			{
				localVertInd[1] = vertInd[1];
				localVertInd[2] = vertInd[2];
			}


			// For each triangle
			//   Calculate tangent & binormal per triangle
			//   Note these are not normalised, are weighted by UV area
			Vector3 faceTsU, faceTsV, faceNorm;
			calculateFaceTangentSpace(localVertInd, faceTsU, faceTsV, faceNorm);

			// Skip invalid UV space triangles
			if (faceTsU.isZeroLength() || faceTsV.isZeroLength())
				continue;

			addFaceTangentSpaceToVertices(i, f, localVertInd, faceTsU, faceTsV, faceNorm, result);

		}


		ibuf->unlock();
	}

}
//---------------------------------------------------------------------
void CTangentSpaceCalc::addFaceTangentSpaceToVertices(
	size_t indexSet, size_t faceIndex, size_t *localVertInd,
	const Vector3& faceTsU, const Vector3& faceTsV, const Vector3& faceNorm,
	Result& result)
{
	// Calculate parity for this triangle
	int faceParity = calculateParity(faceTsU, faceTsV, faceNorm);
	// Now add these to each vertex referenced by the face
	for (int v = 0; v < 3; ++v)
	{
		// index 0 is vertex we're calculating, 1 and 2 are the others

		// We want to re-weight these by the angle the face makes with the vertex
		// in order to obtain tessellation-independent results
		Real angleWeight = calculateAngleWeight(localVertInd[v],
			localVertInd[(v + 1) % 3], localVertInd[(v + 2) % 3]);


		VertexInfo* vertex = &(mVertexArray[localVertInd[v]]);

		// check parity (0 means not set)
		// Locate parity-version of vertex index, or create if doesn't exist
		// If parity-version of vertex index was different, record alteration
		// in triangle remap
		// in vertex split list
		bool splitVertex = false;
		size_t reusedOppositeParity = 0;
		bool splitBecauseOfParity = false;
		bool newVertex = false;
		if (!vertex->parity)
		{
			// init
			vertex->parity = faceParity;
			newVertex = true;
		}
		if (mSplitMirrored)
		{
			if (!newVertex && faceParity != calculateParity(vertex->tangent, vertex->binormal, vertex->norm))//vertex->parity != faceParity)
			{
				// Check for existing alternative parity
				if (vertex->oppositeParityIndex)
				{
					// Ok, have already split this vertex because of parity
					// Use the same one again
					reusedOppositeParity = vertex->oppositeParityIndex;
					vertex = &(mVertexArray[reusedOppositeParity]);
				}
				else
				{
					splitVertex = true;
					splitBecauseOfParity = true;

					LogManager::getSingleton().stream(LML_TRIVIAL)
						<< "TSC parity split - Vpar: " << vertex->parity
						<< " Fpar: " << faceParity
						<< " faceTsU: " << faceTsU
						<< " faceTsV: " << faceTsV
						<< " faceNorm: " << faceNorm
						<< " vertTsU:" << vertex->tangent
						<< " vertTsV:" << vertex->binormal
						<< " vertNorm:" << vertex->norm;

				}
			}
		}

		if (mSplitRotated)
		{

			// deal with excessive tangent space rotations as well as mirroring
			// same kind of split behaviour appropriate
			if (!newVertex && !splitVertex)
			{
				// If more than 90 degrees, split
				Vector3 uvCurrent = vertex->tangent + vertex->binormal;

				// project down to the plane (plane normal = face normal)
				Vector3 vRotHalf = uvCurrent - faceNorm;
				vRotHalf *= faceNorm.dotProduct(uvCurrent);

				if ((faceTsU + faceTsV).dotProduct(vRotHalf) < 0.0f)
				{
					splitVertex = true;
				}
			}
		}

		if (splitVertex)
		{
			size_t newVertexIndex = mVertexArray.size();
			VertexSplit splitInfo(localVertInd[v], newVertexIndex);
			result.vertexSplits.push_back(splitInfo);
			// re-point opposite parity
			if (splitBecauseOfParity)
			{
				vertex->oppositeParityIndex = newVertexIndex;
			}
			// copy old values but reset tangent space
			VertexInfo locVertex = *vertex;
			locVertex.tangent = Vector3::ZERO;
			locVertex.binormal = Vector3::ZERO;
			locVertex.parity = faceParity;
			mVertexArray.push_back(locVertex);
			result.indexesRemapped.push_back(IndexRemap(indexSet, faceIndex, splitInfo));

			vertex = &(mVertexArray[newVertexIndex]);

		}
		else if (reusedOppositeParity)
		{
			// didn't split again, but we do need to record the re-used remapping
			VertexSplit splitInfo(localVertInd[v], reusedOppositeParity);
			result.indexesRemapped.push_back(IndexRemap(indexSet, faceIndex, splitInfo));

		}

		// Add weighted tangent & binormal
		vertex->tangent += (faceTsU * angleWeight);
		vertex->binormal += (faceTsV * angleWeight);


	}

}
//---------------------------------------------------------------------
int CTangentSpaceCalc::calculateParity(const Vector3& u, const Vector3& v, const Vector3& n)
{
	// Note that this parity is the reverse of what you'd expect - this is
	// because the 'V' texture coordinate is actually left handed
	if (u.crossProduct(v).dotProduct(n) >= 0.0f)
		return -1;
	else
		return 1;

}
//---------------------------------------------------------------------
void CTangentSpaceCalc::calculateFaceTangentSpace(const size_t* vertInd,
	Vector3& tsU, Vector3& tsV, Vector3& tsN)
{
	const VertexInfo& v0 = mVertexArray[vertInd[0]];
	const VertexInfo& v1 = mVertexArray[vertInd[1]];
	const VertexInfo& v2 = mVertexArray[vertInd[2]];
	Vector2 deltaUV1 = v1.uv - v0.uv;
	Vector2 deltaUV2 = v2.uv - v0.uv;
	Vector3 deltaPos1 = v1.pos - v0.pos;
	Vector3 deltaPos2 = v2.pos - v0.pos;

	// face normal
	tsN = deltaPos1.crossProduct(deltaPos2);
	tsN.normalise();


	Real uvarea = deltaUV1.crossProduct(deltaUV2) * 0.5f;
	if (Math::RealEqual(uvarea, 0.0f))
	{
		// no tangent, null uv area
		tsU = tsV = Vector3::ZERO;
	}
	else
	{

		// Normalise by uvarea
		Real a = deltaUV2.y / uvarea;
		Real b = -deltaUV1.y / uvarea;
		Real c = -deltaUV2.x / uvarea;
		Real d = deltaUV1.x / uvarea;

		tsU = (deltaPos1 * a) + (deltaPos2 * b);
		tsU.normalise();

		tsV = (deltaPos1 * c) + (deltaPos2 * d);
		tsV.normalise();

		Real abs_uvarea = Math::Abs(uvarea);
		tsU *= abs_uvarea;
		tsV *= abs_uvarea;

		// tangent (tsU) and binormal (tsV) are now weighted by uv area


	}

}
//---------------------------------------------------------------------
Real CTangentSpaceCalc::calculateAngleWeight(size_t vidx0, size_t vidx1, size_t vidx2)
{
	const VertexInfo& v0 = mVertexArray[vidx0];
	const VertexInfo& v1 = mVertexArray[vidx1];
	const VertexInfo& v2 = mVertexArray[vidx2];

	Vector3 diff0 = v1.pos - v0.pos;
	Vector3 diff1 = v2.pos - v1.pos;

	// Weight is just the angle - larger == better
	return diff0.angleBetween(diff1).valueRadians();

}
//---------------------------------------------------------------------
void CTangentSpaceCalc::populateVertexArray(unsigned short sourceTexCoordSet)
{
	// Just pull data out into more friendly structures
	VertexDeclaration *dcl = mVData->vertexDeclaration;
	VertexBufferBinding *bind = mVData->vertexBufferBinding;

	// Get the incoming UV element
	const VertexElement* uvElem = dcl->findElementBySemantic(
		VES_TEXTURE_COORDINATES, sourceTexCoordSet);

	if (!uvElem || uvElem->getType() != VET_FLOAT2)
	{
		OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
			"No 2D texture coordinates with selected index, cannot calculate tangents.",
			"CTangentSpaceCalc::build");
	}

	HardwareVertexBufferSharedPtr uvBuf, posBuf, normBuf;
	unsigned char *pUvBase, *pPosBase, *pNormBase;
	size_t uvInc, posInc, normInc;

	uvBuf = bind->getBuffer(uvElem->getSource());
	pUvBase = static_cast<unsigned char*>(
		uvBuf->lock(HardwareBuffer::HBL_READ_ONLY));
	uvInc = uvBuf->getVertexSize();
	// offset for vertex start
	pUvBase += mVData->vertexStart * uvInc;

	// find position
	const VertexElement *posElem = dcl->findElementBySemantic(VES_POSITION);
	if (posElem->getSource() == uvElem->getSource())
	{
		pPosBase = pUvBase;
		posInc = uvInc;
	}
	else
	{
		// A different buffer
		posBuf = bind->getBuffer(posElem->getSource());
		pPosBase = static_cast<unsigned char*>(
			posBuf->lock(HardwareBuffer::HBL_READ_ONLY));
		posInc = posBuf->getVertexSize();
		// offset for vertex start
		pPosBase += mVData->vertexStart * posInc;
	}
	// find a normal buffer
	const VertexElement *normElem = dcl->findElementBySemantic(VES_NORMAL);
	if (!normElem)
		OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
			"No vertex normals found",
			"CTangentSpaceCalc::build");

	if (normElem->getSource() == uvElem->getSource())
	{
		pNormBase = pUvBase;
		normInc = uvInc;
	}
	else if (normElem->getSource() == posElem->getSource())
	{
		// normals are in the same buffer as position
		// this condition arises when an animated(skeleton) mesh is not built with 
		// an edge list buffer ie no shadows being used.
		pNormBase = pPosBase;
		normInc = posInc;
	}
	else
	{
		// A different buffer
		normBuf = bind->getBuffer(normElem->getSource());
		pNormBase = static_cast<unsigned char*>(
			normBuf->lock(HardwareBuffer::HBL_READ_ONLY));
		normInc = normBuf->getVertexSize();
		// offset for vertex start
		pNormBase += mVData->vertexStart * normInc;
	}

	// Preinitialise vertex info
	mVertexArray.clear();
	mVertexArray.resize(mVData->vertexCount);

	float* pFloat;
	VertexInfo* vInfo = &(mVertexArray[0]);
	for (size_t v = 0; v < mVData->vertexCount; ++v, ++vInfo)
	{
		posElem->baseVertexPointerToElement(pPosBase, &pFloat);
		vInfo->pos.x = *pFloat++;
		vInfo->pos.y = *pFloat++;
		vInfo->pos.z = *pFloat++;
		pPosBase += posInc;

		normElem->baseVertexPointerToElement(pNormBase, &pFloat);
		vInfo->norm.x = *pFloat++;
		vInfo->norm.y = *pFloat++;
		vInfo->norm.z = *pFloat++;
		pNormBase += normInc;

		uvElem->baseVertexPointerToElement(pUvBase, &pFloat);
		vInfo->uv.x = *pFloat++;
		vInfo->uv.y = *pFloat++;
		pUvBase += uvInc;


	}

	// unlock buffers
	uvBuf->unlock();
	if (posBuf)
	{
		posBuf->unlock();
	}
	if (normBuf)
	{
		normBuf->unlock();
	}

}
//---------------------------------------------------------------------
void CTangentSpaceCalc::insertTangents(Result& res,
	VertexElementSemantic targetSemantic, unsigned short sourceTexCoordSet,
	unsigned short index)
{
	// Make a new tangents semantic or find an existing one
	VertexDeclaration *vDecl = mVData->vertexDeclaration;
	VertexBufferBinding *vBind = mVData->vertexBufferBinding;

	const VertexElement *tangentsElem = vDecl->findElementBySemantic(targetSemantic, index);
	bool needsToBeCreated = false;
	VertexElementType tangentsType = mStoreParityInW ? VET_FLOAT4 : VET_FLOAT3;

	if (!tangentsElem)
	{ // no tex coords with index 1
		needsToBeCreated = true;
	}
	else if (tangentsElem->getType() != tangentsType)
	{
		//  buffer exists, but not 3D
		OGRE_EXCEPT(Exception::ERR_INVALIDPARAMS,
			"Target semantic set already exists but is not of the right size, therefore "
			"cannot contain tangents. You should delete this existing entry first. ",
			"CTangentSpaceCalc::insertTangents");
	}

	HardwareVertexBufferSharedPtr targetBuffer, origBuffer;
	unsigned char* pSrc = NULL;

	if (needsToBeCreated)
	{
		// To be most efficient with our vertex streams,
		// tack the new tangents onto the same buffer as the
		// source texture coord set
		const VertexElement* prevTexCoordElem =
			mVData->vertexDeclaration->findElementBySemantic(
				VES_TEXTURE_COORDINATES, sourceTexCoordSet);
		if (!prevTexCoordElem)
		{
			OGRE_EXCEPT(Exception::ERR_ITEM_NOT_FOUND,
				"Cannot locate the first texture coordinate element to "
				"which to append the new tangents.",
				"Mesh::orgagniseTangentsBuffer");
		}
		// Find the buffer associated with  this element
		origBuffer = mVData->vertexBufferBinding->getBuffer(
			prevTexCoordElem->getSource());
		// Now create a new buffer, which includes the previous contents
		// plus extra space for the 3D coords
		targetBuffer = HardwareBufferManager::getSingleton().createVertexBuffer(
			origBuffer->getVertexSize() + VertexElement::getTypeSize(tangentsType),
			origBuffer->getNumVertices(),
			origBuffer->getUsage(),
			origBuffer->hasShadowBuffer());
		// Add the new element
		tangentsElem = &(vDecl->addElement(
			prevTexCoordElem->getSource(),
			origBuffer->getVertexSize(),
			tangentsType,
			targetSemantic,
			index));
		// Set up the source pointer
		pSrc = static_cast<unsigned char*>(
			origBuffer->lock(HardwareBuffer::HBL_READ_ONLY));
		// Rebind the new buffer
		vBind->setBinding(prevTexCoordElem->getSource(), targetBuffer);
	}
	else
	{
		// space already there
		origBuffer = mVData->vertexBufferBinding->getBuffer(
			tangentsElem->getSource());
		targetBuffer = origBuffer;
	}


	unsigned char* pDest = static_cast<unsigned char*>(
		targetBuffer->lock(HardwareBuffer::HBL_DISCARD));
	size_t origVertSize = origBuffer->getVertexSize();
	size_t newVertSize = targetBuffer->getVertexSize();
	//for (size_t v = 0; v < origBuffer->getNumVertices(); ++v)
	for (size_t v = 0; v < mVertexArray.size(); ++v)
	{
		if (needsToBeCreated)
		{
			// Copy original vertex data as well 
			memcpy(pDest, pSrc, origVertSize);
			pSrc += origVertSize;
		}
		// Write in the tangent
		float* pTangent;
		tangentsElem->baseVertexPointerToElement(pDest, &pTangent);
		VertexInfo& vertInfo = mVertexArray[v];
		*pTangent++ = vertInfo.tangent.x;
		*pTangent++ = vertInfo.tangent.y;
		*pTangent++ = vertInfo.tangent.z;
		if (mStoreParityInW)
			*pTangent++ = (float)vertInfo.parity;

		// Next target vertex
		pDest += newVertSize;

	}
	targetBuffer->unlock();

	if (needsToBeCreated)
	{
		origBuffer->unlock();
	}
}



























void CManual_Object::BuildTangentVectors()
{
	if (m_numberOfVertices == 0 || m_numberOfIndices == 0)
		return;

	CTangentSpaceCalc tangentsCalc;
	tangentsCalc.setSplitMirrored(false);
	tangentsCalc.setSplitRotated(false);
	tangentsCalc.setStoreParityInW(false);

	// shared geometry first
	if (m_mesh->sharedVertexData)
	{
		tangentsCalc.setVertexData(m_mesh->sharedVertexData);
		bool found = false;
		for (int i = 0; i < m_mesh->getNumSubMeshes(); i++)
		{
			SubMesh* sm = m_mesh->getSubMesh(i);
			if (sm->useSharedVertices)
			{
				tangentsCalc.addIndexData(sm->indexData);
				found = true;
			}
		}
		if (found)
		{
			CTangentSpaceCalc::Result res = tangentsCalc.build(VES_TANGENT, 0, 0);
		}
	}

	// Dedicated geometry
	for (int i = 0; i < m_mesh->getNumSubMeshes(); i++)
	{
		SubMesh* sm = m_mesh->getSubMesh(i);
		if (!sm->useSharedVertices)
		{
			tangentsCalc.clear();
			tangentsCalc.setVertexData(sm->vertexData);
			tangentsCalc.addIndexData(sm->indexData, sm->operationType);
			CTangentSpaceCalc::Result res = tangentsCalc.build(VES_TANGENT, 0, 0);
		}
	}
}

CManual_Object.h:

Code: Select all

#ifndef _CMANUAL_OBJECT_H_
#define _CMANUAL_OBJECT_H_

using namespace Ogre;

class CManual_Object
{
public:
	CManual_Object();
	~CManual_Object();

	void AddVertexElementSemantic(VertexElementSemantic vertexElementSemantic);
	void Initialize(bool createEntity = false);

	void begin(CString materialName, RenderOperation::OperationType operationType);
	void end();

	void AddUpdateMaterialTarget(Entity* ent)
	{
		if(!m_ent)
			m_ent = ent;

		m_entities.Add(ent);
	}

	inline void position(const Vector3& x)
	{
		// Add the position to the bounds
		m_aabb.merge(x);
		m_radius = max(m_radius, x.squaredLength());

		// Add one to the number of vertices
		m_numberOfVertices++;

		// Check if we have gone over our index
		if (m_vertexDataCurrentIndex + m_vertexSize >= m_vertexDataSize)
			// Enlarge the vertex data
			EnlargeVertexData();

		// Add the data to the vertex data
		AddData(x);
	}

	inline void triangle(unsigned int x, unsigned int y, unsigned int z)
	{
		// Add one to the number of indices
		m_numberOfIndices += 3;

		// Check if we have gone over our index
		const size_t tmpSizeInBytes = sizeof(Ogre::uint32) * 3;
		if (m_indexDataCurrentIndex + tmpSizeInBytes >= m_indexDataSize)
			// Enlarge the index data
			EnlargeIndexData();

		// Add the data to the index data
		uint32* tmpInt;
		baseVertexPointerToElement(m_indexData + m_indexDataCurrentIndex, &tmpInt);
		*tmpInt++ = x;
		*tmpInt++ = y;
		*tmpInt++ = z;
		m_indexDataCurrentIndex += tmpSizeInBytes;
	}

	inline void normal(const Vector3& x)
	{
		AddData(x);
	}

	inline void tangent(const Vector3& x)
	{
		AddData(x);
	}

	inline void textureCoord(float x, float y)
	{
		AddData(x, y);
	}

	inline void textureCoord(const Vector2& x)
	{
		AddData(x);
	}

	inline void colour(ColourValue& x)
	{
		AddData(x);
	}

	inline void baseVertexPointerToElement(void* pBase, float** pElem) const
	{
		*pElem = static_cast<float*>(static_cast<void*>(static_cast<unsigned char*>(pBase)));
	}

	inline void baseVertexPointerToElement(void* pBase, RGBA** pElem) const
	{
		*pElem = static_cast<RGBA*>(static_cast<void*>(static_cast<unsigned char*>(pBase)));
	}

	inline void baseVertexPointerToElement(void* pBase, unsigned short** pElem) const
	{
		*pElem = static_cast<unsigned short*>(static_cast<void*>(static_cast<unsigned char*>(pBase)));
	}

	inline void AddData(float& x, float& y)
	{
		const size_t tmpSizeInBytes = 4 * 2;

		// Add the data to the vertex data
		float* tmpFloat;
		baseVertexPointerToElement(m_vertexData + m_vertexDataCurrentIndex, &tmpFloat);
		*tmpFloat++ = x;
		*tmpFloat++ = y;
		m_vertexDataCurrentIndex += tmpSizeInBytes;
	}

	inline void AddData(const Vector2& x)
	{
		const size_t tmpSizeInBytes = 4 * 2;

		// Add the data to the vertex data
		float* tmpFloat;
		baseVertexPointerToElement(m_vertexData + m_vertexDataCurrentIndex, &tmpFloat);
		*tmpFloat++ = x.x;
		*tmpFloat++ = x.y;
		m_vertexDataCurrentIndex += tmpSizeInBytes;
	}

	inline void AddData(const Vector3& x)
	{
		const size_t tmpSizeInBytes = 4 * 3;

		// Add the data to the vertex data
		float* tmpFloat;
		baseVertexPointerToElement(m_vertexData + m_vertexDataCurrentIndex, &tmpFloat);
		*tmpFloat++ = x.x;
		*tmpFloat++ = x.y;
		*tmpFloat++ = x.z;
		m_vertexDataCurrentIndex += tmpSizeInBytes;
	}

	inline void AddData(const Vector4& x)
	{
		const size_t tmpSizeInBytes = 4 * 4;

		// Add the data to the vertex data
		float* tmpFloat;
		baseVertexPointerToElement(m_vertexData + m_vertexDataCurrentIndex, &tmpFloat);
		*tmpFloat++ = x.x;
		*tmpFloat++ = x.y;
		*tmpFloat++ = x.z;
		*tmpFloat++ = x.w;
		m_vertexDataCurrentIndex += tmpSizeInBytes;
	}

	inline void AddData(const ColourValue& x)
	{
		const size_t tmpSizeInBytes = 4 * 1;

		// Add the data to the vertex data
		RGBA* tmpRGBA;
		baseVertexPointerToElement(m_vertexData + m_vertexDataCurrentIndex, &tmpRGBA);
		m_renderSystem->convertColourValue(x, tmpRGBA++);
		m_vertexDataCurrentIndex += tmpSizeInBytes;
	}

	MeshPtr m_mesh;
	Entity* m_ent;
	bool m_useInfiniteBounds;
	bool m_useIndices;
	bool m_buildTangentVectors;

	AxisAlignedBox m_aabb;
	float m_radius;

private:
	inline void EnlargeVertexData()
	{
		//Beep(500, 100);

		// Enlarge the vertex data
		size_t tmpLastSize = m_vertexDataSize;
		m_vertexDataSize = size_t(float(m_vertexDataSize) * 1.2f);
		char* tmpNewVertexData = new char[m_vertexDataSize];
		memcpy(tmpNewVertexData, m_vertexData, tmpLastSize * sizeof(char));

		delete[] m_vertexData;
		m_vertexData = tmpNewVertexData;
	}

	inline void EnlargeIndexData()
	{
		//Beep(500, 100);

		// Enlarge the vertex data
		size_t tmpLastSize = m_indexDataSize;
		m_indexDataSize = size_t(float(m_indexDataSize) * 1.2f);
		char* tmpNewIndexData = new char[m_indexDataSize];
		memcpy(tmpNewIndexData, m_indexData, tmpLastSize * sizeof(char));

		delete[] m_indexData;
		m_indexData = tmpNewIndexData;
	}

	void BuildTangentVectors();

	CList<VertexElementSemantic> m_vertexElementSemantics;
	RenderOperation::OperationType m_operationType;
	size_t m_vertexSize;

	size_t m_numberOfVertices;
	size_t m_vertexBufferNumberOfVertices;

	size_t m_numberOfIndices;
	size_t m_indexBufferNumberOfIndices;

	static char* m_vertexData;
	static size_t m_vertexDataSize;
	size_t m_vertexDataCurrentIndex;

	static char* m_indexData;
	static size_t m_indexDataSize;
	size_t m_indexDataCurrentIndex;

	static int m_numberOfInstances;
	RenderSystem* m_renderSystem;

	CString m_materialName;
	bool m_createdEntity;

	CList<Entity*> m_entities;
};

#endif

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

ManualObject is meant for convenience and not so much for performance. However, we can probably inline the high frequency functions anyway.
Your per frame update use-case seems to be more suitable for direct vertex buffer creation, which is not that much work, see:
https://ogrecave.github.io/ogre/api/lat ... ation.html

For shader based grass animation and LOD there is also: https://github.com/OGRECave/ogre-pagedgeometry

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

Thanks Paroj; in general what you suggest as better solution to manage 2d animated sprites from a sprite sheet in big numbers in the 3d scene (something like human figures, running, shooting,walking)?

BillboardSet seems work well but not for very interactive sprites.

Build the manualobjects like RPG suggest seems much better, but if I have well understood it requires a careful tuning.

Any third way?

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

ManualObject is meant for convenience and not so much for performance. However, we can probably inline the high frequency functions anyway.
Your per frame update use-case seems to be more suitable for direct vertex buffer creation, which is not that much work, see:
https://ogrecave.github.io/ogre/api/lat ... ation.html
It is correct that it is not that hard making it by hand, but this class of mine takes care of it for you without having to handle special cases or size changes. It also works almost exactly like the ManualObject (very friendly to beginners) but in a much more optimized manner.

This is because ManualObject uses several if-cases for each per-vertex function. If-cases are slow.
ManualObject has 3 for position, 2 for normal, tangent, uv and colour.
Mine has 1 for position and 0 for the rest.
So if you render 10 000 triangles (30 000 vertices) with position, normal, tangent, uv and colour, mine has 30 000 if-cases, while ManualObject has 330 000 if-cases. This is why mine is so much faster.

Also, the link you provided is actually in my source code posted as a link that helped me create it :D

For shader based grass animation and LOD there is also: https://github.com/OGRECave/ogre-pagedgeometry
This is grass for large environment, while mine is not. I have a top-down view where you can only see a limited amount of grass, therefore there is no need for LOD and such. Also, I doubt that the grass there actually supports per-vertex changes dynamically from for example physics, like mine requires and works with. There are no more problems with mine after I made my own ManualObject.

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

rpgplayerrobin wrote:
Tue May 12, 2020 1:38 pm
This is because ManualObject uses several if-cases for each per-vertex function. If-cases are slow.
if cases are fine. Function calls are slow. Therefore, moving the function definitions to header show have made the largest impact.

rpgplayerrobin wrote:
Tue May 12, 2020 1:38 pm
Also, I doubt that the grass there actually supports per-vertex changes dynamically from for example physics, like mine requires and works with. There are no more problems with mine after I made my own ManualObject.
Stuntrally uses pagedgeometry grass, which reacts to the car. But they have some patches on top of what you find in the repo.

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

Niubbo wrote:
Tue May 12, 2020 1:12 pm
Build the manualobjects like RPG suggest seems much better, but if I have well understood it requires a careful tuning.
Use this without careful tuning and come back if it turns out to be slow. Generally, start somewhere and only make things more complex once you actually hit the bottlenecks.
Instancing is not optimal with 2D sprites as the instance buffer will be larger than the vertex buffer itself.

Niubbo
Kobold
Posts: 25
Joined: Sat Jan 23, 2016 11:26 am

Re: Question about 2d mesh

Post by Niubbo »

paroj wrote:
Tue May 12, 2020 2:17 pm
Niubbo wrote:
Tue May 12, 2020 1:12 pm
Build the manualobjects like RPG suggest seems much better, but if I have well understood it requires a careful tuning.
Use this without careful tuning and come back if it turns out to be slow. Generally, start somewhere and only make things more complex once you actually hit the bottlenecks.
Instancing is not optimal with 2D sprites as the instance buffer will be larger than the vertex buffer itself.
Thanks; just my curiosity: how much is heavy to use a big number of billboardset (with at example some hundred of billboards attached) in the scene changing the texture coordinates of billboards every frame)? Thanks

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

the same code path is used by the particle system, so it should be pretty efficient

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

When it comes to instancing, we were talking about only instancing the character models themselves with animations when they were exported from blender/maya/max. So there would only be a few of them at once (at most 200 maybe?), not one instance for each quad, but for each character model. This would also then save 199 draw calls. I also suggested it since it has GPU animation (if I remember correctly?) instead of normal CPU animation (which are slow, having 50 characters with GPU animation instead increases the FPS drastically in my game, though I do not use instancing with those), which would help him get GPU animation going if it was too slow with CPU animation.

if cases are fine
When adding if-cases to my code like it is in the normal ManualObject (3 for position and 2 for the rest, in my case they are always false though, I checked), my FPS falls to 103 from 115. So if-cases do have some impact for sure.
Also take in mind that the FPS does not only have to do with the grass, it has to do with all rendering and other code running as well, so I timed the manual objects update for you for updating the grass here (time taken from updating it 100 times):
With 11 if-cases per vertex (like ManualObject): 0.561773 seconds
With 1 if-cases per vertex (like my own): 0.480686 seconds
There you can see that reducing the if-cases to 1 from 11 makes the update of it 17% faster, or in other words reduces the time taken by 14%.
"Many a little makes a mickle"
And that performance is measured with all the 10 rest if-cases always returning false, which the normal ManualObject does not always do, therefore introducing more fails in Branch Predictions, which decreases the performance even more (though probably not that much more).

Nice that the grass do react to physical objects like you said.
Though mine already works well, example: https://streamable.com/8jnse7

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

rpgplayerrobin wrote:
Tue May 12, 2020 3:45 pm
When it comes to instancing, we were talking about only instancing the character models themselves with animations when they were exported from blender/maya/max. So there would only be a few of them at once (at most 200 maybe?), not one instance for each quad, but for each character model. This would also then save 199 draw calls. I also suggested it since it has GPU animation (if I remember correctly?) instead of normal CPU animation (which are slow, having 50 characters with GPU animation instead increases the FPS drastically in my game, though I do not use instancing with those), which would help him get GPU animation going if it was too slow with CPU animation.
yes, I was thinking about instacing quads. However, even then, the exact threshold is 12 elements (size of a instance matrix), which only allows for un-textured quads.
rpgplayerrobin wrote:
Tue May 12, 2020 3:45 pm
When adding if-cases to my code like it is in the normal ManualObject (3 for position and 2 for the rest, in my case they are always false though, I checked), my FPS falls to 103 from 115. So if-cases do have some impact for sure.
I rather meant compared to function calls, if cases are free. Of course they trash the cache in hot loops - however you can play tricks with likely/ unlikely then.
Anyway.. once youve inlined everything, the next step is to move the if outside the loop to be able to use the SIMD optimized memcpy (vector=) where we speak of order 3x-4x and not 15%.
rpgplayerrobin wrote:
Tue May 12, 2020 3:45 pm
Nice that the grass do react to physical objects like you said.
Though mine already works well, example: https://streamable.com/8jnse7
looks sweet.

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

paroj wrote:
Tue May 12, 2020 6:44 pm
Anyway.. once youve inlined everything, the next step is to move the if outside the loop to be able to use the SIMD optimized memcpy (vector=) where we speak of order 3x-4x and not 15%.
I think I have hijacked this thread a bit now into another direction, I did not mean that haha :D

But now I got curious when you mentioned to move the if-case outside somehow to get 3-4x faster code using SIMD... What do you mean exactly? :shock:
Do you mean if all the if-cases from the per-vertex functions are removed, this would be possible?
Of course you would need to know the exact size of the vertex buffer in that case (calculating it before making anything?), and you would still need to add the values into them in some kind of a way (like "manualObject->position(x);"?).

Is there some code I could check regarding this?

paroj
OGRE Team Member
OGRE Team Member
Posts: 1061
Joined: Sun Mar 30, 2014 2:51 pm
x 359
Contact:

Re: Question about 2d mesh

Post by paroj »

I somehow assumed that you have your values precomputed and only push them to ogre using manualObject->position() one by one. In hindsight this probably not the case, but if it is this applies: https://www.embedded.com/optimizing-mem ... ves-speed/

If you call manualObject->position() in the loop where you compute the values, you probably can still compute several positions at once using SIMD. If you really need the speed, you should check the assembly whether the compiler already uses SIMD.

But even then this applies on top:

rpgplayerrobin
Gremlin
Posts: 191
Joined: Wed Mar 18, 2009 3:03 am
x 56

Re: Question about 2d mesh

Post by rpgplayerrobin »

No, my values are changed every frame, otherwise there would be no need to have a fast ManualObject, I update the grass with the new values I calculate every frame.
This is to make physical interactions work, at the same time I also put in wind though.
As these values can change each frame, it is impossible for me to cache them, I still need to recalculate them each frame.

My code is pretty fast right now but I will for sure experiment a bit with SIMD to understand it a bit better though! :D

Post Reply