|
|
//========= Copyright � 1996-2005, 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.RemoveAll(); 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 );
// If there are any solids, then only use the bounds for those. Otherwise,
// an entity that is off the grid can pull all the solids off the grid and you never want that.
int nSolids = 0; for (int i = 0; i < m_SelectionList.Count(); i++) { CMapClass *pobj = m_SelectionList[i]; CEditGameClass *pEdit = dynamic_cast< CEditGameClass* >( pobj ); if ( (pEdit && pEdit->IsSolidClass()) || dynamic_cast<CMapSolid *>(pobj) ) { ++nSolids; } }
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 if ( nSolids == 0 ) { 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++ ) { CMapClass *pMapClass = (CUtlReference< CMapClass >)m_SelectionList.Element(i); CMapEntity *pObject = (CMapEntity *)pMapClass; 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.FastRemove(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( SAVEDATA_SELECTION_CHANGED ); }
// 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( SAVEDATA_SELECTION_CHANGED ); cmd &= ~scSaveChanges; }
if ( cmd & scClear ) { RemoveAll(); cmd &= ~scClear; }
if ( pList != NULL ) { for (int pos=0;pos<pList->Count();pos++) { CMapClass *pObject = (CUtlReference< CMapClass >)pList->Element(pos); CMapClass *pSelObject = pObject->PrepareSelection( m_eSelectMode ); if (pSelObject) { SelectObject( pSelObject, cmd ); } } } }
|