#include <Ogre.h>
#include <OgreComponents.h>
#include <OgreTerrain.h>
#include <OgreTerrainGroup.h>
#include <pugixml.hpp>
#include "GameData.h"
#include "Components.h"
#include "EventTriggerModule.h"
#include "PhysicsModule.h"
#include "physics.h"
#include "loader.h"

using namespace Ogre;
#if 0
namespace Ogre {
    class TerrainGroup {}; // appease unique_ptr destructor
}
#endif
static String getAttrib(const pugi::xml_node &XMLNode, const String &attrib,
			const String &defaultValue = "")
{
	if (auto anode = XMLNode.attribute(attrib.c_str()))
		return anode.value();
	else
		return defaultValue;
}

static Real getAttribReal(const pugi::xml_node &XMLNode, const String &attrib,
			  Real defaultValue = 0)
{
	if (auto anode = XMLNode.attribute(attrib.c_str()))
		return StringConverter::parseReal(anode.value());
	else
		return defaultValue;
}

static bool getAttribBool(const pugi::xml_node &XMLNode, const String &attrib,
			  bool defaultValue = false)
{
	if (auto anode = XMLNode.attribute(attrib.c_str()))
		return anode.as_bool();
	else
		return defaultValue;

	return false;
}

static Vector3 parseVector3(const pugi::xml_node &XMLNode)
{
	return Vector3(
		StringConverter::parseReal(XMLNode.attribute("x").value()),
		StringConverter::parseReal(XMLNode.attribute("y").value()),
		StringConverter::parseReal(XMLNode.attribute("z").value()));
}

static Quaternion parseQuaternion(const pugi::xml_node &XMLNode)
{
	//! @todo Fix this crap!

	Quaternion orientation;

	if (XMLNode.attribute("qw")) {
		orientation.w = StringConverter::parseReal(
			XMLNode.attribute("qw").value());
		orientation.x = StringConverter::parseReal(
			XMLNode.attribute("qx").value());
		orientation.y = StringConverter::parseReal(
			XMLNode.attribute("qy").value());
		orientation.z = StringConverter::parseReal(
			XMLNode.attribute("qz").value());
	} else if (XMLNode.attribute("axisX")) {
		Vector3 axis;
		axis.x = StringConverter::parseReal(
			XMLNode.attribute("axisX").value());
		axis.y = StringConverter::parseReal(
			XMLNode.attribute("axisY").value());
		axis.z = StringConverter::parseReal(
			XMLNode.attribute("axisZ").value());
		Real angle = StringConverter::parseReal(
			XMLNode.attribute("angle").value());

		orientation.FromAngleAxis(Radian(angle), axis);
	} else if (XMLNode.attribute("angleX")) {
		Matrix3 rot;
		rot.FromEulerAnglesXYZ(
			StringConverter::parseAngle(
				XMLNode.attribute("angleX").value()),
			StringConverter::parseAngle(
				XMLNode.attribute("angleY").value()),
			StringConverter::parseAngle(
				XMLNode.attribute("angleZ").value()));
		orientation.FromRotationMatrix(rot);
	} else if (XMLNode.attribute("x")) {
		orientation.x = StringConverter::parseReal(
			XMLNode.attribute("x").value());
		orientation.y = StringConverter::parseReal(
			XMLNode.attribute("y").value());
		orientation.z = StringConverter::parseReal(
			XMLNode.attribute("z").value());
		orientation.w = StringConverter::parseReal(
			XMLNode.attribute("w").value());
	} else if (XMLNode.attribute("w")) {
		orientation.w = StringConverter::parseReal(
			XMLNode.attribute("w").value());
		orientation.x = StringConverter::parseReal(
			XMLNode.attribute("x").value());
		orientation.y = StringConverter::parseReal(
			XMLNode.attribute("y").value());
		orientation.z = StringConverter::parseReal(
			XMLNode.attribute("z").value());
	}

	return orientation;
}

static ColourValue parseColour(pugi::xml_node &XMLNode)
{
	return ColourValue(
		StringConverter::parseReal(XMLNode.attribute("r").value()),
		StringConverter::parseReal(XMLNode.attribute("g").value()),
		StringConverter::parseReal(XMLNode.attribute("b").value()),
		XMLNode.attribute("a") != NULL ?
			StringConverter::parseReal(
				XMLNode.attribute("a").value()) :
			1);
}

SceneLoader::SceneLoader()
	: mSceneMgr(0)
	, mBackgroundColour(ColourValue::Black)
{
}

SceneLoader::~SceneLoader()
{
}

void SceneLoader::load(DataStreamPtr &stream, const String &groupName,
		       SceneNode *rootNode, flecs::entity e)
{
	m_sGroupName = groupName;
	mSceneMgr = rootNode->getCreator();
	mNamePrefix = rootNode->getName();
	this->counter = (int)e.raw_id();
	if (counter >= 0) {
		if (mNamePrefix.length() > 0)
			mNamePrefix += "/";
		mNamePrefix += std::to_string(counter);
	}
	if (mNamePrefix.length() > 0)
		mNamePrefix += "/";
	base_e = e;

	pugi::xml_document XMLDoc; // character type defaults to char

	auto result = XMLDoc.load_buffer(stream->getAsString().c_str(),
					 stream->size());
	if (!result) {
		LogManager::getSingleton().logError(
			"SceneLoader - " + String(result.description()));
		return;
	}

	// Grab the scene node
	auto XMLRoot = XMLDoc.child("scene");

	// Validate the File
	if (!XMLRoot.attribute("formatVersion")) {
		LogManager::getSingleton().logError(
			"SceneLoader - Invalid .scene File. Missing <scene formatVersion='x.y' >");
		return;
	}

	// figure out where to attach any nodes we create
	mAttachNode = rootNode;

	// Process the scene
	processScene(XMLRoot);
}

void SceneLoader::processScene(pugi::xml_node &XMLRoot)
{
	// Process the scene parameters
	String version = getAttrib(XMLRoot, "formatVersion", "unknown");

	String message =
		"[SceneLoader] Parsing dotScene file with version " + version;
	if (XMLRoot.attribute("sceneManager"))
		message += ", scene manager " +
			   String(XMLRoot.attribute("sceneManager").value());
	if (XMLRoot.attribute("minOgreVersion"))
		message += ", min. Ogre version " +
			   String(XMLRoot.attribute("minOgreVersion").value());
	if (XMLRoot.attribute("author"))
		message += ", author " +
			   String(XMLRoot.attribute("author").value());

	LogManager::getSingleton().logMessage(message);

	// Process environment (?)
	if (auto pElement = XMLRoot.child("environment"))
		processEnvironment(pElement);

	// Process nodes (?)
	if (auto pElement = XMLRoot.child("nodes"))
		processNodes(pElement);

	// Process externals (?)
	if (auto pElement = XMLRoot.child("externals"))
		processExternals(pElement);

	// Process userDataReference (?)
	if (auto pElement = XMLRoot.child("userData"))
		processUserData(pElement, mAttachNode->getUserObjectBindings());

	// Process light (?)
	if (auto pElement = XMLRoot.child("light"))
		processLight(pElement);

	// Process camera (?)
	if (auto pElement = XMLRoot.child("camera"))
		processCamera(pElement);

	// Process terrain (?)
	if (auto pElement = XMLRoot.child("terrainGroup"))
		processTerrainGroup(pElement);
}

void SceneLoader::processNodes(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Nodes...", LML_TRIVIAL);

	// Process node (*)
	for (auto pElement : XMLNode.children("node")) {
		processNode(pElement);
	}

	// Process position (?)
	if (auto pElement = XMLNode.child("position")) {
		mAttachNode->setPosition(parseVector3(pElement));
	}

	// Process rotation (?)
	if (auto pElement = XMLNode.child("rotation")) {
		mAttachNode->setOrientation(parseQuaternion(pElement));
	}

	// Process scale (?)
	if (auto pElement = XMLNode.child("scale")) {
		mAttachNode->setScale(parseVector3(pElement));
	}
}

void SceneLoader::processExternals(pugi::xml_node &XMLNode)
{
	//! @todo Implement this
}

void SceneLoader::processEnvironment(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Environment...", LML_TRIVIAL);

	// Process camera (?)
	if (auto pElement = XMLNode.child("camera"))
		processCamera(pElement);

	// Process fog (?)
	if (auto pElement = XMLNode.child("fog"))
		processFog(pElement);

	// Process skyBox (?)
	if (auto pElement = XMLNode.child("skyBox"))
		processSkyBox(pElement);

	// Process skyDome (?)
	if (auto pElement = XMLNode.child("skyDome"))
		processSkyDome(pElement);

	// Process skyPlane (?)
	if (auto pElement = XMLNode.child("skyPlane"))
		processSkyPlane(pElement);

	// Process colourAmbient (?)
	if (auto pElement = XMLNode.child("colourAmbient"))
		mSceneMgr->setAmbientLight(parseColour(pElement));

	// Process colourBackground (?)
	if (auto pElement = XMLNode.child("colourBackground"))
		mBackgroundColour = parseColour(pElement);
}

