/*
* $Id: scene.cpp 4835 2018-01-29 10:44:24Z antonin.misek-skoda-auto.cz $
*
* Description  : One line description of file.
* Author       : Vaclav Kyba <mail/Jabber: vaseo1@gmail.com>  <ICQ: 98576293>
*
* Purpose      :
*     Long description of what the file is for.
*/


#include "kernel.h"
#include "scene.h"
#include "eventhandler.h"
#include "module.h"
#include "evtstructs.h"
#include "scenemanager.h"

#include <unordered_set>
#include <unordered_map>

using namespace VRUT;

Scene::~Scene()
{
	if (jsCtxVariants && msgSink)
		msgSink->PostEvent(GET_EVT_JAVASCRIPT_RELEASE_CTX((JSScriptingContextPtr)jsCtxVariants));
}

void Scene::GetNodeIDs(std::vector<NODE_ID> & nodes) const
{
	sceneNodesMutex.ReadLock();
	for (auto &node : sceneNodes)
	{
		if (node)
			nodes.push_back(node->GetID());
	}
	sceneNodesMutex.ReadUnlock();
}

void Scene::GetNodeIDs(std::vector<NODE_ID> & nodes, const SceneNode::SCENENODE_TYPE type) const
{
	sceneNodesMutex.ReadLock();
	if (type==SceneNode::LIGHT)
		nodes.insert(nodes.end(), lightNodeIDs.begin(), lightNodeIDs.end());
	else
	{
		for (auto &node : sceneNodes)
		{
			if (node && (node->IsOfType(type)))
				nodes.push_back(node->GetID());
		}
	}
	sceneNodesMutex.ReadUnlock();
}

void Scene::GetChildrenNodeIDs(std::vector<NODE_ID> & nodes, const NODE_ID nodeID) const
{
	sceneNodesMutex.ReadLock();
	SceneNode * parent=getNode(nodeID);
	if (parent)
	{
		WriteLocker wlock(*parent);
		nodes.insert(nodes.end(), parent->children.begin(), parent->children.end());
	}
	sceneNodesMutex.ReadUnlock();
}

void Scene::GetNodeIDs(std::vector<NODE_ID> & nodes, const wxString &name, const bool useMask) const
{
	sceneNodesMutex.ReadLock();
	if (useMask)
	{
		for (auto &node : sceneNodes)
			if (node && node->name.Lower().Matches(name.Lower()))
				nodes.push_back(node->GetID());
	}
	else
	{
		for (auto &node : sceneNodes)
			if (node && (node->name==name))
				nodes.push_back(node->GetID());
	}
	sceneNodesMutex.ReadUnlock();
}

void Scene::GetTopology(std::vector<bool> &out_node_existency, std::vector<NODE_ID> &out_node_parent)
{
	sceneNodesMutex.ReadLock();

	//allocate arrays
	out_node_existency.resize(sceneNodes.size(), false);
	out_node_parent.resize(sceneNodes.size(), NODE_ID_NONE);

	// Lock the nodes
	for (auto &node : sceneNodes)
		if (node)
			node->ReadLock();

	// Get the data
	for (size_t i = 0; i < sceneNodes.size(); ++i)
	{
		if (sceneNodes[i])
		{
			out_node_existency[i] = true;
			out_node_parent[i] = sceneNodes[i]->GetParentID();
		}
	}

	// Unlock the nodes
	for (auto &node : sceneNodes)
		if (node)
			node->ReadUnlock();
	sceneNodesMutex.ReadUnlock();

	// Check the consistency
	assert(CheckTopology(out_node_existency, out_node_parent));
}

bool Scene::CheckTopology(const std::vector<bool> &node_existency, const std::vector<NODE_ID> &node_parent)
{
	if (node_existency.size() != node_parent.size())
		return false;				// Inconsistent size

	NODE_ID root_id = NODE_ID_NONE;
	bool at_least_one_node_exists = false;
	for (size_t i = 0; i < node_existency.size(); i++)
	{
		if (!node_existency[i])
			continue;
		at_least_one_node_exists = true;
		if (node_parent[i] == NODE_ID_NONE)
		{
			if (root_id == NODE_ID_NONE)
				root_id = NODE_ID(i);
			else
				return false;		// Two roots
		}
		else
		{
			if (node_parent[i] >= node_existency.size())
				return false;		// The parent is out of the list of nodes
			if (!node_existency[node_parent[i]])
				return false;		// The parent does not exist
		}
	}

	if (!at_least_one_node_exists)
		return true;				// Empty tree is consistent

	if (root_id == NODE_ID_NONE)
		return false;				// No root

	// Check whether there is a loop. For each node, try to find the root of the tree
	// the node is in. If there is no loop, we check each node once at most, which
	// means we cannot check more than the max number of nodes when looking for the root.
	for (size_t i = 0; i < node_existency.size(); i++)
	{
		if (!node_existency[i])
			continue;

		NODE_ID current_node = NODE_ID(i);
		size_t checked_nodes = 1;
		for (;;)
		{
			if (checked_nodes > node_existency.size())
				return false;		// There is a loop

			if (node_parent[current_node] == NODE_ID_NONE)
				break;		// It is already checked that this is a root

			current_node = node_parent[current_node];
			checked_nodes++;
		}
	}

	return true;		// Consistent
}


bool Scene::CastRay(const Ray & ray, RayIntersectionInfo &info)
{
	info.dist = FLT_MAX;
	info.nodeID = NODE_ID_NONE;
	sceneNodesMutex.ReadLock();
	geometriesMutex.ReadLock();
	std::vector<std::pair<NODE_ID, Matrix> > nodeQueue;
	SceneNode *node=getNode(GetRootID());
	if (!node)
		return NODE_ID_NONE;
	Matrix worldMat=*node->GetLocalTransMatrix();
	while (true)
	{
		if (!node || !node->IsActive())
		{
			if (!nodeQueue.empty())
			{
				worldMat=nodeQueue.back().second;
				node=getNode(nodeQueue.back().first);
				nodeQueue.pop_back();
				continue;
			}
			break;
		}
		Matrix nodeWorldMat((*node->GetLocalTransMatrix())*worldMat);
		if (node->GetType()==SceneNode::GEOMETRY)
		{
			const GeometryNode *gnode=(const GeometryNode *)node;
			const Geometry *geo=getGeometry(gnode->GetGeometryID());
			if (geo)
			{
				float dist = FLT_MAX;
				if (geo->GetAABB().Transform(&nodeWorldMat).Intersects(ray, 0, info.dist, &dist) && (dist<info.dist))
				{
					Matrix invWorldMat(nodeWorldMat);
					invWorldMat.InvertThisPrecise();

					Vector3 modelOrigin(invWorldMat.TransformCoord(ray.origin));
					Vector3 modelDir(invWorldMat.TransformNormal(ray.direction).Normalize());
					const Ray modelRay(modelOrigin, modelDir);
					RayIntersectionInfo gInfo;
					if (geo->CastRay(modelRay, gInfo))
					{
						Vector3 intersection(modelRay.origin + modelRay.direction * gInfo.dist);
						intersection = nodeWorldMat.TransformCoord(intersection);
						dist = (ray.origin - intersection).Length();
						if (dist < info.dist && dist > 0) {
							info.nodeID = gnode->GetID();
							info.dist = dist;
							info.intersection = intersection;
							info.normal = nodeWorldMat.TransformNormal(gInfo.normal);
							info.primitiveIndex = gInfo.primitiveIndex;
							info.index = gInfo.index;
							info.geometryID = geo->GetID();
						}
					}
				}
			}
		}
		WriteLocker wlock(*node);
		if (!node->children.empty())
		{
			NODE_ID tmpNodeId=node->children.front();
			std::list<NODE_ID>::iterator it=node->children.begin();
			for (++it; it!=node->children.end(); ++it)
				nodeQueue.push_back(std::pair<NODE_ID, Matrix>(*it, nodeWorldMat));
			worldMat=nodeWorldMat;
			node=getNode(tmpNodeId);
		}
		else
		{
			if (!nodeQueue.empty())
			{
				worldMat=nodeQueue.back().second;
				node=getNode(nodeQueue.back().first);
				nodeQueue.pop_back();
			}
			else
				break;
		}
	}
	geometriesMutex.ReadUnlock();
	sceneNodesMutex.ReadUnlock();
	return info.nodeID != NODE_ID_NONE;
}

bool Scene::CastRay(const Ray & ray, std::vector<bool> &ignoreNodes, RayIntersectionInfo &info)
{
	info.dist = FLT_MAX;
	info.nodeID = NODE_ID_NONE;
	sceneNodesMutex.ReadLock();
	if (ignoreNodes.size()<sceneNodes.size())
		ignoreNodes.resize(sceneNodes.size(), false);
	geometriesMutex.ReadLock();
	std::vector<std::pair<NODE_ID, Matrix> > nodeQueue;
	SceneNode *node=getNode(GetRootID());
	if (!node)
		return NODE_ID_NONE;
	Matrix worldMat=*node->GetLocalTransMatrix();
	while (true)
	{
		if (!node || !node->IsActive() || ignoreNodes[node->GetID()])
		{
			if (!nodeQueue.empty())
			{
				worldMat=nodeQueue.back().second;
				node=getNode(nodeQueue.back().first);
				nodeQueue.pop_back();
				continue;
			}
			break;
		}
		Matrix nodeWorldMat((*node->GetLocalTransMatrix())*worldMat);
		if (node->GetType()==SceneNode::GEOMETRY)
		{
			const GeometryNode *gnode=(const GeometryNode *)node;
			const Geometry *geo=getGeometry(gnode->GetGeometryID());
			if (geo)
			{
				float dist = FLT_MAX;
				if (geo->GetAABB().Transform(&nodeWorldMat).Intersects(ray, 0, info.dist, &dist) && (dist<info.dist))
				{
					Matrix invWorldMat(nodeWorldMat);
					invWorldMat.InvertThisPrecise();

					Vector3 modelOrigin(invWorldMat.TransformCoord(ray.origin));
					Vector3 modelDir(invWorldMat.TransformNormal(ray.direction).Normalize());
					const Ray modelRay(modelOrigin, modelDir);
					RayIntersectionInfo gInfo;
					if (geo->CastRay(modelRay, gInfo))
					{
						Vector3 intersection(modelRay.origin + modelRay.direction * gInfo.dist);
						intersection = nodeWorldMat.TransformCoord(intersection);
						dist = (ray.origin - intersection).Length();
						if (dist < info.dist && dist > 0) {
							info.nodeID = gnode->GetID();
							info.dist = dist;
							info.intersection = intersection;
							info.normal = nodeWorldMat.TransformNormal(gInfo.normal);
							info.primitiveIndex = gInfo.primitiveIndex;
							info.index = gInfo.index;
							info.geometryID = geo->GetID();
						}
					}
				}
			}
		}
		WriteLocker wlock(*node);
		if (!node->children.empty())
		{
			NODE_ID tmpNodeId=node->children.front();
			std::list<NODE_ID>::iterator it=node->children.begin();
			for (++it; it!=node->children.end(); ++it)
				nodeQueue.push_back(std::pair<NODE_ID, Matrix>(*it, nodeWorldMat));
			worldMat=nodeWorldMat;
			node=getNode(tmpNodeId);
		}
		else
		{
			if (!nodeQueue.empty())
			{
				worldMat=nodeQueue.back().second;
				node=getNode(nodeQueue.back().first);
				nodeQueue.pop_back();
			}
			else
				break;
		}
	}
	geometriesMutex.ReadUnlock();
	sceneNodesMutex.ReadUnlock();
	return info.nodeID != NODE_ID_NONE;
}

