summaryrefslogtreecommitdiff
path: root/hammer/mapsolid.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'hammer/mapsolid.cpp')
-rw-r--r--hammer/mapsolid.cpp2010
1 files changed, 2010 insertions, 0 deletions
diff --git a/hammer/mapsolid.cpp b/hammer/mapsolid.cpp
new file mode 100644
index 0000000..6434bfb
--- /dev/null
+++ b/hammer/mapsolid.cpp
@@ -0,0 +1,2010 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================//
+
+#include "stdafx.h"
+#include "Box3D.h"
+#include "BrushOps.h"
+#include "GlobalFunctions.h"
+#include "MapDefs.h" // dvs: For COORD_NOTINIT
+#include "MapView2D.h" // dvs FIXME: For HitTest2D implementation
+#include "MapWorld.h"
+#include "MapSolid.h"
+#include "Options.h"
+#include "Render2D.h"
+#include "Render3D.h"
+#include "SaveInfo.h"
+#include "MapDoc.h"
+#include "MapDisp.h"
+#include "camera.h"
+#include "ssolid.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+IMPLEMENT_MAPCLASS(CMapSolid)
+
+
+#define CENTER_HANDLE_RADIUS 3
+
+
+int CMapSolid::g_nBadSolidCount = 0;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor. Sets this solid's color to a random blue-green color.
+// Input : Parent0 - The parent of this solid. Typically this is the world.
+//-----------------------------------------------------------------------------
+CMapSolid::CMapSolid(CMapClass *Parent0)
+ : m_bValid(FALSE) // well, no faces
+{
+ m_pParent = Parent0;
+ m_eSolidType = btSolid;
+ m_bIsCordonBrush = false;
+
+ PickRandomColor();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor.
+//-----------------------------------------------------------------------------
+CMapSolid::~CMapSolid(void)
+{
+ Faces.SetCount(0);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds the plane to the given solid.
+// Input : pSolid - Solid to which the plane is being added.
+// p - Plane to add to the solid.
+// Output : Returns true if the solid is still valid after the addition of the
+// plane, false if it was entirely behind the plane.
+//-----------------------------------------------------------------------------
+bool CMapSolid::AddPlane(const CMapFace *p)
+{
+ CMapFace NewFace;
+
+ //
+ // Copy the info from the carving face, including the plane, but not the points.
+ //
+ NewFace.CopyFrom(p, COPY_FACE_PLANE);
+
+ //
+ // Use texture from our first face - this function adds a plane
+ // from the subtraction brush itself.
+ //
+ const CMapFace *pSolidFace = GetFace(0);
+
+ NewFace.SetTexture(pSolidFace->texture.texture);
+ NewFace.texture.q2contents = pSolidFace->texture.q2contents;
+ NewFace.texture.q2surface = pSolidFace->texture.q2surface;
+ NewFace.texture.nLightmapScale = pSolidFace->texture.nLightmapScale;
+
+ //
+ // Set up the texture axes for the new face.
+ //
+ NewFace.InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_ALL | INIT_TEXTURE_FORCE);
+
+ //
+ // Add the face to the solid and rebuild the solid.
+ //
+ AddFace(&NewFace);
+ CreateFromPlanes();
+ SetValid(TRUE);
+
+ PostUpdate(Notify_Changed);
+
+ RemoveEmptyFaces();
+
+ return(GetFaceCount() >= 4);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Carves this solid using another.
+// Input : Inside - Receives the pieces of this solid that were inside the carver.
+// Outside - Receives the pieces of this solid that were outside the carver.
+// pCarver - The solid that is being subtracted from us.
+//-----------------------------------------------------------------------------
+bool CMapSolid::Carve(CMapObjectList *pInside, CMapObjectList *pOutside, CMapSolid *pCarver)
+{
+ int i;
+ CMapSolid *front = NULL;
+ CMapSolid *back = NULL;
+ Vector bmins, bmaxs;
+ Vector carvemins, carvemaxs;
+
+ GetRender2DBox(bmins, bmaxs);
+ pCarver->GetRender2DBox(carvemins, carvemaxs);
+
+ //
+ // If this solid doesn't intersect the carving solid at all, add a copy
+ // to the outside list and exit.
+ //
+ for (i=0 ; i<3 ; i++)
+ {
+ if ((bmins[i] >= carvemaxs[i]) || (bmaxs[i] <= carvemins[i]))
+ {
+ if (pOutside != NULL)
+ {
+ CMapSolid *pCopy = (CMapSolid *)Copy(false);
+ pOutside->AddToTail(pCopy);
+ }
+ return(false);
+ }
+ }
+
+ //
+ // Build a duplicate of this solid to carve from.
+ //
+ CMapSolid CarveFrom;
+ CarveFrom.CopyFrom(this, false);
+
+ //
+ // Carve the solid by the faces in the carving solid.
+ //
+ for (i = 0; i < pCarver->GetFaceCount(); i++)
+ {
+ const CMapFace *pFace = pCarver->GetFace(i);
+
+ //
+ // Split the solid by this face, into a front and a back piece.
+ //
+ CarveFrom.ClipByFace(pFace, pOutside != NULL ? &front : NULL, &back);
+
+ //
+ // If there was a front piece, add it to the outside list.
+ //
+ if ((front != NULL) && (pOutside != NULL))
+ {
+ pOutside->AddToTail(front);
+ }
+ else
+ {
+ delete front;
+ }
+
+ //
+ // If there was no back piece, we have found a face the solid is completely in front of.
+ // Per the separating axis theorem, the two solids cannot intersect, so we are done.
+ //
+ if (back == NULL)
+ {
+ return(false);
+ }
+
+ //
+ // The next clip uses the back piece from this clip to prevent the carve results
+ // from overlapping.
+ //
+ CarveFrom.CopyFrom(back, false);
+
+ //
+ // Add the back piece of the carved solid to the inside list.
+ //
+ if (pInside != NULL)
+ {
+ pInside->AddToTail(back);
+ }
+ else
+ {
+ delete back;
+ }
+ }
+
+ return(true);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Clips the given solid by the given face, returning the results.
+// Input : pSolid - Solid to clip.
+// fa - Face to use for the clipping operation.
+// f - Returns the part of the solid that was in front of the clipping
+// face (in the direction of the face normal).
+// b - Returns the part of the solid that was in back of the clipping
+// face (in the opposite direction of the face normal).
+//-----------------------------------------------------------------------------
+void CMapSolid::ClipByFace(const CMapFace *fa, CMapSolid **fsolid, CMapSolid **bSolid)
+{
+ CMapSolid *front = new CMapSolid;
+ CMapSolid *back = new CMapSolid;
+ CMapFace fb;
+
+ //
+ // Build a back facing version of the clip face by reversing the plane points
+ // and recalculate the plane normal and distance.
+ //
+ fb.CopyFrom(fa);
+ Vector temp = fb.plane.planepts[0];
+ fb.plane.planepts[0] = fb.plane.planepts[2];
+ fb.plane.planepts[2] = temp;
+ fb.CalcPlane();
+
+ front->CopyFrom(this, false);
+ front->SetParent(NULL);
+
+ back->CopyFrom(this, false);
+ back->SetParent(NULL);
+
+ if (!back->AddPlane(fa))
+ {
+ delete back;
+ back = NULL;
+ }
+
+ if (!front->AddPlane(&fb))
+ {
+ delete front;
+ front = NULL;
+ }
+
+ if (fsolid != NULL)
+ {
+ *fsolid = front;
+ }
+ else
+ {
+ delete front;
+ }
+
+ if (bSolid != NULL)
+ {
+ *bSolid = back;
+ }
+ else
+ {
+ delete back;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns true if this solid contains a face with the given ID, false if not.
+// Input : nFaceID - unique face ID to look for.
+//-----------------------------------------------------------------------------
+CMapFace *CMapSolid::FindFaceID(int nFaceID)
+{
+ int nFaceCount = GetFaceCount();
+ for (int nFace = 0; nFace < nFaceCount; nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ if (pFace->GetFaceID() == nFaceID)
+ {
+ return(pFace);
+ }
+ }
+
+ return(NULL);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pWorld -
+//-----------------------------------------------------------------------------
+void CMapSolid::GenerateNewFaceIDs(CMapWorld *pWorld)
+{
+ int nFaceCount = GetFaceCount();
+ for (int nFace = 0; nFace < nFaceCount; nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ pFace->SetFaceID(pWorld->FaceID_GetNext());
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allows the solid to generate new face IDs before being added to the
+// world because of a clone.
+// Input : pClone - The clone of this object that is being added to the world.
+// pWorld - The world that the clone is being added to.
+// OriginalList - The list of objects that were cloned.
+// NewList - The list of clones.
+//-----------------------------------------------------------------------------
+void CMapSolid::OnPreClone(CMapClass *pClone, CMapWorld *pWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList)
+{
+ ((CMapSolid *)pClone)->GenerateNewFaceIDs(pWorld);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Notifies the object that a copy of it is being pasted from the
+// clipboard before the copy is added to the world.
+// Input : pCopy - The copy of this object that is being added to the world.
+// pSourceWorld - The world that the originals were in.
+// pDestWorld - The world that the copies are being added to.
+// OriginalList - The list of original objects that were copied.
+// NewList - The list of copied.
+//-----------------------------------------------------------------------------
+void CMapSolid::OnPrePaste(CMapClass *pCopy, CMapWorld *pSourceWorld, CMapWorld *pDestWorld, const CMapObjectList &OriginalList, CMapObjectList &NewList)
+{
+ ((CMapSolid *)pCopy)->GenerateNewFaceIDs(pDestWorld);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: to split the solid by the given plane into frontside and backside
+// solids; memory is allocated in the function for the solids;
+// solids are only generated for (pointers to) CMapSolid pointers that
+// exist -- if a pointer is NULL that sidedness is ignored; the
+// function returns whether or not an actual split happened (TRUE/FALSE)
+// Input: pPlane - the plane to split the solid with
+// pFront - the front sided solid (if any)
+// pBack - the back sided solid (if any)
+// Output: 0 - on front side
+// 1 - on back side
+// 2 - split
+//-----------------------------------------------------------------------------
+int CMapSolid::Split( PLANE *pPlane, CMapSolid **pFront, CMapSolid **pBack )
+{
+ const float SPLIT_DIST_EPSILON = 0.001f;
+ CMapSolid *pFrontSolid = NULL;
+ CMapSolid *pBackSolid = NULL;
+ CMapFace face;
+
+ //
+ // The newly added face should get its texture from face zero of the solid.
+ //
+ CMapFace *pFirstFace = GetFace(0);
+ if (pFirstFace != NULL)
+ {
+ face.SetTexture(pFirstFace->GetTexture());
+ }
+
+ //
+ // check for plane intersection with solid
+ //
+ int frontCount = 0;
+ int backCount = 0;
+
+ int faceCount = GetFaceCount();
+ for( int i = 0; i < faceCount; i++ )
+ {
+ CMapFace *pFace = GetFace( i );
+
+ for( int j = 0; j < pFace->nPoints; j++ )
+ {
+ float dist = DotProduct( pFace->Points[j], pPlane->normal ) - pPlane->dist;
+ if( dist > SPLIT_DIST_EPSILON )
+ {
+ frontCount++;
+ }
+ else if( dist < -SPLIT_DIST_EPSILON )
+ {
+ backCount++;
+ }
+ }
+ }
+
+ //
+ // If we're all on one side of the splitting plane, copy ourselves into the appropriate
+ // destination solid.
+ //
+ if ((frontCount == 0) || (backCount == 0))
+ {
+ CMapSolid **pReturn;
+
+ if (frontCount == 0)
+ {
+ pReturn = pBack;
+ }
+ else
+ {
+ pReturn = pFront;
+ }
+
+ if (pReturn == NULL)
+ {
+ return -1;
+ }
+
+ //
+ // The returned solid is just a copy of ourselves.
+ //
+ CMapSolid *pReturnSolid = new CMapSolid;
+ pReturnSolid->CopyFrom(this, false);
+ pReturnSolid->SetParent(NULL);
+ pReturnSolid->SetTemporary(TRUE);
+
+ //
+ // Rebuild the solid because mappers are accustomed to using the clipper tool as a way of
+ // verifying that geometry is valid.
+ //
+ if (pReturnSolid->CreateFromPlanes( CREATE_FROM_PLANES_CLIPPING ))
+ {
+ // Initialize the texture axes only of the newly created faces. Leave the others alone.
+ pReturnSolid->InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_ALL);
+ pReturnSolid->PostUpdate(Notify_Changed);
+ }
+
+ *pReturn = pReturnSolid;
+ return 1;
+ }
+
+ //
+ // split the solid and create the "front" solid
+ //
+ if( pFront )
+ {
+ //
+ // copy the original solid info
+ //
+ pFrontSolid = new CMapSolid;
+ pFrontSolid->CopyFrom(this, false);
+ pFrontSolid->SetParent(NULL);
+ pFrontSolid->SetTemporary( TRUE );
+
+ face.plane.normal = pPlane->normal;
+ VectorNegate( face.plane.normal );
+ face.plane.dist = -pPlane->dist;
+
+ pFrontSolid->AddFace( &face );
+
+ //
+ // The new face doesn't have plane points, only a normal and a distance, so we tell CreateFromPlanes
+ // to generate new plane points for us after creation.
+ //
+ if (pFrontSolid->CreateFromPlanes(CREATE_BUILD_PLANE_POINTS | CREATE_FROM_PLANES_CLIPPING))
+ {
+ // Initialize the texture axes only of the newly created faces. Leave the others alone.
+ pFrontSolid->InitializeTextureAxes( Options.GetTextureAlignment(), INIT_TEXTURE_ALL );
+ pFrontSolid->PostUpdate(Notify_Clipped_Intermediate);
+
+ *pFront = pFrontSolid;
+ }
+ }
+
+ //
+ // split the solid and create the "back" solid
+ //
+ if( pBack )
+ {
+ //
+ // copy the original solid info
+ //
+ pBackSolid = new CMapSolid;
+ pBackSolid->CopyFrom(this, false);
+ pBackSolid->SetParent(NULL);
+ pBackSolid->SetTemporary( TRUE );
+
+ face.plane.normal = pPlane->normal;
+ face.plane.dist = pPlane->dist;
+
+ pBackSolid->AddFace( &face );
+
+ //
+ // The new face doesn't have plane points, only a normal and a distance, so we tell CreateFromPlanes
+ // to generate new plane points for us after creation.
+ //
+ if (pBackSolid->CreateFromPlanes(CREATE_BUILD_PLANE_POINTS | CREATE_FROM_PLANES_CLIPPING))
+ {
+ // Initialize the texture axes only of the newly created faces. Leave the others alone.
+ pBackSolid->InitializeTextureAxes( Options.GetTextureAlignment(), INIT_TEXTURE_ALL );
+ pBackSolid->PostUpdate(Notify_Clipped_Intermediate);
+
+ *pBack = pBackSolid;
+ }
+ }
+
+ return 2;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the index (you could use it with GetFace) or -1 if the face doesn't exist in this solid.
+//-----------------------------------------------------------------------------
+int CMapSolid::GetFaceIndex( CMapFace *pFace )
+{
+ for ( int i=0; i < Faces.GetCount(); i++ )
+ {
+ if ( pFace == &Faces[i] )
+ return i;
+ }
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Adds a face to this solid.
+// Input : pFace - The face to add. The face is copied into this solid's face
+// array, so it can be safely freed when AddFace returns.
+//-----------------------------------------------------------------------------
+void CMapSolid::AddFace(CMapFace *pFace)
+{
+ int nFaces = Faces.GetCount();
+ Faces.SetCount(nFaces + 1);
+ CMapFace *pNewFace = &Faces[nFaces];
+
+ pNewFace->CopyFrom(pFace, COPY_FACE_POINTS);
+ pNewFace->SetRenderColor(r, g, b);
+ pNewFace->SetCordonFace( m_bIsCordonBrush );
+ pNewFace->SetParent(this);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Output : CMapClass
+//-----------------------------------------------------------------------------
+CMapClass *CMapSolid::Copy(bool bUpdateDependencies)
+{
+ CMapSolid *pNew = new CMapSolid;
+ pNew->CopyFrom(this, bUpdateDependencies);
+ return pNew;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Turns this solid into a duplicate of the given solid.
+// Input : pobj - Object to copy, must be a CMapSolid.
+// Output : Returns a pointer to this object.
+//-----------------------------------------------------------------------------
+CMapClass *CMapSolid::CopyFrom(CMapClass *pobj, bool bUpdateDependencies)
+{
+ Assert(pobj->IsMapClass(MAPCLASS_TYPE(CMapSolid)));
+ CMapSolid *pFrom = (CMapSolid *)pobj;
+
+ CMapClass::CopyFrom(pobj, bUpdateDependencies);
+ m_eSolidType = pFrom->GetHL1SolidType();
+
+ m_bIsCordonBrush = pFrom->m_bIsCordonBrush;
+
+ int nFaces = pFrom->Faces.GetCount();
+ Faces.SetCount(nFaces);
+
+ // copy faces
+ CMapFace *pFromFace;
+ CMapFace *pToFace;
+
+ for (int i = nFaces - 1; i >= 0; i--)
+ {
+
+ pToFace = &Faces[i];
+ pFromFace = &pFrom->Faces[i];
+
+ if (!pToFace)
+ {
+ continue;
+ }
+
+ pToFace->SetParent(this);
+ pToFace->CopyFrom(pFromFace, COPY_FACE_POINTS, bUpdateDependencies);
+ Assert(pToFace->GetPointCount() != 0);
+ }
+
+ return(this);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Walks the faces of a solid for debugging.
+//-----------------------------------------------------------------------------
+#ifdef _DEBUG
+#pragma warning (disable:4189)
+void CMapSolid::DebugSolid(void)
+{
+ int nFaceCount = Faces.GetCount();
+ for (int nFace = 0; nFace < nFaceCount; nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ }
+}
+#pragma warning (default:4189)
+#endif // _DEBUG
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : iIndex -
+//-----------------------------------------------------------------------------
+void CMapSolid::DeleteFace(int iIndex)
+{
+ // shifts 'em down.
+ int nFaces = Faces.GetCount();
+ for(int j = iIndex; j < nFaces-1; j++)
+ {
+ Faces[j].CopyFrom(&Faces[j+1]);
+ }
+
+ Faces.SetCount(nFaces-1);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+const char* CMapSolid::GetDescription(void)
+{
+ static char szBuf[128];
+ sprintf(szBuf, "solid with %d faces", Faces.GetCount());
+ return szBuf;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates this solid's axis aligned bounding box.
+// Input : bFullUpdate - Whether to evaluate all children when calculating.
+//-----------------------------------------------------------------------------
+void CMapSolid::CalcBounds(BOOL bFullUpdate)
+{
+ CMapClass::CalcBounds(bFullUpdate);
+
+ //
+ // Update mins/maxes based on our faces.
+ //
+ int nFaces = Faces.GetCount();
+ for( int i = 0; i < nFaces; i++ )
+ {
+ // check for valid face
+ if (!Faces[i].Points)
+ continue;
+
+ //
+ // Get the 2d render bounds of this face and update the solid. 2D render bounds
+ // can be different from 3D culling bounds because the 2D bounds do not consider
+ // displacement faces.
+ //
+ Vector mins, maxs;
+ bool result = Faces[i].GetRender2DBox( mins, maxs );
+ if( result )
+ {
+ m_Render2DBox.UpdateBounds( mins, maxs );
+ }
+
+ //
+ // Get the culling bounds and update the solid
+ //
+ result = Faces[i].GetCullBox( mins, maxs );
+ if( result )
+ {
+ m_CullBox.UpdateBounds( mins, maxs );
+ }
+ }
+
+ m_Render2DBox.GetBoundsCenter(m_Origin);
+ m_BoundingBox = m_CullBox;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : t -
+//-----------------------------------------------------------------------------
+void CMapSolid::DoTransform(const VMatrix &matrix)
+{
+ // get all points, transform them
+ int nFaces = Faces.GetCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ Faces[i].DoTransform( matrix );
+ }
+
+ BaseClass::DoTransform(matrix);
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the render color of all of our faces when our render color is set.
+//-----------------------------------------------------------------------------
+void CMapSolid::SetRenderColor(color32 rgbColor)
+{
+ CMapClass::SetRenderColor(rgbColor);
+
+ int nFaces = Faces.GetCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ Faces[i].SetRenderColor(rgbColor);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the render color of all of our faces when our render color is set.
+//-----------------------------------------------------------------------------
+void CMapSolid::SetRenderColor(unsigned char uchRed, unsigned char uchGreen, unsigned char uchBlue)
+{
+ CMapClass::SetRenderColor(uchRed, uchGreen, uchBlue);
+
+ int nFaces = Faces.GetCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ Faces[i].SetRenderColor(uchRed, uchGreen, uchBlue);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input :
+// Output : size_t
+//-----------------------------------------------------------------------------
+size_t CMapSolid::GetSize(void)
+{
+ size_t size = CMapClass::GetSize();
+ size += sizeof *this;
+
+ int nFaces = Faces.GetCount();
+ for( int i = 0; i < nFaces; i++ )
+ {
+ size += Faces[i].GetDataSize();
+ }
+
+ return size;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets the texture for a given face.
+// Input : pszTex - Texture name.
+// iFace - Index of face for which to set texture.
+//-----------------------------------------------------------------------------
+void CMapSolid::SetTexture(LPCTSTR pszTex, int iFace)
+{
+ if(iFace == -1)
+ {
+ int nFaces = Faces.GetCount();
+ for(int i = 0 ; i < nFaces; i++)
+ {
+ Faces[i].SetTexture(pszTex);
+ }
+ }
+ else
+ {
+ Faces[iFace].SetTexture(pszTex);
+ }
+
+ CMapDoc *pMapDoc = CMapDoc::GetActiveMapDoc();
+
+ pMapDoc->RemoveFromAutoVisGroups( this );
+ pMapDoc->AddToAutoVisGroup( this );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the texture name of a given face.
+// Input : iFace - Index of face. If -1, returns the texture of face 0.
+// Output : Returns the texture name.
+//-----------------------------------------------------------------------------
+LPCTSTR CMapSolid::GetTexture(int iFace)
+{
+ return Faces[iFace == -1 ? 0 : iFace].texture.texture;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates the solid using the plane information from the solid's faces.
+//
+// ASSUMPTIONS: This solid's faces are assumed to have valid plane points.
+//
+// Input : dwFlags - Can be any or none of the following flags:
+//
+// CREATE_BUILD_PLANE_POINTS - if this flag is set, the 3-point
+// definition of each face plane will be regenerated based
+// on the face points after the solid is generated.
+//
+// Output : Returns TRUE if the solid is valid, FALSE if not.
+//
+// dvs: this should really use the public API of CMapSolid to add faces so that
+// parentage and render color are set automatically.
+//-----------------------------------------------------------------------------
+int CMapSolid::CreateFromPlanes( DWORD dwFlags )
+{
+ int i, j, k;
+ BOOL useplane[MAPSOLID_MAX_FACES];
+
+ m_Render2DBox.SetBounds(Vector(COORD_NOTINIT, COORD_NOTINIT, COORD_NOTINIT),
+ Vector(-COORD_NOTINIT, -COORD_NOTINIT, -COORD_NOTINIT));
+
+ m_bValid = TRUE;
+
+ //
+ // Free all points from all faces and assign parentage.
+ //
+ int nFaces = GetFaceCount();
+
+ for (i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+
+ pFace->AllocatePoints(0);
+ pFace->SetParent(this);
+ pFace->SetRenderColor(r, g, b);
+
+ useplane[i] = false;
+ }
+
+ //
+ // For every face that is not set to be ignored, check the plane and make sure
+ // it is unique. We mark each plane that we intend to keep with a TRUE in the
+ // 'useplane' array.
+ //
+ for (i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ PLANE *f = &pFace->plane;
+
+ //
+ // Don't use this plane if it has a zero-length normal.
+ //
+ if (VectorCompare(f->normal, vec3_origin))
+ {
+ useplane[i] = FALSE;
+ continue;
+ }
+
+ //
+ // If the plane duplicates another plane, don't use it (assume it is a brush
+ // being edited that will be fixed).
+ //
+ useplane[i] = TRUE;
+ for (j = 0; j < i; j++)
+ {
+ CMapFace *pFaceCheck = GetFace(j);
+
+ Vector& f1 = f->normal;
+ Vector& f2 = pFaceCheck->plane.normal;
+
+ //
+ // Check for duplicate plane within some tolerance.
+ //
+ if ((DotProduct(f1, f2) > 0.999) && (fabs(f->dist - pFaceCheck->plane.dist) < 0.01))
+ {
+ useplane[j] = FALSE;
+ break;
+ }
+ }
+ }
+
+ //
+ // Now we have a set of planes, indicated by TRUE values in the 'useplanes' array,
+ // from which we will build a solid.
+ //
+ BOOL bGotFaces = FALSE;
+
+ for (i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+
+ if (!useplane[i])
+ continue;
+
+ //
+ // Create a huge winding from this face's plane, then clip it by all other
+ // face planes.
+ //
+ winding_t *w = CreateWindingFromPlane(&pFace->plane);
+ for (j = 0; j < nFaces && w; j++)
+ {
+ CMapFace *pFaceClip = GetFace(j);
+
+ //
+ // Flip the plane, because we want to keep the back side
+ //
+ if (j != i)
+ {
+ PLANE plane;
+
+ VectorSubtract(vec3_origin, pFaceClip->plane.normal, plane.normal);
+ plane.dist = -pFaceClip->plane.dist;
+
+ w = ClipWinding(w, &plane);
+ }
+ }
+
+ //
+ // If we still have a winding after all that clipping, build a face from
+ // the winding.
+ //
+ if (w != NULL)
+ {
+ //
+ // Round all points in the winding that are within ROUND_VERTEX_EPSILON of
+ // integer values.
+ //
+ for (j = 0; j < w->numpoints; j++)
+ {
+ for (k = 0; k < 3; k++)
+ {
+ float v = w->p[j][k];
+ float v1 = V_rint(v);
+ if ((v != v1) && (fabs(v - v1) < ROUND_VERTEX_EPSILON))
+ {
+ w->p[j][k] = v1;
+ }
+ }
+ }
+
+ //
+ // The above rounding process may have created duplicate points. Eliminate them.
+ //
+ RemoveDuplicateWindingPoints(w, MIN_EDGE_LENGTH_EPSILON);
+
+ bGotFaces = TRUE;
+
+ //
+ // Create a face from this winding. Leave the face plane
+ // alone because we are still in the process of building our solid.
+ //
+ if ( dwFlags & CREATE_FROM_PLANES_CLIPPING )
+ {
+ pFace->CreateFace( w, CREATE_FACE_PRESERVE_PLANE | CREATE_FACE_CLIPPING );
+ }
+ else
+ {
+ pFace->CreateFace(w, CREATE_FACE_PRESERVE_PLANE);
+ }
+
+ //
+ // Done with the winding, we can free it now.
+ //
+ FreeWinding(w);
+ }
+ }
+
+ if (!bGotFaces)
+ {
+ m_bValid = FALSE;
+ m_Render2DBox.SetBounds(vec3_origin, vec3_origin);
+ }
+ else
+ {
+ //
+ // Remove faces that don't contribute to this solid.
+ //
+ int nFace = GetFaceCount();
+ while (nFace > 0)
+ {
+ nFace--;
+ CMapFace *pFace = GetFace(nFace);
+
+ if ((!useplane[nFace]) || (pFace->GetPointCount() == 0))
+ {
+ DeleteFace(nFace);
+
+ memcpy(useplane + nFace, useplane + nFace + 1, MAPSOLID_MAX_FACES - (nFace + 1));
+ }
+ }
+ }
+
+ //
+ // Now that we have built the faces from the planes that we were given,
+ // calculate the plane normals, distances, and texture coordinates.
+ //
+ nFaces = GetFaceCount();
+ for (i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+
+ if (dwFlags & CREATE_BUILD_PLANE_POINTS)
+ {
+ pFace->CalcPlaneFromFacePoints();
+ }
+ else
+ {
+ pFace->CalcPlane();
+ }
+
+ pFace->CalcTextureCoords();
+
+ //
+ // Make sure the face is valid.
+ //
+ if (!pFace->CheckFace())
+ {
+ m_bValid = FALSE;
+ }
+ }
+
+ //
+ // remove faces that do not contribute -- not just "unused or ignored" faces
+ //
+ int faceCount = Faces.GetCount();
+ for( i = 0; i < faceCount; i++ )
+ {
+ if( Faces[i].nPoints == 0 )
+ {
+ DeleteFace( i );
+ i--;
+ faceCount--;
+ }
+ }
+
+ return(m_bValid ? TRUE : FALSE);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Initializes the texture axes for all faces in the solid.
+// Input : eAlignment - See CMapFace::InitializeTextureAxes
+// dwFlags - See CMapFace::InitializeTextureAxes
+//-----------------------------------------------------------------------------
+void CMapSolid::InitializeTextureAxes(TextureAlignment_t eAlignment, DWORD dwFlags)
+{
+ int nFaces = Faces.GetCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ Faces[i].InitializeTextureAxes(eAlignment, dwFlags);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pLoadInfo -
+// *pSolid -
+// Output : ChunkFileResult_t
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapSolid::LoadSideCallback(CChunkFile *pFile, CMapSolid *pSolid)
+{
+ ChunkFileResult_t eResult = ChunkFile_Ok;
+
+ //
+ // this is hear in place of the AddFace -- may want to handle this better later!!!
+ //
+ int faceCount = pSolid->Faces.GetCount();
+ pSolid->Faces.SetCount( faceCount + 1 );
+ CMapFace *pFace = &pSolid->Faces[faceCount];
+
+ eResult = pFace->LoadVMF(pFile);
+ if (eResult == ChunkFile_Ok)
+ {
+ pFace->SetRenderColor( pSolid->r, pSolid->g, pSolid->b );
+ pFace->SetParent( pSolid );
+ }
+ else
+ {
+ // UNDONE: need a better solution for user errors.
+ AfxMessageBox("Out of memory loading solid.");
+ eResult = ChunkFile_OutOfMemory;
+ }
+
+ return(eResult);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pFile -
+// pData -
+// Output : ChunkFileResult_t
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapSolid::LoadVMF(CChunkFile *pFile, bool &bValid)
+{
+ //
+ // Set up handlers for the subchunks that we are interested in.
+ //
+ CChunkHandlerMap Handlers;
+ Handlers.AddHandler("side", (ChunkHandler_t)LoadSideCallback, this);
+ Handlers.AddHandler("editor", (ChunkHandler_t)LoadEditorCallback, this);
+
+ pFile->PushHandlers(&Handlers);
+ ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadEditorKeyCallback, this);
+ pFile->PopHandlers();
+
+ bValid = false;
+
+ if (eResult == ChunkFile_Ok)
+ {
+ //
+ // Create the solid using the planes that were read from the MAP file.
+ //
+ if (CreateFromPlanes())
+ {
+ bValid = true;
+ CalcBounds();
+
+ //
+ // Set solid type based on texture name.
+ //
+ m_eSolidType = HL1SolidTypeFromTextureName(Faces[0].texture.texture);
+
+ //
+ // create all of the displacement surfaces for faces with the displacement property
+ //
+ int faceCount = GetFaceCount();
+ for( int i = 0; i < faceCount; i++ )
+ {
+ CMapFace *pFace = GetFace( i );
+ if( !pFace->HasDisp() )
+ continue;
+
+ EditDispHandle_t handle = pFace->GetDisp();
+ CMapDisp *pMapDisp = EditDispMgr()->GetDisp( handle );
+ pMapDisp->InitDispSurfaceData( pFace, false );
+ pMapDisp->Create();
+ pMapDisp->PostLoad();
+ }
+
+ // There once was a bug that caused black solids. Fix it here.
+ if ((r == 0) && (g == 0) || (b == 0))
+ {
+ PickRandomColor();
+ }
+ }
+ else
+ {
+ g_nBadSolidCount++;
+ }
+ }
+
+ return(eResult);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Picks a random shade of blue/green for this solid.
+//-----------------------------------------------------------------------------
+void CMapSolid::PickRandomColor()
+{
+ SetRenderColor(0, 100 + (random() % 156), 100 + (random() % 156));
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called before loading a map file.
+//-----------------------------------------------------------------------------
+void CMapSolid::PreloadWorld(void)
+{
+ g_nBadSolidCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the number of solids that could not be loaded due to errors
+// in the VMF file. This should only occur after the first load of an
+// old RMF file.
+//-----------------------------------------------------------------------------
+int CMapSolid::GetBadSolidCount(void)
+{
+ return(g_nBadSolidCount);
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 CMapSolid::OnAddToWorld(CMapWorld *pWorld)
+{
+ CMapClass::OnAddToWorld(pWorld);
+
+ //
+ // First, the common case: all our face IDs are zero. Assign new IDs to all faces
+ // with zero IDs. Add unhandled faces to a list. Those we will need to check against
+ // the world for uniqueness.
+ //
+ CMapFaceList CheckList;
+ int nFaceCount = GetFaceCount();
+ for (int i = 0; i < nFaceCount; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ if (pFace->GetFaceID() == 0)
+ {
+ pFace->SetFaceID(pWorld->FaceID_GetNext());
+ }
+ else
+ {
+ CheckList.AddToTail(pFace);
+ }
+ }
+
+ if (CheckList.Count() > 0)
+ {
+ //
+ // The less common case: make sure all our face IDs are unique in this world.
+ // We do it here instead of in CMapFace in order to save world tree traversals.
+ //
+ EnumChildrenPos_t pos;
+ CMapClass *pChild = pWorld->GetFirstDescendent(pos);
+ while (pChild != NULL)
+ {
+ CMapSolid *pSolid = dynamic_cast<CMapSolid *>(pChild);
+
+ if ( pSolid && pSolid != this )
+ {
+ CUtlRBTree<int,int> faceIDs;
+ SetDefLessFunc( faceIDs );
+
+ nFaceCount = GetFaceCount();
+ for (int nFace = 0; nFace < nFaceCount; nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ faceIDs.Insert( pFace->GetFaceID() );
+ }
+
+ for (int i = CheckList.Count() - 1; i >= 0; i--)
+ {
+ CMapFace *pFace = CheckList.Element(i);
+
+ // If this face ID is not unique, assign it a new unique face ID
+ // and remove it from our list.
+
+ if ( faceIDs.Find( pFace->GetFaceID() ) != faceIDs.InvalidIndex() )
+ {
+ pFace->SetFaceID(pWorld->FaceID_GetNext());
+ CheckList.FastRemove(i);
+ }
+ }
+
+ if (CheckList.Count() <= 0)
+ {
+ // We've handled all the faces in our list, early out.
+ break;
+ }
+ }
+
+ pChild = pWorld->GetNextDescendent(pos);
+ }
+ }
+
+ //
+ // Notify all faces that we are being added to the world.
+ //
+ for (int i = 0; i < nFaceCount; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ pFace->OnAddToWorld(pWorld);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Called after the entire map has been loaded. This allows the object
+// to perform any linking with other map objects or to do other operations
+// that require all world objects to be present.
+// Input : pWorld - The world that we are in.
+//-----------------------------------------------------------------------------
+void CMapSolid::PostloadWorld(CMapWorld *pWorld)
+{
+ CMapClass::PostloadWorld(pWorld);
+
+ //
+ // Make sure all our faces have nonzero IDs. They might if the map was created
+ // before unique IDs were added.
+ //
+ int nFaces = GetFaceCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ if (pFace->GetFaceID() == 0)
+ {
+ pFace->SetFaceID(pWorld->FaceID_GetNext());
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : eSelectMode -
+// Output : CMapClass
+//-----------------------------------------------------------------------------
+CMapClass *CMapSolid::PrepareSelection(SelectMode_t eSelectMode)
+{
+ //
+ // If we have a parent who is not the world object, consider whether we should
+ // select it instead.
+ //
+ if ((eSelectMode != selectSolids) && (m_pParent != NULL) && !IsWorldObject(m_pParent) )
+ {
+ //
+ // If we are in group selection mode or our parent is an entity, select our
+ // parent.
+ //
+
+ if ( (eSelectMode == selectGroups) || (dynamic_cast <CMapEntity *>(m_pParent) != NULL))
+ {
+ return GetParent()->PrepareSelection(eSelectMode);
+ }
+ }
+
+ return this;
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 CMapSolid::OnRemoveFromWorld(CMapWorld *pWorld, bool bNotifyChildren)
+{
+ CMapClass::OnRemoveFromWorld(pWorld, bNotifyChildren);
+
+ //
+ // Notify all faces that we are being removed from the world.
+ //
+ int nFaces = GetFaceCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ pFace->OnRemoveFromWorld();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+void CMapSolid::RemoveEmptyFaces(void)
+{
+ int nFaces = GetFaceCount();
+
+ for (int i = 0; i < nFaces; i++)
+ {
+ //
+ // If this face has no points, delete it.
+ //
+ const CMapFace *pFace = GetFace(i);
+ if (pFace->Points == NULL)
+ {
+ DeleteFace(i);
+ i--;
+ nFaces--;
+ }
+ }
+
+ if (nFaces >= 4)
+ {
+ // dvs: test to verify that the SetFaceCount below is unnecessary
+ int nTest = GetFaceCount();
+ Assert(nTest == nFaces);
+ SetFaceCount(nFaces);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// for sorting
+//-----------------------------------------------------------------------------
+
+bool CMapSolid::ShouldRenderLast()
+{
+ for (int nFace = 0; nFace < GetFaceCount(); nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ if (pFace->ShouldRenderLast())
+ return true;
+ }
+ return false;
+}
+
+void CMapSolid::AddShadowingTriangles( CUtlVector<Vector> &tri_list )
+{
+ for (int nFace = 0; nFace < GetFaceCount(); nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ pFace->AddShadowingTriangles( tri_list );
+ if( pFace->HasDisp() )
+ {
+ EditDispHandle_t handle = pFace->GetDisp();
+ CMapDisp *pMapDisp = EditDispMgr()->GetDisp( handle );
+ pMapDisp->AddShadowingTriangles( tri_list );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Renders the solid using the default render mode. If the solid is
+// currently selected, it will be rendered with a yellow wireframe
+// in a second pass.
+// Input : pRender - Rendering interface.
+//-----------------------------------------------------------------------------
+void CMapSolid::Render3D(CRender3D *pRender)
+{
+ //
+ // determine whether or not this is a displacement solid - i.e. one of the faces
+ // on this solid is displaced
+ //
+ CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
+ if( !pDoc )
+ return;
+
+ bool bMaskFaces = pDoc->IsDispSolidDrawMask() && HasDisp();
+
+ //
+ // Determine whether we need to render in one or two passes. If we are selected,
+ // and rendering in flat or textured mode, we need to render using two passes.
+ //
+ int nPasses = 1;
+ int iStartPass = 1;
+
+ SelectionState_t eSolidSelectionState = GetSelectionState();
+ EditorRenderMode_t eDefaultRenderMode = pRender->GetDefaultRenderMode();
+
+ if ((eSolidSelectionState != SELECT_NONE) && (eDefaultRenderMode != RENDER_MODE_WIREFRAME))
+ {
+ nPasses = 2;
+ }
+
+ if ( ( eSolidSelectionState == SELECT_MODIFY ) )
+ {
+ nPasses = 2;
+ iStartPass = 2;
+ }
+
+ for (int nPass = iStartPass; nPass <= nPasses; nPass++)
+ {
+ //
+ // Render the second pass in wireframe.
+ //
+ if (nPass == 1)
+ {
+ pRender->PushRenderMode(RENDER_MODE_CURRENT);
+ }
+ else
+ {
+ pRender->PushRenderMode(RENDER_MODE_WIREFRAME);
+ }
+
+ for (int nFace = 0; nFace < GetFaceCount(); nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+
+ // only render displaced faces on a displaced solid when the displacement
+ // solid render mask is set
+ if( bMaskFaces && !pFace->HasDisp() )
+ continue;
+
+ if( pRender->IsInLightingPreview() )
+ {
+ if( nPass == 1 )
+ {
+ if( pFace->GetSelectionState() != SELECT_NONE )
+ {
+ pRender->BeginRenderHitTarget(this, nFace);
+ pFace->Render3D( pRender );
+ pRender->EndRenderHitTarget();
+ }
+ }
+ else
+ {
+ pFace->Render3D( pRender );
+ }
+ }
+ else
+ {
+ pRender->BeginRenderHitTarget(this, nFace);
+ pFace->Render3D( pRender );
+ pRender->EndRenderHitTarget();
+ }
+ }
+
+ pRender->PopRenderMode();
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CMapSolid::HasDisp( void )
+{
+ for( int ndxFace = 0; ndxFace < GetFaceCount(); ndxFace++ )
+ {
+ CMapFace *pFace = GetFace( ndxFace );
+ if( pFace->HasDisp() )
+ return true;
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns a solid type for the given texture name.
+// Input : pszTexture -
+//-----------------------------------------------------------------------------
+HL1_SolidType_t CMapSolid::HL1SolidTypeFromTextureName(const char *pszTexture)
+{
+ HL1_SolidType_t eSolidType;
+
+ if (pszTexture[0] == '*')
+ {
+ if (!strncmp(pszTexture + 1, "slime", 5))
+ {
+ eSolidType = btSlime;
+ }
+ else if (!strncmp(pszTexture + 1, "lava", 4))
+ {
+ eSolidType = btLava;
+ }
+ else
+ {
+ eSolidType = btWater;
+ }
+ }
+ else
+ {
+ eSolidType = btSolid;
+ }
+
+ return(eSolidType);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pFile -
+// Output : ChunkFileResult_t
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapSolid::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo)
+{
+ //
+ // Check rules before saving this object.
+ //
+ if (!pSaveInfo->ShouldSaveObject(this))
+ {
+ return(ChunkFile_Ok);
+ }
+
+ ChunkFileResult_t eResult = ChunkFile_Ok;
+
+ //
+ // If we are hidden, place this object inside of a hidden chunk.
+ //
+ if (!IsVisible())
+ {
+ eResult = pFile->BeginChunk("hidden");
+ }
+
+ //
+ // Begin the solid chunk.
+ //
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->BeginChunk("solid");
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ //
+ // Save the solid's ID.
+ //
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->WriteKeyValueInt("id", GetID());
+ }
+
+ //
+ // Save all the brush faces.
+ //
+ int nFaceCount = GetFaceCount();
+ for (int nFace = 0; nFace < nFaceCount; nFace++)
+ {
+ CMapFace *pFace = GetFace(nFace);
+ eResult = pFace->SaveVMF(pFile, pSaveInfo);
+
+ if (eResult != ChunkFile_Ok)
+ {
+ break;
+ }
+ }
+
+ //
+ // Save our base class' information within our chunk.
+ //
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = CMapClass::SaveVMF(pFile, pSaveInfo);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->EndChunk();
+ }
+ }
+
+ //
+ // End the hidden chunk if we began it.
+ //
+ if (!IsVisible())
+ {
+ eResult = pFile->EndChunk();
+ }
+
+ return(eResult);
+}
+
+
+bool CMapSolid::ShouldAppearInLightingPreview(void)
+{
+ return true;
+}
+
+bool CMapSolid::ShouldAppearInRaytracedLightingPreview(void)
+{
+ return true;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pFile -
+// Output : ChunkFileResult_t
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapSolid::SaveEditorData(CChunkFile *pFile)
+{
+ if (m_bIsCordonBrush)
+ {
+ return(pFile->WriteKeyValueBool("cordonsolid", true));
+ }
+
+ return(ChunkFile_Ok);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets whether this brush was created by the cordon tool. Brushes that
+// were created by the cordon tool are not loaded.
+// Input : bSet - true to set, false to clear.
+//-----------------------------------------------------------------------------
+void CMapSolid::SetCordonBrush(bool bSet)
+{
+ m_bIsCordonBrush = bSet;
+
+ for ( int i = 0; i < GetFaceCount(); i++ )
+ {
+ CMapFace *pFace = GetFace( i );
+ pFace->SetCordonFace( bSet );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Subtracts one solid from another.
+// Input : pSubtraction - Solid (or group of solids) to subtract with.
+// pOther - Solid (or group of solids) to subtract from.
+// pSubParent - Receives the results of the subtraction as children.
+// Output : Returns true if the objects intersected (subtraction was performed),
+// false if the objects did not intersect (no subtraction was performed).
+//-----------------------------------------------------------------------------
+bool CMapSolid::Subtract(CMapObjectList *pInside, CMapObjectList *pOutside, CMapClass *pSubtractWith)
+{
+ //
+ // Build a list of solids to subtract with.
+ //
+ CMapObjectList SubList;
+ if (pSubtractWith->IsMapClass(MAPCLASS_TYPE(CMapSolid)))
+ {
+ SubList.AddToTail(pSubtractWith);
+ }
+
+ EnumChildrenPos_t pos;
+ CMapClass *pChild = pSubtractWith->GetFirstDescendent(pos);
+ while (pChild != NULL)
+ {
+ CMapSolid *pSolid = dynamic_cast <CMapSolid *> (pChild);
+ if (pSolid != NULL)
+ {
+ SubList.AddToTail(pSolid);
+ }
+
+ pChild = pSubtractWith->GetNextDescendent(pos);
+ }
+
+ //
+ // For every solid that we are subtracting with...
+ //
+ bool bIntersected = false;
+
+ FOR_EACH_OBJ( SubList, p )
+ {
+ CMapSolid *pCarver = (CMapSolid *)SubList.Element(p);
+
+ //
+ // Subtract the 'with' solid from the 'from' solid, and place the
+ // results in the carve_in and carve_out lists.
+ //
+ CMapObjectList carve_in;
+ CMapObjectList carve_out;
+
+ CMapObjectList *pCarveIn = NULL;
+ CMapObjectList *pCarveOut = NULL;
+
+ if (pInside != NULL)
+ {
+ pCarveIn = &carve_in;
+ }
+
+ if (pOutside != NULL)
+ {
+ pCarveOut = &carve_out;
+ }
+
+ bIntersected |= Carve(pCarveIn, pCarveOut, pCarver);
+
+ if (pInside != NULL)
+ {
+ pInside->AddVectorToTail(carve_in);
+ carve_in.RemoveAll();
+ }
+
+ if (pOutside != NULL)
+ {
+ pOutside->AddVectorToTail(carve_out);
+ carve_out.RemoveAll();
+ }
+ }
+
+ return(bIntersected);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+//-----------------------------------------------------------------------------
+color32 CMapSolid::GetLineColor( CRender2D *pRender )
+{
+ //
+ // If the solid is not selected, determine the appropriate pen color.
+ //
+ if ( !IsSelected() )
+ {
+ //
+ // If this is a solid entity, use the entity pen color.
+ //
+ CMapEntity *pEntity = dynamic_cast<CMapEntity *>(GetParent());
+ if (pEntity != NULL)
+ {
+ GDclass *pClass = pEntity->GetClass();
+ if (pClass)
+ {
+ return pClass->GetColor();
+ }
+ else
+ {
+ color32 clr;
+ clr.r = GetRValue(Options.colors.clrEntity);
+ clr.g = GetGValue(Options.colors.clrEntity);
+ clr.b = GetBValue(Options.colors.clrEntity);
+ clr.a = 255;
+ return clr;
+ }
+ }
+ //
+ // Otherwise, use the solid color.
+ //
+ else
+ {
+ if (Options.view2d.bUsegroupcolors)
+ {
+ return GetRenderColor();
+ }
+ else
+ {
+ color32 clr;
+ clr.r = GetRValue(Options.colors.clrBrush);
+ clr.g = GetGValue(Options.colors.clrBrush);
+ clr.b = GetBValue(Options.colors.clrBrush);
+ clr.a = 255;
+ return clr;
+ }
+ }
+ }
+ //
+ // The solid is selected, use the selected pen color.
+ //
+ else
+ {
+ color32 clr;
+ clr.r = GetRValue(Options.colors.clrSelection);
+ clr.g = GetGValue(Options.colors.clrSelection);
+ clr.b = GetBValue(Options.colors.clrSelection);
+ clr.a = 255;
+ return clr;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pRender -
+//-----------------------------------------------------------------------------
+
+void CMapSolid::Render2D(CRender2D *pRender)
+{
+ Vector vecMins, vecMaxs, vViewNormal;
+
+ GetRender2DBox(vecMins, vecMaxs);
+
+
+ pRender->GetCamera()->GetViewForward( vViewNormal );
+
+ Vector2D pt, pt2;
+
+ pRender->TransformPoint(pt, vecMins);
+ pRender->TransformPoint(pt2, vecMaxs);
+
+ int sizex = abs(pt2.x-pt.x)+1;
+ int sizey = abs(pt2.y-pt.y)+1;
+
+ color32 rgbLineColor = GetLineColor( pRender );
+
+ // check if we should draw handles & vertices
+
+ bool bIsSmall = sizex < (HANDLE_RADIUS*2) || sizey < (HANDLE_RADIUS*2);
+ bool bIsTiny = sizex < 2 || sizey < 2;
+ bool bDrawHandles = pRender->IsActiveView() && !bIsSmall && IsEditable();
+ bool bDrawVertices = Options.view2d.bDrawVertices && !bIsTiny;
+
+ pRender->SetDrawColor( rgbLineColor.r, rgbLineColor.g, rgbLineColor.b );
+
+ //
+ // Draw center handle if the solid is larger than the handle along either axis.
+ //
+ if ( bDrawHandles )
+ {
+ // draw center handle as cross
+ pRender->SetHandleStyle( HANDLE_RADIUS, CRender::HANDLE_CROSS );
+ pRender->SetHandleColor( rgbLineColor.r, rgbLineColor.g, rgbLineColor.b );
+ pRender->DrawHandle( (vecMins+vecMaxs)/2 );
+ }
+
+ if ( bDrawVertices )
+ {
+ // set handle style for upcoming vertex drawing
+ pRender->SetHandleStyle( 2, CRender::HANDLE_SQUARE );
+ pRender->SetHandleColor( GetRValue(Options.colors.clrVertex), GetGValue(Options.colors.clrVertex), GetBValue(Options.colors.clrVertex) );
+ }
+
+ // is solid projection is too small, draw simple line
+ if ( bIsTiny )
+ {
+ pRender->DrawLine( vecMins, vecMaxs );
+ }
+ else
+ {
+ int nFaces = GetFaceCount();
+
+ for ( int i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ pFace->Render2D( pRender );
+ }
+
+ if ( bDrawVertices )
+ {
+ bool bPop = pRender->BeginClientSpace();
+
+ for ( int i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ pFace->RenderVertices( pRender );
+ }
+
+ if ( bPop )
+ pRender->EndClientSpace();
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pView -
+// vecPoint -
+// nHitData -
+// Output :
+//-----------------------------------------------------------------------------
+bool CMapSolid::HitTest2D(CMapView2D *pView, const Vector2D &point, HitInfo_t &HitData)
+{
+ if (!IsVisible())
+ return false;
+
+ //
+ // First check center X.
+ //
+ Vector vecCenter, vecViewPoint;
+ GetBoundsCenter(vecCenter);
+
+ Vector2D vecClientCenter;
+ pView->WorldToClient(vecClientCenter, vecCenter);
+ pView->GetCamera()->GetViewPoint( vecViewPoint );
+
+ HitData.pObject = this;
+ HitData.nDepth = vecViewPoint[pView->axThird]-vecCenter[pView->axThird];
+ HitData.uData = 0;
+
+ if (pView->CheckDistance(point, vecClientCenter, HANDLE_RADIUS))
+ {
+ return true;
+ }
+ else if (!Options.view2d.bSelectbyhandles || !IsEditable() )
+ {
+ //
+ // See if any edges are within certain distance from the the point.
+ //
+ int iSelUnits = 2;
+ int x1 = point.x - iSelUnits;
+ int x2 = point.x + iSelUnits;
+ int y1 = point.y - iSelUnits;
+ int y2 = point.y + iSelUnits;
+
+ int nFaces = GetFaceCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ int nPoints = pFace->nPoints;
+ if (nPoints > 0)
+ {
+ Vector *pPoints = pFace->Points;
+
+ Vector2D vec1;
+ pView->WorldToClient(vec1, pPoints[0]);
+
+ for (int j = 1; j < nPoints; j++)
+ {
+ Vector2D vec2;
+ pView->WorldToClient(vec2, pPoints[j]);
+
+ if (IsLineInside(vec1, vec2, x1, y1, x2, y2))
+ {
+ return true;
+ }
+ else
+ {
+ vec1 = vec2;
+ }
+ }
+ }
+ }
+ }
+
+ HitData.pObject = NULL;
+
+ return false;
+}
+
+bool CMapSolid::SaveDXF(ExportDXFInfo_s *pInfo)
+{
+ if (pInfo->bVisOnly)
+ {
+ if (!IsVisible())
+ {
+ return true;
+ }
+ }
+
+ CSSolid *pStrucSolid = new CSSolid;
+ pStrucSolid->Attach(this);
+ pStrucSolid->Convert( true, true );
+ pStrucSolid->SerializeDXF(pInfo->fp, pInfo->nObject++);
+ delete pStrucSolid;
+
+ // Serialize displacements
+ for (int i = 0; i < GetFaceCount(); ++i)
+ {
+ CMapFace *pMapFace = GetFace( i );
+ if (pMapFace->HasDisp())
+ {
+ EditDispHandle_t hDisp = pMapFace->GetDisp();
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( hDisp );
+ if (!pDisp->SaveDXF( pInfo ))
+ return FALSE;
+ }
+ }
+
+ return TRUE;
+}
+
+//-----------------------------------------------------------------------------
+// Called any time this object is modified by Undo or Redo.
+//-----------------------------------------------------------------------------
+void CMapSolid::OnUndoRedo()
+{
+ int nFaces = GetFaceCount();
+ for (int i = 0; i < nFaces; i++)
+ {
+ CMapFace *pFace = GetFace(i);
+ pFace->OnUndoRedo();
+ }
+}
+