void SceneLoader::processTerrainGroup(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Terrain Group...", LML_TRIVIAL);

	Real worldSize = getAttribReal(XMLNode, "worldSize");
	int mapSize =
		StringConverter::parseInt(XMLNode.attribute("size").value());
	int compositeMapDistance = StringConverter::parseInt(
		XMLNode.attribute("tuningCompositeMapDistance").value());
	int maxPixelError = StringConverter::parseInt(
		XMLNode.attribute("tuningMaxPixelError").value());

	auto terrainGlobalOptions = TerrainGlobalOptions::getSingletonPtr();
	OgreAssert(terrainGlobalOptions, "TerrainGlobalOptions not available");

	terrainGlobalOptions->setMaxPixelError((Real)maxPixelError);
	terrainGlobalOptions->setCompositeMapDistance(
		(Real)compositeMapDistance);

	auto terrainGroup = std::make_shared<TerrainGroup>(
		mSceneMgr, Terrain::ALIGN_X_Z, mapSize, worldSize);
	terrainGroup->setOrigin(Vector3::ZERO);
	terrainGroup->setResourceGroup(m_sGroupName);

	// Process terrain pages (*)
	for (auto pPageElement : XMLNode.children("terrain")) {
		int pageX = StringConverter::parseInt(
			pPageElement.attribute("x").value());
		int pageY = StringConverter::parseInt(
			pPageElement.attribute("y").value());

		terrainGroup->defineTerrain(
			pageX, pageY,
			pPageElement.attribute("dataFile").value());
	}
	terrainGroup->loadAllTerrains(true);

	terrainGroup->freeTemporaryResources();

	mAttachNode->getUserObjectBindings().setUserAny("TerrainGroup",
							terrainGroup);
}

void SceneLoader::processLight(pugi::xml_node &XMLNode, SceneNode *pParent)
{
	// Process attributes
	String name = getAttrib(XMLNode, "name");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Light: " + name, LML_TRIVIAL);

	// Create the light
	Light *pLight = mSceneMgr->createLight(name);
	if (pParent)
		pParent->attachObject(pLight);

	String sValue = getAttrib(XMLNode, "type");
	if (sValue == "point")
		pLight->setType(Light::LT_POINT);
	else if (sValue == "directional")
		pLight->setType(Light::LT_DIRECTIONAL);
	else if (sValue == "spot")
		pLight->setType(Light::LT_SPOTLIGHT);
	else if (sValue == "radPoint")
		pLight->setType(Light::LT_POINT);
	else if (sValue == "rect")
		pLight->setType(Light::LT_RECTLIGHT);

	pLight->setVisible(getAttribBool(XMLNode, "visible", true));
	pLight->setCastShadows(getAttribBool(XMLNode, "castShadows", true));
	pLight->setPowerScale(getAttribReal(XMLNode, "powerScale", 1.0));

	// Process colourDiffuse (?)
	if (auto pElement = XMLNode.child("colourDiffuse"))
		pLight->setDiffuseColour(parseColour(pElement));

	// Process colourSpecular (?)
	if (auto pElement = XMLNode.child("colourSpecular"))
		pLight->setSpecularColour(parseColour(pElement));

	if (sValue != "directional") {
		// Process lightRange (?)
		if (auto pElement = XMLNode.child("lightRange"))
			processLightRange(pElement, pLight);

		// Process lightAttenuation (?)
		if (auto pElement = XMLNode.child("lightAttenuation"))
			processLightAttenuation(pElement, pLight);
	}

	if (sValue == "rect") {
		// Process lightSourceSize (?)
		if (auto pElement = XMLNode.child("lightSourceSize"))
			processLightSourceSize(pElement, pLight);
	}

	// Process userDataReference (?)
	if (auto pElement = XMLNode.child("userData"))
		processUserData(pElement, pLight->getUserObjectBindings());
}

void SceneLoader::processCamera(pugi::xml_node &XMLNode, SceneNode *pParent)
{
	// Process attributes
	String name = getAttrib(XMLNode, "name");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Camera: " + name, LML_TRIVIAL);

	// Real fov = getAttribReal(XMLNode, "fov", 45);
	Real aspectRatio = getAttribReal(XMLNode, "aspectRatio", 1.3333);
	String projectionType =
		getAttrib(XMLNode, "projectionType", "perspective");

	// Create the camera
	Camera *pCamera = mSceneMgr->createCamera(name);

	// construct a scenenode is no parent
	if (!pParent)
		pParent = mAttachNode->createChildSceneNode(name);

	pParent->attachObject(pCamera);

	// Set the field-of-view
	//! @todo Is this always in degrees?
	// pCamera->setFOVy(Degree(fov));

	// Set the aspect ratio
	pCamera->setAspectRatio(aspectRatio);

	// Set the projection type
	if (projectionType == "perspective")
		pCamera->setProjectionType(PT_PERSPECTIVE);
	else if (projectionType == "orthographic")
		pCamera->setProjectionType(PT_ORTHOGRAPHIC);

	// Process clipping (?)
	if (auto pElement = XMLNode.child("clipping")) {
		Real nearDist = getAttribReal(pElement, "near");
		pCamera->setNearClipDistance(nearDist);

		Real farDist = getAttribReal(pElement, "far");
		pCamera->setFarClipDistance(farDist);
	}

	// Process userDataReference (?)
	if (auto pElement = XMLNode.child("userData"))
		processUserData(pElement, static_cast<MovableObject *>(pCamera)
						  ->getUserObjectBindings());
}

namespace SceneLoaderPhysics
{
typedef std::vector<Ogre::Vector3> Vector3Array;
#if 0
struct EntityCollisionListener {
	const MovableObject *entity;
	Ogre::Bullet::CollisionListener *listener;
};

/// wrapper with automatic memory management
class CollisionObject {
protected:
	btCollisionObject *mBtBody;
	btCollisionWorld *mBtWorld;

public:
	CollisionObject(btCollisionObject *btBody, btCollisionWorld *btWorld)
		: mBtBody(btBody)
		, mBtWorld(btWorld)
	{
	}
	virtual ~CollisionObject()
	{
		mBtWorld->removeCollisionObject(mBtBody);
		delete mBtBody->getCollisionShape();
		delete mBtBody;
	}
};
class RigidBody : public CollisionObject {
public:
	using CollisionObject::CollisionObject;

	~RigidBody()
	{
		delete (EntityCollisionListener *)(mBtBody)->getUserPointer();
		delete ((btRigidBody *)mBtBody)->getMotionState();
	}
};

class VertexIndexToShape {
public:
	VertexIndexToShape(const Affine3 &transform = Affine3::IDENTITY);
	VertexIndexToShape(Ogre::Renderable *rend,
			   const Affine3 &transform = Affine3::IDENTITY);
	VertexIndexToShape(const Ogre::Entity *entity,
			   const Affine3 &transform = Affine3::IDENTITY);
	VertexIndexToShape(const Ogre::MeshPtr &mesh,
			   const Affine3 &transform = Affine3::IDENTITY)
		: VertexIndexToShape(transform)
	{
		addMesh(mesh);
	}
	~VertexIndexToShape();

	Real getRadius();
	Vector3 getSize();

	btBvhTriangleMeshShape *createTrimesh();
	btConvexHullShape *createConvex();

	void addEntity(const Entity *entity,
		       const Affine3 &transform = Affine3::IDENTITY);
	void addMesh(const MeshPtr &mesh,
		     const Affine3 &transform = Affine3::IDENTITY);

	const Vector3 *getVertices()
	{
		return mVertexBuffer;
	}
	unsigned int getVertexCount()
	{
		return mVertexCount;
	};

private:
	void addStaticVertexData(const VertexData *vertex_data);

	void addAnimatedVertexData(const VertexData *vertex_data,
				   const VertexData *blended_data,
				   const Mesh::IndexMap *indexMap);

	void addIndexData(IndexData *data, const unsigned int offset = 0);

	Vector3 *mVertexBuffer;
	unsigned int *mIndexBuffer;
	unsigned int mVertexCount;
	unsigned int mIndexCount;

	Vector3 mBounds;
	Real mBoundRadius;

	typedef std::map<unsigned char, std::vector<Vector3> *> BoneIndex;
	BoneIndex *mBoneIndex;

	Affine3 mTransform;

	Vector3 mScale;
};
void VertexIndexToShape::addStaticVertexData(const VertexData *vertex_data)
{
	if (!vertex_data)
		return;

	const VertexData *data = vertex_data;

	const unsigned int prev_size = mVertexCount;
	mVertexCount += (unsigned int)data->vertexCount;

	Vector3 *tmp_vert = new Vector3[mVertexCount];
	if (mVertexBuffer) {
		memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size);
		delete[] mVertexBuffer;
	}
	mVertexBuffer = tmp_vert;