AABB Scene::GetAABB(NODE_ID nodeID)
{
	AABB aabb;
	std::vector<std::pair<NODE_ID, Matrix> > nodeQueue;
	sceneNodesMutex.ReadLock();
	geometriesMutex.ReadLock();
	SceneNode *node=getNode((nodeID!=NODE_ID_NONE)?nodeID:GetRootID());
	if (!node)
	{
		geometriesMutex.ReadUnlock();
		sceneNodesMutex.ReadUnlock();
		return aabb;
	}
	Matrix worldMat = getWorldTransMatrix(getNode(node->GetParentID()));
	while (true)
	{
		if (!node || !node->IsActive())
		{
			if (!nodeQueue.empty())
			{
				worldMat=nodeQueue.back().second;
				node=getNode(nodeQueue.back().first);
				nodeQueue.pop_back();
				continue;
			}
			break;
		}
		Matrix nodeWorldMat((*node->GetLocalTransMatrix())*worldMat);
		if (node->GetType()==SceneNode::GEOMETRY)
		{
			const GeometryNode *gnode=(const GeometryNode *)node;
			const Geometry *geo=getGeometry(gnode->GetGeometryID());
			if (!geo)
			{
				if (!nodeQueue.empty())
				{
					worldMat=nodeQueue.back().second;
					node=getNode(nodeQueue.back().first);
					nodeQueue.pop_back();
					continue;
				}
				break;
			}
			aabb.Merge(geo->GetAABB().Transform(&nodeWorldMat));
		}
		WriteLocker wlock(*node);
		if (!node->children.empty())
		{
			NODE_ID tmpNodeId=node->children.front();
			std::list<NODE_ID>::iterator it=node->children.begin();
			for (++it; it!=node->children.end(); ++it)
				nodeQueue.push_back(std::pair<NODE_ID, Matrix>(*it, nodeWorldMat));
			worldMat=nodeWorldMat;
			node=getNode(tmpNodeId);
		}
		else
		{
			if (!nodeQueue.empty())
			{
				worldMat=nodeQueue.back().second;
				node=getNode(nodeQueue.back().first);
				nodeQueue.pop_back();
			}
			else
				break;
		}
	}
	geometriesMutex.ReadUnlock();
	sceneNodesMutex.ReadUnlock();
	return aabb;
}

MATERIAL_ID Scene::AddMaterial(Material * material)
{
	if (!material)
	{
		LOGERROR(wxT("<Scene>No material to add"));
		return MATERIAL_ID_NONE;
	}

	material->UpdateBumpMapFlag();
	if (material->imageName.EndsWith(EnvironmentProbe::CubeMapNameSuffix))
		material->texFlags |= Material::TEX_CUBE_MAP;

	MATERIAL_ID mid;
	WriteLocker wlock(materialsMutex);
	MaterialIDHashMap::iterator old = materialIDMap.find(material->name);

	if (old != materialIDMap.end())
	{
#ifndef NDEBUG
		LOGDEBUG(wxT("<Scene>Material with name '") + material->name + wxT("' already exists, replacing with new one"));
#endif
		material->mid =	mid = old->second;
		materials[old->second].assign(material);
	}
	else
	{
		if (!material->imageName.empty())
		{
			const_smart_pointer<Image> img = KERNEL->sceneManager->GetImage(material->imageName, false);
			material->FixBlendingAndCubeMap(img.getPtr());
		}

		material->mid = mid = MATERIAL_ID(materials.size());
		materials.push_back(smart_pointer<Material>());
		materials.back().assign(material);
		materialIDMap[material->name] = mid;
	}

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_MATERIAL_ADDED(sceneID, mid));
#ifndef NDEBUG
	if (wxLog::GetLogLevel() >= wxLOG_Debug)
		LOGDEBUG(wxT("<Scene>Material '")+material->name+wxString::Format(wxT("' ID '%lu' added to scene '%lu'"), (unsigned long)mid, (unsigned long)sceneID));
#endif
	return mid;
}


bool Scene::RemoveMaterial(MATERIAL_ID mid)
{
	WriteLocker wlock(materialsMutex);
	if (getMaterial(mid))
	{
		if (materialNodes.size()>mid)
			if (materialNodes.at(mid).size()>0)
				return false;
		RemoveMaterialFromVariants(materials[mid]->name);
		materialIDMap.erase(materials[mid]->name);
		materials[mid].assign((Material *)NULL);
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_MATERIAL_REMOVED(sceneID, mid));
		return true;
	}
	return false;
}


bool Scene::ConvertMaterialTransparencyToReflexivity(const wxString &materialName)
{
	smart_pointer<Material> material;
	GetMaterial(material, GetMaterialID(materialName));
	if (material && !(material->flags & Material::FLAT_REFLECTION))
	{
		Material *newMaterial = material->Clone();
		newMaterial->flags |= Material::FLAT_REFLECTION;
		newMaterial->flags &= (unsigned)~(unsigned(0))^Material::BLENDED;
		newMaterial->reflectivity = 1-material->diffuse._v[3];
		newMaterial->diffuse._v[3] = 1.0f;
		AddMaterial(newMaterial);
		return true;
	}
	return false;
}


GEOMETRY_ID Scene::AddGeometry(Geometry * geometry)
{
	if (!geometry)
	{
		LOGERROR(wxT("<Scene>No geometry to add"));
		return GEOMETRY_ID_NONE;
	}
	///Build local AABB for geometry
	geometry->BuildAABB();

	GEOMETRY_ID geomID=geometry->GetID();
	if (geomID!=GEOMETRY_ID_NONE)
	{
		geometriesMutex.WriteLock();
		if (geometries.size()<=geomID)
			geometries.resize(geomID + 1, smart_pointer<Geometry>());
#ifndef NDEBUG
		else if (geometries.at(geomID))
		{
			LOGDEBUG(wxString::Format(wxT("<Scene>Geometry with ID '%lu' already exists, replacing with new one"), (unsigned long)geomID));
		}
#endif
		geometries[geomID].assign(geometry);
		geometriesMutex.WriteUnlock();
	}
	else
	{
		geomID = geometry->gid = GetNewGeometryID();
		geometriesMutex.WriteLock();
		if (geometries.size()<=geomID)
			geometries.resize(geomID + 1, smart_pointer<Geometry>());
		geometries[geomID].assign(geometry);
		geometriesMutex.WriteUnlock();
	}
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_GEOMETRY_ADDED(sceneID, geomID));

#ifndef NDEBUG
	if (wxLog::GetLogLevel() >= wxLOG_Debug)
		LOGDEBUG(wxString::Format(wxT("<Scene>Geometry ID '%lu' added to scene '%lu'"), (unsigned long)geomID, (unsigned long)sceneID));
#endif
	return geomID;
}


GEOMETRY_ID Scene::CopyGeometry(GEOMETRY_ID gid)
{
	smart_pointer<Geometry> geo;
	GetGeometry(geo, gid);
	if (!geo)
		return GEOMETRY_ID_NONE;
	Geometry *newGeo=geo->Clone();
	newGeo->gid=GEOMETRY_ID_NONE;
	return AddGeometry(newGeo);
}


bool Scene::RemoveGeometry(GEOMETRY_ID geomID, GEOMETRY_ID geomIDnew)
{
	WriteLocker wlock(geometriesMutex);
	if (getGeometry(geomID))
	{
		if ((geomID!=geomIDnew) && getGeometry(geomIDnew))
		{
			//replace in all nodes
			if (geometriesNodes.size()>geomID)
			{
				while (!geometriesNodes[geomID].empty())
					SetGeometry(*geometriesNodes[geomID].begin(), geomIDnew);
			}
		}
		else
		{
			if (geometriesNodes.size()>geomID)
				if (geometriesNodes.at(geomID).size()>0)
					return false;
		}
		geometries[geomID].assign((Geometry *)NULL);
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_GEOMETRY_REMOVED(sceneID, geomID));
		return true;
	}
	return false;
}

/////////////////////////////////////////////////////////////
/// Node operations                                       ///
/////////////////////////////////////////////////////////////

NODE_ID Scene::Insert(smart_pointer<SceneNode> node, NODE_ID targetID)
{
	SceneNode * parentNode = NULL;
	wxASSERT_MSG((node->parentID == NODE_ID_NONE), wxT("<Scene>Inserting node that is probably already part of a scene"));

	sceneNodesMutex.WriteLock();
	if (node->GetID()==NODE_ID_NONE)
	{
		node.ptr->id = GetNewNodeID();
		parentNode = getNode(targetID);
		if (sceneNodes.size()<=node->GetID())
			sceneNodes.resize(node->GetID()+1, smart_pointer<SceneNode>(NULL));
	}
	else
	{
		parentNode = getNode(targetID);
		if (sceneNodes.size()>node->GetID())
		{
			if (sceneNodes.at(node->GetID()))
				Remove(node->GetID());
		}
		else
		{
			//fix sceneNodesCount (for example on cluster client)
			NODE_ID tmpID = sceneNodesCount;
			if (tmpID <= node->GetID())
				sceneNodesCount += node->GetID() + 1 - tmpID;//TODO: Fix it! It is not fully correct
			//end of fix sceneNodesCount
			sceneNodes.resize(node->GetID() + 1, smart_pointer<SceneNode>(NULL));
		}
	}
	if (!parentNode)
	{
		//wxASSERT_MSG(targetID == NODE_ID_NONE, wxT("<Scene>Invalid target node ID when inserting"));
		rootID = node->id;
	}
	else
	{
		//update active status
		if (parentNode->GetType()==SceneNode::SWITCH)
		{
			//solve switch node
			size_t activeChild=((SwitchNode *)parentNode)->GetActiveChild();
			node.ptr->active=((activeChild==(size_t)-2) || (activeChild==parentNode->children.size()));
		}
		WriteLocker wlock(*parentNode);
		parentNode->children.push_back(node->id);
	}

	node.ptr->parentID = targetID;
	sceneNodes[node->GetID()].assign(node.ptr);

	if (node->IsOfType(SceneNode::GEOMETRY))
	{
		sceneNodesMutex.WriteUnlock();
		GeometryNode *gnode=(GeometryNode*)node.getPtr();
		if (gnode->GetGeometryID() != GEOMETRY_ID_NONE)
		{
			geometriesMutex.WriteLock();
			addGeometryNodeReference(gnode->GetGeometryID(), node->id);
			geometriesMutex.WriteUnlock();
		}
		if (gnode->GetMaterialID() != MATERIAL_ID_NONE)
			addMaterialNodeReference(gnode->GetMaterialID(), node->id);
	}
	else if (node->IsOfType(SceneNode::BACKGROUND))
	{
		sceneNodesMutex.WriteUnlock();
		if (((BackgroundNode *)node.getPtr())->GetMaterialID() != MATERIAL_ID_NONE)
			addMaterialNodeReference(((BackgroundNode *)node.getPtr())->GetMaterialID(), node->id);
		backgroundsMutex.WriteLock();
		backgrounds.push_back(node);
		backgroundsMutex.WriteUnlock();
	}
	else if (node->IsOfType(SceneNode::LIGHT))
	{
		lightNodeIDs.push_back(node->id);
		sceneNodesMutex.WriteUnlock();
	}
	else
		sceneNodesMutex.WriteUnlock();

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_INSERTED(sceneID, node->id, targetID));
#ifndef NDEBUG
	if (wxLog::GetLogLevel() >= wxLOG_Debug)
		LOGDEBUG(wxT("<Scene>Node '")+node->GetName()+wxString::Format(wxT("' ID '%lu' added to scene '%lu'"), (unsigned long)node->GetID(), (unsigned long)sceneID));
#endif
	return node->id;
}


