//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "stdafx.h" #include "ChunkFile.h" #include "SaveInfo.h" #include "MapClass.h" #include "MapEntity.h" // dvs: evil - base knows about the derived class #include "MapGroup.h" // dvs: evil - base knows about the derived class #include "MapWorld.h" // dvs: evil - base knows about the derived class #include "GlobalFunctions.h" #include "MapDoc.h" #include "VisGroup.h" #include "mapdefs.h" #include "tier0/minidump.h" int CMapAtom::s_nObjectIDCtr = 1; static CUtlVector s_Classes; // memdbgon must be the last include file in a .cpp file!!! #include bool CMapClass::s_bLoadingVMF = false; //----------------------------------------------------------------------------- // Purpose: // Input : Type - // pfnNew - //----------------------------------------------------------------------------- CMapClassManager::CMapClassManager(MAPCLASSTYPE Type, CMapClass *(*pfnNew)()) { MCMSTRUCT mcms; mcms.Type = Type; mcms.pfnNew = pfnNew; s_Classes.AddToTail(mcms); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CMapClassManager::~CMapClassManager(void) { s_Classes.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: // Input : Type - // Output : CMapClass //----------------------------------------------------------------------------- CMapClass *CMapClassManager::CreateObject(MAPCLASSTYPE Type) { unsigned uLen = strlen(Type)+1; for (int i = s_Classes.Count() - 1; i >= 0; i--) { MCMSTRUCT &mcms = s_Classes[i]; if (!memcmp(mcms.Type, Type, uLen)) { return (*mcms.pfnNew)(); } } Assert(FALSE); return(NULL); } //----------------------------------------------------------------------------- // Purpose: Constructor. Initializes data members. //----------------------------------------------------------------------------- CMapClass::CMapClass(void) { m_pSafeObject = CSafeObject::Create( this ); // // The document manages the unique object IDs. Eventually all object construction // should be done through the document, eliminating the need for CMapClass to know // about CMapDoc. // CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if (pDoc != NULL) { m_nID = pDoc->GetNextMapObjectID(); } else { m_nID = 0; } dwKept = 0; m_bTemporary = FALSE; m_bVisible = true; m_bVisible2D = true; m_bVisGroupShown = true; m_bVisGroupAutoShown = true; m_pColorVisGroup = NULL; r = g = b = 220; m_pParent = NULL; m_nRenderFrame = 0; m_pEditorKeys = NULL; m_Dependents.Purge(); } //----------------------------------------------------------------------------- // Purpose: Destructor. Deletes all children. //----------------------------------------------------------------------------- CMapClass::~CMapClass(void) { // Delete all of our children. m_Children.PurgeAndDeleteElements(); delete m_pEditorKeys; // In case any CMapDocs are pointing at us, let them know we're gone. m_pSafeObject->m_pObject = NULL; // Show a warning if anyone is left pointing at us. static bool bCheckSafeObjects = true; if ( bCheckSafeObjects && m_pSafeObject->GetRefCount() != 1 ) { int ret = AfxMessageBox( "Warning: a CMapClass is being deleted but is still referenced by a CMapDoc.\n" "Please tell a programmer.\n" "Click Yes to write a minidump and continue.\n" "Click No to ignore.", MB_YESNO ); if ( ret == IDYES ) { WriteMiniDump(); } else if ( ret == IDNO ) { // Ignore it and don't get in here again. bCheckSafeObjects = false; } } } const CSmartPtr< CSafeObject< CMapClass > >& CMapClass::GetSafeObjectSmartPtr() { return m_pSafeObject; } //----------------------------------------------------------------------------- // Purpose: // Input : pDependent - //----------------------------------------------------------------------------- void CMapClass::AddDependent(CMapClass *pDependent) { // // Never add ourselves to our dependents. It creates a circular dependency // which is bad. // if (pDependent == this) return; // // Don't add the same dependent twice. // int nIndex = m_Dependents.Find(pDependent); if (nIndex != -1) return; // // Also, never add one of our ancestors as a dependent. This too creates a // nasty circular dependency. // bool bIsOurAncestor = false; CMapClass *pTestParent = GetParent(); while (pTestParent != NULL) { if (pTestParent == pDependent) { bIsOurAncestor = true; break; } pTestParent = pTestParent->GetParent(); } if (!bIsOurAncestor) { m_Dependents.AddToTail(pDependent); Assert(m_Dependents.Count() < 1000); } } //----------------------------------------------------------------------------- // Purpose: Returns a copy of this object. We should never call this implementation // since CMapClass cannot be instantiated. // Input : bUpdateDependencies - Whether to update object dependencies when copying object pointers. //----------------------------------------------------------------------------- CMapClass *CMapClass::Copy(bool bUpdateDependencies) { Assert(FALSE); return(NULL); } //----------------------------------------------------------------------------- // Purpose: Turns this object into a duplicate of the given object. // Input : pFrom - The object to replicate. // Output : Returns a pointer to this object. //----------------------------------------------------------------------------- CMapClass *CMapClass::CopyFrom(CMapClass *pFrom, bool bUpdateDependencies) { // Copy CMapPoint stuff. dvs: should be in CMapPoint implementation! m_Origin = pFrom->m_Origin; // // Copy CMapClass stuff. // int nVisGroupCount = pFrom->GetVisGroupCount(); for (int nVisGroup = 0; nVisGroup < nVisGroupCount; nVisGroup++) { CVisGroup *pVisGroup = pFrom->GetVisGroup(nVisGroup); if (!pVisGroup->IsAutoVisGroup()) { AddVisGroup(pVisGroup); } } //m_bVisible = pFrom->m_bVisible; //m_bVisGroupShown = pFrom->m_bVisGroupShown; m_bTemporary = pFrom->m_bTemporary; m_bVisible2D = pFrom->m_bVisible2D; m_nRenderFrame = pFrom->m_nRenderFrame; m_CullBox = pFrom->m_CullBox; m_BoundingBox = pFrom->m_BoundingBox; m_Render2DBox = pFrom->m_Render2DBox; r = pFrom->r; g = pFrom->g; b = pFrom->b; m_Dependents.RemoveAll(); m_Dependents.AddVectorToTail(pFrom->m_Dependents); // dvs: should I copy m_pEditorKeys? // // Don't link to the parent if we're not updating dependencies, just copy the pointer. // if (bUpdateDependencies) { UpdateParent( pFrom->GetParent() ); } else { m_pParent = pFrom->GetParent(); } return(this); } //----------------------------------------------------------------------------- // Purpose: Returns the culling bbox of this object. // Input : mins - receives the minima for culling // maxs - receives the maxima for culling. //----------------------------------------------------------------------------- void CMapClass::GetCullBox(Vector &mins, Vector &maxs) { m_CullBox.GetBounds(mins, maxs); } //----------------------------------------------------------------------------- // Purpose: Initialize the cull box with the bounds of the faces. //----------------------------------------------------------------------------- void CMapClass::SetCullBoxFromFaceList( CMapFaceList *pFaces ) { SetBoxFromFaceList( pFaces, m_CullBox ); } //----------------------------------------------------------------------------- // Purpose: Returns the bounding bbox of this object. // Input : mins - receives the minima for culling // maxs - receives the maxima for culling. //----------------------------------------------------------------------------- void CMapClass::GetBoundingBox( Vector &mins, Vector &maxs ) { m_BoundingBox.GetBounds( mins, maxs ); } //----------------------------------------------------------------------------- // Purpose: Initialize the bounding box with the bounds of the faces. //----------------------------------------------------------------------------- void CMapClass::SetBoundingBoxFromFaceList( CMapFaceList *pFaces ) { SetBoxFromFaceList( pFaces, m_BoundingBox ); } //----------------------------------------------------------------------------- // Purpose: Initialize box with the bounds of the faces. //----------------------------------------------------------------------------- void CMapClass::SetBoxFromFaceList( CMapFaceList *pFaces, BoundBox &Box ) { // // Calculate our 3D bounds. // Box.ResetBounds(); for (int iFace = 0; iFace < pFaces->Count(); iFace++) { CMapFace *pFace = pFaces->Element( iFace ); int nPoints = pFace->GetPointCount(); for (int i = 0; i < nPoints; i++) { Vector point; pFace->GetPoint(point, i); // // Push the culling box out in all directions. // TODO: rotate the culling box based on the cone orientation // for (int nDim = 0; nDim < 3; nDim++) { Box.bmins[0] = min(Box.bmins[0], m_Origin[0] - point[nDim]); Box.bmins[1] = min(Box.bmins[1], m_Origin[1] - point[nDim]); Box.bmins[2] = min(Box.bmins[2], m_Origin[2] - point[nDim]); Box.bmaxs[0] = max(Box.bmaxs[0], m_Origin[0] + point[nDim]); Box.bmaxs[1] = max(Box.bmaxs[1], m_Origin[1] + point[nDim]); Box.bmaxs[2] = max(Box.bmaxs[2], m_Origin[2] + point[nDim]); } } } } //----------------------------------------------------------------------------- // Purpose: Returns the bbox for 2D rendering of this object. // FIXME: this can be removed if we do all our 2D rendering in this->Render2D. // Input : mins - receives the minima for culling // maxs - receives the maxima for culling. //----------------------------------------------------------------------------- void CMapClass::GetRender2DBox(Vector &mins, Vector &maxs) { m_Render2DBox.GetBounds(mins, maxs); } //----------------------------------------------------------------------------- // Purpose: Returns the number of keys that were loaded from the "editor" // section of the VMF. These keys are held until they are handled, then // the memory is freed. //----------------------------------------------------------------------------- int CMapClass::GetEditorKeyCount(void) { if (m_pEditorKeys == NULL) { return NULL; } return m_pEditorKeys->GetCount(); } //----------------------------------------------------------------------------- // Purpose: Returns the key name for the given editor key index. //----------------------------------------------------------------------------- const char *CMapClass::GetEditorKey(int nIndex) { if (m_pEditorKeys == NULL) { return NULL; } return m_pEditorKeys->GetKey(nIndex); } //----------------------------------------------------------------------------- // Purpose: Returns the value for the given editor key index. //----------------------------------------------------------------------------- const char *CMapClass::GetEditorKeyValue(int nIndex) { if (m_pEditorKeys == NULL) { return NULL; } return m_pEditorKeys->GetValue(nIndex); } //----------------------------------------------------------------------------- // Purpose: Returns the value for the given editor key name. // NOTE: this is used for unique keys and will return the value for the // FIRST key with the given name. //----------------------------------------------------------------------------- const char *CMapClass::GetEditorKeyValue(const char *szKey) { if (m_pEditorKeys == NULL) { return NULL; } return m_pEditorKeys->GetValue(szKey); } //----------------------------------------------------------------------------- // Purpose: Begins a depth-first search of the map heirarchy. // Input : pos - An iterator // Output : CMapClass //----------------------------------------------------------------------------- CMapClass *CMapClass::GetFirstDescendent(EnumChildrenPos_t &pos) { pos.nDepth = 0; pos.Stack[0].pParent = this; if ( m_Children.Count() ) { pos.Stack[0].pos = 0; return(GetNextDescendent(pos)); } else { pos.Stack[0].pos = -1; return NULL; } } //----------------------------------------------------------------------------- // Purpose: Continues a depth-first search of the map heirarchy. // Input : &pos - // Output : CMapClass //----------------------------------------------------------------------------- CMapClass *CMapClass::GetNextDescendent(EnumChildrenPos_t &pos) { while (pos.nDepth >= 0) { while (pos.Stack[pos.nDepth].pos != -1) { // // Get the next child of the parent on top of the stack. // CMapClass *pParent = pos.Stack[pos.nDepth].pParent; CMapClass *pChild = pParent->m_Children[pos.Stack[pos.nDepth].pos]; pos.Stack[pos.nDepth].pos++; if ( pos.Stack[pos.nDepth].pos == pParent->m_Children.Count() ) pos.Stack[pos.nDepth].pos= -1; // If this object has children, push it onto the stack. if ( pChild->m_Children.Count() ) { pos.nDepth++; if (pos.nDepth < MAX_ENUM_CHILD_DEPTH) { pos.Stack[pos.nDepth].pParent = pChild; pos.Stack[pos.nDepth].pos = 0; } else { // dvs: stack overflow! pos.nDepth--; } } // // If this object has no children, return it. // else { return(pChild); } } // // Finished with this object's children, pop the stack and return the object. // pos.nDepth--; if (pos.nDepth >= 0) { return(pos.Stack[pos.nDepth + 1].pParent); } } return(NULL); } //----------------------------------------------------------------------------- // Purpose: Returns the world object that the given object belongs to. // Input : pStart - Object to traverse up from to find the world object. //----------------------------------------------------------------------------- CMapWorld *CMapClass::GetWorldObject(CMapAtom *pStart) { CMapAtom *pObject = pStart; while (pObject != NULL) { if ( IsWorldObject( pObject ) ) { return (CMapWorld*)pObject; } pObject = pObject->GetParent(); } // has no world: return NULL; } BOOL CMapClass::IsChildOf(CMapAtom *pObject) { CMapAtom *pParent = m_pParent; while( pParent ) { if( pParent == pObject ) return TRUE; if( IsWorldObject(pParent) ) return FALSE; // world object, not parent .. return false. pParent = pParent->GetParent(); } return FALSE; } //----------------------------------------------------------------------------- // Purpose: Returns whether this object belongs to the given visgroup. // Input : pVisGroup - //----------------------------------------------------------------------------- int CMapClass::IsInVisGroup(CVisGroup *pVisGroup) { if (pVisGroup != NULL) { if ( m_VisGroups.Find( pVisGroup ) != -1 ) { return 1; } else { return 0; } } return 0; } //----------------------------------------------------------------------------- // Purpose: // Output : Returns true if the color was specified by this call, false if not. //----------------------------------------------------------------------------- bool CMapClass::UpdateObjectColor(void) { // // The user can choose a visgroup from which to get the color from. // If one was chosen, set our color from that visgroup. // if (m_pColorVisGroup) { color32 rgbColor = m_pColorVisGroup->GetColor(); SetRenderColor(rgbColor); return true; } else if (m_pParent && !IsWorldObject(m_pParent)) { color32 rgbColor = m_pParent->GetRenderColor(); SetRenderColor(rgbColor); return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Sets the visgroup that this object gets its color from. //----------------------------------------------------------------------------- void CMapClass::SetColorVisGroup(CVisGroup *pVisGroup) { m_pColorVisGroup = pVisGroup; UpdateObjectColor(); } //----------------------------------------------------------------------------- // Purpose: Adds the given visgroup to the list of visgroups that this object // belongs to. //----------------------------------------------------------------------------- void CMapClass::AddVisGroup(CVisGroup *pVisGroup) { if (m_VisGroups.Find(pVisGroup) == -1) { m_VisGroups.AddToTail(pVisGroup); } } //----------------------------------------------------------------------------- // Purpose: Removes the given visgroup from the list of visgroups that this object // belongs to. //----------------------------------------------------------------------------- void CMapClass::RemoveVisGroup(CVisGroup *pVisGroup) { int nIndex = m_VisGroups.Find(pVisGroup); if (nIndex != -1 ) { m_VisGroups.FastRemove(nIndex); CheckVisibility(); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- int CMapClass::GetVisGroupCount(void) { return m_VisGroups.Count(); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- CVisGroup *CMapClass::GetVisGroup(int nIndex) { return m_VisGroups.Element(nIndex); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CMapClass::RemoveAllVisGroups(void) { m_VisGroups.RemoveAll(); // Remove all visgroups from children as well. FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->RemoveAllVisGroups(); } // Not in any visgroups; can't be hidden that way. VisGroupShow(true); } //----------------------------------------------------------------------------- // Purpose: Adds the specified child to this object. // Input : pChild - Object to add as a child of this object. //----------------------------------------------------------------------------- void CMapClass::AddChild(CMapClass *pChild) { if ( m_Children.Find(pChild) != -1 ) { pChild->m_pParent = this; return; } m_Children.AddToTail(pChild); pChild->m_pParent = this; // // Update our bounds with the child's bounds. // Vector vecMins; Vector vecMaxs; pChild->GetCullBox(vecMins, vecMaxs); m_CullBox.UpdateBounds(vecMins, vecMaxs); pChild->GetBoundingBox( vecMins, vecMaxs ); m_BoundingBox.UpdateBounds( vecMins, vecMaxs ); pChild->GetRender2DBox(vecMins, vecMaxs); m_Render2DBox.UpdateBounds(vecMins, vecMaxs); if (m_pParent != NULL) { GetParent()->UpdateChild(this); } } //----------------------------------------------------------------------------- // Purpose: Removes all of this object's children. //----------------------------------------------------------------------------- void CMapClass::RemoveAllChildren(void) { // // Detach the children from us. They are no longer in our world heirarchy. // FOR_EACH_OBJ( m_Children, pos ) { m_Children[pos]->m_pParent = NULL; } // // Remove them from our list. // m_Children.RemoveAll(); } //----------------------------------------------------------------------------- // Purpose: Removes the specified child from this object. // Input : pChild - The child to remove. // bUpdateBounds - TRUE to calculate new bounds, FALSE not to. //----------------------------------------------------------------------------- void CMapClass::RemoveChild(CMapClass *pChild, bool bUpdateBounds) { int index = m_Children.Find(pChild); if (index == -1) { pChild->m_pParent = NULL; return; } m_Children.Remove(index); pChild->m_pParent = NULL; if (bUpdateBounds) { PostUpdate(Notify_Removed); } } //----------------------------------------------------------------------------- // Purpose: Copies all children of a given object as children of this object. // NOTE: The child objects are replicated, not merely added as children. // Input : pobj - The object whose children are to be copied. //----------------------------------------------------------------------------- void CMapClass::CopyChildrenFrom(CMapClass *pobj, bool bUpdateDependencies) { FOR_EACH_OBJ( pobj->m_Children, pos ) { CMapClass *pChild = pobj->m_Children.Element(pos); CMapClass *pChildCopy = pChild->Copy(bUpdateDependencies); pChildCopy->CopyChildrenFrom(pChild, bUpdateDependencies); AddChild(pChildCopy); } } //----------------------------------------------------------------------------- // Purpose: Recalculate's this object's bounding boxes. CMapClass-derived classes // should call this first, then update using their local data. // Input : bFullUpdate - When set to TRUE, call CalcBounds on all children // before updating our bounds. //----------------------------------------------------------------------------- void CMapClass::CalcBounds(BOOL bFullUpdate) { if ( CMapClass::s_bLoadingVMF ) return; m_CullBox.ResetBounds(); m_BoundingBox.ResetBounds(); m_Render2DBox.ResetBounds(); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); if (bFullUpdate) { pChild->CalcBounds(TRUE); } m_CullBox.UpdateBounds(&pChild->m_CullBox); m_BoundingBox.UpdateBounds(&pChild->m_BoundingBox); m_Render2DBox.UpdateBounds(&pChild->m_Render2DBox); } } //----------------------------------------------------------------------------- // Purpose: Sets the render color of this object and all its children. // Input : uchRed, uchGreen, uchBlue - Color components. //----------------------------------------------------------------------------- void CMapClass::SetRenderColor(color32 rgbColor) { CMapAtom::SetRenderColor(rgbColor); // // Set the render color of all our children. // FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); if (pChild != NULL) { pChild->SetRenderColor(rgbColor); } } } //----------------------------------------------------------------------------- // Purpose: Sets the render color of this object and all its children. // Input : uchRed, uchGreen, uchBlue - Color components. //----------------------------------------------------------------------------- void CMapClass::SetRenderColor(unsigned char uchRed, unsigned char uchGreen, unsigned char uchBlue) { CMapAtom::SetRenderColor(uchRed, uchGreen, uchBlue); // // Set the render color of all our children. // FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); if (pChild != NULL) { pChild->SetRenderColor(uchRed, uchGreen, uchBlue); } } } //----------------------------------------------------------------------------- // Purpose: Returns a pointer to the object that should be added to the selection // list because this object was clicked on with a given selection mode. // Input : eSelectMode - //----------------------------------------------------------------------------- CMapClass *CMapClass::PrepareSelection(SelectMode_t eSelectMode) { if ((eSelectMode == selectGroups) && (m_pParent != NULL) && !IsWorldObject(m_pParent)) { return GetParent()->PrepareSelection(eSelectMode); } return this; } //----------------------------------------------------------------------------- // Purpose: Calls an enumerating function for each of our children that are of // of a given type, recursively enumerating their children also. // Input : pfn - Enumeration callback function. Called once per child. // dwParam - User data to pass into the enumerating callback. // Type - Unless NULL, only objects of the given type will be enumerated. // Output : Returns FALSE if the enumeration was terminated early, TRUE if it completed. //----------------------------------------------------------------------------- BOOL CMapClass::EnumChildren(ENUMMAPCHILDRENPROC pfn, unsigned int dwParam, MAPCLASSTYPE Type) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); if (!Type || pChild->IsMapClass(Type)) { if(!(*pfn)(pChild, dwParam)) { return FALSE; } } // enum this child's children if (!pChild->EnumChildren(pfn, dwParam, Type)) { return FALSE; } } return TRUE; } //----------------------------------------------------------------------------- // Purpose: Enumerates a this object's children, only recursing into groups. // Children of entities will not be enumerated. // Input : pfn - Enumeration callback function. Called once per child. // dwParam - User data to pass into the enumerating callback. // Type - Unless NULL, only objects of the given type will be enumerated. // Output : Returns FALSE if the enumeration was terminated early, TRUE if it completed. //----------------------------------------------------------------------------- BOOL CMapClass::EnumChildrenRecurseGroupsOnly(ENUMMAPCHILDRENPROC pfn, unsigned int dwParam, MAPCLASSTYPE Type) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); if (!Type || pChild->IsMapClass(Type)) { if (!(*pfn)(pChild, dwParam)) { return FALSE; } } if (pChild->IsGroup()) { if (!pChild->EnumChildrenRecurseGroupsOnly(pfn, dwParam, Type)) { return FALSE; } } } return TRUE; } //----------------------------------------------------------------------------- // Purpose: Iterates through an object, and all it's children, looking for an // entity with a matching key and value // Input : key - // value - // Output : CMapEntity - the entity found //----------------------------------------------------------------------------- CMapEntity *CMapClass::FindChildByKeyValue( const char* key, const char* value, bool *bIsInInstance, VMatrix *InstanceMatrix ) { if ( !key || !value ) return NULL; FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element( pos ); CMapEntity *e = pChild->FindChildByKeyValue( key, value, bIsInInstance, InstanceMatrix ); if ( e ) return e; } return NULL; } //----------------------------------------------------------------------------- // Purpose: Called after this object is added to the world. // // NOTE: This function is NOT called during serialization. Use PostloadWorld // to do similar bookkeeping after map load. // // Input : pWorld - The world that we have been added to. //----------------------------------------------------------------------------- void CMapClass::OnAddToWorld(CMapWorld *pWorld) { // // Notify all our children. // FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->OnAddToWorld(pWorld); } } //----------------------------------------------------------------------------- // Purpose: Called to notify the object that it has just been cloned // iterates through and notifies all the children of their cloned state // NOTE: assumes that the children are in the same order in both the // original and the clone // Input : pNewObj - the clone of this object // OriginalList - The list of objects that were cloned // NewList - The parallel list of clones of objects in OriginalList //----------------------------------------------------------------------------- void CMapClass::OnClone( CMapClass *pNewObj, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList ) { Assert( m_Children.Count() == pNewObj->m_Children.Count() ); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element( pos ); CMapClass *pNewChild = pNewObj->m_Children.Element( pos ); pChild->OnClone( pNewChild, pWorld, OriginalList, NewList ); } } //----------------------------------------------------------------------------- // Purpose: Called to notify the object that it has just been cloned // iterates through and notifies all the children of their cloned state // NOTE: assumes that the children are in the same order in both the // original and the clone // Input : pNewObj - the clone of this object // OriginalList - The list of objects that were cloned // NewList - The parallel list of clones of objects in OriginalList //----------------------------------------------------------------------------- void CMapClass::OnPreClone( CMapClass *pNewObj, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList ) { Assert( m_Children.Count() == pNewObj->m_Children.Count() ); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element( pos ); CMapClass *pNewChild = pNewObj->m_Children.Element( pos ); pChild->OnPreClone( pNewChild, pWorld, OriginalList, NewList ); } } //----------------------------------------------------------------------------- // Purpose: Notifies this object that a copy of itself is about to be pasted. // Allows the object to generate new unique IDs in the copy of itself. // Input : pCopy - // pSourceWorld - // pDestWorld - // OriginalList - // NewList - //----------------------------------------------------------------------------- void CMapClass::OnPrePaste(CMapClass *pCopy, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList) { Assert( m_Children.Count() == pCopy->m_Children.Count() ); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); CMapClass *pCopyChild = pCopy->m_Children.Element(pos); pChild->OnPrePaste(pCopyChild, pSourceWorld, pDestWorld, OriginalList, NewList); } } //----------------------------------------------------------------------------- // Purpose: Notifies this object that a copy of itself is being pasted. // Allows the object to fixup any references to other objects in the // clipboard with references to their copies. // Input : pCopy - // pSourceWorld - // pDestWorld - // OriginalList - // NewList - //----------------------------------------------------------------------------- void CMapClass::OnPaste(CMapClass *pCopy, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList) { Assert( m_Children.Count() == pCopy->m_Children.Count() ); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); CMapClass *pCopyChild = pCopy->m_Children.Element(pos); pChild->OnPaste(pCopyChild, pSourceWorld, pDestWorld, OriginalList, NewList); } } //----------------------------------------------------------------------------- // Purpose: Called just after this object has been removed from the world so // that it can unlink itself from other objects in the world. // Input : pWorld - The world that we were just removed from. // bNotifyChildren - Whether we should forward notification to our children. //----------------------------------------------------------------------------- void CMapClass::OnRemoveFromWorld(CMapWorld *pWorld, bool bNotifyChildren) { // // Since we are being removed from the world, we cannot have any dependents. // Notify any dependent objects, so they can release pointers to us. // Our dependencies will be regenerated if we are added back into the world. // NotifyDependents(Notify_Removed); m_Dependents.RemoveAll(); if (bNotifyChildren) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->OnRemoveFromWorld(pWorld, true); } } } //----------------------------------------------------------------------------- // Purpose: Called after a map file has been completely loaded. // Input : pWorld - The world that we are in. //----------------------------------------------------------------------------- void CMapClass::PostloadWorld(CMapWorld *pWorld) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->PostloadWorld(pWorld); } } //----------------------------------------------------------------------------- // Purpose: Called after all visgroups have been completely loaded. Checks for // objects hidden but without a visgroup. // Input : void //----------------------------------------------------------------------------- bool CMapClass::PostloadVisGroups( bool bLoading ) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->PostloadVisGroups( bLoading); } return CheckVisibility( bLoading ); } //----------------------------------------------------------------------------- // Purpose: Calls RenderPreload for each of our children. This allows them to // cache any resources that they need for rendering. // Input : pRender - Pointer to the 3D renderer. //----------------------------------------------------------------------------- bool CMapClass::RenderPreload(CRender3D *pRender, bool bNewContext) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->RenderPreload(pRender, bNewContext); } return(true); } //----------------------------------------------------------------------------- // Purpose: // Input : *pRender - //----------------------------------------------------------------------------- void CMapClass::Render2D(CRender2D *pRender) { // This is not needed because the recursion is performed in CMapView2D::Render // POSITION pos = Children.GetHeadPosition(); // while (pos != NULL) // { // CMapClass *pChild = Children.GetNext(pos); // if (pChild->IsVisible() && pChild->IsVisible2D()) // { // pChild->Render2D(pRender); // } // } } //----------------------------------------------------------------------------- // Purpose: // Input : pRender - //----------------------------------------------------------------------------- void CMapClass::Render3D(CRender3D *pRender) { } //----------------------------------------------------------------------------- // Purpose: Transforms all children. Derived implementations should call this, // then do their own thing. // Input : t - Pointer to class containing transformation information. //----------------------------------------------------------------------------- void CMapClass::DoTransform(const VMatrix &matrix) { CMapPoint::DoTransform(matrix); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->Transform( matrix ); } } //----------------------------------------------------------------------------- // Default logical box //----------------------------------------------------------------------------- void CMapClass::GetRenderLogicalBox( Vector2D &mins, Vector2D &maxs ) { mins.Init( COORD_NOTINIT, COORD_NOTINIT ); maxs.Init( COORD_NOTINIT, COORD_NOTINIT ); } const Vector2D& CMapClass::GetLogicalPosition( ) { static Vector2D pos( COORD_NOTINIT, COORD_NOTINIT ); return pos; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- size_t CMapClass::GetSize(void) { return(sizeof(*this)); } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMapClass::HitTest2D(CMapView2D *pView, const Vector2D &point, HitInfo_t &HitData) { HitData.pObject = NULL; HitData.nDepth = g_MAX_MAP_COORD*3; HitData.uData = 0; bool bFoundHit = false; if ( !IsVisible() ) return false; FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); HitInfo_t testHitData; if ( pChild->HitTest2D(pView, point, testHitData) ) { Assert( testHitData.pObject != NULL ); if ( testHitData.nDepth < HitData.nDepth ) { HitData = testHitData; bFoundHit = true; } } } return bFoundHit; } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CMapClass::HitTestLogical(CMapViewLogical *pView, const Vector2D &point, HitInfo_t &hitData) { if ( !IsVisibleLogical() ) return false; FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); if ( pChild->HitTestLogical(pView, point, hitData) ) return true; } return false; } //----------------------------------------------------------------------------- // Purpose: Sets the selection state of this object's children. // Input : eSelectionState - //----------------------------------------------------------------------------- SelectionState_t CMapClass::SetSelectionState(SelectionState_t eSelectionState) { FOR_EACH_OBJ( m_Children, pos ) { CMapAtom *pObject = m_Children.Element(pos); pObject->SetSelectionState(eSelectionState); } return CMapAtom::SetSelectionState(eSelectionState); } //----------------------------------------------------------------------------- // Purpose: Our child's bounding box has changed - notify our parent. The real // work will be done in CMapWorld::UpdateChild. // Input : pChild - The child whose bounding box changed. //----------------------------------------------------------------------------- void CMapClass::UpdateChild(CMapClass *pChild) { if (m_pParent != NULL) { GetParent()->UpdateChild(this); } } //----------------------------------------------------------------------------- // Purpose: Returns a coordinate frame to render in // Input : matrix - // Output : returns true if a new matrix is returned, false if it is invalid //----------------------------------------------------------------------------- bool CMapClass::GetTransformMatrix( VMatrix& matrix ) { // try and get our parents transform matrix CMapClass *p = CMapClass::GetParent(); if ( p ) { return p->GetTransformMatrix( matrix ); } return false; } //----------------------------------------------------------------------------- // Purpose: // Input : pLoadInfo - // pWorld - // Output : //----------------------------------------------------------------------------- ChunkFileResult_t CMapClass::LoadEditorCallback(CChunkFile *pFile, CMapClass *pObject) { return(pFile->ReadChunk((KeyHandler_t)LoadEditorKeyCallback, pObject)); } //----------------------------------------------------------------------------- // Purpose: Handles keyvalues when loading the editor chunk of an object from the // MAP file. Keys are transferred to a special keyvalue list for use after // the entire map has been loaded. // Input : szKey - Key to handle. // szValue - Value of key. // pObject - Object being loaded. // Output : Returns ChunkFile_Ok. //----------------------------------------------------------------------------- ChunkFileResult_t CMapClass::LoadEditorKeyCallback(const char *szKey, const char *szValue, CMapClass *pObject) { if (!stricmp(szKey, "color")) { CChunkFile::ReadKeyValueColor(szValue, pObject->r, pObject->g, pObject->b); } else if (!stricmp(szKey, "id")) { CChunkFile::ReadKeyValueInt(szValue, pObject->m_nID); } else if (!stricmp(szKey, "comments")) { // // Load the object comments. // HACK: upcast to CEditGameClass * // CEditGameClass *pEdit = dynamic_cast (pObject); if (pEdit != NULL) { pEdit->SetComments(szValue); } } else if (!stricmp(szKey, "visgroupshown")) { CChunkFile::ReadKeyValueBool(szValue, pObject->m_bVisGroupShown); } else if ( !stricmp(szKey, "visgroupautoshown") ) { CChunkFile::ReadKeyValueBool(szValue, pObject->m_bVisGroupAutoShown); } else { pObject->SetEditorKeyValue(szKey, szValue); } return(ChunkFile_Ok); } //----------------------------------------------------------------------------- // Purpose: Call this function after changing this object via transformation, // etc. Notifies dependents and updates the parent with this object's // new size. //----------------------------------------------------------------------------- void CMapClass::PostUpdate(Notify_Dependent_t eNotifyType) { if (m_pParent != NULL) { GetParent()->UpdateChild(this); } else if (eNotifyType != Notify_Removed) { CalcBounds(TRUE); } NotifyDependents(eNotifyType); } //----------------------------------------------------------------------------- // Purpose: Notifies all our dependents that something about us has changed, // giving them the chance to update themselves. //----------------------------------------------------------------------------- void CMapClass::NotifyDependents(Notify_Dependent_t eNotifyType) { Assert(m_Dependents.Count() < 1000); if (m_Dependents.Count() != 0) { CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if (pDoc) { pDoc->NotifyDependents(this, eNotifyType); } } } //----------------------------------------------------------------------------- // Purpose: Informs us that an object that we are dependent upon has changed, // giving us the opportunity to update ourselves accordingly. // Input : pObject - Object that we are dependent upon that has changed. //----------------------------------------------------------------------------- void CMapClass::OnNotifyDependent(CMapClass *pObject, Notify_Dependent_t eNotifyType) { } //----------------------------------------------------------------------------- // Purpose: Default implementation for saving editor-specific data. Does nothing. // Input : pFile - // Output : ChunkFileResult_t //----------------------------------------------------------------------------- ChunkFileResult_t CMapClass::SaveEditorData(CChunkFile *pFile) { return(ChunkFile_Ok); } //----------------------------------------------------------------------------- // Purpose: // Input : *pFile - // Output : ChunkFileResult_t //----------------------------------------------------------------------------- ChunkFileResult_t CMapClass::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo) { // // Write the editor chunk. // ChunkFileResult_t eResult = pFile->BeginChunk("editor"); // // Save the object's color. // if (eResult == ChunkFile_Ok) { eResult = pFile->WriteKeyValueColor("color", r, g, b); } // // Save the group ID, if any. // if (eResult == ChunkFile_Ok) { CMapGroup *pGroup = dynamic_cast(m_pParent); if (pGroup != NULL) { eResult = pFile->WriteKeyValueInt("groupid", pGroup->GetID()); } } // // Save the visgroup IDs, if any. // if (m_VisGroups.Count()) { if ((eResult == ChunkFile_Ok) && m_VisGroups.Count()) { for (int i = 0; i < m_VisGroups.Count(); i++) { CVisGroup *pVisGroup = m_VisGroups.Element(i); if ( !pVisGroup->IsAutoVisGroup() ) { eResult = pFile->WriteKeyValueInt("visgroupid", pVisGroup->GetID()); if (eResult != ChunkFile_Ok) { break; } } } } } if (eResult == ChunkFile_Ok) { eResult = pFile->WriteKeyValueBool("visgroupshown", m_bVisGroupShown); } if (eResult == ChunkFile_Ok) { eResult = pFile->WriteKeyValueBool("visgroupautoshown", m_bVisGroupAutoShown); } // // Save the object comments, if any. // HACK: upcast to CEditGameClass * // CEditGameClass *pEdit = dynamic_cast (this); if (pEdit != NULL) { if ((eResult == ChunkFile_Ok) && (strlen(pEdit->GetComments()) > 0)) { eResult = pFile->WriteKeyValue("comments", pEdit->GetComments()); } } // // Save any other editor-specific data. // if (eResult == ChunkFile_Ok) { eResult = SaveEditorData(pFile); } if (eResult == ChunkFile_Ok) { eResult = pFile->EndChunk(); } return(eResult); } //----------------------------------------------------------------------------- // Purpose: // Input : *pDependent - //----------------------------------------------------------------------------- void CMapClass::RemoveDependent(CMapClass *pDependent) { int nIndex = m_Dependents.Find(pDependent); if (nIndex != -1) { m_Dependents.FastRemove(nIndex); } } //----------------------------------------------------------------------------- // Purpose: Frees all the keys that were loaded from the editor chunk of the MAP file. //----------------------------------------------------------------------------- void CMapClass::RemoveEditorKeys(void) { delete m_pEditorKeys; m_pEditorKeys = NULL; } //----------------------------------------------------------------------------- // Purpose: // Input : *szOldName - // *szNewName - //----------------------------------------------------------------------------- void CMapClass::ReplaceTargetname(const char *szOldName, const char *szNewName) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pObject = m_Children.Element(pos); pObject->ReplaceTargetname(szOldName, szNewName); } } //----------------------------------------------------------------------------- // Purpose: Updates an object attachment, making this object no longer dependent // on changes to the old object, and dependent on changes to the new object. // Input : pOldAttached - Object that this object was attached to (possibly NULL). // pNewAttached - New object being attached to (possibly NULL). // Output : Returns pNewAttached. //----------------------------------------------------------------------------- CMapClass *CMapClass::UpdateDependency(CMapClass *pOldAttached, CMapClass *pNewAttached) { if (pOldAttached != pNewAttached) { // // If we were attached to another object via this pointer, detach us now. // if (pOldAttached != NULL) { pOldAttached->RemoveDependent(this); } // // Attach ourselves as a dependent of the other object. We will now be notified // of any changes to that object. // if (pNewAttached != NULL) { pNewAttached->AddDependent(this); } } return(pNewAttached); } //----------------------------------------------------------------------------- // Purpose: Updates this object's parent, removing it from it's old parent (if any) // attaching it to the new parent (if any). // Input : pNewParent - A pointer to the new parent for this object. // Output : Returns a pointer to the new parent. //----------------------------------------------------------------------------- void CMapClass::UpdateParent(CMapClass *pNewParent) { CMapClass *pOldParent = GetParent(); if (pOldParent != pNewParent) { if (pOldParent != NULL) { pOldParent->RemoveChild(this); } if (pNewParent != NULL) { pNewParent->AddChild(this); } m_pParent = pNewParent; UpdateObjectColor(); } } //----------------------------------------------------------------------------- // Purpose: // Input : *szKey - // Output : const char //----------------------------------------------------------------------------- void CMapClass::SetEditorKeyValue(const char *szKey, const char *szValue) { if (m_pEditorKeys == NULL) { m_pEditorKeys = new WCKeyValuesVector; } Assert( m_pEditorKeys != NULL ); m_pEditorKeys->AddKeyValue(szKey, szValue); } //----------------------------------------------------------------------------- // Purpose: Sets the origin of this object and its children. // FIXME: Should our children necessarily have the same origin as us? // Seems like we should translate our children by our origin delta //----------------------------------------------------------------------------- void CMapClass::SetOrigin( Vector &origin ) { CMapPoint::SetOrigin( origin ); FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element( pos ); pChild->SetOrigin( origin ); } PostUpdate(Notify_Changed); } //----------------------------------------------------------------------------- // Purpose: // Input : bVisible - //----------------------------------------------------------------------------- void CMapClass::SetVisible(bool bVisible) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->SetVisible(bVisible); } m_bVisible = bVisible; } //----------------------------------------------------------------------------- // Purpose: // Input : bShow - //----------------------------------------------------------------------------- void CMapClass::VisGroupShow(bool bShow, VisGroupSelection eVisGroup) { FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->VisGroupShow(bShow, eVisGroup); } if ( eVisGroup == AUTO ) { m_bVisGroupAutoShown = bShow; } if ( eVisGroup == USER ) { //since user visgroup visibility has precedence over auto, it is possible to change an object's auto //visibility through an action in a user visgroup. if ( bShow ) { m_bVisGroupAutoShown = bShow; } m_bVisGroupShown = bShow; } } //----------------------------------------------------------------------------- // Purpose: Causes all objects in the world to update any object dependencies (pointers) // that they might be holding. This is a static function. //----------------------------------------------------------------------------- void CMapClass::UpdateAllDependencies(CMapClass *pObject) { // // Try to locate the world object. // CMapWorld *pWorld; if (pObject == NULL) { CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); if ((pDoc == NULL) || (pDoc->IsLoading())) { return; } pWorld = pDoc->GetMapWorld(); } else { pWorld = pObject->GetWorldObject(pObject); } if (pWorld == NULL) { return; } pWorld->UpdateAllDependencies( pObject ); EnumChildrenPos_t pos; CMapClass *pChild = pWorld->GetFirstDescendent( pos ); while ( pChild != NULL ) { pChild->UpdateDependencies( pWorld, pObject ); pChild = pWorld->GetNextDescendent( pos ); } } //----------------------------------------------------------------------------- // Purpose: Returns whether this object should be hidden based on the given // cordon bounds. // Output : Returns true to cull the object, false to keep it. //----------------------------------------------------------------------------- bool CMapClass::IsCulledByCordon(const Vector &vecMins, const Vector &vecMaxs) { return !IsIntersectingBox(vecMins, vecMaxs); } //----------------------------------------------------------------------------- // Purpose: Checks to see if the object is hidden by auto or user visgroups // without being assigned to one. This solves the problem of objects // being destructively hidden by obsolete visgroups. //----------------------------------------------------------------------------- bool CMapClass::CheckVisibility( bool bLoading ) { CVisGroup* pVisGroup; bool bInUser = false; bool bInAuto = false; int nVisGroupCount = m_VisGroups.Count(); bool bFoundOrphans = false; CMapDoc *pDoc = CMapDoc::GetActiveMapDoc(); for ( int i = 0; i < nVisGroupCount; i++ ) { pVisGroup = m_VisGroups.Element( i ); if ( pVisGroup->IsAutoVisGroup() ) { bInAuto = true; } else { bInUser = true; } } if ( !bInAuto && !m_bVisGroupAutoShown ) { VisGroupShow( true, AUTO ); } if ( !bInUser && !m_bVisGroupShown ) { VisGroupShow( true, USER ); if ( bLoading && pDoc->VisGroups_ObjectCanBelongToVisGroup( this ) ) { //if this object is an orphan, we want it to be hidden but placed in a new visgroup. bFoundOrphans = true; VisGroupShow( false, USER ); } } return bFoundOrphans; } //----------------------------------------------------------------------------- // Purpose: this routine will indicate if the object is editable. Generally it // will not be editable if it is located in a separate instance or // submap. //----------------------------------------------------------------------------- bool CMapClass::IsEditable( void ) { if ( GetParent() ) { return GetParent()->IsEditable(); } return true; } //----------------------------------------------------------------------------- // Purpose: this function will notify all children that the instance they belong to has been moved. // it will also notify dependents of a translation. this function is currently not // used but may be. //----------------------------------------------------------------------------- void CMapClass::InstanceMoved( void ) { #if 0 FOR_EACH_OBJ( m_Children, pos ) { CMapClass *pChild = m_Children.Element(pos); pChild->InstanceMoved(); } CMapWorld *pThisWorld = GetWorldObject( this ); for (int i = 0; i < m_Dependents.Count(); i++) { CMapClass *pDependent = m_Dependents.Element(i); CMapWorld *pDependentWorld = GetWorldObject( pDependent ); if ( pDependentWorld != pThisWorld ) { pDependent->OnNotifyDependent( this, Notify_Transform ); } } #endif }