	// Get the positional buffer element
	{
		const VertexElement *posElem =
			data->vertexDeclaration->findElementBySemantic(
				VES_POSITION);
		HardwareVertexBufferSharedPtr vbuf =
			data->vertexBufferBinding->getBuffer(
				posElem->getSource());
		const unsigned int vSize = (unsigned int)vbuf->getVertexSize();

		unsigned char *vertex = static_cast<unsigned char *>(
			vbuf->lock(HardwareBuffer::HBL_READ_ONLY));
		float *pReal;
		Vector3 *curVertices = &mVertexBuffer[prev_size];
		const unsigned int vertexCount =
			(unsigned int)data->vertexCount;
		for (unsigned int j = 0; j < vertexCount; ++j) {
			posElem->baseVertexPointerToElement(vertex, &pReal);
			vertex += vSize;

			curVertices->x = (*pReal++);
			curVertices->y = (*pReal++);
			curVertices->z = (*pReal++);

			*curVertices = mTransform * (*curVertices);

			curVertices++;
		}
		vbuf->unlock();
	}
}
//------------------------------------------------------------------------------------------------
void VertexIndexToShape::addAnimatedVertexData(const VertexData *vertex_data,
					       const VertexData *blend_data,
					       const Mesh::IndexMap *indexMap)
{
	// Get the bone index element
	assert(vertex_data);

	const VertexData *data = blend_data;
	const unsigned int prev_size = mVertexCount;
	mVertexCount += (unsigned int)data->vertexCount;
	Vector3 *tmp_vert = new Vector3[mVertexCount];
	if (mVertexBuffer) {
		memcpy(tmp_vert, mVertexBuffer, sizeof(Vector3) * prev_size);
		delete[] mVertexBuffer;
	}
	mVertexBuffer = tmp_vert;

	// Get the positional buffer element
	{
		const VertexElement *posElem =
			data->vertexDeclaration->findElementBySemantic(
				VES_POSITION);
		assert(posElem);
		HardwareVertexBufferSharedPtr vbuf =
			data->vertexBufferBinding->getBuffer(
				posElem->getSource());
		const unsigned int vSize = (unsigned int)vbuf->getVertexSize();

		unsigned char *vertex = static_cast<unsigned char *>(
			vbuf->lock(HardwareBuffer::HBL_READ_ONLY));
		float *pReal;
		Vector3 *curVertices = &mVertexBuffer[prev_size];
		const unsigned int vertexCount =
			(unsigned int)data->vertexCount;
		for (unsigned int j = 0; j < vertexCount; ++j) {
			posElem->baseVertexPointerToElement(vertex, &pReal);
			vertex += vSize;

			curVertices->x = (*pReal++);
			curVertices->y = (*pReal++);
			curVertices->z = (*pReal++);

			*curVertices = mTransform * (*curVertices);

			curVertices++;
		}
		vbuf->unlock();
	}
	{
		const VertexElement *bneElem =
			vertex_data->vertexDeclaration->findElementBySemantic(
				VES_BLEND_INDICES);
		assert(bneElem);

		HardwareVertexBufferSharedPtr vbuf =
			vertex_data->vertexBufferBinding->getBuffer(
				bneElem->getSource());
		const unsigned int vSize = (unsigned int)vbuf->getVertexSize();
		unsigned char *vertex = static_cast<unsigned char *>(
			vbuf->lock(HardwareBuffer::HBL_READ_ONLY));

		unsigned char *pBone;

		if (!mBoneIndex)
			mBoneIndex = new BoneIndex();
		BoneIndex::iterator i;

		Vector3 *curVertices = &mVertexBuffer[prev_size];

		const unsigned int vertexCount =
			(unsigned int)vertex_data->vertexCount;
		for (unsigned int j = 0; j < vertexCount; ++j) {
			bneElem->baseVertexPointerToElement(vertex, &pBone);
			vertex += vSize;

			const unsigned char currBone =
				(indexMap) ? (*indexMap)[*pBone] : *pBone;
			i = mBoneIndex->find(currBone);
			Vector3Array *l = 0;
			if (i == mBoneIndex->end()) {
				l = new Vector3Array;
				mBoneIndex->emplace(currBone, l);
			} else {
				l = i->second;
			}

			l->push_back(*curVertices);

			curVertices++;
		}
		vbuf->unlock();
	}
}
//------------------------------------------------------------------------------------------------
void VertexIndexToShape::addIndexData(IndexData *data,
				      const unsigned int offset)
{
	const unsigned int prev_size = mIndexCount;
	mIndexCount += (unsigned int)data->indexCount;

	unsigned int *tmp_ind = new unsigned int[mIndexCount];
	if (mIndexBuffer) {
		memcpy(tmp_ind, mIndexBuffer, sizeof(unsigned int) * prev_size);
		delete[] mIndexBuffer;
	}
	mIndexBuffer = tmp_ind;

	const unsigned int numTris = (unsigned int)data->indexCount / 3;
	HardwareIndexBufferSharedPtr ibuf = data->indexBuffer;
	const bool use32bitindexes =
		(ibuf->getType() == HardwareIndexBuffer::IT_32BIT);
	unsigned int index_offset = prev_size;

	if (use32bitindexes) {
		const unsigned int *pInt = static_cast<unsigned int *>(
			ibuf->lock(HardwareBuffer::HBL_READ_ONLY));
		for (unsigned int k = 0; k < numTris; ++k) {
			mIndexBuffer[index_offset++] = offset + *pInt++;
			mIndexBuffer[index_offset++] = offset + *pInt++;
			mIndexBuffer[index_offset++] = offset + *pInt++;
		}
		ibuf->unlock();
	} else {
		const unsigned short *pShort = static_cast<unsigned short *>(
			ibuf->lock(HardwareBuffer::HBL_READ_ONLY));
		for (unsigned int k = 0; k < numTris; ++k) {
			mIndexBuffer[index_offset++] =
				offset + static_cast<unsigned int>(*pShort++);
			mIndexBuffer[index_offset++] =
				offset + static_cast<unsigned int>(*pShort++);
			mIndexBuffer[index_offset++] =
				offset + static_cast<unsigned int>(*pShort++);
		}
		ibuf->unlock();
	}
}
//------------------------------------------------------------------------------------------------
Real VertexIndexToShape::getRadius()
{
	if (mBoundRadius == (-1)) {
		getSize();
		mBoundRadius =
			(std::max(mBounds.x, std::max(mBounds.y, mBounds.z)) *
			 0.5);
	}
	return mBoundRadius;
}
//------------------------------------------------------------------------------------------------
Vector3 VertexIndexToShape::getSize()
{
	const unsigned int vCount = getVertexCount();
	if (mBounds == Vector3(-1, -1, -1) && vCount > 0) {
		const Vector3 *const v = getVertices();

		Vector3 vmin(v[0]);
		Vector3 vmax(v[0]);

		for (unsigned int j = 1; j < vCount; j++) {
			vmin.x = std::min(vmin.x, v[j].x);
			vmin.y = std::min(vmin.y, v[j].y);
			vmin.z = std::min(vmin.z, v[j].z);

			vmax.x = std::max(vmax.x, v[j].x);
			vmax.y = std::max(vmax.y, v[j].y);
			vmax.z = std::max(vmax.z, v[j].z);
		}

		mBounds.x = vmax.x - vmin.x;
		mBounds.y = vmax.y - vmin.y;
		mBounds.z = vmax.z - vmin.z;
	}

	return mBounds;
}
//------------------------------------------------------------------------------------------------
btConvexHullShape *VertexIndexToShape::createConvex()
{
	assert(mVertexCount && (mIndexCount >= 6) &&
	       ("Mesh must have some vertices and at least 6 indices (2 triangles)"));

	btConvexHullShape *shape = new btConvexHullShape(
		(btScalar *)&mVertexBuffer[0].x, mVertexCount, sizeof(Vector3));

	shape->setLocalScaling(Ogre::Bullet::convert(mScale));

	return shape;
}
//------------------------------------------------------------------------------------------------
btBvhTriangleMeshShape *VertexIndexToShape::createTrimesh()
{
	assert(mVertexCount && (mIndexCount >= 6) &&
	       ("Mesh must have some vertices and at least 6 indices (2 triangles)"));

	unsigned int numFaces = mIndexCount / 3;

	btTriangleMesh *trimesh = new btTriangleMesh();
	unsigned int *indices = mIndexBuffer;
	Vector3 *vertices = mVertexBuffer;

	btVector3 vertexPos[3];
	for (unsigned int n = 0; n < numFaces; ++n) {
		{
			const Vector3 &vec = vertices[*indices];
			vertexPos[0][0] = vec.x;
			vertexPos[0][1] = vec.y;
			vertexPos[0][2] = vec.z;
		}
		{
			const Vector3 &vec = vertices[*(indices + 1)];
			vertexPos[1][0] = vec.x;
			vertexPos[1][1] = vec.y;
			vertexPos[1][2] = vec.z;
		}
		{
			const Vector3 &vec = vertices[*(indices + 2)];
			vertexPos[2][0] = vec.x;
			vertexPos[2][1] = vec.y;
			vertexPos[2][2] = vec.z;
		}

		indices += 3;

		trimesh->addTriangle(vertexPos[0], vertexPos[1], vertexPos[2]);
	}

	const bool useQuantizedAABB = true;
	btBvhTriangleMeshShape *shape =
		new btBvhTriangleMeshShape(trimesh, useQuantizedAABB);

	shape->setLocalScaling(Ogre::Bullet::convert(mScale));

	return shape;
}
//------------------------------------------------------------------------------------------------
VertexIndexToShape::~VertexIndexToShape()
{
	delete[] mVertexBuffer;
	delete[] mIndexBuffer;

	if (mBoneIndex) {
		for (auto &i : *mBoneIndex) {
			delete i.second;
		}
		delete mBoneIndex;
	}
}
//------------------------------------------------------------------------------------------------
VertexIndexToShape::VertexIndexToShape(const Affine3 &transform)
	: mVertexBuffer(0)
	, mIndexBuffer(0)
	, mVertexCount(0)
	, mIndexCount(0)
	, mBounds(Vector3(-1, -1, -1))
	, mBoundRadius(-1)
	, mBoneIndex(0)
	, mTransform(transform)
	, mScale(1)
{
}
//------------------------------------------------------------------------------------------------
VertexIndexToShape::VertexIndexToShape(const Entity *entity,
				       const Affine3 &transform)
	: VertexIndexToShape(transform)
{
	addEntity(entity, transform);
}
//------------------------------------------------------------------------------------------------
VertexIndexToShape::VertexIndexToShape(Renderable *rend,
				       const Affine3 &transform)
	: VertexIndexToShape(transform)
{
	RenderOperation op;
	rend->getRenderOperation(op);
	addStaticVertexData(op.vertexData);
	if (op.useIndexes)
		addIndexData(op.indexData);
}
//------------------------------------------------------------------------------------------------
void VertexIndexToShape::addEntity(const Entity *entity,
				   const Affine3 &transform)
{
	// Each entity added need to reset size and radius
	// next time getRadius and getSize are asked, they're computed.
	mBounds = Vector3(-1, -1, -1);
	mBoundRadius = -1;

	auto node = entity->getParentSceneNode();
	mTransform = transform;
	mScale = node ? node->getScale() : Vector3(1, 1, 1);

	bool hasSkeleton = entity->hasSkeleton();

	if (entity->getMesh()->sharedVertexData) {
		if (hasSkeleton)
			addAnimatedVertexData(
				entity->getMesh()->sharedVertexData,
				entity->_getSkelAnimVertexData(),
				&entity->getMesh()
					 ->sharedBlendIndexToBoneIndexMap);
		else
			addStaticVertexData(
				entity->getMesh()->sharedVertexData);
	}

	for (unsigned int i = 0; i < entity->getNumSubEntities(); ++i) {
		SubMesh *sub_mesh = entity->getSubEntity(i)->getSubMesh();

		if (!sub_mesh->useSharedVertices) {
			addIndexData(sub_mesh->indexData, mVertexCount);

			if (hasSkeleton)
				addAnimatedVertexData(
					sub_mesh->vertexData,
					entity->getSubEntity(i)
						->_getSkelAnimVertexData(),
					&sub_mesh->blendIndexToBoneIndexMap);
			else
				addStaticVertexData(sub_mesh->vertexData);
		} else {
			addIndexData(sub_mesh->indexData);
		}
	}
}
//------------------------------------------------------------------------------------------------
void VertexIndexToShape::addMesh(const MeshPtr &mesh, const Affine3 &transform)
{
	// Each entity added need to reset size and radius
	// next time getRadius and getSize are asked, they're computed.
	mBounds = Vector3(-1, -1, -1);
	mBoundRadius = -1;

	mTransform = transform;

	if (mesh->hasSkeleton())
		LogManager::getSingleton().logWarning(
			"Mesh " + mesh->getName() +
			" has a skeleton but added non animated");

	if (mesh->sharedVertexData) {
		VertexIndexToShape::addStaticVertexData(mesh->sharedVertexData);
	}

	for (unsigned int i = 0; i < mesh->getNumSubMeshes(); ++i) {
		SubMesh *sub_mesh = mesh->getSubMesh(i);

		if (!sub_mesh->useSharedVertices) {
			VertexIndexToShape::addIndexData(sub_mesh->indexData,
							 mVertexCount);
			VertexIndexToShape::addStaticVertexData(
				sub_mesh->vertexData);
		} else {
			VertexIndexToShape::addIndexData(sub_mesh->indexData);
		}
	}
}
#endif
}