NODE_ID Scene::Copy(Scene * srcScene, NODE_ID srcID, NODE_ID targetID)
{
	ReadLocker *rlocker=NULL;
	if (!srcScene)
		srcScene = this;
	else if (srcScene!=this)
		rlocker=new ReadLocker(srcScene->sceneNodesMutex);

	if (srcID == NODE_ID_NONE)
	{
		if (srcScene != this)
			srcID = srcScene->GetRootID();
		else
		{
			LOGERROR(wxString::Format(wxT("<Scene>Cannot copy entire scene into the same scene root...")));
			if (rlocker)
				delete rlocker;
			return NODE_ID_NONE;
		}
	}

	ReadLocker rlock(sceneNodesMutex);

	if (targetID == NODE_ID_NONE)
		targetID = GetRootID();

	///First is where to insert in this scene, second is node to insert from given scene
	std::deque<std::pair<NODE_ID, NODE_ID> > queue;
	std::pair<NODE_ID, NODE_ID> rootPair(targetID, srcID);
	queue.push_back(rootPair);

	NODE_ID retID=NODE_ID_NONE;

	while (!queue.empty())
	{
		std::pair<NODE_ID, NODE_ID> actPair = queue.front();
		queue.pop_front();
		SceneNode * node = srcScene->getNode(actPair.second);
		if (node)
		{
			///Make a copy of children list if it is modified during next steps
			SceneNode::NodeIDList childrenCpy(node->children);

			SceneNode * cloneNode = node->Clone();
			///Get rid of connections to old scene
			cloneNode->id = NODE_ID_NONE;
			cloneNode->parentID = NODE_ID_NONE;
			cloneNode->children.clear();
			if (srcScene != this)
			{
				if (cloneNode->IsOfType(SceneNode::GEOMETRY))
				{
					((GeometryNode *)cloneNode)->geometryID = GEOMETRY_ID_NONE;
					((GeometryNode *)cloneNode)->materialID = MATERIAL_ID_NONE;
				}
				else if (cloneNode->IsOfType(SceneNode::BACKGROUND))
					((BackgroundNode *)cloneNode)->materialID = MATERIAL_ID_NONE;
			}
			sceneNodesMutex.ReadUnlock();
			NODE_ID parID = Insert(cloneNode, actPair.first);
			if (retID==NODE_ID_NONE)
				retID=parID;
			sceneNodesMutex.ReadLock();

			if (srcScene != this)
			{
				if (cloneNode->IsOfType(SceneNode::GEOMETRY))
				{
					GeometryNode * gnode = (GeometryNode *)node;
					///Check if material should be copied to a new scene
					if (gnode->GetMaterialID() != MATERIAL_ID_NONE)
					{
						smart_pointer<Material> mat;
						srcScene->GetMaterial(mat, gnode->GetMaterialID());
						if (mat)
						{
							Material * cloneMat = mat->Clone();
							///Check if name unique before inserting
							while (GetMaterialID(cloneMat->name) != MATERIAL_ID_NONE)
								cloneMat->name.append(wxT("_"));
							if (!mat->name.IsSameAs(cloneMat->name))
							{
								LOGVERBOSE(wxT("<Scene>Material '")+mat->name+wxString::Format(wxT("' from scene ID %lu is copied under new name '"),
										(unsigned long)srcScene->GetID())+cloneMat->name+wxT("'"));
							}
							MATERIAL_ID matID = AddMaterial(cloneMat);
							SetMaterial(parID, matID);
						}
						else
							LOGERROR(wxT("<Scene>Invalid material defined in node '")+node->GetName()+wxString::Format(wxT("' from scene ID %lu"), (unsigned long)srcScene->GetID()));
					}

					///Check if geometry should be copied to a new scene
					if (gnode->GetGeometryID() != GEOMETRY_ID_NONE)
					{
						smart_pointer<Geometry> geom;
						srcScene->GetGeometry(geom, gnode->GetGeometryID());
						if (geom)
						{
							Geometry * cloneGeom = geom->Clone();
							GEOMETRY_ID geomID = GetNewGeometryID();
							cloneGeom->gid=geomID;
							AddGeometry(cloneGeom);
							SetGeometry(parID, geomID);
						}
						else
							LOGERROR(wxT("<Scene>Invalid geometry defined in node '")+node->GetName()+wxString::Format(wxT("' from scene ID %lu"), (unsigned long)srcScene->GetID()));
					}
				}
				else if (cloneNode->IsOfType(SceneNode::BACKGROUND))
				{
					BackgroundNode * bnode = (BackgroundNode *)node;
					///Check if material should be copied to a new scene
					if (bnode->GetMaterialID() != MATERIAL_ID_NONE)
					{
						smart_pointer<Material> mat;
						srcScene->GetMaterial(mat, bnode->GetMaterialID());
						if (mat)
						{
							Material * cloneMat = mat->Clone();
							///Check if name unique before inserting
							while (GetMaterialID(cloneMat->name) != MATERIAL_ID_NONE)
								cloneMat->name.append(wxT("_"));
							if (!mat->name.IsSameAs(cloneMat->name))
							{
								LOGVERBOSE(wxT("<Scene>Material '")+mat->name+wxString::Format(wxT("' from scene ID %lu is copied under new name '"),
										(unsigned long)srcScene->GetID())+cloneMat->name+wxT("'"));
							}
							MATERIAL_ID matID = AddMaterial(cloneMat);
							SetMaterial(parID, matID);
						}
						else
							LOGERROR(wxT("<Scene>Invalid material defined in node '")+node->GetName()+wxString::Format(wxT("' from scene ID %lu"), (unsigned long)srcScene->GetID()));
					}
				}
			}
			//workaround for cluster
			//maybe better is to solve the problem on cluster module side
			if ((srcScene == this) && msgSink)
			{
				if (cloneNode->IsOfType(SceneNode::GEOMETRY))
				{
					msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_GEOMETRY_CHANGED(GetID(), cloneNode->GetID(), ((GeometryNode *)cloneNode)->GetGeometryID()));
					msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_MATERIAL_CHANGED(GetID(), cloneNode->GetID(), ((GeometryNode *)cloneNode)->GetMaterialID()));
				}
				else if (cloneNode->IsOfType(SceneNode::LIGHT))
				{
					msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_LIGHT_CHANGED(GetID(), cloneNode->GetID()));
				}
				else if (cloneNode->IsOfType(SceneNode::BACKGROUND))
					msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_MATERIAL_CHANGED(GetID(), cloneNode->GetID(), ((BackgroundNode *)cloneNode)->GetMaterialID()));
				else if (cloneNode->IsOfType(SceneNode::SWITCH))
					msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_SWITCH_ACTIVECHILD_CHANGED(GetID(), cloneNode->GetID(), ((SwitchNode *)cloneNode)->GetActiveChild()));
			}
			if (msgSink && !node->GetLocalTransMatrix()->IsUnit())
				msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_TRANSFORMED(GetID(), cloneNode->GetID(), *node->GetLocalTransMatrix()));
			//end of workaround for cluster

			for (auto childID : childrenCpy)
			{
				std::pair<NODE_ID, NODE_ID> childPair(parID, childID);
				queue.push_back(childPair);
			}
		}
	}
	/*	const SceneNode * oldRoot = srcScene->GetNode(targetID);
	if (oldRoot)
	return GetNodeID(oldRoot->GetUid());
	else*/
	if (rlocker)
		delete rlocker;
	return retID;
}


void Scene::Move(NODE_ID srcID, NODE_ID targetID)
{
	ReadLocker rlock(this->sceneNodesMutex);

	if (srcID == GetRootID())
	{
		LOGERROR(wxT("<Scene>Source cannot be root when moving nodes"));
		return;
	}
	SceneNode * srcNode = getNode(srcID);
	SceneNode * tgtNode = getNode(targetID);
	if (!srcNode)
	{
		LOGERROR(wxString::Format(wxT("<Scene>Source not defined properly when moving nodes (id=%lu)"), (unsigned long)srcID));
		return;
	}
	if (!tgtNode)
	{
		LOGERROR(wxString::Format(wxT("<Scene>Target not defined properly when moving nodes (id=%lu)"), (unsigned long)targetID));
		return;
	}
	wxASSERT_MSG(srcNode->parentID != NODE_ID_NONE, wxT("<Scene>Source node must have parents when moving"));
	//check cyclic dependency
	SceneNode * cycNode=tgtNode;
	while (cycNode)
	{
		if (cycNode->GetID()==srcID)
		{
			LOGERROR(wxString::Format(wxT("<Scene>Cannot move node into it's children")));
			return;
		}
		cycNode=getNode(cycNode->parentID);
	}
	///Disconnect from old parent
	SceneNode* parNode = getNode(srcNode->parentID);
	{
		WriteLocker wlock(*parNode);
		SceneNode::NodeIDList::iterator nodePos=std::find(parNode->children.begin(), parNode->children.end(), srcID);
		if (nodePos==parNode->children.end())
		{
			LOGERROR(wxString::Format(wxT("<Scene>Move: Detected scene corruption (id=%lu)"), (unsigned long)srcID));
			return;
		}
		parNode->children.erase(nodePos);
	}
	//update switch
	if (parNode->GetType()==SceneNode::SWITCH)
		SetSwitchActiveChild(parNode->GetID(), ((SwitchNode *)parNode)->GetActiveChild());
	///Connect to new parent
	srcNode->parentID = targetID;
	{
		WriteLocker wlock(*tgtNode);
		tgtNode->children.push_back(srcID);
	}
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_MOVED(sceneID, srcID, targetID));
	//solve switch node
	if (tgtNode->IsOfType(SceneNode::SWITCH))
		SetSwitchActiveChild(tgtNode->GetID(), ((SwitchNode *)tgtNode)->GetActiveChild());
	//maybe better way is to use the same code like in insert, but it not emits event...
		//size_t activeChild=((SwitchNode *)tgtNode)->GetActiveChild();
		//node.ptr->active=(((activeChild==(size_t)-2) || (activeChild==parentNode->children.size())));
}


void Scene::Remove(NODE_ID id)
{
	WriteLocker wlock(this->sceneNodesMutex);
	SceneNode * node = getNode(id);
	if (!node)
	{
		LOGERROR(wxString::Format(wxT("<Scene>Cannot remove node with ID %lu, node not found in scene"), (unsigned long)id));
		return;
	}

	while (!node->children.empty())
		Remove(*node->children.begin());

	SceneNode* parNode = getNode(node->parentID);
	if (parNode)
	{
		SceneNode::NodeIDList::iterator nodePos=std::find(parNode->children.begin(), parNode->children.end(), id);
		if (nodePos==parNode->children.end())
		{
			LOGERROR(wxString::Format(wxT("<Scene>Remove: Detected scene corruption (id=%lu)"), (unsigned long)id));
			return;
		}
		parNode->children.erase(nodePos);
		if (parNode->IsOfType(SceneNode::LOD))
			((LODNode *)parNode)->RemoveDistance(id);
		else if (parNode->IsOfType(SceneNode::SWITCH))
			SetSwitchActiveChild(parNode->GetID(), ((SwitchNode *)parNode)->GetActiveChild());
	}
	node->parentID = NODE_ID_NONE;
	removeNode(id);
}

void Scene::Remove(const std::vector<NODE_ID> & ids)
{
	WriteLocker wlock(this->sceneNodesMutex);

	// Groupe nodes by theirs parent
	typedef std::unordered_set<NODE_ID> NodeIdSet;
	typedef std::unordered_map<NODE_ID, NodeIdSet> ParentToChildrenMap;
	ParentToChildrenMap parentToChildrenMap;
	for (NODE_ID nodeId : ids)
	{
		SceneNode *node = getNode(nodeId);
		if (!node)
		{
			LOGERROR(wxString::Format(wxT("<Scene>Cannot remove node with ID %lu, node not found in scene"), (unsigned long)nodeId));
			continue;
		}
		NODE_ID parentId = node->GetParentID();
		parentToChildrenMap[parentId].insert(nodeId);
	}

	for (auto &mapPair : parentToChildrenMap)
	{
		NODE_ID parentId = mapPair.first;
		NodeIdSet &children = mapPair.second;

		// Remove children
		for (NODE_ID childId : children)
		{
			SceneNode *childNode = getNode(childId);
			if (childNode)
			{
				while (!childNode->children.empty())
					Remove(*childNode->children.begin());
			}
		}

		// Remove nodes from their parent
		std::vector<NODE_ID> nodeIdsToRemove;
		SceneNode *parentNode = getNode(parentId);
		if (parentNode)
		{
			SceneNode::NodeIDList::iterator it = parentNode->children.begin();
			while ((it != parentNode->children.end()) && !children.empty())
			{
				// Remove node or move to next
				//NODE_ID childId = *it;
				if (children.erase(*it))
				{
					if (parentNode->IsOfType(SceneNode::LOD))
					{
						((LODNode *)parentNode)->RemoveDistance(*it);
					}
					SceneNode *childNode = getNode(*it);
					assert(childNode);
					childNode->parentID = NODE_ID_NONE;
					nodeIdsToRemove.push_back(*it);
					it = parentNode->children.erase(it);
				}
				else
				{
					++it;
				}
			}
			if (parentNode->IsOfType(SceneNode::SWITCH))
			{
				SetSwitchActiveChild(parentNode->GetID(), ((SwitchNode *)parentNode)->GetActiveChild());
			}
		}
		else if (NODE_ID_NONE == parentId)
		{
			std::copy(children.begin(), children.end(), nodeIdsToRemove.end());
			children.clear();
		}
		for (NODE_ID nodeId : children)
		{
			// Node could have been removed as a part of a subtree. Test it before logging the error.
			if (getNode(nodeId))
			{
				LOGERROR(wxString::Format(wxT("<Scene>Remove: Detected scene corruption (id=%lu)"), (unsigned long)nodeId));
			}
		}

		// Remove nodes themselfs
		for (NODE_ID nodeId : nodeIdsToRemove)
		{
			removeNode(nodeId);
		}
	}
}

