////////////////////////////////////////////////////////////////////////////
//
//  Crytek Engine Source File.
//  Copyright (C), Crytek Studios, 2001.
// -------------------------------------------------------------------------
//  File name:   SelectionGroup.cpp
//  Version:     v1.00
//  Created:     10/10/2001 by Timur.
//  Compilers:   Visual C++ 6.0
//  Description: CSelectionGroup implementation.
// -------------------------------------------------------------------------
//  History:
//
////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "SelectionGroup.h"

#include "BaseObject.h"
#include "ObjectManager.h"
#include "PrefabObject.h"

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::AddObject( CBaseObject *obj )
{
	if (!IsContainObject(obj))
	{
		m_objects.push_back(obj);
		m_objectsSet.insert(obj);
		m_filtered.clear();
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::RemoveObject( CBaseObject *obj )
{
	for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it)
	{
		if (*it == obj)
		{
			m_objects.erase(it);
			m_objectsSet.erase(obj);
			m_filtered.clear();
			break;
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::RemoveAll()
{
	m_objects.clear();
	m_objectsSet.clear();
	m_filtered.clear();
}
	
bool CSelectionGroup::IsContainObject( CBaseObject *obj )
{
	return (m_objectsSet.find(obj) != m_objectsSet.end());
}

//////////////////////////////////////////////////////////////////////////
bool CSelectionGroup::IsEmpty() const
{
	return m_objects.empty();
}

//////////////////////////////////////////////////////////////////////////
bool CSelectionGroup::SameObjectType()
{
	if (IsEmpty())
		return false;
	CBaseObjectPtr pFirst=(*(m_objects.begin()));
	for (Objects::iterator it = m_objects.begin(); it != m_objects.end(); ++it)
	{
		if ((*it)->GetRuntimeClass()!=pFirst->GetRuntimeClass())
			return false;
	}
	return true;
}

//////////////////////////////////////////////////////////////////////////
int CSelectionGroup::GetCount() const
{
	return m_objects.size();
}

//////////////////////////////////////////////////////////////////////////
CBaseObject* CSelectionGroup::GetObject( int index ) const
{
	ASSERT( index >= 0 && index < m_objects.size() );
	return m_objects[index];
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Copy( const CSelectionGroup &from )
{
	m_name = from.m_name;
	m_objects = from.m_objects;
	m_objectsSet = from.m_objectsSet;
	m_filtered = from.m_filtered;
}

//////////////////////////////////////////////////////////////////////////
Vec3	CSelectionGroup::GetCenter() const
{
	Vec3 c(0,0,0);
	for (int i = 0; i < GetCount(); i++)
	{
		c += GetObject(i)->GetWorldPos();
	}
	if (GetCount() > 0)
		c /= GetCount();
	return c;
}

//////////////////////////////////////////////////////////////////////////
BBox CSelectionGroup::GetBounds() const
{
	BBox b;
	BBox box;
	box.Reset();
	for (int i = 0; i < GetCount(); i++)
	{
		GetObject(i)->GetBoundBox( b );
		box.Add( b.min );
		box.Add( b.max );
	}
	return box;
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::FilterParents()
{
	if (!m_filtered.empty())
		return;

	m_filtered.reserve( m_objects.size() );
	for (int i = 0; i < m_objects.size(); i++)
	{
		CBaseObject *obj = m_objects[i];
		CBaseObject *parent = obj->GetParent();
		bool bParentInSet = false;
		while (parent)
		{
			if (m_objectsSet.find(parent) != m_objectsSet.end())
			{
				bParentInSet = true;
				break;
			}
			parent = parent->GetParent();
		}
		if (!bParentInSet)
		{
			m_filtered.push_back(obj);
		}
	}
}

//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Move( const Vec3 &offset,bool keepHeight,bool inWorldSpace )
{
	// [MichaelS - 17/3/2005] Removed this code from the three edit functions (move,
	// rotate and scale). This was causing a bug where the render node of objects
	// was not being updated when objects were dragged away from their position
	// and then back again, since movement is re-calculated from the initial position
	// each mouse message (ie first the previous movement is undone and then the
	// movement is applied). This meant that when moving back to the start position
	// it appeared like no movement was applied, although it was still necessary to
	// update the graphics resources. The object transform is explicitly reset
	// below.

	//if (offset.x == 0 && offset.y == 0 && offset.z == 0)
	//	return;

	FilterParents();

	Vec3 newPos;
	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		
		Matrix34 wtm = obj->GetWorldTM();
		Vec3 wp = wtm.GetTranslation();
		newPos = wp + offset;
		if (keepHeight)
		{
			// Make sure object keeps it height.
			float height = wp.z - GetIEditor()->GetTerrainElevation( wp.x,wp.y );
			newPos.z = GetIEditor()->GetTerrainElevation( newPos.x,newPos.y ) + height;
		}
		wtm.SetTranslation( newPos );
		
		obj->SetWorldTM( wtm,TM_USER_INPUT );
		//obj->SetPos(newPos);	// this is better i guess

		obj->InvalidateTM( TM_USER_INPUT );
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Rotate( const Ang3& angles,bool inWorldSpace )
{
	//if (angles.x == 0 && angles.y == 0 && angles.z == 0)
	//	return;

	// Rotate selection about selection center.
	Vec3 center = GetCenter();

	Matrix34 rotateTM;
	rotateTM.SetIdentity();
	//rotateTM.RotateMatrix_fix( angles );
	rotateTM = rotateTM*Matrix34::CreateRotationXYZ(DEG2RAD(-angles)); //NOTE: angles in radians and negated 

	Matrix34 ToOrigin;
	Matrix34 FromOrigin;

	ToOrigin.SetIdentity();
	FromOrigin.SetIdentity();

	if (inWorldSpace)
	{
		ToOrigin.SetTranslation( -center );
		FromOrigin.SetTranslation( center );
	}

	FilterParents();

	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		
		Matrix34 m = obj->GetWorldTM();
		if (inWorldSpace)
		{
			m = FromOrigin * rotateTM * ToOrigin * m;
		}
		else
		{
			m = m * rotateTM;
		}
		obj->SetWorldTM( m,TM_USER_INPUT );
		obj->InvalidateTM( TM_USER_INPUT );
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Scale( const Vec3 &scale,bool inWorldSpace )
{
	//if (scale.x == 1 && scale.y == 1 && scale.z == 1)
	//	return;

	Vec3 scl = scale;
	if (scl.x == 0) scl.x = 0.01f;
	if (scl.y == 0) scl.y = 0.01f;
	if (scl.z == 0) scl.z = 0.01f;

	// Scale selection relative to selection center.
	Vec3 center = GetCenter();

	Matrix34 scaleTM;
	scaleTM.SetIdentity();

	scaleTM=Matrix33::CreateScale( Vec3(scl.x,scl.y,scl.z) ) * scaleTM;

	Matrix34 ToOrigin;
	Matrix34 FromOrigin;

	ToOrigin.SetIdentity();
	FromOrigin.SetIdentity();

	if (inWorldSpace)
	{
		ToOrigin.SetTranslation( -center );
		FromOrigin.SetTranslation( center );
	}

	FilterParents();

	for (int i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *obj = GetFilteredObject(i);
		
		Matrix34 m = obj->GetWorldTM();
		if (inWorldSpace)
			m = FromOrigin * scaleTM * ToOrigin * m;
		else
			m = m * scaleTM;
		obj->SetWorldTM( m,TM_USER_INPUT );
		obj->InvalidateTM( TM_USER_INPUT );
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::Clone( CSelectionGroup &newGroup )
{
	IObjectManager *pObjMan = GetIEditor()->GetObjectManager();
	assert( pObjMan );

	int i;
	CObjectCloneContext cloneContext;

	FilterParents();

	//////////////////////////////////////////////////////////////////////////
	// Clone every object.
	for (i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *pFromObject = GetFilteredObject(i);
		CBaseObject *newObj = pObjMan->CloneObject( pFromObject );
		assert( newObj );

		cloneContext.AddClone( pFromObject,newObj );
		newGroup.AddObject( newObj );
	}

	//////////////////////////////////////////////////////////////////////////
	// Only after everything was cloned, call PostClone on all cloned objects.
	for (i = 0; i < GetFilteredCount(); i++)
	{
		CBaseObject *pFromObject = GetFilteredObject(i);
		CBaseObject *pClonedObject = newGroup.GetObject(i);
		pClonedObject->PostClone( pFromObject,cloneContext );
	}
}

//////////////////////////////////////////////////////////////////////////
static void RecursiveFlattenHierarchy( CBaseObject *pObj,CSelectionGroup &newGroup )
{
	newGroup.AddObject( pObj );

	if(!pObj->IsKindOf(RUNTIME_CLASS(CPrefabObject)))
	{
		for (int i = 0; i < pObj->GetChildCount(); i++)
		{
			RecursiveFlattenHierarchy( pObj->GetChild(i),newGroup );
		}
	}
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::FlattenHierarchy( CSelectionGroup &newGroup )
{
	for (int i = 0; i < GetCount(); i++)
	{
		RecursiveFlattenHierarchy( GetObject(i),newGroup );
	}
}

//////////////////////////////////////////////////////////////////////////
class CAttachToParentPickCallback : public IPickObjectCallback
{
public:
	CAttachToParentPickCallback() { m_bActive = true; };
	//! Called when object picked.
	virtual void OnPick( CBaseObject *picked )
	{
		CUndo undo( "Attach Selection" );

		CSelectionGroup *selGroup = GetIEditor()->GetSelection();
		selGroup->FilterParents();

		for (int i = 0; i < selGroup->GetFilteredCount(); i++)
		{
			if (ChildIsValid( picked,selGroup->GetFilteredObject(i) ))
				picked->AttachChild( selGroup->GetFilteredObject(i) );
		}
		m_bActive = false;
		delete this;
	}
	//! Called when pick mode cancelled.
	virtual void OnCancelPick()
	{
		m_bActive = false;
		delete this;
	}
	//! Return true if specified object is pickable.
	virtual bool OnPickFilter( CBaseObject *filterObject )
	{
		return true;
	}

	//////////////////////////////////////////////////////////////////////////
	bool ChildIsValid(CBaseObject *pParent, CBaseObject *pChild, int nDir=3)
	{
		if (!pParent)
			return false;
		if (!pChild)
			return false;
		if (pParent==pChild)
			return false;
		CBaseObject *pObj;
		if (nDir & 1)
		{
			if (pObj=pChild->GetParent())
			{
				if (!ChildIsValid(pParent, pObj, 1))
				{
					return false;
				}
			}
		}
		if (nDir & 2)
		{
			for (int i=0;i<pChild->GetChildCount();i++)
			{
				if (pObj=pChild->GetChild(i))
				{
					if (!ChildIsValid(pParent, pObj, 2))
					{
						return false;
					}
				}
			}
		}
		return true;
	}

	static bool IsActive() { return m_bActive; }
private:
	static bool m_bActive;
};
bool CAttachToParentPickCallback::m_bActive = false;

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::PickAndAttach()
{
	CAttachToParentPickCallback *pCallback = new CAttachToParentPickCallback;
	GetIEditor()->PickObject( pCallback,0,"Attach Selection To Parent" );
}

//////////////////////////////////////////////////////////////////////////
void CSelectionGroup::SendEvent( ObjectEvent event )
{
	for (int i = 0; i < m_objects.size(); i++)
	{
		CBaseObject *obj = m_objects[i];
		obj->OnEvent( event );
	}
}