void SceneLoader::processNode(pugi::xml_node &XMLNode, SceneNode *pParent)
{
	// Construct the node's name
	String name = getAttrib(XMLNode, "name");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Node: " + name, LML_TRIVIAL);

	// Create the scene node
	SceneNode *pNode;
	if (name.empty()) {
		// Let Ogre choose the name
		if (pParent)
			pNode = pParent->createChildSceneNode();
		else
			pNode = mAttachNode->createChildSceneNode();
	} else {
		if (mNamePrefix.length() > 0) {
			// Provide the name
			if (pParent)
				pNode = pParent->createChildSceneNode(
					mNamePrefix + name);
			else
				pNode = mAttachNode->createChildSceneNode(
					mNamePrefix + name);
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Processing Node: " +
				mNamePrefix + name);
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Processing Node: prefix: " +
				mNamePrefix);
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Processing Node: name: " + name);
		} else {
			// Let Ogre choose the name
			Ogre::String pname = mNamePrefix += name;
			if (pParent)
				pNode = pParent->createChildSceneNode(pname);
			else
				pNode = mAttachNode->createChildSceneNode(
					pname);
			pNode->getUserObjectBindings().setUserAny("object_name",
								  name);
		}
	}

	// Process other attributes

	// Process position (?)
	if (auto pElement = XMLNode.child("position")) {
		pNode->setPosition(parseVector3(pElement));
	}

	// Process rotation (?)
	if (auto pElement = XMLNode.child("rotation")) {
		pNode->setOrientation(parseQuaternion(pElement));
	}

	// Process scale (?)
	if (auto pElement = XMLNode.child("scale")) {
		pNode->setScale(parseVector3(pElement));
	}

	// Process lookTarget (?)
	if (auto pElement = XMLNode.child("lookTarget"))
		processLookTarget(pElement, pNode);

	// Process trackTarget (?)
	if (auto pElement = XMLNode.child("trackTarget"))
		processTrackTarget(pElement, pNode);

	// process physics
	{
		if (auto e = XMLNode.child("userData")) {
			String collisionType;
			String collisionFile;
			Ogre::String parentCollisionType;
			JPH::ShapeRefC shape;
			JPH::ShapeRefC parentShape;
			for (auto pElement : e.children("property")) {
				String name = getAttrib(pElement, "name");
				String type = getAttrib(pElement, "type");
				String data = getAttrib(pElement, "data");
				Ogre::Any value;
				if (type == "bool")
					value = StringConverter::parseBool(
						data);
				else if (type == "float")
					value = StringConverter::parseReal(
						data);
				else if (type == "int")
					value = StringConverter::parseInt(data);
				else
					value = data;
				if (name == "collisionType") {
					collisionType =
						Ogre::any_cast<Ogre::String>(
							value);
					pNode->getUserObjectBindings()
						.setUserAny("collisionType",
							    collisionType);
				} else if (name == "collisionFile")
					collisionFile =
						Ogre::any_cast<Ogre::String>(
							value);
			}
			if (collisionFile.length() > 0) {
				if (collisionType == "convexHull") {
					shape = JoltPhysicsWrapper::getSingleton()
							.createConvexHullShape(
								collisionFile);
					pNode->getUserObjectBindings()
						.setUserAny("_btCollisionShape",
							    shape);
					OgreAssert(shape.GetPtr(),
						   "bad shape ");
				} else if (collisionType == "mesh") {
					shape = JoltPhysicsWrapper::getSingleton()
							.createMeshShape(
								collisionFile);
					pNode->getUserObjectBindings()
						.setUserAny("_btCollisionShape",
							    shape);
					OgreAssert(shape.GetPtr(),
						   "bad shape ");
				}
			}
			if (pParent)
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name +
					" parent name: " + pParent->getName());
			else
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name);
			if (collisionType.length() > 0) {
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + collisionType);
			}
			if (collisionType.length() > 0) {
				if (collisionType != "compound") {
					OgreAssert(shape.GetPtr(),
						   "bad shape ");
				}
				if (pParent) {
					Ogre::Any parentCollisionTypeA =
						pParent->getUserObjectBindings()
							.getUserAny(
								"collisionType");
					if (parentCollisionTypeA.has_value())
						parentCollisionType = Ogre::any_cast<
							Ogre::String>(
							parentCollisionTypeA);
					Ogre::Any parentShapeA =
						pParent->getUserObjectBindings()
							.getUserAny(
								"_btCollisionShape");
					if (parentShapeA.has_value()) {
						parentShape = Ogre::any_cast<
							JPH::ShapeRefC>(
							parentShapeA);
					}
				}
				if (pParent)
					LogManager::getSingleton().logMessage(
						"[SceneLoader] Node: " + name +
						" parent name: " +
						pParent->getName());
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name +
					" parentCollisionType: " +
					parentCollisionType);
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name +
					" collisionType: " + collisionType);
				if (collisionType == "compound") {
					shape = JoltPhysicsWrapper::getSingleton()
							.createMutableCompoundShape(
								std::vector<
									JPH::ShapeRefC>(),
								std::vector<
									Ogre::Vector3>(),
								std::vector<
									Ogre::Quaternion>());
					pNode->getUserObjectBindings()
						.setUserAny("_btCollisionShape",
							    shape);
					LogManager::getSingleton().logMessage(
						"[SceneLoader] Node: " + name +
						" created compound shape");

				} else if (pParent) {
					if (parentCollisionType == "compound") {
						LogManager::getSingleton().logMessage(
							"[SceneLoader] Node: " +
							name +
							" before addChildShape: " +
							pParent->getName());
						OgreAssert(parentShape.GetPtr(),
							   "Bad parent shape");
						OgreAssert(
							parentShape->GetType() ==
								JPH::EShapeType::
									Compound,
							"Bad parent shape");
						OgreAssert(shape.GetPtr(),
							   "Bad shape");
						JPH::Ref<JPH::Shape> parentShapeMutable(
							const_cast<JPH::Shape *>(
								parentShape
									.GetPtr()));
						JoltPhysicsWrapper::getSingleton()
							.addShapeToCompound(
								parentShapeMutable,
								shape,
								pNode->getPosition(),
								pNode->getOrientation());
						LogManager::getSingleton().logMessage(
							"[SceneLoader] Node: " +
							name +
							" addChildShape: " +
							pParent->getName());
					}
				}
			}
		}
	}