void Scene::SetName(NODE_ID id, const wxString & name)
{
	ReadLocker(this->sceneNodesMutex);
	SceneNode * node = getNode(id);
	if (node && node->name != name)
	{
		wxString oldName = node->name;
		node->name = CloneWxString(name);
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_NAME_CHANGED(sceneID, id));
#ifndef NDEBUG
		if (wxLog::GetLogLevel() >= wxLOG_Debug)
			LOGDEBUG(wxT("<Scene>Node '")+oldName+wxString::Format(wxT("' ID '%lu' renamed to '"), (unsigned long)id)+node->name+wxString::Format(wxT("' in scene '%lu'"), (unsigned long)sceneID));
#endif
	}
	else
	{
		if (!node)
			LOGERROR(wxString::Format(wxT("<Scene>Cannot rename node with ID %lu, node not found in scene"), (unsigned long)id));
	}
}


bool Scene::setTransformation(NODE_ID id, SceneNode * node, Matrix matrix)
{
	if (node)
	{
		//process synchronous events
		if (msgSink)
		{
			//module now can change transformation matrix - it should be problem if more modules are registered to the event
			// correct solution is two (three?, iterative?) pass algorithm - if some module will change matrix then all other modules will be called again
			// note that iterative solution can significantly slow down whole VRUT and must be done very carefully
			size_t index = Event::EVT_SCENE_NODE_TRANSFORM_PRE;
			SceneNodeTransformPreData preEventData(this, node, &matrix);
			for (auto &mid : msgSink->moduleListeners[index])
			{
				Module * module = KERNEL->GetModule(mid);
				if (module)
				{
					switch (module->processSyncEvent(Event::EVT_SCENE_NODE_TRANSFORM_PRE, (void*)&preEventData))
					{
					case Module::Allowed:
						// Nothing changed, try the next module
						break;
					case Module::Changed:
						// Not resolved yet, so it tries the next module
						break;
					case Module::Denied:
						// Transformation is not allowed at all
						return false;
					}
				}
			}
		}
		//do the transformation
		node->SetLocalTransMatrix(matrix);
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_TRANSFORMED(sceneID, id, matrix));
	}
	else
	{
		LOGERROR(wxString::Format(wxT("<Scene>Cannot transform node with ID %lu, node not found in scene"), (unsigned long)id));
		return false;
	}
	return true;
}

void Scene::SetTransformation(NODE_ID id, const Matrix & matrix)
{
	ReadLocker rlock(sceneNodesMutex);
	setTransformation(id, getNode(id), matrix);
}


void Scene::Transform(NODE_ID id, const Matrix & matrix)
{
	ReadLocker rlock(sceneNodesMutex);
	SceneNode* node = getNode(id);
	if (node)
		setTransformation(id, node, *(node->GetLocalTransMatrix())*matrix);
	else
		LOGERROR(wxString::Format(wxT("<Scene>Cannot transform node with ID %lu, node not found in scene"), (unsigned long)id));
}

void Scene::TranslateAbs(NODE_ID id, const Vector3 & transl)
{
	ReadLocker rlock(sceneNodesMutex);
	SceneNode * node = getNode(id);
	if (node)
	{
		Matrix trans(*node->GetLocalTransMatrix());
		trans._m41 = transl.x;
		trans._m42 = transl.y;
		trans._m43 = transl.z;
		setTransformation(id, node, trans);
	}
	else
		LOGERROR(wxString::Format(wxT("<Scene>Cannot transform node with ID %lu, node not found in scene"), (unsigned long)id));
}

void Scene::SetGeometry(NODE_ID id, GEOMETRY_ID geometryID)
{
	ReadLocker rlock(sceneNodesMutex);
	GeometryNode *node = (GeometryNode *)getNode(id);

	if (node && node->IsOfType(SceneNode::GEOMETRY))
	{
		geometriesMutex.WriteLock();
		removeGeometryNodeReference(node->geometryID, id);
		node->geometryID = geometryID;
		addGeometryNodeReference(geometryID, id);
		geometriesMutex.WriteUnlock();
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_GEOMETRY_CHANGED(sceneID, id, geometryID));
#ifndef NDEBUG
		LOGDEBUG(wxT("<Scene>Node '")+node->GetName()+wxString::Format(wxT("' ID '%lu' has new geometry ID '%lu' in scene '%lu'"), (unsigned long)node->GetID(), (unsigned long)geometryID, (unsigned long)sceneID));
#endif
	}
	else
		LOGERROR(wxString::Format(wxT("<Scene>Cannot set geometry to node with ID %lu, node not found in scene"), (unsigned long)id));
}

size_t Scene::DereferenceGeometries(std::vector<NODE_ID> nodesIDs)
{
	size_t dereferencedCount = 0;
	for (auto nodeID : nodesIDs)
	{
		smart_pointer<SceneNode> snode;
		GetNode(snode, nodeID);
		if (snode && (snode->GetType() == SceneNode::GEOMETRY))
		{
			GEOMETRY_ID gid = ((GeometryNode*)snode.getPtr())->GetGeometryID();
			geometriesMutex.ReadLock();
			if ((gid<geometriesNodes.size()) && (geometriesNodes[gid].size()>1))
			{
				geometriesMutex.ReadUnlock();
				GEOMETRY_ID gid2 = CopyGeometry((GEOMETRY_ID)gid);
				if (gid2 != GEOMETRY_ID_NONE)
				{
					SetGeometry(nodeID, gid2);
					++dereferencedCount;
				}
			}
			else
				geometriesMutex.ReadUnlock();
		}
	}
	return dereferencedCount;
}


void Scene::SetMaterial(NODE_ID id, MATERIAL_ID mid)
{
	WriteLocker wlock(materialsMutex);
	smart_pointer<SceneNode> snode;
	GetNode(snode, id);
	if (!snode)
	{
		LOGERROR(wxString::Format(wxT("<Scene>Cannot set material to node with ID %lu, node not found in scene"), (unsigned long)id));
		return;
	}

	if (snode->IsOfType(SceneNode::GEOMETRY))
	{
		smart_pointer<GeometryNode> node ((GeometryNode *)snode.ptr);
		removeMaterialNodeReference(node->materialID, id);
		node.ptr->materialID = mid;
		addMaterialNodeReference(mid, id);
	}
	else if (snode->IsOfType(SceneNode::BACKGROUND))
	{
		smart_pointer<BackgroundNode> node = (smart_pointer<BackgroundNode>)snode;
		removeMaterialNodeReference(node->materialID, id);
		node.ptr->materialID = mid;
		addMaterialNodeReference(mid, id);
	}
	else
	{
		LOGERROR(wxString::Format(wxT("<Scene>Cannot set material to node with type %i (ID %lu)"), snode->type, (unsigned long)id));
		return;
	}

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_MATERIAL_CHANGED(sceneID, id, mid));
#ifndef NDEBUG
	if (wxLog::GetLogLevel() >= wxLOG_Debug)
		LOGDEBUG(wxT("<Scene>Node '")+snode->GetName()+wxString::Format(wxT("' ID '%lu' has new material '"), (unsigned long)snode->GetID())+
				(getMaterial(mid) ? getMaterial(mid)->name : wxT("NULL"))+wxString::Format(wxT("' ID '%lu' in scene '%lu'"), (unsigned long)mid, (unsigned long)sceneID));
#endif
}


void Scene::SetLight(NODE_ID id, const Light & light) const
{
	smart_pointer<LightNode> snode;
	GetNode(snode, id);
	if (snode && snode->IsOfType(SceneNode::LIGHT))
	{
		// TODO There are several problems in the following line:
		//		- If the light node is used by another module, it's data is overriden while currently used,
		//			and holding a reference to the node does not help.
		//		- The original light is memory leaking. However, it cannot be deleted, because in may be in
		//			use by another module, see the previous point.
		(snode).ptr->light = light;

		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_LIGHT_CHANGED(sceneID, id));
#ifndef NDEBUG
		if (wxLog::GetLogLevel() >= wxLOG_Debug)
			LOGDEBUG(wxT("<Scene>Node '")+snode->GetName()+wxString::Format(wxT("' ID '%lu' has new light in scene '%lu'"), (unsigned long)id, (unsigned long)sceneID));
#endif
	}
	else
		// TODO The light variable is memory leaking.
		LOGERROR(wxString::Format(wxT("<Scene>Cannot set light to node with ID %lu, node not found in scene or is not of light type"), (unsigned long)id));
}

void Scene::SetLODDistance(NODE_ID LODAssemblyId, NODE_ID LODId, float distance)
{
	smart_pointer<LODNode> snode;
	GetNode(snode, LODAssemblyId);
	if (snode && snode->IsOfType(SceneNode::LOD))
	{
		(snode).ptr->SetDistance(LODId, distance);
	}
}

void Scene::SetSwitchActiveChild(NODE_ID switchID, size_t activeChild)
{
	smart_pointer<SceneNode> snode;
	GetNode(snode, switchID);
	if (snode && snode->IsOfType(SceneNode::SWITCH))
	{
		std::vector<NODE_ID> children;
		GetChildrenNodeIDs(children, switchID);
		if ((activeChild!=(size_t)-1) && (activeChild!=(size_t)-2) && (children.size()<=activeChild))
		{
			LOGWARNING(wxString::Format(wxT("<Scene>Not existing activeChild %lu in switch node with ID %lu"), (unsigned long)activeChild, (unsigned long)switchID));
			//activeChild=(size_t)-1;
		}
		((smart_pointer<SwitchNode>)snode).ptr->activeChild=activeChild;
		if ((activeChild==(size_t)-1) || (activeChild==(size_t)-2) || (children.size()<=activeChild))
		{
			bool active=(activeChild==(size_t)-2);
			for (auto childID : children)
				SetActive(childID, active, true);
		}
		else
		{
			size_t childID=0;
			for (auto childID2 : children)
			{
				SetActive(childID2, (activeChild==childID), true);
				++childID;
			}
		}
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_SWITCH_ACTIVECHILD_CHANGED(sceneID, switchID, activeChild));
	}
	else
		LOGERROR(wxString::Format(wxT("<Scene>Cannot set activeChild to switch node with ID %lu, node not found in scene or is not of switch type"), (unsigned long)switchID));
}

void Scene::SetActive(NODE_ID id, bool active, bool isImplied)
{
	smart_pointer<SceneNode> node;
	GetNode(node, id);
	if (node)
	{
		if (node->IsActive()!=active)
		{
			node.ptr->active=active;
			if (msgSink)
				msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_ACTIVE_CHANGED(sceneID, id, active, isImplied));
		}
	}
	else
		LOGERROR(wxString::Format(wxT("<Scene>SetActive: nodeID %lu not found"), (unsigned long)id));
}

