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
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 ); |
|
} |
|
} |
|
} |
|
}
|
|
|