#if 0
	{
		if (auto e = XMLNode.child("userData")) {
			String collisionType;
			String collisionFile;
			Ogre::String parentCollisionType;
			std::shared_ptr<btCollisionShape> shape;
			std::shared_ptr<btCollisionShape> parentShape;
			for (auto pElement : e.children("property")) {
				String name = getAttrib(pElement, "name");
				String type = getAttrib(pElement, "type");
				String data = getAttrib(pElement, "data");
				Ogre::Any value;
				if (type == "bool")
					value = StringConverter::parseBool(
						data);
				else if (type == "float")
					value = StringConverter::parseReal(
						data);
				else if (type == "int")
					value = StringConverter::parseInt(data);
				else
					value = data;
				if (name == "collisionType") {
					collisionType =
						Ogre::any_cast<Ogre::String>(
							value);
					pNode->getUserObjectBindings()
						.setUserAny("collisionType",
							    collisionType);
				} else if (name == "collisionFile")
					collisionFile =
						Ogre::any_cast<Ogre::String>(
							value);
			}
			if (collisionFile.length() > 0) {
				Ogre::MeshPtr mesh =
					MeshManager::getSingletonPtr()->load(
						collisionFile, m_sGroupName);
				SceneLoaderPhysics::VertexIndexToShape toshape(
					mesh);
				if (collisionType == "convexHull") {
					shape.reset(toshape.createConvex());
					pNode->getUserObjectBindings()
						.setUserAny("_btCollisionShape",
							    shape);
					OgreAssert(shape.get(), "bad shape ");
				} else if (collisionType == "mesh") {
					shape.reset(toshape.createTrimesh());
					pNode->getUserObjectBindings()
						.setUserAny("_btCollisionShape",
							    shape);
					OgreAssert(shape.get(), "bad shape ");
				}
			}
			if (pParent)
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name +
					" parent name: " + pParent->getName());
			else
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name);
			if (collisionType.length() > 0) {
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + collisionType);
			}
			if (collisionType.length() > 0) {
				if (collisionType != "compound") {
					OgreAssert(shape.get(), "bad shape ");
				}
				if (pParent) {
					Ogre::Any parentCollisionTypeA =
						pParent->getUserObjectBindings()
							.getUserAny(
								"collisionType");
					if (parentCollisionTypeA.has_value())
						parentCollisionType = Ogre::any_cast<
							Ogre::String>(
							parentCollisionTypeA);
					Ogre::Any parentShapeA =
						pParent->getUserObjectBindings()
							.getUserAny(
								"_btCollisionShape");
					if (parentShapeA.has_value()) {
						parentShape = Ogre::any_cast<
							std::shared_ptr<
								btCollisionShape> >(
							parentShapeA);
					}
				}
				if (pParent)
					LogManager::getSingleton().logMessage(
						"[SceneLoader] Node: " + name +
						" parent name: " +
						pParent->getName());
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name +
					" parentCollisionType: " +
					parentCollisionType);
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " + name +
					" collisionType: " + collisionType);
				if (collisionType == "compound") {
					shape.reset(new btCompoundShape);
					pNode->getUserObjectBindings()
						.setUserAny("_btCollisionShape",
							    shape);
					LogManager::getSingleton().logMessage(
						"[SceneLoader] Node: " + name +
						" created compound shape");

				} else if (pParent) {
					if (parentCollisionType == "compound") {
						btTransform transform;
						transform.setIdentity();
						transform.setOrigin(Ogre::Bullet::convert(
							pNode->getPosition()));
						transform.setRotation(Ogre::Bullet::convert(
							pNode->getOrientation()));
						LogManager::getSingleton().logMessage(
							"[SceneLoader] Node: " +
							name +
							" before addChildShape: " +
							pParent->getName());
						OgreAssert(parentShape.get(),
							   "Bad parent shape");
						OgreAssert(
							parentShape
								->isCompound(),
							"Bad parent shape");
						OgreAssert(shape.get(),
							   "Bad shape");
						static_cast<btCompoundShape *>(
							parentShape.get())
							->addChildShape(
								transform,
								shape.get());
						LogManager::getSingleton().logMessage(
							"[SceneLoader] Node: " +
							name +
							" addChildShape: " +
							pParent->getName());
					}
				}
			}
		}
	}
#endif

	// Process node (*)
	for (auto pElement : XMLNode.children("node")) {
		processNode(pElement, pNode);
	}

	// Process entity (*)
	for (auto pElement : XMLNode.children("entity")) {
		processEntity(pElement, pNode);
	}

	// Process light (*)
	for (auto pElement : XMLNode.children("light")) {
		processLight(pElement, pNode);
	}

	// Process camera (*)
	for (auto pElement : XMLNode.children("camera")) {
		processCamera(pElement, pNode);
	}

	// Process particleSystem (*)
	for (auto pElement : XMLNode.children("particleSystem")) {
		processParticleSystem(pElement, pNode);
	}

	// Process billboardSet (*)
	for (auto pElement : XMLNode.children("billboardSet")) {
		processBillboardSet(pElement, pNode);
	}

	// Process plane (*)
	for (auto pElement : XMLNode.children("plane")) {
		processPlane(pElement, pNode);
	}

	// Process userDataReference (?)
	if (auto pElement = XMLNode.child("userData"))
		processUserData(pElement, pNode->getUserObjectBindings());

	// Process node animations (?)
	if (auto pElement = XMLNode.child("animations"))
		processNodeAnimations(pElement, pNode);
	// -- physics
	{
		LogManager::getSingleton().logMessage(
			"[SceneLoader] Node: " + pNode->getName() +
			" position: " +
			Ogre::StringConverter::toString(pNode->getPosition()));
		LogManager::getSingleton().logMessage(
			"[SceneLoader] Node: " + pNode->getName() +
			" orientation: " +
			Ogre::StringConverter::toString(
				pNode->getOrientation()));
	}
	Ogre::Any triggerAny =
		pNode->getUserObjectBindings().getUserAny("trigger");
	if (triggerAny.has_value()) {
		flecs::entity e = ECS::get().entity().child_of(base_e);
		Ogre::String event = Ogre::any_cast<Ogre::String>(triggerAny);
		float h = 2.0f, r = 3.0f;
		Ogre::Any anyh = pNode->getUserObjectBindings().getUserAny(
			"trigger_height");
		if (anyh.has_value())
			h = Ogre::any_cast<float>(anyh);
		Ogre::Any anyr = pNode->getUserObjectBindings().getUserAny(
			"trigger_radius");
		if (anyr.has_value())
			r = Ogre::any_cast<float>(anyr);
		e.set<ECS::EventTrigger>(
			{ pNode, Ogre::Vector3(0, 0, 0), h, r, event, pNode });
		Ogre::Node::ChildNodeMap children = pNode->getChildren();
		if (children.size() == 1)
			pNode->getUserObjectBindings().setUserAny(
				"target",
				static_cast<Ogre::SceneNode *>(children[0]));
		else if (children.size() == 0)
			pNode->getUserObjectBindings().setUserAny("target",
								  pNode);
		else {
			int i;
			bool ok = false;
			for (i = 0; i < children.size(); i++) {
				Ogre::Any nodeName =
					children[i]
						->getUserObjectBindings()
						.getUserAny("name");
				if (nodeName.has_value()) {
					Ogre::String snodeName =
						Ogre::any_cast<Ogre::String>(
							nodeName);
					if (snodeName == "target") {
						pNode->getUserObjectBindings().setUserAny(
							"target",
							static_cast<Ogre::SceneNode
									    *>(
								children[i]));
						ok = true;
						break;
					}
				}
			}
			if (!ok)
				pNode->getUserObjectBindings().setUserAny(
					"target",
					static_cast<Ogre::SceneNode *>(
						children[0]));
		}
	}
}

void SceneLoader::processLookTarget(pugi::xml_node &XMLNode, SceneNode *pParent)
{
	//! @todo Is this correct? Cause I don't have a clue actually

	// Process attributes
	String nodeName = getAttrib(XMLNode, "nodeName");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Look Target, nodeName: " + nodeName,
		LML_TRIVIAL);

	Node::TransformSpace relativeTo = Node::TS_PARENT;
	String sValue = getAttrib(XMLNode, "relativeTo");
	if (sValue == "local")
		relativeTo = Node::TS_LOCAL;
	else if (sValue == "parent")
		relativeTo = Node::TS_PARENT;
	else if (sValue == "world")
		relativeTo = Node::TS_WORLD;

	// Process position (?)
	Vector3 position;
	if (auto pElement = XMLNode.child("position"))
		position = parseVector3(pElement);

	// Process localDirection (?)
	Vector3 localDirection = Vector3::NEGATIVE_UNIT_Z;
	if (auto pElement = XMLNode.child("localDirection"))
		localDirection = parseVector3(pElement);

	// Setup the look target
	try {
		if (!nodeName.empty()) {
			SceneNode *pLookNode =
				mSceneMgr->getSceneNode(nodeName);
			position = pLookNode->_getDerivedPosition();
		}

		pParent->lookAt(position, relativeTo, localDirection);
	} catch (const Exception &e) {
		LogManager::getSingleton().logError("SceneLoader - " +
						    e.getDescription());
	}
}

void SceneLoader::processTrackTarget(pugi::xml_node &XMLNode,
				     SceneNode *pParent)
{
	// Process attributes
	String nodeName = getAttrib(XMLNode, "nodeName");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Track Target, nodeName: " + nodeName,
		LML_TRIVIAL);

	// Process localDirection (?)
	Vector3 localDirection = Vector3::NEGATIVE_UNIT_Z;
	if (auto pElement = XMLNode.child("localDirection"))
		localDirection = parseVector3(pElement);

	// Process offset (?)
	Vector3 offset = Vector3::ZERO;
	if (auto pElement = XMLNode.child("offset"))
		offset = parseVector3(pElement);

	// Setup the track target
	try {
		SceneNode *pTrackNode = mSceneMgr->getSceneNode(nodeName);
		pParent->setAutoTracking(true, pTrackNode, localDirection,
					 offset);
	} catch (const Exception &e) {
		LogManager::getSingleton().logError("SceneLoader - " +
						    e.getDescription());
	}
}