void Scene::ChangeChildIndex(NODE_ID nodeID, size_t newIndex)
{
	{
		ReadLocker rlock(this->sceneNodesMutex);

		SceneNode * node = getNode(nodeID);
		if (!node)
		{
			LOGERROR(wxString::Format(wxT("<Scene>ChangeChildIndex: Node not exists (id=%lu)"), (unsigned long)nodeID));
			return;
		}
		NODE_ID parentID=node->GetParentID();
		SceneNode * parent = getNode(parentID);
		if (!parent)
		{
			LOGERROR(wxString::Format(wxT("<Scene>ChangeChildIndex: Node parent not exists (id=%lu)"), (unsigned long)nodeID));
			return;
		}
		///change the children list
		{
			WriteLocker wlock(*parent);
			SceneNode::NodeIDList::iterator nodePos=std::find(parent->children.begin(), parent->children.end(), nodeID);
			if (nodePos==parent->children.end())
			{
				LOGERROR(wxString::Format(wxT("<Scene>ChangeChildIndex: Detected scene corruption (id=%lu)"), (unsigned long)nodeID));
				return;
			}
			parent->children.erase(nodePos);
			size_t i=0;
			SceneNode::NodeIDList::iterator newPos=parent->children.begin();
			for ( ; newPos!=parent->children.end(); ++newPos, ++i)
			{
				if (i==newIndex)
					break;
			}
			parent->children.insert(newPos, nodeID);
			/*size_t i=0;
			for (SceneNode::NodeIDList::iterator pos=parent->children.begin(); pos!=parent->children.end(); ++pos, ++i)
			{
				if (i==newIndex)
				{
					NODE_ID prevVal=nodeID;
					for (; pos!=parent->children.end(); ++pos)
					{
						NODE_ID tmpVal=*pos;
						*pos=prevVal;
						if (tmpVal==nodeID)
							break;
						prevVal=tmpVal;
					}
					break;
				}
				if (*pos==nodeID)
				{
					for (; i<newIndex; ++i)
					{
						SceneNode::NodeIDList::iterator nextPos=pos;
						++nextPos;
						if (nextPos==parent->children.end())
						{
							LOGERROR(wxString::Format(wxT("<Scene>ChangeChildIndex: Detected scene corruption (id=%lu)"), (unsigned long)nodeID));
							return;
						}
						*pos=*nextPos;
						pos=nextPos;
					}
					*pos=nodeID;
					break;
				}
			}*/
/*			size_t i=0;
			int found=0;
			SceneNode::NodeIDList::iterator oldPos=parent->children.end();
			SceneNode::NodeIDList::iterator newPos=parent->children.begin();
			for (SceneNode::NodeIDList::iterator pos=parent->children.begin(); pos!=parent->children.end(); ++pos, ++i)
			{
				if (i==newIndex)
				{
					newPos=pos;
					++found;
					if (found>1)
						break;
				}
				if (*pos==nodeID)
				{
					if (found==0)
					{
						--i;
						++found;
					}
					else
					{
						oldPos=pos;
						++found;
						break;
					}
				}
			}
			if (oldPos==newPos)
				return;

			if (found!=2)
			{
				LOGERROR(wxString::Format(wxT("<Scene>ChangeChildIndex: Detected scene corruption (id=%lu)"), (unsigned long)nodeID));
				return;
			}

			parent->children.splice(newPos, parent->children, oldPos);*/
		}

		//update switch
		if (parent->GetType()==SceneNode::SWITCH)
			SetSwitchActiveChild(parent->GetID(), ((SwitchNode *)parent)->GetActiveChild());
	}
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_INDEX_CHANGED(sceneID, nodeID, newIndex));
}

void Scene::AddEnvironmentProbe(EnvironmentProbe *environmentProbe)
{
	if (!environmentProbe)
	{
		LOGERROR(wxT("<Scene>No Environment probe to add"));
		return;
	}

	ENVIRONMENT_PROBE_ID epid = environmentProbe->GetID();
	environmentProbesMutex.WriteLock();
	if (environmentProbes.size() <= epid)
		environmentProbes.resize(epid+1, NULL);
	if (environmentProbes[epid])
		LOGWARNING(wxString::Format(wxT("<Scene>Environment probe with ID '%lu' already exists, replacing with new one"), (unsigned long)epid));
	environmentProbes[epid].assign(environmentProbe);
	environmentProbesMutex.WriteUnlock();

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_ENVIRONMENT_PROBE_ADDED(sceneID, epid));

	LOGDEBUG(wxString::Format(wxT("<Scene>Environment probe ID '%lu' added to scene '%lu'"), (unsigned long)epid, (unsigned long)sceneID));
}

void Scene::AddEnvironmentProbe(NODE_ID nodeID, float nearPlaneDistance, float farPlaneDistance, float envProbeResolution, bool singleShot)
{
	bool probeFound=false;
	environmentProbesMutex.ReadLock();
	for (auto &probe : environmentProbes)
	{
		if (probe && (probe->GetAttachedNodeID() == nodeID))
		{
			EnableEnvironmentProbe(probe->GetID(), !probe->IsEnabled());
			SetSingleShotEnvironmentProbe(probe->GetID(), singleShot);
			probeFound = true;
		}
	}
	environmentProbesMutex.ReadUnlock();

	if (!probeFound)
	{
		smart_pointer<SceneNode> node;
		GetNode(node, nodeID);
		EnvironmentProbe *ep(new EnvironmentProbe(GetNewEnvironmentProbeID(), nodeID, nearPlaneDistance, farPlaneDistance, envProbeResolution, true, singleShot));
		AddEnvironmentProbe(ep);

		//If we create the probe directly for a geometry node, we set its material to use the probe for convenience.
		if (node->GetType() == SceneNode::GEOMETRY)
		{
			smart_pointer<GeometryNode> geomNode(node);
			smart_pointer <Material> cmat;
			GetMaterial(cmat, geomNode->GetMaterialID());

			if (cmat)
			{
				Material *mat=cmat->Clone();
				mat->imageName = EnvironmentProbe::BuildEnvironmentProbeName(node);
				mat->texFlags &= (unsigned)~(unsigned(0))^(Material::TEX_GEN_ENABLED | Material::TEX_UWRAP_REPEAT | Material::TEX_VWRAP_REPEAT);
				mat->texFlags |= (Material::TEX_GEN_REFLECTION | Material::TEX_CUBE_MAP | Material::TEX_UWRAP_CLAMP | Material::TEX_VWRAP_CLAMP); //Note: We set Clamp to eliminate cube edge seams.

				AddMaterial(mat);
			}
		}
	}
}

void Scene::AssignEnvironmentProbeTexToNodesMaterial(NODE_ID sourceEnvProbeNodeID, NODE_ID targetNodeID)
{
	environmentProbesMutex.ReadLock();
	for (auto &probe : environmentProbes)
	{
		if (probe->GetAttachedNodeID() == sourceEnvProbeNodeID)
		{
			smart_pointer<SceneNode> sourceNode;
			GetNode(sourceNode, sourceEnvProbeNodeID);

			smart_pointer<SceneNode> targetNode;
			GetNode(targetNode, targetNodeID);

			smart_pointer<GeometryNode> geomNode(targetNode);
			smart_pointer <Material> mat;
			GetMaterial(mat, geomNode->GetMaterialID());

			if (mat)
			{
				Material *clmat=mat->Clone();
				clmat->imageName = EnvironmentProbe::BuildEnvironmentProbeName(sourceNode);
				clmat->texFlags &= (unsigned)~(unsigned(0))^(Material::TEX_GEN_ENABLED | Material::TEX_UWRAP_REPEAT | Material::TEX_VWRAP_REPEAT);
				clmat->texFlags |= (Material::TEX_GEN_REFLECTION | Material::TEX_CUBE_MAP | Material::TEX_UWRAP_CLAMP | Material::TEX_VWRAP_CLAMP); //Note: We set Clamp to eliminate cube edge seams.
				// this should be changed
				msgSink->PostEvent(GET_EVT_SCENE_MATERIAL_ADD(sceneID, clmat));
			}
		}
	}
	environmentProbesMutex.ReadUnlock();
}

bool Scene::BindAndSetupEnvironmentProbe(ENVIRONMENT_PROBE_ID id, GLuint &environmentProbeTextureID)
{
#ifndef VRUT_HPC
	environmentProbesMutex.ReadLock();
	EnvironmentProbe *environmentProbe=getEnvironmentProbe(id);
	if (environmentProbe)
	{
		smart_pointer<SceneNode> node;
		GetNode(node, environmentProbe->GetAttachedNodeID());
		if (node)
		{
			environmentProbe->Bind();
			bool ret = environmentProbe->SetupAttachments();
			if (ret)
				environmentProbeTextureID = environmentProbe->GetTextureAttachmentOfType(GL_COLOR_ATTACHMENT0_EXT)->object;
			environmentProbesMutex.ReadUnlock();
			return ret;
		}
	}
	environmentProbesMutex.ReadUnlock();
#endif
	return false;
}

void Scene::EnableEnvironmentProbe(ENVIRONMENT_PROBE_ID id, bool enable)
{
	environmentProbesMutex.ReadLock();
	EnvironmentProbe *environmentProbe=getEnvironmentProbe(id);
	if (environmentProbe)
		environmentProbe->Enable(enable);
	environmentProbesMutex.ReadUnlock();
}

void Scene::SetSingleShotEnvironmentProbe(ENVIRONMENT_PROBE_ID id, bool singleShot)
{
	environmentProbesMutex.ReadLock();
	EnvironmentProbe *environmentProbe=getEnvironmentProbe(id);
	if (environmentProbe)
		environmentProbe->SetSingleShot(singleShot);
	environmentProbesMutex.ReadUnlock();
}

/*
Commented out because it must be solved inside renderer (probably inside BVH::cull) - one scene can have more cameras so it cannot be done globally
void Scene::UpdateVisibleLOD(NODE_ID LODAssemblyId, const Vector3 &camPos)
{
SceneNode *snode = getNode(LODAssemblyId);
if (snode && snode->IsOfType(SceneNode::LOD))
{
const SceneNode::NodeIDList * children=snode->GetChildren();
if (children->size()>0)
{
//find node to show
NODE_ID bestChildID=*children->begin();
float bestDist = ((LODNode*)snode)->GetDistance(bestChildID);
for (SceneNode::NodeIDList::const_iterator it=children->begin(); it!=children->end(); ++it)
{
float dist = ((LODNode*)snode)->GetDistance(*it);
if (dist<bestDist)//TODO: do real distance computation here
{
dist=bestDist;
bestChildID=*it;
}
}
//set nodes active as needed
for (SceneNode::NodeIDList::const_iterator it=children->begin(); it!=children->end(); ++it)
{
bool active=(*it==bestChildID);
SetActive(*it, active);
}
}
}
}*/

/////////////////////////////////////////////////////////////
/// Camera operations                                     ///
/////////////////////////////////////////////////////////////

void Scene::SetProjectionParams(NODE_ID camID, unsigned width, unsigned height, float nearPlane, float farPlane, float fov, int upFix)
{
	ReadLocker rlock(sceneNodesMutex);
	Camera * cam = (Camera *) getNode(camID);
	if (!cam || !cam->IsOfType(SceneNode::CAMERA))
	//{
	//	LOG(wxString::Format(wxT("<Scene::SetProjectionParams> camID=%lu, width %i, camera not exists="), (unsigned long)camID, width)+((!cam)?wxT("NULL"):wxT("node is not camera")));
		return;
	//}

	bool changed = false;
	bool nearFarChanged = false;
	if ((width && (cam->lastWidth != width)) || (height && (cam->lastHeight != height)) || (nearPlane && (cam->nearPlane != nearPlane)) || (farPlane && (cam->farPlane != farPlane))
		|| (fov && (cam->fov != fov)) || ((upFix>=0) && (cam->upFix != (Camera::UP_FIX)upFix)))
		changed=true;

	if (changed)
	{
		WriteLocker wlock(*cam);

		if (width && (cam->lastWidth != width))
		{
			cam->lastWidth = width;
			//LOG(wxString::Format(wxT("<Scene::SetProjectionParams> camID=%lu, width %i"), (unsigned long)camID, width));
		}
		if (height && (cam->lastHeight != height))
			cam->lastHeight = height;

		if (nearPlane && (cam->nearPlane != nearPlane))
		{
			nearFarChanged = true;
			cam->nearPlane = nearPlane;
		}
		if (farPlane && (cam->farPlane != farPlane))
		{
			nearFarChanged = true;
			cam->farPlane = farPlane;
		}
		if (fov && (cam->fov != fov))
			cam->fov = fov;
		if ((upFix>=0) && (cam->upFix != (Camera::UP_FIX)upFix))
			cam->SetUpFix((Camera::UP_FIX)upFix);

		/*// Meister: Set centered perspective again.
		cam->projectionType=Camera::PERSPECTIVE_CENTERED;*/
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_CAM_PROJECTION_CHANGED(sceneID, camID));
	}

	if (nearFarChanged)
		setCarpetCamsNearFar(camID, nearPlane, farPlane);
}

