Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

612 lines
14 KiB

//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: The document. Exposes functions for object creation, deletion, and
// manipulation. Holds the current tool. Handles GUI messages that are
// view-independent.
//
//=============================================================================//
#include "stdafx.h"
#include "Selection.h"
#include "mapdoc.h"
#include "MapHelper.h"
#include "MapSolid.h"
#include "Manifest.h"
#include "mapdefs.h"
#include "globalfunctions.h"
#include "mainfrm.h"
#include "objectproperties.h"
CSelection::CSelection(void)
{
m_pDocument = NULL;
}
CSelection::~CSelection(void)
{
}
void CSelection::Init( CMapDoc *pDocument )
{
m_pDocument = pDocument;
m_eSelectMode = selectGroups;
m_SelectionList.Purge();
ClearHitList();
m_LastValidBounds.bmins = Vector(0, 0, 0);
m_LastValidBounds.bmaxs = Vector(64, 64, 64);
UpdateSelectionBounds();
}
bool CSelection::IsSelected(CMapClass *pobj)
{
return (m_SelectionList.Find(pobj) != m_SelectionList.InvalidIndex());
}
void CSelection::GetLastValidBounds(Vector &vecMins, Vector &vecMaxs)
{
vecMins = m_LastValidBounds.bmins;
vecMaxs = m_LastValidBounds.bmaxs;
}
bool CSelection::GetBounds(Vector &vecMins, Vector &vecMaxs)
{
if ( m_bBoundsDirty )
UpdateSelectionBounds();
if ( m_SelectionList.Count() == 0)
return false;
vecMins = m_Bounds.bmins;
vecMaxs = m_Bounds.bmaxs;
return true;;
}
bool CSelection::GetLogicalBounds(Vector2D &vecMins, Vector2D &vecMaxs)
{
if ( m_bBoundsDirty )
UpdateSelectionBounds();
if ( m_SelectionList.Count() == 0)
return false;
vecMins = m_vecLogicalMins;
vecMaxs = m_vecLogicalMaxs;
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Used for translations. Uses entity origins and brush bounds.
// That way, when moving stuff, the entity origins will stay on the grid.
//-----------------------------------------------------------------------------
void CSelection::GetBoundsForTranslation( Vector &vecMins, Vector &vecMaxs )
{
vecMins.Init( COORD_NOTINIT, COORD_NOTINIT, 0 );
vecMaxs.Init( -COORD_NOTINIT, -COORD_NOTINIT, 0 );
for (int i = 0; i < m_SelectionList.Count(); i++)
{
CMapClass *pobj = m_SelectionList[i];
// update physical bounds
Vector mins, maxs;
CEditGameClass *pEdit = dynamic_cast< CEditGameClass* >( pobj );
if ( (pEdit && pEdit->IsSolidClass()) || dynamic_cast<CMapSolid *>(pobj) )
{
pobj->GetRender2DBox(mins, maxs);
}
else
{
pobj->GetOrigin( mins );
maxs = mins;
}
VectorMin( mins, vecMins, vecMins );
VectorMax( maxs, vecMaxs, vecMaxs );
}
}
void CSelection::UpdateSelectionBounds( void )
{
m_Bounds.ResetBounds();
m_vecLogicalMins[0] = m_vecLogicalMins[1] = COORD_NOTINIT;
m_vecLogicalMaxs[0] = m_vecLogicalMaxs[1] = -COORD_NOTINIT;
for (int i = 0; i < m_SelectionList.Count(); i++)
{
CMapClass *pobj = m_SelectionList[i];
// update physical bounds
Vector mins,maxs;
pobj->GetRender2DBox(mins, maxs);
m_Bounds.UpdateBounds(mins, maxs);
// update logical bounds
Vector2D logicalMins,logicalMaxs;
pobj->GetRenderLogicalBox( logicalMins, logicalMaxs );
Vector2DMin( logicalMins, m_vecLogicalMins, m_vecLogicalMins );
Vector2DMax( logicalMaxs, m_vecLogicalMaxs, m_vecLogicalMaxs );
}
// remeber bounds if valid
if ( m_Bounds.IsValidBox() )
{
m_LastValidBounds = m_Bounds;
}
m_bBoundsDirty = false;
}
bool CSelection::GetBoundsCenter(Vector &vecCenter)
{
if ( m_bBoundsDirty )
UpdateSelectionBounds();
if ( m_SelectionList.Count() == 0 )
return false;
m_Bounds.GetBoundsCenter( vecCenter );
return true;
}
bool CSelection::GetLogicalBoundsCenter( Vector2D &vecCenter )
{
if ( m_bBoundsDirty )
UpdateSelectionBounds();
if ( m_SelectionList.Count() == 0 )
return false;
vecCenter = (m_vecLogicalMins+m_vecLogicalMaxs)/2;
return true;
}
bool CSelection::IsEmpty()
{
return m_SelectionList.Count() == 0;
}
const CMapObjectList *CSelection::GetList()
{
return &m_SelectionList;
}
const CMapObjectList* CSelection::GetHitList()
{
return &m_HitList;
}
int CSelection::GetCount()
{
return m_SelectionList.Count();
}
//-----------------------------------------------------------------------------
// Purpose: Returns the current selection mode. The selection mode determines
// what gets selected when the user clicks on something - the group,
// the entity, or the solid.
//-----------------------------------------------------------------------------
SelectMode_t CSelection::GetMode()
{
return m_eSelectMode;
}
void CSelection::SetSelectionState(SelectionState_t eSelectionState)
{
for ( int i=0; i<m_SelectionList.Count(); i++ )
{
CMapEntity *pObject = (CMapEntity *)m_SelectionList.Element(i);
pObject->SetSelectionState( eSelectionState );
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool CSelection::IsAnEntitySelected(void)
{
if (m_SelectionList.Count() > 0)
{
int nSelCount = m_SelectionList.Count();
for (int i = 0; i < nSelCount; i++)
{
CMapClass *pObject = m_SelectionList.Element(i);
CMapEntity *pEntity = dynamic_cast <CMapEntity *> (pObject);
if (pEntity != NULL)
{
return true;
}
}
}
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if the selection is editable. Every object must be
// individually editable for this routine to return true.
//-----------------------------------------------------------------------------
bool CSelection::IsEditable()
{
if ( m_SelectionList.Count() > 0 )
{
int nSelCount = m_SelectionList.Count();
for (int i = 0; i < nSelCount; i++)
{
CMapClass *pObject = m_SelectionList.Element(i);
if ( pObject->IsEditable() == false )
{
return false;
}
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Returns true if the selection is copyable. CManifestInstance classes
// are not copyable.
//-----------------------------------------------------------------------------
bool CSelection::IsCopyable()
{
if ( m_SelectionList.Count() > 0 )
{
int nSelCount = m_SelectionList.Count();
for (int i = 0; i < nSelCount; i++)
{
CMapClass *pObject = m_SelectionList.Element(i);
if ( pObject->IsMapClass( MAPCLASS_TYPE( CManifestInstance ) ) )
{
return false;
}
}
}
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Sets the current selection mode, which determines which objects
// are selected when the user clicks on things.
//-----------------------------------------------------------------------------
void CSelection::SetMode(SelectMode_t eNewSelectMode)
{
SelectMode_t eOldSelectMode = m_eSelectMode;
m_eSelectMode = eNewSelectMode;
if ((eOldSelectMode == selectSolids) ||
((eOldSelectMode == selectObjects) && (eNewSelectMode == selectGroups)))
{
//
// If we are going from a more specific selection mode to a less specific one,
// clear the selection. This avoids unexpectedly selecting new things.
//
SelectObject(NULL, scClear|scSaveChanges);
}
else
{
//
// Put all the children of the selected objects in a list, along with their children.
//
CMapObjectList NewList;
int nSelCount = m_SelectionList.Count();
for (int i = 0; i < nSelCount; i++)
{
CMapClass *pObject = m_SelectionList[i];
AddLeavesToListCallback(pObject, &NewList);
pObject->EnumChildren((ENUMMAPCHILDRENPROC)AddLeavesToListCallback, (DWORD)&NewList);
}
SelectObject(NULL, scClear|scSaveChanges);
//
// Add child objects to selection.
//
for (int pos=0;pos<NewList.Count();pos++)
{
CMapClass *pObject = NewList[pos];
CMapClass *pSelObject = pObject->PrepareSelection(eNewSelectMode);
if (pSelObject)
{
SelectObject(pSelObject, scSelect);
}
}
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : *pObject -
//-----------------------------------------------------------------------------
void CSelection::AddHit(CMapClass *pObject)
{
if ( m_HitList.Find(pObject) == -1 )
{
m_HitList.AddToTail(pObject);
}
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
void CSelection::ClearHitList(void)
{
m_HitList.RemoveAll();
m_iCurHit = -1;
}
bool CSelection::RemoveAll(void)
{
for ( int i=0;i<m_SelectionList.Count(); i++ )
{
CMapClass *pObject = m_SelectionList.Element(i);
pObject->SetSelectionState(SELECT_NONE);
}
m_SelectionList.RemoveAll();
SetBoundsDirty();
return true;
}
bool CSelection::RemoveDead(void)
{
bool bFoundOne = false;
for ( int i=m_SelectionList.Count()-1; i>=0; i-- )
{
CMapClass *pObject = m_SelectionList.Element(i);
if (!pObject->GetParent())
{
m_SelectionList.FastRemove(i);
pObject->SetSelectionState(SELECT_NONE);
bFoundOne = true;
}
}
// TODO check if we do the same as in SelectObject
SetBoundsDirty();
return bFoundOne;
}
//-----------------------------------------------------------------------------
// Purpose: Removes objects that are not visible from the selection set.
//-----------------------------------------------------------------------------
bool CSelection::RemoveInvisibles(void)
{
bool bFoundOne = false;
for ( int i=m_SelectionList.Count()-1; i>=0; i-- )
{
CMapClass *pObject = m_SelectionList.Element(i);
if ( !pObject->IsVisible() )
{
m_SelectionList.FastRemove(i);
pObject->SetSelectionState(SELECT_NONE);
bFoundOne = true;
}
}
SetBoundsDirty();
return bFoundOne;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : iIndex -
// bUpdateViews -
//-----------------------------------------------------------------------------
void CSelection::SetCurrentHit(int iIndex, bool bCascading)
{
if ( m_HitList.Count() == 0)
{
Assert( m_iCurHit == -1);
return;
}
// save & toggle old selection off
if (m_iCurHit != -1)
{
CMapClass *pObject = m_HitList[m_iCurHit];
SelectObject(pObject, scToggle|scSaveChanges);
}
if (iIndex == hitNext)
{
// hit next object
m_iCurHit++;
}
else if (iIndex == hitPrev)
{
// hit prev object
m_iCurHit--;
}
else
{
m_iCurHit = iIndex;
}
// make sure curhit is valid
if (m_iCurHit >= m_HitList.Count())
{
m_iCurHit = 0;
}
else if (m_iCurHit < 0)
{
m_iCurHit = m_HitList.Count() - 1;
}
CMapClass *pObject = m_HitList[m_iCurHit];
if ( bCascading )
{
// Build actual selection list based on cascading...
CUtlRBTree< CMapClass*, unsigned short > tree( 0, 0, DefLessFunc( CMapClass* ) );
bool bRecursive = false; // not used yet
m_pDocument->BuildCascadingSelectionList( pObject, tree, bRecursive );
CMapObjectList list;
list.AddToTail( pObject );
bool bRootIsSelected = IsSelected(pObject);
bool bUniformSelectionState = true;
for ( unsigned short h = tree.FirstInorder(); h != tree.InvalidIndex(); h = tree.NextInorder(h) )
{
list.AddToTail( list[h] );
if ( IsSelected( list[h] ) != bRootIsSelected )
{
bUniformSelectionState = false;
}
}
/* Change toggle to select or unselect if we're toggling and cascading
// but the root + children have different selection state
if ( ( !bUniformSelectionState ) && ( cmd == scToggle ) )
{
cmd = bRootIsSelected ? scSelect : scUnselect;
}*/
SelectObjectList( &list, scSelect );
}
else
{
SelectObject(pObject, scToggle );
}
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : pobj -
// cmd -
//-----------------------------------------------------------------------------
bool CSelection::SelectObject(CMapClass *pObj, int cmd)
{
// if no object is given we only can execute the clear command
if ( pObj == NULL )
{
// check if selection is already empty
if (m_SelectionList.Count() == 0)
return false; // nothing to do
if ( cmd & scClear )
{
RemoveAll();
}
}
else // object oriented operation
{
int iIndex = m_SelectionList.Find(pObj);
bool bAlreadySelected = iIndex != -1;
if ( cmd & scToggle )
{
if ( bAlreadySelected )
cmd |= scUnselect;
else
cmd |= scSelect;
}
if ( cmd & scSelect )
{
if ( cmd & scClear )
{
// if we re-selected the only selected element, nothing changes
if ( bAlreadySelected && m_SelectionList.Count() == 1 )
return false;
RemoveAll();
bAlreadySelected = false; // reset that flag
}
if ( bAlreadySelected )
return false;
m_SelectionList.AddToTail(pObj);
pObj->SetSelectionState(SELECT_NORMAL);
}
else if ( (cmd & scUnselect) && bAlreadySelected )
{
// ok unselect an yet selected object
m_SelectionList.Remove(iIndex);
pObj->SetSelectionState(SELECT_NONE);
}
else
{
return false; // nothing was changed
}
}
// ok something in the selection was changed, set dirty flags
SetBoundsDirty();
if ( cmd & scSaveChanges )
{
// changing the selection automatically saves changes made to the properties dialog
GetMainWnd()->pObjectProperties->SaveData();
}
// always mark data dirty
GetMainWnd()->pObjectProperties->MarkDataDirty();
// uddate all views
m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_SELECTION );
return true;
}
//-----------------------------------------------------------------------------
// Purpose: Clears the current selection and selects everything in the given list.
// Input : pList - Objects to select.
//-----------------------------------------------------------------------------
void CSelection::SelectObjectList( const CMapObjectList *pList, int cmd )
{
// Clear the current selection.
// Clear the current selection.
if ( cmd & scSaveChanges )
{
GetMainWnd()->pObjectProperties->SaveData();
cmd &= ~scSaveChanges;
}
if ( cmd & scClear )
{
RemoveAll();
cmd &= ~scClear;
}
if ( pList != NULL )
{
for (int pos=0;pos<pList->Count();pos++)
{
CMapClass *pObject = pList->Element(pos);
CMapClass *pSelObject = pObject->PrepareSelection( m_eSelectMode );
if (pSelObject)
{
SelectObject( pSelObject, cmd );
}
}
}
}