void SceneLoader::processEntity(pugi::xml_node &XMLNode, SceneNode *pParent)
{
	// Process attributes
	String name = getAttrib(XMLNode, "name");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Entity: " + name, LML_TRIVIAL);

	String meshFile = getAttrib(XMLNode, "meshFile");
	String staticGeometry = getAttrib(XMLNode, "static");
	String instancedManager = getAttrib(XMLNode, "instanced");
	String material = getAttrib(XMLNode, "material");
	bool castShadows = getAttribBool(XMLNode, "castShadows", true);
	bool visible = getAttribBool(XMLNode, "visible", true);

	// Create the entity
	MovableObject *pEntity = 0;
	Ogre::String pname = mNamePrefix + name;
	try {
		// If the Entity is instanced then the creation path is different
		if (!instancedManager.empty()) {
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Adding entity: " + name +
					" to Instance Manager: " +
					instancedManager,
				LML_TRIVIAL);

			// Load the Mesh to get the material name of the first submesh
			Ogre::MeshPtr mesh =
				MeshManager::getSingletonPtr()->load(
					meshFile, m_sGroupName);

			// Get the material name of the entity
			if (!material.empty())
				pEntity = mSceneMgr->createInstancedEntity(
					material, instancedManager);
			else
				pEntity = mSceneMgr->createInstancedEntity(
					mesh->getSubMesh(0)->getMaterialName(),
					instancedManager);

			pParent->attachObject(
				static_cast<InstancedEntity *>(pEntity));
		} else {
			pEntity = mSceneMgr->createEntity(pname, meshFile,
							  m_sGroupName);

			static_cast<Entity *>(pEntity)->setCastShadows(
				castShadows);
			static_cast<Entity *>(pEntity)->setVisible(visible);

			if (!material.empty())
				static_cast<Entity *>(pEntity)->setMaterialName(
					material);

			// If the Entity belongs to a Static Geometry group then it doesn't get attached to a node
			// * TODO * : Clean up nodes without attached entities or children nodes? (should be done afterwards if the hierarchy is being processed)
			if (!staticGeometry.empty()) {
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Adding entity: " + name +
						" to Static Group: " +
						staticGeometry,
					LML_TRIVIAL);
				mSceneMgr->getStaticGeometry(staticGeometry)
					->addEntity(
						static_cast<Entity *>(pEntity),
						pParent->_getDerivedPosition(),
						pParent->_getDerivedOrientation(),
						pParent->_getDerivedScale());
			} else {
				LogManager::getSingleton().logMessage(
					"[SceneLoader] pParent->attachObject(): " +
						name,
					LML_TRIVIAL);
				pParent->attachObject(
					static_cast<Entity *>(pEntity));
			}
		}
	} catch (const Exception &e) {
		LogManager::getSingleton().logError("SceneLoader - " +
						    e.getDescription());
		return;
	}

	// Process userDataReference (?)
	if (auto pElement = XMLNode.child("userData"))
		processUserData(pElement, pEntity->getUserObjectBindings());
}

void SceneLoader::processParticleSystem(pugi::xml_node &XMLNode,
					SceneNode *pParent)
{
	// Process attributes
	String name = getAttrib(XMLNode, "name");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Particle System: " + name,
		LML_TRIVIAL);

	String templateName = getAttrib(XMLNode, "template");

	if (templateName.empty())
		templateName = getAttrib(
			XMLNode, "file"); // compatibility with old scenes

	Ogre::String pname = mNamePrefix + name;
	// Create the particle system
	try {
		ParticleSystem *pParticles =
			mSceneMgr->createParticleSystem(pname, templateName);
		pParent->attachObject(pParticles);
	} catch (const Exception &e) {
		LogManager::getSingleton().logError("SceneLoader - " +
						    e.getDescription());
	}
}

void SceneLoader::processBillboardSet(pugi::xml_node &XMLNode,
				      SceneNode *pParent)
{
	//! @todo Implement this
}

void SceneLoader::processPlane(pugi::xml_node &XMLNode, SceneNode *pParent)
{
	String name = getAttrib(XMLNode, "name");

	Ogre::String pname = mNamePrefix + name;

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Plane: " + name, LML_TRIVIAL);
	Real distance = getAttribReal(XMLNode, "distance");
	Real width = getAttribReal(XMLNode, "width");
	Real height = getAttribReal(XMLNode, "height");
	int xSegments =
		StringConverter::parseInt(getAttrib(XMLNode, "xSegments"));
	int ySegments =
		StringConverter::parseInt(getAttrib(XMLNode, "ySegments"));
	int numTexCoordSets = StringConverter::parseInt(
		getAttrib(XMLNode, "numTexCoordSets"));
	Real uTile = getAttribReal(XMLNode, "uTile");
	Real vTile = getAttribReal(XMLNode, "vTile");
	String material = getAttrib(XMLNode, "material");
	bool hasNormals = getAttribBool(XMLNode, "hasNormals");
	Vector3 normal = parseVector3(XMLNode.child("normal"));
	Vector3 up = parseVector3(XMLNode.child("upVector"));

	Plane plane(normal, distance);
	MeshPtr res = MeshManager::getSingletonPtr()->createPlane(
		pname + "mesh", m_sGroupName, plane, width, height, xSegments,
		ySegments, hasNormals, numTexCoordSets, uTile, vTile, up);
	Entity *ent = mSceneMgr->createEntity(pname, pname + "mesh");

	ent->setMaterialName(material);

	pParent->attachObject(ent);
}

void SceneLoader::processFog(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage("[SceneLoader] Processing Fog...",
					      LML_TRIVIAL);

	// Process attributes
	Real expDensity = getAttribReal(XMLNode, "density", 0.001);
	Real linearStart = getAttribReal(XMLNode, "start", 0.0);
	Real linearEnd = getAttribReal(XMLNode, "end", 1.0);

	FogMode mode = FOG_NONE;
	String sMode = getAttrib(XMLNode, "mode");
	if (sMode == "none")
		mode = FOG_NONE;
	else if (sMode == "exp")
		mode = FOG_EXP;
	else if (sMode == "exp2")
		mode = FOG_EXP2;
	else if (sMode == "linear")
		mode = FOG_LINEAR;
	else
		mode = (FogMode)StringConverter::parseInt(sMode);

	// Process colourDiffuse (?)
	ColourValue colourDiffuse = ColourValue::White;

	if (auto pElement = XMLNode.child("colour"))
		colourDiffuse = parseColour(pElement);

	// Setup the fog
	mSceneMgr->setFog(mode, colourDiffuse, expDensity, linearStart,
			  linearEnd);
}

void SceneLoader::processSkyBox(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing SkyBox...", LML_TRIVIAL);

	// Process attributes
	String material = getAttrib(XMLNode, "material", "BaseWhite");
	Real distance = getAttribReal(XMLNode, "distance", 5000);
	bool drawFirst = getAttribBool(XMLNode, "drawFirst", true);

	// Process rotation (?)
	Quaternion rotation = Quaternion::IDENTITY;

	if (auto pElement = XMLNode.child("rotation"))
		rotation = parseQuaternion(pElement);

	// Setup the sky box
	mSceneMgr->setSkyBox(true, material, distance, drawFirst, rotation,
			     m_sGroupName);
}

void SceneLoader::processSkyDome(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing SkyDome...", LML_TRIVIAL);

	// Process attributes
	String material = XMLNode.attribute("material").value();
	Real curvature = getAttribReal(XMLNode, "curvature", 10);
	Real tiling = getAttribReal(XMLNode, "tiling", 8);
	Real distance = getAttribReal(XMLNode, "distance", 4000);
	bool drawFirst = getAttribBool(XMLNode, "drawFirst", true);
	bool active = getAttribBool(XMLNode, "active", false);
	if (!active)
		return;

	// Process rotation (?)
	Quaternion rotation = Quaternion::IDENTITY;
	if (auto pElement = XMLNode.child("rotation"))
		rotation = parseQuaternion(pElement);

	// Setup the sky dome
	mSceneMgr->setSkyDome(true, material, curvature, tiling, distance,
			      drawFirst, rotation, 16, 16, -1, m_sGroupName);
}

void SceneLoader::processSkyPlane(pugi::xml_node &XMLNode)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing SkyPlane...", LML_TRIVIAL);

	// Process attributes
	String material = getAttrib(XMLNode, "material");
	Real planeX = getAttribReal(XMLNode, "planeX", 0);
	Real planeY = getAttribReal(XMLNode, "planeY", -1);
	Real planeZ = getAttribReal(XMLNode, "planeZ", 0);
	Real planeD = getAttribReal(XMLNode, "planeD", 5000);
	Real scale = getAttribReal(XMLNode, "scale", 1000);
	Real bow = getAttribReal(XMLNode, "bow", 0);
	Real tiling = getAttribReal(XMLNode, "tiling", 10);
	bool drawFirst = getAttribBool(XMLNode, "drawFirst", true);

	// Setup the sky plane
	Plane plane;
	plane.normal = Vector3(planeX, planeY, planeZ);
	plane.d = planeD;
	mSceneMgr->setSkyPlane(true, plane, material, scale, tiling, drawFirst,
			       bow, 1, 1, m_sGroupName);
}