void Scene::SetPerspectiveParams(VRUT::NODE_ID camID, float nearDist, float farDist, const Vector3 &offset, int eye, float eyeSeparation, const Vector3 &pa, const Vector3 &pb, const Vector3 &pc, const Vector3 &eyeAxis, const Vector3& lookAt)
{
	ReadLocker rlock(sceneNodesMutex);
	Camera * cam = (Camera *) getNode(camID);
	if (!cam || !cam->IsOfType(SceneNode::CAMERA))
		return;

	WriteLocker wlock(*cam);

	cam->nearPlane=nearDist;
	cam->farPlane=farDist;
	cam->offset=offset;
	cam->eye=(Camera::EYE_TYPE)eye;
	cam->eyeSeparation=eyeSeparation;
	cam->pa=pa;
	cam->pb=pb;
	cam->pc=pc;
	cam->eyeAxis=eyeAxis;
	cam->lookAt = lookAt;
	//LOG(wxString::Format(wxT("<Scene::SetPerspectiveParams> camID=%i, eyeSeparation="), (int)camID)+TextWriter::WriteToWxString(eyeSeparation));

	cam->projectionType=Camera::PERSPECTIVE_GENERIC; //WHY?
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_CAM_PROJECTION_CHANGED(sceneID, camID));

	setCarpetCamsNearFar(camID, nearDist, farDist);
}

void Scene::SetPerspectiveOffsetAndEyeAxis(NODE_ID camID, const Vector3 &offset, const Vector3 &eyeAxis, const Vector3& lookAt)
{
	ReadLocker rlock(sceneNodesMutex);
	Camera * cam = (Camera *) getNode(camID);
	if (!cam || !cam->IsOfType(SceneNode::CAMERA))
		return;

	WriteLocker wlock(*cam);

	cam->offset=offset;
	cam->eyeAxis=eyeAxis;

	if((lookAt.x + lookAt.y + lookAt.z) !=0)
		cam->lookAt = lookAt;

	//LOG(wxString::Format(wxT("<Scene::SetPerspectiveOffset> camID=%i, offset="), camID)+TextWriter::WriteToWxString(offset._v, 3, ","));
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_CAM_PROJECTION_CHANGED(sceneID, camID));
}

void Scene::SetCameraEyeSeparation(NODE_ID camID, float eyeSeparation)
{
	ReadLocker rlock(sceneNodesMutex);
	smart_pointer<Camera> cam;
	GetNode(cam, camID);
	if (!cam || !cam->IsOfType(SceneNode::CAMERA))
		return;

	WriteLocker wlock(*cam);

	cam.ptr->eyeSeparation = eyeSeparation;

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_CAM_PROJECTION_CHANGED(sceneID, camID));
}

Matrix Scene::GetProjectionMatrix(NODE_ID camID, float aspectRatio, bool activeStereoEye)
{
	smart_pointer<SceneNode> cam;
	GetNode(cam, camID);
	if (!cam || !cam->IsOfType(SceneNode::CAMERA))
		return Matrix::Unit;
	Camera * cam_ptr = static_cast<Camera *>(cam.ptr);

	// $$JB
	// can be done localy - no dependence on other scene nodes like for the worldtransformation matrix!!
	// projection matrix should use its own validation flag!
	// for now just recompute the projection
	cam_ptr->updateProjection(aspectRatio, activeStereoEye);
	return cam_ptr->projMat;
}

Matrix Scene::GetTMTMatrix(NODE_ID camID, float aspectRatio, bool activeStereoEye)
{
        smart_pointer<SceneNode> cam;
        GetNode(cam, camID);
        if (!cam || !cam->IsOfType(SceneNode::CAMERA))
                return Matrix::Unit;
        Camera * cam_ptr = static_cast<Camera *>(cam.ptr);

        // $$JB
        // can be done localy - no dependence on other scene nodes like for the worldtransformation matrix!!
        // projection matrix should use its own validation flag!
        // for now just recompute the projection
        cam_ptr->updateProjection(aspectRatio, activeStereoEye);
        return cam_ptr->TMTMatrix;
}

void Scene::Fit(NODE_ID camID, bool farNearOnly, NODE_ID nodeID)
{
	//get camera
	smart_pointer<Camera> cam;
	GetNode(cam, camID);
	if (!cam || !cam->IsOfType(SceneNode::CAMERA))
		return;

	//get AABB of all affected nodes
	AABB aabb=GetAABB(nodeID);
	if (!aabb.IsValid())
		return;

	//do the job
	ReadLocker rlock(sceneNodesMutex);
	WriteLocker wlock(*cam);
	Vector3 camTranslation(getWorldTransMatrix((SceneNode*)cam.getPtr()).ExtractTranslation());
	if (_finite(camTranslation.Normalize().LengthSq())==0)
		camTranslation=Vector3();//workaround for the infinite case
	if (!farNearOnly)
	{
		Matrix parentMatInvOrto(getWorldTransMatrix(getNode(cam->GetParentID())).InverseOrto());
		Vector3 viewDir(GetView(cam->id).Normalize());
		if (_finite(viewDir.LengthSq())==0)
			viewDir=Vector3(0,0,-1);//workaround for the infinite case
		TranslateAbs(camID, parentMatInvOrto.TransformCoord(aabb.Center() - viewDir * (aabb.Radius() * 2)));
		cam.ptr->centerDist = (camTranslation - aabb.Center()).Length();
	}
	float farPlane = (camTranslation - aabb.Center()).Length() + aabb.Radius() * 2;
	SetProjectionParams(camID, 0, 0, 0, farPlane);
}

void Scene::ResetView(NODE_ID camID)
{
	{
		ReadLocker rlock(sceneNodesMutex);
		smart_pointer<Camera> cam;
		GetNode(cam, camID);
		if (!cam || !cam->IsOfType(SceneNode::CAMERA))
			return;

		Matrix mat;
		mat.Translate(Vector3(0,0,30));
		cam.ptr->centerDist = 30.0f;
		SetTransformation(camID, mat);
	}
	Fit(camID);
}

void Scene::CreateCarpet(NODE_ID root, bool isTracked, float eyeSeparation, int bodyID) {
	FlyingCarpet fc(root,isTracked,eyeSeparation,bodyID);
	WriteLocker lock(carpetsMutex);
	carpets.push_back(fc);
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_CARPET_REGISTERED(sceneID, carpets.size()-1));
	SetCarpetCamParams(root, eyeSeparation, isTracked, bodyID);
}

void Scene::RemoveCarpet(NODE_ID root) {
	WriteLocker lock(carpetsMutex);
	for (size_t i=0; i<carpets.size();)
	{
		if (carpets[i].root==root)
		{
			carpets.erase(carpets.begin()+i);
			if (msgSink)
				msgSink->DistributeToListeners(GET_EVT_SCENE_CARPET_UNREGISTERED(sceneID, i));
		}
		else
			++i;
	}
}

void Scene::UpdateCarpets(int bodyID, const Vector3 &position, const Matrix &orientation) {
	ReadLocker crLock(carpetsMutex);
	if (carpets.empty())
		return;
	//for all the carpets
	for (auto &carpet : carpets)
	{
		//only tracked and based on this body
		if ((!carpet.isTracked)||(carpet.bodyID!=bodyID))
			continue;
		ReadLocker rlock(sceneNodesMutex);
		SceneNode * snode =	getNode(carpet.root);
		if (!snode)
			continue;
		std::vector<smart_pointer<SceneNode> > children;
		GetChildrenNodes(children, carpet.root);
		//we will iterate through all the root's immediate children
		for (auto &node : children) {
			//and for all those, that are cameras
			if (node->IsOfType(SceneNode::CAMERA))
			{
				smart_pointer<Camera> cam=(smart_pointer<Camera>&)node;

				//designated as having generic perspective
				if (cam->projectionType == Camera::PERSPECTIVE_GENERIC) 
				{
					Matrix tbn(*cam->GetLocalTransMatrix());
					tbn.InvertThisPrecise();

					//we set the new origin and eyeAxis
					SetPerspectiveOffsetAndEyeAxis(cam->id, tbn.TransformCoordHom(position), tbn.TransformNormal(orientation.TransformNormal(Vector3(1,0,0))), tbn.TransformNormal(orientation.TransformNormal(Vector3(0, 1, 0))));
				}
			}
		}
	}
}

void Scene::SetCarpetCamParams(NODE_ID root, float eyeSeparation, int tracked, int bodyID)
{
	ReadLocker clock(carpetsMutex);
	for (auto &carpet : carpets)
	{
		if (carpet.root!=root)
			continue;
		if (tracked >= 0)
			carpet.isTracked = (tracked!=0);
		carpet.bodyID=(bodyID>0)?bodyID:carpet.bodyID;
		if (eyeSeparation<0)
			continue;
		carpet.eyeSeparation=eyeSeparation;
		ReadLocker slock(sceneNodesMutex);
		SceneNode * snode=getNode(root);
		if (!snode)
			continue;
		std::vector<smart_pointer<SceneNode> > children;
		GetChildrenNodes(children, snode->GetID());
		///we will iterate through all the root's immediate children
		for (auto &node : children) {
			///and for all those, that are cameras
			if (node->IsOfType(SceneNode::CAMERA))
			{
				Camera * cam = (Camera *)node.getPtr();
				if (!cam || !cam->IsOfType(SceneNode::CAMERA) || (cam->projectionType != Camera::PERSPECTIVE_GENERIC))
					return;

				WriteLocker wlock(*cam);
				cam->eyeSeparation=eyeSeparation;
				if (msgSink)
					msgSink->DistributeToListeners(GET_EVT_SCENE_CAM_PROJECTION_CHANGED(sceneID, cam->GetID()));
			}
		}
		//LOG(wxString::Format(wxT("<Scene::SetCarpetCamParams> rootID=%i, eyeSeparation="), (int)rootID)+TextWriter::WriteToWxString(eyeSeparation));
	}
}

void Scene::setCarpetCamsNearFar(NODE_ID root, float nearPlane, float farPlane)
{
	ReadLocker clock(carpetsMutex);
	for (auto &carpet : carpets)
	{
		if (carpet.root==root)
		{
			ReadLocker slock(sceneNodesMutex);
			SceneNode * snode=getNode(root);
			if (snode)
			{
				std::vector<NODE_ID> children;
				GetChildrenNodeIDs(children, root);
				for (auto childID : children)
					SetProjectionParams(childID, 0, 0, nearPlane, farPlane);
			}
		}
	}
}

void Scene::UpdateCarpetOffset(VRUT::NODE_ID root, const Vector3 &position) {
	ReadLocker clock(carpetsMutex);
	for (auto &carpet : carpets)
	{
		if ((carpet.root!=root)||(carpet.isTracked))
			continue;
		ReadLocker slock(sceneNodesMutex);
		SceneNode * snode= getNode(carpet.root);
		if (!snode)
			continue;
		std::vector<smart_pointer<SceneNode> > children;
		GetChildrenNodes(children, snode->GetID());
		///we will iterate through all the root's immediate children
		for (auto &node : children)
		{
			///and for all those, that are cameras
			if (node->IsOfType(SceneNode::CAMERA))
			{
				smart_pointer<Camera> cam=(smart_pointer<Camera>&)node;

				///designated as having generic perspective
				if (cam->projectionType == Camera::PERSPECTIVE_GENERIC) {
					///we set the new origin, respecting their "eyedness"
					Matrix tbn(*cam->GetLocalTransMatrix());
					if (!tbn.InvertThisPrecise())
						LOGWARNING(wxT("<Scene>UpdateCarpetOffset: TBN matrix not invertible!"));
					//we set the new origin and eyeAxis
					SetPerspectiveOffsetAndEyeAxis(cam->id, tbn.TransformCoordHom(position), tbn.TransformNormal(Vector3(1,0,0)), Vector3(0, 0, -1));
				}
			}
		}
	}
}

const std::vector<FlyingCarpet> &Scene::GetCarpets() const
{
	return carpets;
}

