diff options
Diffstat (limited to 'hammer/mapsolid.cpp')
| -rw-r--r-- | hammer/mapsolid.cpp | 2010 |
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(); + } +} + |