void SceneLoader::processLightRange(pugi::xml_node &XMLNode, Light *pLight)
{
	// Process attributes
	Real inner = getAttribReal(XMLNode, "inner");
	Real outer = getAttribReal(XMLNode, "outer");
	Real falloff = getAttribReal(XMLNode, "falloff", 1.0);

	// Setup the light range
	pLight->setSpotlightRange(Radian(inner), Radian(outer), falloff);
}

void SceneLoader::processLightAttenuation(pugi::xml_node &XMLNode,
					  Light *pLight)
{
	// Process attributes
	Real range = getAttribReal(XMLNode, "range");
	Real constant = getAttribReal(XMLNode, "constant");
	Real linear = getAttribReal(XMLNode, "linear");
	Real quadratic = getAttribReal(XMLNode, "quadratic");

	// Setup the light attenuation
	pLight->setAttenuation(range, constant, linear, quadratic);
}

void SceneLoader::processLightSourceSize(pugi::xml_node &XMLNode, Light *pLight)
{
	// Process attributes
	Real width = getAttribReal(XMLNode, "width");
	Real height = getAttribReal(XMLNode, "height");

	// Setup the light range
	pLight->setSourceSize(width, height);
}

void SceneLoader::setupPhysicsBody(Ogre::SceneNode *node,
				   Ogre::MovableObject *entity,
				   flecs::entity base_e)
{
	Ogre::SceneNode *pNode = node;
	Ogre::String name = node->getName();
	Ogre::String collisionType, collisionBodyType;
	JPH::ShapeRefC shape;
	float mass = 0.0f;
	JPH::ObjectLayer layer = Layers::MOVING;
	JPH::EMotionType motion = JPH::EMotionType::Static;
	Ogre::Any collisionTypeA =
		pNode->getUserObjectBindings().getUserAny("collisionType");
	Ogre::Any collisionBodyTypeA =
		pNode->getUserObjectBindings().getUserAny("collisionBodyType");
	Ogre::Any collisionBodyMassA =
		pNode->getUserObjectBindings().getUserAny("collisionBodyMass");
	Ogre::Any collisionShapeA =
		pNode->getUserObjectBindings().getUserAny("_btCollisionShape");
	if (!collisionTypeA.has_value())
		return;
	if (collisionBodyMassA.has_value())
		mass = Ogre::any_cast<float>(collisionBodyMassA);
	if (collisionTypeA.has_value() && collisionShapeA.has_value())
		collisionType = Ogre::any_cast<Ogre::String>(collisionTypeA);
	if (collisionBodyTypeA.has_value()) {
		collisionBodyType =
			Ogre::any_cast<Ogre::String>(collisionBodyTypeA);
		/* we can only create RigidBody where the entity is because how it works */
		if (collisionShapeA.has_value())
			shape = Ogre::any_cast<JPH::ShapeRefC>(collisionShapeA);
		if (collisionBodyType == "dynamic" ||
		    collisionBodyType == "kinematic" ||
		    collisionBodyType == "static") {
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Node: " + name +
				" collision body type: " + collisionBodyType);
			if (collisionBodyType == "static" ||
			    collisionBodyType == "kinematic") {
				mass = 0;
			}
			OgreAssert(shape.GetPtr(), "Bad shape");
			if (shape->GetType() == JPH::EShapeType::Compound) {
				const JPH::CompoundShape *cshape =
					static_cast<const JPH::CompoundShape *>(
						shape.GetPtr());
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " +
					pNode->getName() + " numChildShapes: " +
					Ogre::StringConverter::toString(
						cshape->GetNumSubShapes()));
			}
			if (collisionBodyType == "dynamic") {
				motion = JPH::EMotionType::Dynamic;
				layer = Layers::MOVING;
			} else if (collisionBodyType == "kinematic") {
				motion = JPH::EMotionType::Kinematic;
				layer = Layers::MOVING;
				mass = 0.0f;
			} else if (collisionBodyType == "static") {
				motion = JPH::EMotionType::Static;
				layer = Layers::NON_MOVING;
				mass = 0.0f;
			}
#if 1
			JPH::ShapeRefC com_shape =
				JoltPhysicsWrapper::getSingleton()
					.createOffsetCenterOfMassShape(
						Ogre::Vector3(0, 0, 0.45f),
						shape);
			JPH::BodyID id =
				JoltPhysicsWrapper::getSingleton().createBody(
					com_shape.GetPtr(), mass, pNode, motion,
					layer);
			JoltPhysicsWrapper::getSingleton().setFriction(id, 0.95f);
#else
			JPH::BodyID id =
				JoltPhysicsWrapper::getSingleton().createBody(
					shape.GetPtr(), mass, pNode, motion,
					layer);
#endif
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Node: " + pNode->getName() +
				" btRigidBody created: " + collisionBodyType);
			auto objWrapper = std::make_shared<JPH::BodyID>(id);
			pNode->getUserObjectBindings().setUserAny(
				"BtCollisionObject", objWrapper);
			pNode->getUserObjectBindings().setUserAny("bodyPointer",
								  id);
			/* do not add body to physics yet */
			base_e.set<JPH::BodyID>(id);
			base_e.set<ECS::CachedMass>({mass});
		} else if (collisionBodyType == "ghost") {
			/* TODO: later */
		}
	}
#if 0
	Ogre::SceneNode *pNode = node;
	Ogre::String name = node->getName();
	Ogre::String collisionType, collisionBodyType;
	std::shared_ptr<btCollisionShape> shape;
	float mass = 0.0f;
	int group = 2;
	int mask = 0x7fffffff;
	btVector3 inertia(0, 0, 0);
	Ogre::Any collisionTypeA =
		pNode->getUserObjectBindings().getUserAny("collisionType");
	Ogre::Any collisionBodyTypeA =
		pNode->getUserObjectBindings().getUserAny("collisionBodyType");
	Ogre::Any collisionBodyMassA =
		pNode->getUserObjectBindings().getUserAny("collisionBodyMass");
	Ogre::Any collisionShapeA =
		pNode->getUserObjectBindings().getUserAny("_btCollisionShape");
	if (!collisionTypeA.has_value())
		return;
	if (collisionBodyMassA.has_value())
		mass = Ogre::any_cast<float>(collisionBodyMassA);
	if (collisionTypeA.has_value() && collisionShapeA.has_value())
		collisionType = Ogre::any_cast<Ogre::String>(collisionTypeA);
	if (collisionBodyTypeA.has_value()) {
		collisionBodyType =
			Ogre::any_cast<Ogre::String>(collisionBodyTypeA);
		/* we can only create RigidBody where the entity is because how it works */
		if (collisionShapeA.has_value())
			shape = Ogre::any_cast<
				std::shared_ptr<btCollisionShape> >(
				collisionShapeA);
		if (collisionBodyType == "dynamic" ||
		    collisionBodyType == "kinematic" ||
		    collisionBodyType == "static") {
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Node: " + name +
				" collision body type: " + collisionBodyType);
			if (collisionBodyType == "static" ||
			    collisionBodyType == "kinematic") {
				mass = 0;
				inertia = btVector3(0, 0, 0);
			}
			if (mass != 0) {
				shape->calculateLocalInertia(mass, inertia);
			}
			OgreAssert(shape.get(), "Bad shape");
			Ogre::Bullet::RigidBodyState *state =
				new Ogre::Bullet::RigidBodyState(pNode);
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Node: " + pNode->getName() +
				" isCompound: " +
				Ogre::StringConverter::toString(
					shape->isCompound()));
			if (shape->isCompound())
				LogManager::getSingleton().logMessage(
					"[SceneLoader] Node: " +
					pNode->getName() + " numChildShapes: " +
					Ogre::StringConverter::toString(
						static_cast<btCompoundShape *>(
							shape.get())
							->getNumChildShapes()));
			btRigidBody *body = OGRE_NEW btRigidBody(
				mass, state, shape.get(), inertia);
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Node: " + pNode->getName() +
				" btRigidBody created: " + collisionBodyType);
#if 1
			if (collisionBodyType == "kinematic")
				body->setCollisionFlags(
					body->getCollisionFlags() |
					btCollisionObject::CF_KINEMATIC_OBJECT);
			else if (collisionBodyType == "static")
				body->setCollisionFlags(
					body->getCollisionFlags() |
					btCollisionObject::CF_STATIC_OBJECT);
#endif
			ECS::get<ECS::EngineData>()
				.mWorld->getBtWorld()
				->addRigidBody(body, 3, 0x7fffffff);
			body->setActivationState(DISABLE_DEACTIVATION);
			LogManager::getSingleton().logMessage(
				"[SceneLoader] Node: " + name +
				" btRigidBody added to world: " +
				collisionBodyType + " group: " +
				Ogre::StringConverter::toString(group) +
				" mash: " +
				Ogre::StringConverter::toString(mask));
			body->setUserPointer(OGRE_NEW SceneLoaderPhysics::
						     EntityCollisionListener{
							     entity, nullptr });
			auto objWrapper =
				std::make_shared<SceneLoaderPhysics::RigidBody>(
					body, ECS::get<ECS::EngineData>()
						      .mWorld->getBtWorld());
			pNode->getUserObjectBindings().setUserAny(
				"BtCollisionObject", objWrapper);
			pNode->getUserObjectBindings().setUserAny("bodyPointer",
								  body);
		} else if (collisionBodyType == "ghost") {
			/* TODO: later */
		}
	}
#endif
}