VARIANT_ID Scene::AddVariant(Variant * variant)
{
	if (!variant)
	{
		LOGERROR(wxT("<Scene>No variant to add"));
		return VARIANT_ID_NONE;
	}
	variantsMutex.WriteLock();
	VariantIDHashMap::iterator old = variantIDMap.find(variant->GetName());
	VARIANT_ID vid = VARIANT_ID_NONE;
	if (old != variantIDMap.end())
	{
#ifndef NDEBUG
		LOGDEBUG(wxT("<Scene>Variant with name '") + variant->GetName() + wxT("' already exists, replacing with new one"));
#endif
		variant->vid = vid = old->second;
		RemoveVariant(vid);
		variants[vid].assign(variant);
	}
	else
	{
		variant->vid = vid = VARIANT_ID(variants.size());
		variants.push_back(smart_pointer<Variant>());
		variants.back().assign(variant);
		variantIDMap[variant->GetName()] = vid;
	}
	variantsMutex.WriteUnlock();

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_VARIANT_ADDED(sceneID, vid));

	//activate variant
	//AM: commented out because import of variant should not activate them, when some IO module needs to activate them then it must activate them explicitelly
	//SetVariantActiveState(vid, variant->activeState);

	LOGDEBUG(wxString::Format(wxT("<Scene>Variant ID '%lu' added to scene '%lu'"), (unsigned long)vid, (unsigned long)sceneID));
	return vid;
}

bool Scene::RemoveVariant(VARIANT_ID vid)
{
	WriteLocker wlock(variantsMutex);
	if (getVariant(vid))
	{
		for (auto &variant : variants)
		{
			if (variant && (variant->type == Variant::VARIANT_SET))
			{
				if (((VariantSet*)variant.getPtr())->RemoveVariant(vid))
				{
					if (msgSink)
						msgSink->PostEvent(GET_EVT_SCENE_VARIANT_ADDED(sceneID, variant->GetID()));
				}
			}
		}
		variantIDMap.erase(variants[vid]->GetName());
		variants[vid].assign((Variant *)NULL);
		if (msgSink)
			msgSink->DistributeToListeners(GET_EVT_SCENE_VARIANT_REMOVED(sceneID, vid));
		return true;
	}
	return false;
}

void Scene::SetVariantActiveState(VARIANT_ID vid, size_t activeState)
{
	WriteLocker wlock(variantsMutex);
	Variant *variant=getVariant(vid);
	if (!variant)
		return;
	switch (variant->type)
	{
	case Variant::VARIANT_GEOMETRY:
		{
			VariantGeometry *gvariant=(VariantGeometry *)variant;
			if ((activeState==(size_t)-2) || (activeState==(size_t)-1))
			{
				bool active=(activeState==(size_t)-2);
				for (auto nodeID : gvariant->nodesList)
					SetActive(nodeID, active);
			}
			else
			{
				if (activeState<gvariant->statesList.size())
				{
					for (size_t i=0; i<gvariant->stateNodesTable.at(activeState).size(); ++i)
						SetActive(gvariant->nodesList.at(i), gvariant->stateNodesTable.at(activeState).at(i));
				}
			}
			break;
		}
	case Variant::VARIANT_MATERIAL:
		{
			VariantMaterial *mvariant=(VariantMaterial *)variant;
			if (activeState<mvariant->statesList.size())
			{
				smart_pointer<Material> mat1, mat2;
				GetMaterial(mat1, GetMaterialID(mvariant->GetName()));
				if (!mat1)
				{
					LOGWARNING(wxT("<Scene>SetVariantActiveState base variant material not exists: ") + mvariant->GetName());
					return;
				}
				GetMaterial(mat2, GetMaterialID(mvariant->statesList[activeState]));
				if (!mat2)
				{
					LOGWARNING(wxT("<Scene>SetVariantActiveState material variant <") + mvariant->GetName() + wxT("> cannot be switched to state: ") + mvariant->statesList[activeState] + wxT(" (material not exists)"));
					return;
				}
				Material *mat=mat2->Clone();
				mat->mid=mat1->mid;
				mat->name=mat1->name;
				AddMaterial(mat);
			}
			break;
		}
	case Variant::VARIANT_SET:
		{
			VariantSet *svariant=(VariantSet *)variant;
			if ((activeState==(size_t)-2) || (activeState==(size_t)-1))
			{
				for (size_t i=0; i<svariant->detailsList.size(); ++i)
					switch (svariant->detailsTypes.at(i))
					{
						case VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_VARIANT:
							SetVariantActiveState((VARIANT_ID)svariant->detailsList.at(i), activeState);
							break;
						case VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_SWITCH:
							SetSwitchActiveChild((NODE_ID)svariant->detailsList.at(i), activeState);
							break;
						case VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_SCRIPT:
							break;
					}
					//if (svariant->detailsTypes.at(i))
					//	SetSwitchActiveChild((NODE_ID)svariant->detailsList.at(i), activeState);
					//else
					//	SetVariantActiveState((VARIANT_ID)svariant->detailsList.at(i), activeState);
			}
			else
			{
				if (activeState<svariant->statesList.size())
				{
					for (size_t i=0; i<svariant->stateDetailsTable.at(activeState).size(); ++i)
						switch (svariant->detailsTypes.at(i))
						{
							case VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_VARIANT:
								SetVariantActiveState((VARIANT_ID)svariant->detailsList.at(i), svariant->stateDetailsTable.at(activeState).at(i));
								break;
							case VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_SWITCH:
							{
								long child;
								TextParser(svariant->stateDetailsTable.at(activeState).at(i).utf8_str()).ParseLong(&child);
								SetSwitchActiveChild((NODE_ID)svariant->detailsList.at(i), (size_t)child);
								break;
							}
							case VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_SCRIPT:
							{
								// run script
								if (!msgSink || svariant->stateDetailsTable.at(activeState).at(i).IsEmpty())
									break;

								wxString result;
								JavascriptRunScriptData data((JSScriptingContextPtr*)&jsCtxVariants, svariant->stateDetailsTable.at(activeState).at(i), result);
								for (auto &moID : msgSink->moduleListeners[Event::EVT_JAVASCRIPT_RUN_SCRIPT])
								{
									Module * module = KERNEL->GetModule(moID);
									if (module && (module->processSyncEvent(Event::EVT_JAVASCRIPT_RUN_SCRIPT, (void*)&data) != Module::Allowed))
										if (data.processed)
											break;
								}
								break;
							}
						}
						//if (svariant->detailsTypes.at(i))
						//{
						//	long child;
						//	TextParser(svariant->stateDetailsTable.at(activeState).at(i).utf8_str()).ParseLong(&child);
						//	SetSwitchActiveChild((NODE_ID)svariant->detailsList.at(i), (size_t)child);
						//}
						//else
						//	SetVariantActiveState((VARIANT_ID)svariant->detailsList.at(i), svariant->stateDetailsTable.at(activeState).at(i));
				}
			}
			break;
		}
	case Variant::VARIANT_CAMERA:
		{
			VariantCamera *cvariant=(VariantCamera *)variant;
			if ((activeState<cvariant->statesList.size()) && getNode(cvariant->camID))
			{
				SetTransformation(cvariant->camID, cvariant->transformations.at(activeState));
				SetProjectionParams(cvariant->camID, 0, 0, 0, 0, cvariant->FOVs.at(activeState));
			}
			break;
		}
	default:
		LOGWARNING(wxString::Format(wxT("<SetVariantActiveState> Unknown variant type %i"), variant->type));
		break;
	}
	variant->activeState=activeState;
	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_VARIANT_ACTIVESTATE_CHANGED(sceneID, vid, activeState));
}

void Scene::SetVariantActiveState(VARIANT_ID vid, const wxString &activeState)
{
	size_t stateID=0;
	{
		ReadLocker rlock(variantsMutex);
		Variant *variant=getVariant(vid);
		if (!variant)
			return;
		stateID=variant->FindFirstStateIDByName(activeState);
	}
	SetVariantActiveState(vid, stateID);
}

void Scene::SetVariantDetailState(VARIANT_ID vid, const wxString &state, const wxString &detail, const wxString &status)
{
	Variant *variant=getVariant(vid);
	if (!variant)
		return;
	switch (variant->type)
	{
	case Variant::VARIANT_GEOMETRY:
		{
			VariantGeometry *gvariant=(VariantGeometry *)variant;
			std::vector<NODE_ID> nodeIDs;
			GetNodeIDs(nodeIDs, detail);
			bool statusb=(status==wxT("1"));
			for (auto nodeID : nodeIDs)
			{
				if (gvariant->FindNodeIDPos(nodeID)==(size_t)-1)
					gvariant->AddNode(nodeID);
				gvariant->SetStateNodeStatus(state, nodeID, statusb);
			}
			break;
		}
	case Variant::VARIANT_MATERIAL:
		{
			VariantMaterial *mvariant=(VariantMaterial *)variant;
			MATERIAL_ID matID=GetMaterialID(status);
			if (matID==MATERIAL_ID_NONE)
			{
				//create new default material with given name to handle DeltaGen extended materials at least somehow
				Material *mat;
				smart_pointer<Material> mat1;
				GetMaterial(mat1, GetMaterialID(mvariant->name));
				if (mat1)
				{
					mat=mat1->Clone();
					mat->mid=MATERIAL_ID_NONE;
					mat->name=status;
				}
				else
					mat=new Material(status);
				AddMaterial(mat);
				matID=GetMaterialID(status);
			}
			mvariant->AddState(state);
			break;
		}
	case Variant::VARIANT_SET:
		{
			VariantSet *svariant=(VariantSet *)variant;
			if (detail.size()<2)
				break;
			wxChar type=detail.at(0);
			wxString detail1(detail.substr(2));
			switch (type)
			{
			case wxT('V'):
				variantsMutex.ReadLock();
				for (size_t i=0; i<variants.size(); ++i)
				{
					Variant *var=getVariant((VARIANT_ID)i);
					if (var && (var->name==detail1))
					{
						if (svariant->FindVariantIDPos((VARIANT_ID)i)==(size_t)-1)
							svariant->AddVariant((VARIANT_ID)i);
						svariant->SetStateDetailStatus(state, i, VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_VARIANT, status);
					}
				}
				variantsMutex.ReadUnlock();
				break;
			case wxT('S'):
				std::vector<NODE_ID> nodeIDs;
				GetNodeIDs(nodeIDs, detail1);
				for (auto nodeID : nodeIDs)
				{
					if (svariant->FindSwitchIDPos(nodeID)==(size_t)-1)
						svariant->AddSwitch(nodeID);
					svariant->SetStateDetailStatus(state, nodeID, VariantSet::VariantSetDetailType::VARIANT_SET_DETAIL_SWITCH, status);
				}
				break;
			}
			break;
		}
	case Variant::VARIANT_CAMERA:
		{
			VariantCamera *cvariant=(VariantCamera *)variant;
			size_t statePos=cvariant->FindFirstStateIDByName(state);
			if (statePos==(size_t)-1)
				break;
			TextParser textParser(status.utf8_str());
			const char *keywords[] = {"PO:", "FLY:", "FOV:", "CID:", ""};
			while (!textParser.IsEmpty())
			{
				switch (textParser.KeywordsSwitch(keywords))
				{
				case 0://PO
					{
						Vector3 position, rotationAxis;
						float angle;
						if (textParser.ParseVector(position._v, 3)!=3)
							LOGERROR(wxT("<Scene::SetVariantDetailState>: Error parsing camera position ")+status);
						if (textParser.ParseVector(rotationAxis._v, 3)!=3)
							LOGERROR(wxT("<Scene::SetVariantDetailState>: Error parsing camera rotation axis ")+status);
						if (!textParser.ParseFloat(&angle))
							LOGERROR(wxT("<Scene::SetVariantDetailState>: Error parsing camera rotation angle ")+status);
						Matrix trans(Matrix::Translation(position)*Matrix::RotationAxis(rotationAxis, -angle/180.0*3.14159));
						cvariant->SetStateCamera(state, trans, cvariant->FOVs.at(statePos));
						break;
					}
				case 1://FLY
					{
						float FLY;
						if (!textParser.ParseFloat(&FLY))
							LOGERROR(wxT("<Scene::SetVariantDetailState>: Error parsing camera FLY ")+status);
						//TODO:
						break;
					}
				case 2://FOV
					{
						float FOV;
						if (!textParser.ParseFloat(&FOV))
							LOGERROR(wxT("<Scene::SetVariantDetailState>: Error parsing camera FOV ")+status);
						cvariant->SetStateCamera(state, cvariant->transformations.at(statePos), FOV);
						break;
					}
				case 3://CID
					{
						long cid;
						if (!textParser.ParseLong(&cid))
							LOGERROR(wxT("<Scene::SetVariantDetailState>: Error parsing camera ID ")+status);
						cvariant->camID=cid;
						break;
					}
				default:
					LOGERROR(wxT("<Scene::SetVariantDetailState>: Unknown camera parameter ")+textParser.GetWxLine());
				}
			}
			break;
		}
	default:
		LOGWARNING(wxString::Format(wxT("<SetVariantDetailState> Unknown variant type %i"), variant->type));
		break;
	}
	SetVariantActiveState(vid, variant->activeState);
}

void Scene::SetVariantDetailState(const wxString &variantName, const wxString &state, const wxString &detail, const wxString &status)
{
	VariantIDHashMap::iterator variant = variantIDMap.find(variantName);
	SetVariantDetailState(variant->second, state, detail, status);
}

void Scene::SetAllVariantsCameraID(NODE_ID cameraID)
{
	variantsMutex.ReadLock();
	for (auto &variant : variants)
		if (variant)
			if (variant->type == Variant::VARIANT_CAMERA)
				((VariantCamera*)variant.getPtr())->camID = cameraID;
	variantsMutex.ReadUnlock();
}

void Scene::RemoveNodeFromVariants(NODE_ID id)
{
	variantsMutex.ReadLock();
	for (auto &variant : variants)
	{
		if (variant)
		{
			if (variant->type == Variant::VARIANT_GEOMETRY)
			{
				if (((VariantGeometry*)variant.getPtr())->RemoveNode(id))
				{
					if (msgSink)
						msgSink->PostEvent(GET_EVT_SCENE_VARIANT_ADDED(sceneID, variant->GetID()));
				}
			}
			else if (variant->type == Variant::VARIANT_SET)
			{
				if (((VariantSet*)variant.getPtr())->RemoveSwitch(id))
				{
					if (msgSink)
						msgSink->PostEvent(GET_EVT_SCENE_VARIANT_ADDED(sceneID, variant->GetID()));
				}
			}
		}
	}
	variantsMutex.ReadUnlock();
}

void Scene::ReplaceNodeInVariants(NODE_ID id, NODE_ID newID)
{
	variantsMutex.ReadLock();
	for (auto &variant : variants)
	{
		if (variant && (variant->type==Variant::VARIANT_GEOMETRY))
		{
			if (((VariantGeometry*)variant.getPtr())->ReplaceNode(id, newID))
			{
				if (msgSink)
					msgSink->PostEvent(GET_EVT_SCENE_VARIANT_ADDED(sceneID, variant->GetID()));
			}
		}
	}
	variantsMutex.ReadUnlock();
}

void Scene::RemoveMaterialFromVariants(const wxString &materialName)
{
	std::vector<VARIANT_ID> varsToRemove;
	variantsMutex.ReadLock();
	for (auto &variant : variants)
	{
		if (variant && (variant->type==Variant::VARIANT_MATERIAL))
		{
			if (variant->name == materialName)
				varsToRemove.push_back(variant->GetID());
			if (const_cast<Variant*>(variant.getPtr())->RemoveState(materialName))
			{
				if (msgSink)
					msgSink->PostEvent(GET_EVT_SCENE_VARIANT_ADDED(sceneID, variant->GetID()));
			}
		}
	}
	variantsMutex.ReadUnlock();
	for (auto varID : varsToRemove)
		RemoveVariant(varID);
}

void Scene::SetSoundState(NODE_ID soundNodeID, SoundSourceNode::SoundSourceState state) {
	smart_pointer<SoundSourceNode> node;
	GetNode(node, soundNodeID);
	if (!node || !node->IsOfType(SceneNode::SOUND_SOURCE))
		return;
	node.ptr->state = state;
}

CLIPPING_PLANE_ID Scene::AddClippingPlane(ClippingPlane *clippingPlane)
{
	if (!clippingPlane)
	{
		LOGERROR(wxT("<Scene>No clippingPlane to add"));
		return CLIPPING_PLANE_ID_NONE;
	}

	CLIPPING_PLANE_ID cpid = clippingPlane->cpid;
	clippingPlanesMutex.WriteLock();
	if (cpid==CLIPPING_PLANE_ID_NONE)
	{
		cpid=(CLIPPING_PLANE_ID)clippingPlanes.size();
		clippingPlane->cpid=cpid;
	}
	if (clippingPlanes.size()<=cpid)
		clippingPlanes.resize(cpid+1);
	clippingPlanes[cpid] = *clippingPlane;
	clippingPlanesMutex.WriteUnlock();

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_CLIPPING_PLANE_ADDED(sceneID, cpid));
	LOGDEBUG(wxString::Format(wxT("<Scene>ClippingPlane ID '%lu' added to scene '%lu'"), (unsigned long)cpid, (unsigned long)sceneID));
	return cpid;
}

size_t Scene::MakeReferencedNamesUnique()
{
	size_t renCount = 0;
	std::vector<std::vector<NODE_ID> > geoRefs(GetGeometryMaxID(), std::vector<NODE_ID>());
	WX_DECLARE_STRING_HASH_MAP(size_t, NodeNamesHash);
	NodeNamesHash nodeNamesHash;
	//count references and create hash with names
	std::vector<smart_pointer<GeometryNode> > gnodes;
	GetNodes(gnodes, SceneNode::GEOMETRY);
	for (auto &gnode : gnodes)
	{
		GEOMETRY_ID gid=gnode->GetGeometryID();
		if (gid!=GEOMETRY_ID_NONE)
		{
			geoRefs.at(gid).push_back(gnode->GetID());
			NodeNamesHash::iterator nameIt = nodeNamesHash.find(gnode->GetName());
			if (nameIt == nodeNamesHash.end())
				nodeNamesHash.insert(NodeNamesHash::value_type(gnode->GetName(), 1));
			else
				++nameIt->second;
		}
	}
	//do referenced names unique
	for (auto &geoRefI : geoRefs)
	{
		//do only for referenced geometries
		if (geoRefI.size()>1)
		{
			//collect all node names for given geometry
			NodeNamesHash refNamesHash;
			for (auto nodeID : geoRefI)
			{
				smart_pointer<SceneNode> node;
				GetNode(node, nodeID);
				if (node)
				{
					NodeNamesHash::iterator nameIt = refNamesHash.find(node->GetName());
					if (nameIt==refNamesHash.end())
						refNamesHash.insert(NodeNamesHash::value_type(node->GetName(), 1));
					else
						++nameIt->second;
				}
			}
			//cycle for group of the same names
			for (auto &nameHash : refNamesHash)
			{
				NodeNamesHash::iterator nameIt = nodeNamesHash.find(nameHash.first);
				//if the name is not unique we must change it
				if (nameIt->second!=nameHash.second)
				{
					//search until name is unique
					wxString uniqName, baseOfName(nameHash.first+wxT("_"));
					size_t index=0;
					do
					{
						uniqName=baseOfName+TextWriter::WriteToWxStringSize_t(index);
						//++index;//nice but too slow if number of nodes with the same name is about 100000, because it has O(N^2) comlexity
						index+=rand();//the += is used because rand has only 32767 values
					} while (nodeNamesHash.find(uniqName) != nodeNamesHash.end());
					//we have unique name for the group now, so rename all nodes in the group
					for (auto nodeID : geoRefI)
					{
						smart_pointer<SceneNode> node;
						GetNode(node, nodeID);
						if (node && (node->GetName()==nameHash.first))
						{
							SetName(nodeID, uniqName);
							++renCount;
						}
					}
					//update hash
					nameIt = nodeNamesHash.find(uniqName);
					if (nameIt == nodeNamesHash.end())
						nodeNamesHash.insert(NodeNamesHash::value_type(uniqName, geoRefI.size()));
					else
						nameIt->second+=geoRefI.size();//this should newer be hit
					nameIt = nodeNamesHash.find(nameHash.first);
					nameIt->second-=nameHash.second;
				}
			}
		}
	}
	return renCount;
}

size_t Scene::FixNodeNamesForVRML()
{
	size_t renCount = 0;
	///change node names so they are not the same like any material name
	for (size_t i=0; i<GetNodeMaxID(); ++i)
	{
		smart_pointer<SceneNode> node;
		GetNode(node, (NODE_ID)i);
		if (!node)
			continue;
		if (GetMaterialID(node->GetName())==MATERIAL_ID_NONE)
			continue;
		SetName(i, node->GetName()+wxT("_"));
		++renCount;
	}
	return renCount;
}

wxString Scene::ToString() const
{
	wxString res(wxString::Format(wxT("Scene ID=%lu "), (unsigned long)sceneID)+sceneName);
	res+=wxString::Format(wxT("\nrootID=%lu, "), (unsigned long)GetRootID());
	sceneNodesMutex.ReadLock();
	res+=wxString::Format(wxT("\nmaxNodeID=%lu, "), (unsigned long)sceneNodes.size());
	sceneNodesMutex.ReadUnlock();
	geometriesMutex.ReadLock();
	res+=wxString::Format(wxT("maxGeometryID=%lu, "), (unsigned long)geometries.size());
	geometriesMutex.ReadUnlock();
	materialsMutex.ReadLock();
	res+=wxString::Format(wxT("maxMaterialID=%lu, "), (unsigned long)materials.size());
	materialsMutex.ReadUnlock();
	variantsMutex.ReadLock();
	res+=wxString::Format(wxT("maxVariantsID=%lu\n"), (unsigned long)variants.size());
	variantsMutex.ReadUnlock();
	return res;
}

#if 0
/// Find nodes of the given name using the traversal of the scene graph
/// This function has to be completed and its interface unified with GetNodeIDs
/// It will traverse the scene graph and match the nodes using an expression given by the name parameter

int
Scene::Find(const wxString & name, const int rootID) const
{
	const SceneNode *node;
	if (rootID == NODE_ID_NONE)
		node = GetNode(GetRootID());
	else
		node = GetNode(rootID);

	if (node == NULL)
		return NODE_ID_NONE;

	std::stack<const SceneNode *> nodeStack;

	nodeStack.push(node);

	while (!nodeStack.empty()) {
		node = nodeStack.top();
		nodeStack.pop();
		if (node->GetName() == name)
			return node->GetID();

		for (SceneNode::NodeIDList::const_iterator it = node->GetChildren()->begin(); it != node->GetChildren()->end();	++it) {
			nodeStack.push(GetNode(*it));
		}
	}

	return NODE_ID_NONE;
}
#endif

void Scene::removeNode(NODE_ID id)
{
	SceneNode *node = getNode(id);
	assert(node);
	assert(NODE_ID_NONE == node->parentID);
	assert(node->children.empty());

	if (node->IsOfType(SceneNode::GEOMETRY))
	{
		geometriesMutex.WriteLock();
		removeGeometryNodeReference(((GeometryNode *)node)->GetGeometryID(), id);
		geometriesMutex.WriteUnlock();
		removeMaterialNodeReference(((GeometryNode *)node)->GetMaterialID(), id);
	}
	else if (node->IsOfType(SceneNode::BACKGROUND))
	{
		removeMaterialNodeReference(((BackgroundNode *)node)->GetMaterialID(), id);
		backgroundsMutex.ReadLock();
		for (size_t i=0; i<backgrounds.size(); ++i)
		{
			if (backgrounds[i]->GetID()==id)
			{
				backgrounds.erase(backgrounds.begin()+i);
				break;
			}
		}
		backgroundsMutex.ReadUnlock();
	}
	else if (node->IsOfType(SceneNode::CAMERA))
		RemoveCarpet(id);

	RemoveNodeFromVariants(id);
	//TODO: remove unreferenced material, geometry if any
	if (node->IsOfType(SceneNode::LIGHT))
	{
		for (size_t i=0; i<lightNodeIDs.size();)
		{
			if (lightNodeIDs.at(i)==id)
				lightNodeIDs.erase(lightNodeIDs.begin()+i);
			else
				++i;
		}
	}

#ifndef NDEBUG
	LOGDEBUG(wxT("<Scene>Node '")+node->GetName()+wxString::Format(wxT("' ID '%lu' removed from scene '%lu'"), (unsigned long)id, (unsigned long)sceneID));
#endif
	node->id = NODE_ID_NONE;
	sceneNodes[id]=smart_pointer<SceneNode>(NULL);

	if (msgSink)
		msgSink->DistributeToListeners(GET_EVT_SCENE_NODE_REMOVED(sceneID, id));
}