void SceneLoader::processUserData(pugi::xml_node &XMLNode,
				  UserObjectBindings &userData)
{
	// Process node (*)
	for (auto pElement : XMLNode.children("property")) {
		String name = getAttrib(pElement, "name");
		String type = getAttrib(pElement, "type");
		String data = getAttrib(pElement, "data");

		Any value;
		if (type == "bool")
			value = StringConverter::parseBool(data);
		else if (type == "float")
			value = StringConverter::parseReal(data);
		else if (type == "int")
			value = StringConverter::parseInt(data);
		else
			value = data;

		userData.setUserAny(name, value);
	}
}

void SceneLoader::processNodeAnimations(pugi::xml_node &XMLNode,
					SceneNode *pParent)
{
	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Node Animations for SceneNode: " +
			pParent->getName(),
		LML_TRIVIAL);

	// Process node animations (*)
	for (auto pElement : XMLNode.children("animation")) {
		processNodeAnimation(pElement, pParent);
	}
}

void SceneLoader::processNodeAnimation(pugi::xml_node &XMLNode,
				       SceneNode *pParent)
{
	// Process node animation (*)

	// Construct the animation name
	String name = getAttrib(XMLNode, "name");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Node Animation: " + name,
		LML_TRIVIAL);

	Real length = getAttribReal(XMLNode, "length");

	Animation *anim = mSceneMgr->createAnimation(name, length);

	bool enable = getAttribBool(XMLNode, "enable", false);
	bool loop = getAttribBool(XMLNode, "loop", false);

	String interpolationMode = getAttrib(XMLNode, "interpolationMode");

	if (interpolationMode == "linear")
		anim->setInterpolationMode(Animation::IM_LINEAR);
	else if (interpolationMode == "spline")
		anim->setInterpolationMode(Animation::IM_SPLINE);
	else
		LogManager::getSingleton().logError(
			"SceneLoader - Invalid interpolationMode: " +
			interpolationMode);

	String rotationInterpolationMode =
		getAttrib(XMLNode, "rotationInterpolationMode");

	if (rotationInterpolationMode == "linear")
		anim->setRotationInterpolationMode(Animation::RIM_LINEAR);
	else if (rotationInterpolationMode == "spherical")
		anim->setRotationInterpolationMode(Animation::RIM_SPHERICAL);
	else
		LogManager::getSingleton().logError(
			"SceneLoader - Invalid rotationInterpolationMode: " +
			rotationInterpolationMode);

	// create a track to animate the camera's node
	NodeAnimationTrack *track = anim->createNodeTrack(0, pParent);
	pParent->setInitialState();

	// Process keyframes (*)
	for (auto pElement : XMLNode.children("keyframe")) {
		processKeyframe(pElement, track);
	}

	// create a new animation state to track this
	auto animState = mSceneMgr->createAnimationState(name);
	animState->setEnabled(enable);
	animState->setLoop(loop);
}

void SceneLoader::processKeyframe(pugi::xml_node &XMLNode,
				  NodeAnimationTrack *pTrack)
{
	// Process node animation keyframe (*)
	Real time = getAttribReal(XMLNode, "time");

	LogManager::getSingleton().logMessage(
		"[SceneLoader] Processing Keyframe: " +
			StringConverter::toString(time),
		LML_TRIVIAL);

	auto keyframe = pTrack->createNodeKeyFrame(time);

	// Process translation (?)
	if (auto pElement = XMLNode.child("position")) {
		Vector3 translation = parseVector3(pElement);
		keyframe->setTranslate(translation);
	}

	// Process rotation (?)
	//Quaternion rotation = Quaternion::IDENTITY;
	if (auto pElement = XMLNode.child("rotation")) {
		Quaternion rotation = parseQuaternion(pElement);
		keyframe->setRotation(rotation);
	}

	// Process scale (?)
	//Vector3 scale = parseVector3(XMLNode.child("scale"));
	if (auto pElement = XMLNode.child("scale")) {
		Vector3 scale = parseVector3(pElement);
		keyframe->setScale(scale);
	}
}

void SceneLoader::exportScene(SceneNode *rootNode, const String &outFileName)
{
	pugi::xml_document XMLDoc; // character type defaults to char
	auto comment = XMLDoc.append_child(pugi::node_comment);
	comment.set_value(
		StringUtil::format(" exporter: Plugin_Scene %d.%d.%d ",
				   OGRE_VERSION_MAJOR, OGRE_VERSION_MINOR,
				   OGRE_VERSION_PATCH)
			.c_str());
	auto scene = XMLDoc.append_child("scene");
	scene.append_attribute("formatVersion") = "1.1";
	scene.append_attribute("sceneManager") =
		rootNode->getCreator()->getTypeName().c_str();

	auto nodes = scene.append_child("nodes");

	for (auto c : rootNode->getChildren())
		writeNode(nodes, static_cast<SceneNode *>(c));

	//writeNode(nodes, rootNode);

	XMLDoc.save_file(outFileName.c_str());
}

static void write(pugi::xml_node &node, const Vector3 &v)
{
	node.append_attribute("x") = StringConverter::toString(v.x).c_str();
	node.append_attribute("y") = StringConverter::toString(v.y).c_str();
	node.append_attribute("z") = StringConverter::toString(v.z).c_str();
}

static void write(pugi::xml_node &node, const ColourValue &c)
{
	node.append_attribute("r") = StringConverter::toString(c.r).c_str();
	node.append_attribute("g") = StringConverter::toString(c.g).c_str();
	node.append_attribute("b") = StringConverter::toString(c.b).c_str();
	node.append_attribute("a") = StringConverter::toString(c.a).c_str();
}

void SceneLoader::writeNode(pugi::xml_node &parentXML, const SceneNode *n)
{
	auto nodeXML = parentXML.append_child("node");
	if (!n->getName().empty())
		nodeXML.append_attribute("name") = n->getName().c_str();

	auto pos = nodeXML.append_child("position");
	write(pos, n->getPosition());

	auto scale = nodeXML.append_child("scale");
	write(scale, n->getScale());

	auto rot = nodeXML.append_child("rotation");
	rot.append_attribute("qw") =
		StringConverter::toString(n->getOrientation().w).c_str();
	rot.append_attribute("qx") =
		StringConverter::toString(n->getOrientation().x).c_str();
	rot.append_attribute("qy") =
		StringConverter::toString(n->getOrientation().y).c_str();
	rot.append_attribute("qz") =
		StringConverter::toString(n->getOrientation().z).c_str();

	for (auto mo : n->getAttachedObjects()) {
		if (auto c = dynamic_cast<Camera *>(mo)) {
			auto camera = nodeXML.append_child("camera");
			camera.append_attribute("name") = c->getName().c_str();
			auto clipping = camera.append_child("clipping");
			clipping.append_attribute("near") =
				StringConverter::toString(
					c->getNearClipDistance())
					.c_str();
			clipping.append_attribute("far") =
				StringConverter::toString(
					c->getFarClipDistance())
					.c_str();
			continue;
		}

		if (auto l = dynamic_cast<Light *>(mo)) {
			auto light = nodeXML.append_child("light");
			light.append_attribute("name") = l->getName().c_str();
			light.append_attribute("castShadows") =
				StringConverter::toString(l->getCastShadows())
					.c_str();

			if (!l->isVisible())
				light.append_attribute("visible") = "false";

			auto diffuse = light.append_child("colourDiffuse");
			write(diffuse, l->getDiffuseColour());
			auto specular = light.append_child("colourSpecular");
			write(specular, l->getSpecularColour());
			switch (l->getType()) {
			case Light::LT_POINT:
				light.append_attribute("type") = "point";
				break;
			case Light::LT_DIRECTIONAL:
				light.append_attribute("type") = "directional";
				break;
			case Light::LT_SPOTLIGHT:
				light.append_attribute("type") = "spot";
				break;
			case Light::LT_RECTLIGHT:
				light.append_attribute("type") = "rect";
				break;
			}

			if (l->getType() != Light::LT_DIRECTIONAL) {
				auto range = light.append_child("lightRange");
				range.append_attribute("inner") =
					StringConverter::toString(
						l->getSpotlightInnerAngle())
						.c_str();
				range.append_attribute("outer") =
					StringConverter::toString(
						l->getSpotlightOuterAngle())
						.c_str();
				range.append_attribute("falloff") =
					StringConverter::toString(
						l->getSpotlightFalloff())
						.c_str();
				auto atten =
					light.append_child("lightAttenuation");
				atten.append_attribute("range") =
					StringConverter::toString(
						l->getAttenuationRange())
						.c_str();
				atten.append_attribute("constant") =
					StringConverter::toString(
						l->getAttenuationConstant())
						.c_str();
				atten.append_attribute("linear") =
					StringConverter::toString(
						l->getAttenuationLinear())
						.c_str();
				atten.append_attribute("quadratic") =
					StringConverter::toString(
						l->getAttenuationQuadric())
						.c_str();
			}

			continue;
		}

		if (auto e = dynamic_cast<Entity *>(mo)) {
			auto entity = nodeXML.append_child("entity");
			entity.append_attribute("name") = e->getName().c_str();
			entity.append_attribute("meshFile") =
				e->getMesh()->getName().c_str();

			if (!e->isVisible())
				entity.append_attribute("visible") = "false";

			// Heuristic: assume first submesh is representative
			auto sub0mat = e->getSubEntity(0)->getMaterial();
			if (sub0mat !=
			    e->getMesh()->getSubMesh(0)->getMaterial())
				entity.append_attribute("material") =
					sub0mat->getName().c_str();
			continue;
		}

		LogManager::getSingleton().logWarning(
			"SceneLoader - unsupported MovableType " +
			mo->getMovableType());
	}

	// recurse
	for (auto c : n->getChildren())
		writeNode(nodeXML, static_cast<SceneNode *>(c));
}
