summaryrefslogtreecommitdiff
path: root/hammer/mapface.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /hammer/mapface.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'hammer/mapface.cpp')
-rw-r--r--hammer/mapface.cpp3467
1 files changed, 3467 insertions, 0 deletions
diff --git a/hammer/mapface.cpp b/hammer/mapface.cpp
new file mode 100644
index 0000000..6b89bba
--- /dev/null
+++ b/hammer/mapface.cpp
@@ -0,0 +1,3467 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+//=============================================================================
+
+#include "stdafx.h"
+#include "collisionutils.h"
+#include "mainfrm.h"
+#include "MapDefs.h"
+#include "MapFace.h"
+#include "MapDisp.h"
+#include "MapWorld.h"
+#include "fgdlib/WCKeyValues.h"
+#include "GlobalFunctions.h"
+#include "Render3D.h"
+#include "Render2D.h"
+#include "SaveInfo.h"
+#include "TextureSystem.h"
+#include "MapDoc.h"
+#include "materialsystem/imesh.h"
+#include "Material.h"
+#include "utlrbtree.h"
+#include "mathlib/vector.h"
+#include "camera.h"
+#include "options.h"
+#include "hammer.h"
+
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include <tier0/memdbgon.h>
+
+
+//#define DEBUGPTS
+
+
+#define TEXTURE_AXIS_LENGTH 10 // Rendered texture axis length in world units.
+
+//
+// Components of the texture axes are rounded to integers within this tolerance. This tolerance corresponds
+// to an angle of about 0.06 degrees.
+//
+#define TEXTURE_AXIS_ROUND_EPSILON 0.001
+
+
+//
+// For passing into LoadKeyCallback. Collects key value data while loading.
+//
+struct LoadFace_t
+{
+ CMapFace *pFace;
+ char szTexName[MAX_PATH];
+};
+
+
+BOOL CheckFace(Vector *Points, int nPoints, Vector *normal, float dist, CCheckFaceInfo *pInfo);
+LPCTSTR GetDefaultTextureName();
+
+
+#pragma warning(disable:4244)
+
+
+//
+// Static member data initialization.
+//
+bool CMapFace::m_bShowFaceSelection = true;
+IEditorTexture *CMapFace::m_pLightmapGrid = NULL;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Constructor. Initializes data members and sets the texture to the
+// default texture.
+//-----------------------------------------------------------------------------
+CMapFace::CMapFace(void)
+{
+ memset(&texture, 0, sizeof(texture));
+ memset(&plane, 0, sizeof(plane));
+
+ m_pTexture = NULL;
+ m_pTangentAxes = NULL;
+ m_DispHandle = EDITDISPHANDLE_INVALID;
+
+ Points = NULL;
+ nPoints = 0;
+ m_nFaceID = 0;
+ m_pTextureCoords = NULL;
+ m_pLightmapCoords = NULL;
+ m_uchAlpha = 255;
+
+ m_pDetailObjects = NULL;
+
+ texture.nLightmapScale = g_pGameConfig->GetDefaultLightmapScale();
+
+ texture.scale[0] = g_pGameConfig->GetDefaultTextureScale();
+ texture.scale[1] = g_pGameConfig->GetDefaultTextureScale();
+
+ SetTexture(GetNullTextureName());
+
+ if (m_pLightmapGrid == NULL)
+ {
+ m_pLightmapGrid = g_Textures.FindActiveTexture("Debug/debugluxelsnoalpha");
+ }
+
+ m_bIgnoreLighting = false;
+ m_fSmoothingGroups = SMOOTHING_GROUP_DEFAULT;
+ UpdateFaceFlags();
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Destructor. Frees points and texture coordinates.
+//-----------------------------------------------------------------------------
+CMapFace::~CMapFace(void)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ delete [] Points;
+ Points = NULL;
+
+ delete [] m_pTextureCoords;
+ m_pTextureCoords = NULL;
+
+ delete [] m_pLightmapCoords;
+ m_pLightmapCoords = NULL;
+
+ delete m_pDetailObjects;
+ m_pDetailObjects = NULL;
+
+ FreeTangentSpaceAxes();
+
+ if( HasDisp() )
+ {
+ IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
+
+ if( pDispMgr )
+ {
+ pDispMgr->RemoveFromWorld( GetDisp() );
+ }
+
+ // destroy handle
+ SetDisp( EDITDISPHANDLE_INVALID );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Attempts to fix this face. This is called by the check for problems
+// code when a face is reported as invalid.
+// Output : Returns TRUE on success, FALSE on failure.
+//-----------------------------------------------------------------------------
+BOOL CMapFace::Fix(void)
+{
+ CalcPlane();
+ CalcTextureCoords();
+
+ // Create any detail objects if appropriate
+ DetailObjects::BuildAnyDetailObjects(this);
+
+ return(CheckFace());
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the short texture name in 'pszName'. Places an empty string
+// in 'pszName' if the face has no texture.
+//-----------------------------------------------------------------------------
+void CMapFace::GetTextureName(char *pszName) const
+{
+ Assert(pszName != NULL);
+
+ if (pszName != NULL)
+ {
+ if (m_pTexture != NULL)
+ {
+ m_pTexture->GetShortName(pszName);
+ }
+ else
+ {
+ pszName[0] = '\0';
+ }
+ }
+}
+
+static char *InvisToolTextures[]={
+ "occluder",
+ "areaportal",
+ "invisible",
+ "skip",
+ "trigger",
+ "hint",
+ "fog",
+ "origin",
+ "toolsnodraw",
+};
+
+void CMapFace::UpdateFaceFlags( void )
+{
+ char tname[2048];
+ GetTextureName( tname );
+ m_nFaceFlags = 0;
+ if (strstr(tname,"tools"))
+ {
+ if ( strstr( tname, "blocklight" ) )
+ {
+ m_nFaceFlags |= FACE_FLAGS_NODRAW_IN_LPREVIEW;
+ }
+ if ( strstr( tname, "skybox") )
+ {
+ m_nFaceFlags |= FACE_FLAGS_NOSHADOW;
+ }
+ for(int i=0;i<NELEMS(InvisToolTextures); i++)
+ if (strstr( tname, InvisToolTextures[i] ) )
+ {
+ m_nFaceFlags |= FACE_FLAGS_NODRAW_IN_LPREVIEW | FACE_FLAGS_NOSHADOW;
+ break;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Populates this face with another face's information.
+// Input : pFrom - The face to copy.
+// Output : CMapFace
+//-----------------------------------------------------------------------------
+CMapFace *CMapFace::CopyFrom(const CMapFace *pObject, DWORD dwFlags, bool bUpdateDependencies)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ const CMapFace *pFrom = dynamic_cast<const CMapFace *>(pObject);
+ Assert(pFrom != NULL);
+
+ if (pFrom != NULL)
+ {
+ //
+ // Free our points first.
+ //
+ if (Points != NULL)
+ {
+ delete [] Points;
+ Points = NULL;
+ }
+
+ if (m_pTextureCoords != NULL)
+ {
+ delete [] m_pTextureCoords;
+ m_pTextureCoords = NULL;
+ }
+
+ if (m_pLightmapCoords != NULL)
+ {
+ delete [] m_pLightmapCoords;
+ m_pLightmapCoords = NULL;
+ }
+
+ FreeTangentSpaceAxes();
+
+ nPoints = 0;
+
+ //
+ // Copy the member data.
+ //
+ m_nFaceID = pFrom->m_nFaceID;
+ m_eSelectionState = pFrom->GetSelectionState();
+ texture = pFrom->texture;
+ m_pTexture = pFrom->m_pTexture;
+ m_bIsCordonFace = pFrom->m_bIsCordonFace;
+
+ //
+ // Allocate points memory.
+ //
+ if (dwFlags & COPY_FACE_POINTS)
+ {
+ Points = NULL;
+ nPoints = pFrom->nPoints;
+
+ if (pFrom->Points && nPoints)
+ {
+ AllocatePoints(nPoints);
+ AllocTangentSpaceAxes( nPoints );
+ memcpy(Points, pFrom->Points, sizeof(Vector) * nPoints);
+ memcpy(m_pTextureCoords, pFrom->m_pTextureCoords, sizeof(Vector2D) * nPoints);
+ memcpy(m_pLightmapCoords, pFrom->m_pLightmapCoords, sizeof(Vector2D) * nPoints);
+ memcpy(m_pTangentAxes, pFrom->m_pTangentAxes, sizeof(TangentSpaceAxes_t) * nPoints);
+ }
+ }
+ else
+ {
+ Points = NULL;
+ m_pTextureCoords = 0;
+ m_pLightmapCoords = 0;
+ m_pTangentAxes = 0;
+ nPoints = 0;
+ }
+
+ //
+ // Copy the plane. You shouldn't copy the points without copying the plane,
+ // so we do it if either bit is set.
+ //
+ if ((dwFlags & COPY_FACE_POINTS) || (dwFlags & COPY_FACE_PLANE))
+ {
+ plane = pFrom->plane;
+ }
+ else
+ {
+ memset(&plane, 0, sizeof(plane));
+ }
+
+ //
+ // copy the displacement info.
+ //
+ // If we do have displacement, then we'll never be asked to become a copy of
+ // a face that does not have a displacement, because you cannot undo a Generate
+ // Displacement operation.
+ //
+ if( pFrom->HasDisp() )
+ {
+ //
+ // Allocate a new displacement info if we don't already have one.
+ //
+ if( !HasDisp() )
+ {
+ SetDisp( EditDispMgr()->Create() );
+ }
+
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ pDisp->SetParent( this );
+
+ CMapDisp *pFromDisp = EditDispMgr()->GetDisp( pFrom->m_DispHandle );
+ pDisp->CopyFrom( pFromDisp, bUpdateDependencies );
+ }
+ else
+ {
+ SetDisp( EDITDISPHANDLE_INVALID );
+ }
+
+ // Copy CMapAtom fields. dvs: this should be done in CMapAtom::CopyFrom!
+ r = pFrom->r;
+ g = pFrom->g;
+ b = pFrom->b;
+
+ m_uchAlpha = pFrom->m_uchAlpha;
+ m_bIgnoreLighting = pFrom->m_bIgnoreLighting;
+
+ // Copy the smoothing group data.
+ m_fSmoothingGroups = pFrom->m_fSmoothingGroups;
+
+ // Delete any existing and build any new detail objects
+ delete m_pDetailObjects;
+ m_pDetailObjects = NULL;
+ DetailObjects::BuildAnyDetailObjects(this);
+ }
+ UpdateFaceFlags();
+ return(this);
+}
+
+
+//-----------------------------------------------------------------------------
+// Called any time this object is modified due to an Undo or Redo.
+//-----------------------------------------------------------------------------
+void CMapFace::OnUndoRedo()
+{
+ // It's not valid to have selected faces outside of face edit mode.
+ // If the user modified this face, then closed the texture application
+ // dialog, then did an Undo, clear our selection state.
+ if ( !GetMainWnd()->IsInFaceEditMode() )
+ {
+ m_eSelectionState = SELECT_NONE;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Creates a face from a list of points.
+// Input : pPoints - An array of points.
+// _nPoints - Number of points. If nPoints < 0, reverse points.
+//-----------------------------------------------------------------------------
+void CMapFace::CreateFace(Vector *pPoints, int _nPoints, bool bIsCordonFace)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ if (_nPoints > 0)
+ {
+ AllocatePoints(_nPoints);
+ Assert(nPoints > 0);
+ if (nPoints > 0)
+ {
+ memcpy(Points, pPoints, nPoints * sizeof(Vector));
+ }
+ }
+ else
+ {
+ AllocatePoints(-_nPoints);
+ Assert(nPoints > 0);
+ if (nPoints > 0)
+ {
+ int j = 0;
+ for (int i = nPoints - 1; i >= 0; i--)
+ {
+ Points[j++] = pPoints[i];
+ }
+ }
+ }
+
+ SetCordonFace( bIsCordonFace );
+
+#ifdef DEBUGPTS
+ DebugPoints();
+#endif
+
+ CalcPlaneFromFacePoints();
+ CalcTextureCoords();
+
+ // Create any detail objects if appropriate
+ DetailObjects::BuildAnyDetailObjects(this);
+
+#if 0
+ //
+ // create the displacement map -- if need be
+ //
+ if( m_pMapDisp )
+ {
+ m_pMapDisp->InitSurfData( this, false );
+ m_pMapDisp->Create();
+ }
+#endif
+}
+
+
+Vector FaceNormals[6] =
+{
+ Vector(0, 0, 1), // floor
+ Vector(0, 0, -1), // ceiling
+ Vector(0, -1, 0), // north wall
+ Vector(0, 1, 0), // south wall
+ Vector(-1, 0, 0), // east wall
+ Vector(1, 0, 0), // west wall
+};
+
+
+Vector DownVectors[6] =
+{
+ Vector(0, -1, 0), // floor
+ Vector(0, -1, 0), // ceiling
+ Vector(0, 0, -1), // north wall
+ Vector(0, 0, -1), // south wall
+ Vector(0, 0, -1), // east wall
+ Vector(0, 0, -1), // west wall
+};
+
+
+Vector RightVectors[6] =
+{
+ Vector(1, 0, 0), // floor
+ Vector(1, 0, 0), // ceiling
+ Vector(1, 0, 0), // north wall
+ Vector(1, 0, 0), // south wall
+ Vector(0, 1, 0), // east wall
+ Vector(0, 1, 0), // west wall
+};
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : index -
+// downVect -
+//-----------------------------------------------------------------------------
+void CMapFace::GetDownVector( int index, Vector& downVect )
+{
+ downVect = DownVectors[index];
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : Center -
+//-----------------------------------------------------------------------------
+void CMapFace::GetCenter(Vector& Center)
+{
+ Assert(nPoints > 0);
+
+ Center.Init();
+
+ if (nPoints != 0)
+ {
+ for (int i = 0; i < nPoints; i++)
+ {
+ Center[0] += Points[i][0];
+ Center[1] += Points[i][1];
+ Center[2] += Points[i][2];
+ }
+
+ Center[0] /= nPoints;
+ Center[1] /= nPoints;
+ Center[2] /= nPoints;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines the general orientation of a face based on its normal vector.
+// Output : FaceOrientation_t
+//-----------------------------------------------------------------------------
+FaceOrientation_t CMapFace::GetOrientation(void) const
+{
+ // The normal must have a nonzero length!
+ if ((plane.normal[0] == 0) && (plane.normal[1] == 0) && (plane.normal[2] == 0))
+ {
+ return(FACE_ORIENTATION_INVALID);
+ }
+
+ //
+ // Find the axis that the surface normal has the greatest projection onto.
+ //
+ float fDot;
+ float fMaxDot;
+ Vector Normal;
+
+ FaceOrientation_t eOrientation = FACE_ORIENTATION_INVALID;
+
+ Normal = plane.normal;
+ VectorNormalize(Normal);
+
+ fMaxDot = 0;
+ for (int i = 0; i < 6; i++)
+ {
+ fDot = DotProduct(Normal, FaceNormals[i]);
+
+ if (fDot >= fMaxDot)
+ {
+ fMaxDot = fDot;
+ eOrientation = (FaceOrientation_t)i;
+ }
+ }
+
+ return(eOrientation);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : eAlignment -
+// dwFlags -
+//-----------------------------------------------------------------------------
+void CMapFace::InitializeTextureAxes(TextureAlignment_t eAlignment, DWORD dwFlags)
+{
+ FaceOrientation_t eOrientation;
+
+ //
+ // If the texture axis information has been initialized, don't reinitialize unless
+ // the FORCE flag is set.
+ //
+ if ((!(dwFlags & INIT_TEXTURE_FORCE)) &&
+ ((texture.UAxis[0] != 0) || (texture.UAxis[1] != 0) || (texture.UAxis[2] != 0) ||
+ (texture.VAxis[0] != 0) || (texture.VAxis[1] != 0) || (texture.VAxis[2] != 0)))
+ {
+ return;
+ }
+
+ if (dwFlags & INIT_TEXTURE_ROTATION)
+ {
+ texture.rotate = 0;
+ }
+
+ if (dwFlags & INIT_TEXTURE_SHIFT)
+ {
+ texture.UAxis[3] = 0;
+ texture.VAxis[3] = 0;
+ }
+
+ if (dwFlags & INIT_TEXTURE_SCALE)
+ {
+ texture.scale[0] = g_pGameConfig->GetDefaultTextureScale();
+ texture.scale[1] = g_pGameConfig->GetDefaultTextureScale();
+ }
+
+ if (dwFlags & INIT_TEXTURE_AXES)
+ {
+ // don't reset the shift component [3]
+ texture.UAxis.AsVector3D().Init();
+ texture.VAxis.AsVector3D().Init();
+
+ // Determine the general orientation of this face (floor, ceiling, n wall, etc.)
+ eOrientation = GetOrientation();
+ if (eOrientation == FACE_ORIENTATION_INVALID)
+ {
+ CalcTextureCoords();
+ return;
+ }
+
+ // Pick a world axis aligned V axis based on the face orientation.
+ texture.VAxis.AsVector3D() = DownVectors[eOrientation];
+
+ //
+ // If we are using face aligned textures, calculate the texture axes.
+ //
+ if (eAlignment == TEXTURE_ALIGN_FACE)
+ {
+ // Using that axis-aligned V axis, calculate the true U axis
+ CrossProduct(plane.normal, texture.VAxis.AsVector3D(), texture.UAxis.AsVector3D());
+ VectorNormalize(texture.UAxis.AsVector3D());
+
+ // Now use the true U axis to calculate the true V axis.
+ CrossProduct(texture.UAxis.AsVector3D(), plane.normal, texture.VAxis.AsVector3D());
+ VectorNormalize(texture.VAxis.AsVector3D());
+ }
+ //
+ // If we are using world (or "natural") aligned textures, use the V axis as is
+ // and pick the corresponding U axis from the table.
+ //
+ else if (eAlignment == TEXTURE_ALIGN_WORLD)
+ {
+ texture.UAxis.AsVector3D() = RightVectors[eOrientation];
+ }
+ //
+ // Quake-style texture alignment used a different axis convention.
+ //
+ else
+ {
+ InitializeQuakeStyleTextureAxes(texture.UAxis, texture.VAxis);
+ }
+
+ if (texture.rotate != 0)
+ {
+ RotateTextureAxes(texture.rotate);
+ }
+ }
+
+ CalcTextureCoords();
+
+ // Create any detail objects if appropriate
+ DetailObjects::BuildAnyDetailObjects(this);
+
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks for a texture axis perpendicular to the face.
+// Output : Returns TRUE on success, FALSE on failure.
+//-----------------------------------------------------------------------------
+BOOL CMapFace::IsTextureAxisValid(void) const
+{
+ //
+ // Generate the texture normal axis, which may be different from the
+ // face normal, depending on texture alignment.
+ //
+ Vector TexNormalAxis;
+ CrossProduct(texture.VAxis.AsVector3D(), texture.UAxis.AsVector3D(), TexNormalAxis);
+ return(DotProduct(plane.normal, TexNormalAxis) != 0);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Normalize the U/V shift values to be less than the texture width/height.
+//-----------------------------------------------------------------------------
+void CMapFace::NormalizeTextureShifts(void)
+{
+ //
+ // HACK: this should really be elsewhere, but it can live here for now.
+ // Round all components of our texture axes within an epsilon.
+ //
+ for (int nDim = 0; nDim < 4; nDim++)
+ {
+ int nValue = V_rint(texture.UAxis[nDim]);
+ if (fabs(texture.UAxis[nDim] - nValue) < TEXTURE_AXIS_ROUND_EPSILON)
+ {
+ texture.UAxis[nDim] = nValue;
+ }
+
+ nValue = V_rint(texture.VAxis[nDim]);
+ if (fabs(texture.VAxis[nDim] - nValue) < TEXTURE_AXIS_ROUND_EPSILON)
+ {
+ texture.VAxis[nDim] = nValue;
+ }
+ }
+
+ if (m_pTexture == NULL)
+ {
+ return;
+ }
+
+ if (m_pTexture->GetWidth() != 0)
+ {
+ texture.UAxis[3] = fmod(texture.UAxis[3], m_pTexture->GetWidth());
+ }
+
+ if (m_pTexture->GetHeight() != 0)
+ {
+ texture.VAxis[3] = fmod(texture.VAxis[3], m_pTexture->GetHeight());
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines the bounding box of a face in world space.
+// Input : pfMins - Receives the face X, Y, Z minima.
+// pfMaxs - Receives the face X, Y, Z maxima.
+//-----------------------------------------------------------------------------
+void CMapFace::GetFaceBounds(Vector& pfMins, Vector& pfMaxs) const
+{
+ for (int nPoint = 0; nPoint < nPoints; nPoint++)
+ {
+ if ((Points[nPoint][0] < pfMins[0]) || (nPoint == 0))
+ {
+ pfMins[0] = Points[nPoint][0];
+ }
+
+ if ((Points[nPoint][1] < pfMins[1]) || (nPoint == 0))
+ {
+ pfMins[1] = Points[nPoint][1];
+ }
+
+ if ((Points[nPoint][2] < pfMins[2]) || (nPoint == 0))
+ {
+ pfMins[2] = Points[nPoint][2];
+ }
+
+ if ((Points[nPoint][0] > pfMaxs[0]) || (nPoint == 0))
+ {
+ pfMaxs[0] = Points[nPoint][0];
+ }
+
+ if ((Points[nPoint][1] > pfMaxs[1]) || (nPoint == 0))
+ {
+ pfMaxs[1] = Points[nPoint][1];
+ }
+
+ if ((Points[nPoint][2] > pfMaxs[2]) || (nPoint == 0))
+ {
+ pfMaxs[2] = Points[nPoint][2];
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the top left and bottom right points on the face in texture space.
+// These points are returned in texture space, not world space.
+// Input : TopLeft -
+// BottomRight -
+//-----------------------------------------------------------------------------
+void CMapFace::GetFaceTextureExtents(Vector2D & TopLeft, Vector2D & BottomRight) const
+{
+ BOOL bFirst = TRUE;
+
+ for (int nPoint = 0; nPoint < nPoints; nPoint++)
+ {
+ Vector2D Test;
+
+ Test[0] = DotProduct(Points[nPoint], texture.UAxis.AsVector3D()) / texture.scale[0];
+ Test[1] = DotProduct(Points[nPoint], texture.VAxis.AsVector3D()) / texture.scale[1];
+
+ if ((Test[0] < TopLeft[0]) || (bFirst))
+ {
+ TopLeft[0] = Test[0];
+ }
+
+ if ((Test[1] < TopLeft[1]) || (bFirst))
+ {
+ TopLeft[1] = Test[1];
+ }
+
+ if ((Test[0] > BottomRight[0]) || (bFirst))
+ {
+ BottomRight[0] = Test[0];
+ }
+
+ if ((Test[1] > BottomRight[1]) || (bFirst))
+ {
+ BottomRight[1] = Test[1];
+ }
+
+ bFirst = FALSE;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns the distance along the face normal of a given point. The
+// distance will be negative if the point is behind the face, positive
+// if the point is in front of the face.
+// Input : fPoint - Point to calculate normal distance.
+//-----------------------------------------------------------------------------
+float CMapFace::GetNormalDistance(Vector& fPoint)
+{
+ float fDot = DotProduct(fPoint, plane.normal);
+ return(fDot - plane.dist);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines the texture alignment(s) of this face. The alignments are
+// are returned as TextureAlignment_t values OR'ed together.
+//
+// Output : Returns an integer with any of the following flags set:
+//
+// TEXTURE_ALIGN_FACE - the texture axes are face aligned.
+// TEXTURE_ALIGN_WORLD - the texture axes are world aligned.
+//
+// If the returned value is zero (TEXTURE_ALIGN_NONE), the texture axes
+// are neither face aligned nor world aligned.
+//-----------------------------------------------------------------------------
+int CMapFace::GetTextureAlignment(void) const
+{
+ Vector TexNormalAxis;
+ int nAlignment = TEXTURE_ALIGN_NONE;
+
+ //
+ // Generate the texture normal axis, which may be different from the
+ // face normal, depending on texture alignment.
+ //
+ CrossProduct(texture.VAxis.AsVector3D(), texture.UAxis.AsVector3D(), TexNormalAxis);
+ VectorNormalize(TexNormalAxis);
+
+ //
+ // Check for face alignment.
+ //
+ if (DotProduct(TexNormalAxis, plane.normal) > 0.9999)
+ {
+ nAlignment |= TEXTURE_ALIGN_FACE;
+ }
+
+ //
+ // Check for world alignment.
+ //
+ FaceOrientation_t eOrientation = GetOrientation();
+ if (eOrientation != FACE_ORIENTATION_INVALID)
+ {
+ Vector WorldTexNormal;
+
+ CrossProduct(DownVectors[eOrientation], RightVectors[eOrientation], WorldTexNormal);
+ if (DotProduct(TexNormalAxis, WorldTexNormal) > 0.9999)
+ {
+ nAlignment |= TEXTURE_ALIGN_WORLD;
+ }
+ }
+
+ return(nAlignment);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Finds the top left and bottom right points of the given world extents
+// in texture space. These points are returned in texture space, not world space,
+// so a simple rectangle will suffice.
+// Input : Extents -
+// TopLeft -
+// BottomRight -
+//-----------------------------------------------------------------------------
+void CMapFace::GetTextureExtents(Extents_t Extents, Vector2D & TopLeft, Vector2D & BottomRight) const
+{
+ BOOL bFirst = TRUE;
+
+ for (int nPoint = 0; nPoint < NUM_EXTENTS_DIMS; nPoint++)
+ {
+ Vector2D Test;
+
+ Test[0] = DotProduct(Extents[nPoint], texture.UAxis.AsVector3D()) / texture.scale[0];
+ Test[1] = DotProduct(Extents[nPoint], texture.VAxis.AsVector3D()) / texture.scale[1];
+
+ if ((Test[0] < TopLeft[0]) || (bFirst))
+ {
+ TopLeft[0] = Test[0];
+ }
+
+ if ((Test[1] < TopLeft[1]) || (bFirst))
+ {
+ TopLeft[1] = Test[1];
+ }
+
+ if ((Test[0] > BottomRight[0]) || (bFirst))
+ {
+ BottomRight[0] = Test[0];
+ }
+
+ if ((Test[1] > BottomRight[1]) || (bFirst))
+ {
+ BottomRight[1] = Test[1];
+ }
+
+ bFirst = FALSE;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Determines the world extents of the face. Different from a bounding
+// box in that each point in the returned extents is actually on the face.
+// Input : Extents -
+//-----------------------------------------------------------------------------
+void CMapFace::GetFaceExtents(Extents_t Extents) const
+{
+ BOOL bFirst = TRUE;
+
+ for (int nPoint = 0; nPoint < nPoints; nPoint++)
+ {
+ if ((Points[nPoint][0] < Extents[EXTENTS_XMIN][0]) || (bFirst))
+ {
+ Extents[EXTENTS_XMIN] = Points[nPoint];
+ }
+
+ if ((Points[nPoint][0] > Extents[EXTENTS_XMAX][0]) || (bFirst))
+ {
+ Extents[EXTENTS_XMAX] = Points[nPoint];
+ }
+
+ if ((Points[nPoint][1] < Extents[EXTENTS_YMIN][1]) || (bFirst))
+ {
+ Extents[EXTENTS_YMIN] = Points[nPoint];
+ }
+
+ if ((Points[nPoint][1] > Extents[EXTENTS_YMAX][1]) || (bFirst))
+ {
+ Extents[EXTENTS_YMAX] = Points[nPoint];
+ }
+
+ if ((Points[nPoint][2] < Extents[EXTENTS_ZMIN][2]) || (bFirst))
+ {
+ Extents[EXTENTS_ZMIN] = Points[nPoint];
+ }
+
+ if ((Points[nPoint][2] > Extents[EXTENTS_ZMAX][2]) || (bFirst))
+ {
+ Extents[EXTENTS_ZMAX] = Points[nPoint];
+ }
+
+ bFirst = FALSE;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : eJustification -
+// Extents -
+//-----------------------------------------------------------------------------
+void CMapFace::JustifyTextureUsingExtents(TextureJustification_t eJustification, Extents_t Extents)
+{
+ Vector2D Center;
+
+ if (!texture.scale[0])
+ {
+ texture.scale[0] = g_pGameConfig->GetDefaultTextureScale();
+ }
+
+ if (!texture.scale[1])
+ {
+ texture.scale[1] = g_pGameConfig->GetDefaultTextureScale();
+ }
+
+ // Skip all the mucking about for a justification of NONE.
+ if (eJustification == TEXTURE_JUSTIFY_NONE)
+ {
+ texture.UAxis[3] = 0;
+ texture.VAxis[3] = 0;
+ CalcTextureCoords();
+ return;
+ }
+
+ // For fit justification, use a scale of 1 for initial calculations.
+ if (eJustification == TEXTURE_JUSTIFY_FIT)
+ {
+ texture.scale[0] = 1.0;
+ texture.scale[1] = 1.0;
+ }
+
+ Vector2D TopLeft;
+ Vector2D BottomRight;
+
+ GetTextureExtents(Extents, TopLeft, BottomRight);
+
+ // Find the face center in U/V space.
+ Center[0] = (TopLeft[0] + BottomRight[0]) / 2;
+ Center[1] = (TopLeft[1] + BottomRight[1]) / 2;
+
+ //
+ // Perform the justification.
+ //
+ switch (eJustification)
+ {
+ // Align the top left corner of the texture with the top left corner of the face.
+ case TEXTURE_JUSTIFY_TOP:
+ {
+ texture.VAxis[3] = -TopLeft[1];
+ break;
+ }
+
+ // Align the top left corner of the texture with the top left corner of the face.
+ case TEXTURE_JUSTIFY_BOTTOM:
+ {
+ texture.VAxis[3] = -BottomRight[1] + m_pTexture->GetHeight();
+ break;
+ }
+
+ // Align the left side of the texture with the left side of the face.
+ case TEXTURE_JUSTIFY_LEFT:
+ {
+ texture.UAxis[3] = -TopLeft[0];
+ break;
+ }
+
+ // Align the right side of the texture with the right side of the face.
+ case TEXTURE_JUSTIFY_RIGHT:
+ {
+ texture.UAxis[3] = -BottomRight[0] + m_pTexture->GetWidth();
+ break;
+ }
+
+ // Center the texture on the face.
+ case TEXTURE_JUSTIFY_CENTER:
+ {
+ texture.UAxis[3] = -Center[0] + (m_pTexture->GetWidth() / 2);
+ texture.VAxis[3] = -Center[1] + (m_pTexture->GetHeight() / 2);
+ break;
+ }
+
+ // Scale the texture to exactly fit the face.
+ case TEXTURE_JUSTIFY_FIT:
+ {
+ // Calculate the appropriate scale.
+ if (m_pTexture && m_pTexture->GetWidth() && m_pTexture->GetHeight())
+ {
+ texture.scale[0] = (BottomRight[0] - TopLeft[0]) / m_pTexture->GetWidth();
+ texture.scale[1] = (BottomRight[1] - TopLeft[1]) / m_pTexture->GetHeight();
+ }
+ else
+ {
+ texture.scale[0] = g_pGameConfig->GetDefaultTextureScale();
+ texture.scale[1] = g_pGameConfig->GetDefaultTextureScale();
+ }
+
+ // Justify top left.
+ JustifyTextureUsingExtents(TEXTURE_JUSTIFY_TOP, Extents);
+ JustifyTextureUsingExtents(TEXTURE_JUSTIFY_LEFT, Extents);
+
+ break;
+ }
+ }
+
+ NormalizeTextureShifts();
+ CalcTextureCoords();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : eJustification -
+//-----------------------------------------------------------------------------
+void CMapFace::JustifyTexture(TextureJustification_t eJustification)
+{
+ Extents_t Extents;
+ GetFaceExtents(Extents);
+ JustifyTextureUsingExtents(eJustification, Extents);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Offsets a texture due to texture locking when moving a face.
+// Input : Delta - The x, y, z translation that was applied to the face points.
+//-----------------------------------------------------------------------------
+void CMapFace::OffsetTexture(const Vector &Delta)
+{
+ //
+ // Find the projection in U/V space of this movement
+ // and shift the textures by that.
+ //
+ texture.UAxis[3] -= DotProduct(Delta, texture.UAxis.AsVector3D()) / texture.scale[0];
+ texture.VAxis[3] -= DotProduct(Delta, texture.VAxis.AsVector3D()) / texture.scale[1];
+
+ NormalizeTextureShifts();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Rotates the texture axes fDegrees counterclockwise around the
+// texture normal axis.
+// Input : fDegrees - Degrees to rotate the texture axes.
+//-----------------------------------------------------------------------------
+void CMapFace::RotateTextureAxes(float fDegrees)
+{
+ VMatrix Matrix;
+ Vector TexNormalAxis;
+ Vector4D UAxis;
+ Vector4D VAxis;
+
+ // Generate the texture normal axis, which may be different from the
+ // face normal, depending on texture alignment.
+ CrossProduct(texture.VAxis.AsVector3D(), texture.UAxis.AsVector3D(), TexNormalAxis);
+
+ // Rotate the texture axes around the texture normal.
+ AxisAngleMatrix(Matrix, TexNormalAxis, fDegrees);
+
+ Matrix.V4Mul( texture.UAxis, UAxis );
+ Matrix.V4Mul( texture.VAxis, VAxis );
+
+ texture.UAxis = UAxis;
+ texture.VAxis = VAxis;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Rebuilds the plane normal and distance from the plane points.
+//-----------------------------------------------------------------------------
+void CMapFace::CalcPlane(void)
+{
+ //
+ // Build the plane normal and distance from the three plane points.
+ //
+ plane.normal = GetNormalFromPoints( plane.planepts[0], plane.planepts[1], plane.planepts[2] );
+ plane.dist = DotProduct(plane.planepts[0], plane.normal);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Rebuilds the plane points from our face points.
+//-----------------------------------------------------------------------------
+void CMapFace::CalcPlaneFromFacePoints(void)
+{
+ if ((nPoints >= 3) && (Points != NULL))
+ {
+ //
+ // Use the face points as a preliminary set of plane points.
+ //
+ memcpy(plane.planepts, Points, sizeof(Vector) * 3);
+
+ //
+ // Generate the plane normal and distance from the plane points.
+ //
+ CalcPlane();
+
+ //
+ // Now project large coordinates onto the plane to generate new
+ // plane points that will be less prone to error creep.
+ //
+ // UNDONE: push out the points along the plane for better precision
+ }
+}
+
+
+void CMapFace::AddShadowingTriangles( CUtlVector<Vector> &tri_list )
+{
+ // create a fan
+ if (! (m_nFaceFlags & FACE_FLAGS_NOSHADOW ))
+ for(int i=2;i<nPoints;i++)
+ {
+ tri_list.AddToTail( Points[0] );
+ tri_list.AddToTail( Points[i-1] );
+ tri_list.AddToTail( Points[i] );
+ }
+}
+
+#ifdef DEBUGPTS
+void CMapFace::DebugPoints(void)
+{
+ // check for dup points
+ for(i = 0; i < nPoints; i++)
+ {
+ for(int j = 0; j < nPoints; j++)
+ {
+ if(j == i)
+ continue;
+ if(Points[j][0] == Points[i][0] &&
+ Points[j][1] == Points[i][1] &&
+ Points[j][2] == Points[i][2])
+ {
+ AfxMessageBox("Dup Points in CMapFace::Create(winding_t*)");
+ break;
+ }
+ }
+ }
+}
+#endif
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Create the face from a winding type.
+// w - Winding from which to create the face.
+// nFlags -
+// CREATE_FACE_PRESERVE_PLANE:
+// CREATE_FACE_CLIPPING: the new face is a clipped version of this face
+//-----------------------------------------------------------------------------
+void CMapFace::CreateFace(winding_t *w, int nFlags)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ AllocatePoints(w->numpoints);
+ for (int i = 0; i < nPoints; i++)
+ {
+ Points[i][0] = w->p[i][0];
+ Points[i][1] = w->p[i][1];
+ Points[i][2] = w->p[i][2];
+ }
+
+ if (!(nFlags & CREATE_FACE_PRESERVE_PLANE))
+ {
+ CalcPlaneFromFacePoints();
+ }
+
+ //
+ // Create a new displacement surface if the clipped surfaces is a quad.
+ //
+ // This assumes it is being called by the clipper!!! (Bad assumption).
+ //
+ if( HasDisp() && ( nFlags & CREATE_FACE_CLIPPING ) )
+ {
+ if ( nPoints == 4 )
+ {
+ // Setup new displacement surface.
+ EditDispHandle_t hClipDisp = EditDispMgr()->Create();
+ CMapDisp *pClipDisp = EditDispMgr()->GetDisp( hClipDisp );
+
+ // Get older displacement surface.
+ EditDispHandle_t hDisp = GetDisp();
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( hDisp );
+
+ // Init new displacement surface.
+ pClipDisp->SetParent( this );
+
+ // Apply the new displacement to this face, but keep the old one
+ // around -- we need it for the split operation.
+ SetDisp( hClipDisp, false );
+ pClipDisp->InitData( pDisp->GetPower() );
+
+ // Calculate texture coordinates before splitting because we
+ // need the texture coords during the split.
+ CalcTextureCoords();
+
+ // Split the old displacement and put the results into hClipDisp.
+ pDisp->Split( hClipDisp );
+
+ // Delete the old displacement that was on this face.
+ EditDispMgr()->Destroy( hDisp );
+ }
+ else
+ {
+ SetDisp( EDITDISPHANDLE_INVALID );
+ }
+ }
+ else
+ {
+ CalcTextureCoords();
+ }
+#ifdef ENSUREDETAILS
+ // Create any detail objects if appropriate
+ DetailObjects::BuildAnyDetailObjects(this);
+#endif
+
+#ifdef DEBUGPTS
+ DebugPoints();
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Allocates space in Points array for nPoints worth of Vectors and
+// the corresponding texture and lightmap coordinates (Vector2D's). Frees
+// current points if there are any.
+// Input : _nPoints - number of points needed.
+// Output : Total size of memory used by the points, texture, and lightmap coordinates.
+//-----------------------------------------------------------------------------
+size_t CMapFace::AllocatePoints(int _nPoints)
+{
+ //
+ // If we have already allocated this many points, do nothing.
+ //
+ if ((Points != NULL) && (_nPoints == nPoints))
+ {
+ return(nPoints * (sizeof(Vector) + sizeof(Vector2D) + sizeof(Vector2D)));
+ }
+
+ //
+ // If we have the wrong number of points allocated, free the memory.
+ //
+ if (Points != NULL)
+ {
+ delete [] Points;
+ Points = NULL;
+
+ delete [] m_pTextureCoords;
+ m_pTextureCoords = NULL;
+
+ delete [] m_pLightmapCoords;
+ m_pLightmapCoords = NULL;
+ }
+
+ Assert( nPoints == 0 || nPoints > 2 );
+
+ nPoints = _nPoints;
+
+ if (!_nPoints)
+ {
+ return(0);
+ }
+
+ //
+ // Allocate the correct number of points, texture coords, and lightmap coords.
+ //
+ Points = new Vector[nPoints];
+ m_pTextureCoords = new Vector2D[nPoints];
+ m_pLightmapCoords = new Vector2D[nPoints];
+
+ // dvs: check for failure here and report an out of memory error
+ Assert(Points != NULL);
+ Assert(m_pTextureCoords != NULL);
+ Assert(m_pLightmapCoords != NULL);
+
+ return(nPoints * (sizeof(Vector) + sizeof(Vector2D) + sizeof(Vector2D)));
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pTexture -
+//-----------------------------------------------------------------------------
+void CMapFace::SetTexture(IEditorTexture *pTexture, bool bRescaleTextureCoordinates)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ if ( m_pTexture && pTexture && bRescaleTextureCoordinates )
+ {
+ float flXFactor = (float)m_pTexture->GetWidth() / pTexture->GetWidth();
+ float flYFactor = (float)m_pTexture->GetHeight() / pTexture->GetHeight();
+
+ texture.scale[0] *= flXFactor;
+ texture.scale[1] *= flYFactor;
+
+ texture.UAxis[3] /= flXFactor;
+ texture.VAxis[3] /= flYFactor;
+ }
+
+ m_pTexture = pTexture;
+
+ // Copy other things from m_pTexture.
+ m_pTexture->GetShortName(texture.texture);
+ texture.q2surface = m_pTexture->GetSurfaceAttributes();
+ texture.q2contents = m_pTexture->GetSurfaceContents();
+
+ BOOL bTexValid = FALSE;
+ if (m_pTexture != NULL)
+ {
+ // Insure that the texture is loaded.
+ m_pTexture->Load();
+
+ bTexValid = !(
+ m_pTexture->GetWidth() == 0 ||
+ m_pTexture->GetHeight() == 0 ||
+ m_pTexture->GetImageWidth() == 0 ||
+ m_pTexture->GetImageHeight() == 0 ||
+ !m_pTexture->HasData()
+ );
+ }
+
+ if (bTexValid)
+ {
+ CalcTextureCoords();
+ }
+
+ UpdateFaceFlags();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Sets this face's texture by name.
+// Input : pszNewTex - Short name of texture to apply to this face.
+//-----------------------------------------------------------------------------
+void CMapFace::SetTexture(const char *pszNewTex, bool bRescaleTextureCoordinates)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ IEditorTexture *pTexture = g_Textures.FindActiveTexture(pszNewTex);
+ SetTexture(pTexture, bRescaleTextureCoordinates);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapFace::CalcTextureCoordAtPoint( const Vector& pt, Vector2D &texCoord )
+{
+ // sanity check
+ if( m_pTexture == NULL )
+ return;
+
+ //
+ // projected s, t (u, v) texture coordinates
+ //
+ float s = DotProduct( texture.UAxis.AsVector3D(), pt ) / texture.scale[0] + texture.UAxis[3];
+ float t = DotProduct( texture.VAxis.AsVector3D(), pt ) / texture.scale[1] + texture.VAxis[3];
+
+ //
+ // "normalize" the texture coordinates
+ //
+ if (m_pTexture->GetWidth())
+ texCoord[0] = s / ( float )m_pTexture->GetWidth();
+ else
+ texCoord[0] = 0.0;
+
+ if (m_pTexture->GetHeight())
+ texCoord[1] = t / ( float )m_pTexture->GetHeight();
+ else
+ texCoord[1] = 0.0;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapFace::CalcLightmapCoordAtPoint( const Vector& pt, Vector2D &lightCoord )
+{
+ lightCoord[0] = DotProduct( texture.UAxis.AsVector3D(), pt ) / texture.nLightmapScale + 0.5f;
+ lightCoord[1] = DotProduct( texture.VAxis.AsVector3D(), pt ) / texture.nLightmapScale + 0.5f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Calculates the U,V texture coordinates of all points on this face.
+//-----------------------------------------------------------------------------
+void CMapFace::CalcTextureCoords(void)
+{
+ float s, t;
+ int i;
+
+ if (m_pTexture == NULL)
+ {
+ return;
+ }
+
+ //
+ // Make sure that scales are nonzero.
+ //
+ if (texture.scale[0] == 0)
+ {
+ texture.scale[0] = g_pGameConfig->GetDefaultTextureScale();
+ }
+
+ if (texture.scale[1] == 0)
+ {
+ texture.scale[1] = g_pGameConfig->GetDefaultTextureScale();
+ }
+
+ //
+ // Recalculate U,V coordinates for all points.
+ //
+ for (i = 0; i < nPoints; i++)
+ {
+ //
+ // Generate texture coordinates.
+ //
+ s = DotProduct(texture.UAxis.AsVector3D(), Points[i]) / texture.scale[0] + texture.UAxis[3];
+ t = DotProduct(texture.VAxis.AsVector3D(), Points[i]) / texture.scale[1] + texture.VAxis[3];
+
+ if (m_pTexture->GetWidth())
+ m_pTextureCoords[i][0] = s / (float)m_pTexture->GetWidth();
+ else
+ m_pTextureCoords[i][0] = 0.0f;
+
+ if (m_pTexture->GetHeight())
+ m_pTextureCoords[i][1] = t / (float)m_pTexture->GetHeight();
+ else
+ m_pTextureCoords[i][1] = 0.0f;
+
+ //
+ // Generate lightmap coordinates. Lightmap coordinates for displacements happens below.
+ //
+ if ( m_DispHandle == EDITDISPHANDLE_INVALID )
+ {
+ float shiftScaleU = texture.scale[0] / (float)texture.nLightmapScale;
+ float shiftScaleV = texture.scale[1] / (float)texture.nLightmapScale;
+
+ m_pLightmapCoords[i][0] = DotProduct(texture.UAxis.AsVector3D(), Points[i]) / texture.nLightmapScale + texture.UAxis[3] * shiftScaleU + 0.5;
+ m_pLightmapCoords[i][1] = DotProduct(texture.VAxis.AsVector3D(), Points[i]) / texture.nLightmapScale + texture.VAxis[3] * shiftScaleV + 0.5;
+ }
+ }
+
+ //
+ // update the displacement map with new texture coordinates and calculate lightmap coordinates
+ //
+ if( ( m_DispHandle != EDITDISPHANDLE_INVALID ) && nPoints == 4 )
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ pDisp->InitDispSurfaceData( this, false );
+ pDisp->Create();
+ }
+
+ // re-calculate the tangent space
+ CalcTangentSpaceAxes();
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the max lightmap size for this face
+//-----------------------------------------------------------------------------
+int CMapFace::MaxLightmapSize() const
+{
+ return HasDisp() ? MAX_DISP_LIGHTMAP_DIM_WITHOUT_BORDER : MAX_BRUSH_LIGHTMAP_DIM_WITHOUT_BORDER;
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Checks the validity of this face.
+// Input : pInfo -
+// Output : Returns TRUE on success, FALSE on failure.
+//-----------------------------------------------------------------------------
+BOOL CMapFace::CheckFace(CCheckFaceInfo *pInfo)
+{
+ if (!::CheckFace(Points, nPoints, &plane.normal, plane.dist, pInfo))
+ {
+ return(FALSE);
+ }
+
+ //
+ // Check for duplicate plane points. All three plane points must be unique
+ // or it isn't a valid plane.
+ //
+ for (int nPlane = 0; nPlane < 3; nPlane++)
+ {
+ for (int nPlaneCheck = 0; nPlaneCheck < 3; nPlaneCheck++)
+ {
+ if (nPlane != nPlaneCheck)
+ {
+ if (VectorCompare(plane.planepts[nPlane], plane.planepts[nPlaneCheck]))
+ {
+ if (pInfo != NULL)
+ {
+ strcpy(pInfo->szDescription, "face has duplicate plane points");
+ }
+ return(FALSE);
+ }
+ }
+ }
+ }
+
+ return(TRUE);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Included for loading old (quake-style) maps. This sets up the texture axes
+// the same way QCSG and pre-2.2 Hammer did.
+// Input : UAxis -
+// VAxis -
+//-----------------------------------------------------------------------------
+void CMapFace::InitializeQuakeStyleTextureAxes(Vector4D& UAxis, Vector4D& VAxis)
+{
+ static Vector baseaxis[18] =
+ {
+ Vector(0,0,1), Vector(1,0,0), Vector(0,-1,0), // floor
+ Vector(0,0,-1), Vector(1,0,0), Vector(0,-1,0), // ceiling
+ Vector(1,0,0), Vector(0,1,0), Vector(0,0,-1), // west wall
+ Vector(-1,0,0), Vector(0,1,0), Vector(0,0,-1), // east wall
+ Vector(0,1,0), Vector(1,0,0), Vector(0,0,-1), // south wall
+ Vector(0,-1,0), Vector(1,0,0), Vector(0,0,-1) // north wall
+ };
+
+ int bestaxis;
+ vec_t dot,best;
+ int i;
+
+ best = 0;
+ bestaxis = 0;
+
+ for (i=0 ; i<6 ; i++)
+ {
+ dot = DotProduct(plane.normal, baseaxis[i*3]);
+ if (dot > best)
+ {
+ best = dot;
+ bestaxis = i;
+ }
+ }
+
+ UAxis.AsVector3D() = baseaxis[bestaxis * 3 + 1];
+ VAxis.AsVector3D() = baseaxis[bestaxis * 3 + 2];
+}
+
+
+//-----------------------------------------------------------------------------
+// Should we render this lit or not
+//-----------------------------------------------------------------------------
+void CMapFace::RenderUnlit( bool enable )
+{
+ m_bIgnoreLighting = enable;
+}
+
+
+
+inline void Modulate( Color &pColor, float f )
+{
+ pColor[0] *= f;
+ pColor[1] *= f;
+ pColor[2] *= f;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the color and texture to use
+//-----------------------------------------------------------------------------
+
+void CMapFace::ComputeColor( CRender3D* pRender, bool bRenderAsSelected,
+ SelectionState_t faceSelectionState,
+ bool ignoreLighting, Color &pColor )
+{
+ EditorRenderMode_t eCurrentRenderMode = pRender->GetCurrentRenderMode();
+
+ // White w/alpha by default
+ pColor[0] = pColor[1] = pColor[2] = 255;
+ pColor[3] = m_uchAlpha;
+
+ float fShade;
+ if (!ignoreLighting)
+ fShade = pRender->LightPlane(plane.normal);
+ else
+ fShade = 1.0;
+
+ switch (eCurrentRenderMode)
+ {
+ case RENDER_MODE_TEXTURED:
+ case RENDER_MODE_TEXTURED_SHADED:
+ case RENDER_MODE_LIGHT_PREVIEW2:
+ case RENDER_MODE_LIGHT_PREVIEW_RAYTRACED:
+ Modulate( pColor, fShade );
+ break;
+
+ case RENDER_MODE_SELECTION_OVERLAY:
+ if( faceSelectionState == SELECT_MULTI_PARTIAL )
+ {
+ pColor[2] = 100;
+ pColor[3] = 64;
+ }
+ else if( ( faceSelectionState == SELECT_NORMAL ) || bRenderAsSelected )
+ {
+ SelectFaceColor( pColor );
+ pColor[3] = 64;
+ }
+ break;
+
+ case RENDER_MODE_LIGHTMAP_GRID:
+ if (bRenderAsSelected)
+ {
+ SelectFaceColor( pColor );
+ }
+ else if (texture.nLightmapScale > DEFAULT_LIGHTMAP_SCALE)
+ {
+ pColor[0] = 150;
+ }
+ else if (texture.nLightmapScale < DEFAULT_LIGHTMAP_SCALE)
+ {
+ pColor[2] = 100;
+ }
+
+ Modulate( pColor, fShade );
+ break;
+
+ case RENDER_MODE_TRANSLUCENT_FLAT:
+ case RENDER_MODE_FLAT:
+ if (bRenderAsSelected)
+ SelectFaceColor( pColor );
+ else
+ pColor.SetColor( r,g,b,m_uchAlpha );
+
+ Modulate( pColor, fShade );
+ break;
+
+ case RENDER_MODE_WIREFRAME:
+ if (bRenderAsSelected)
+ SelectEdgeColor( pColor );
+ else
+ pColor.SetColor( r,g,b,m_uchAlpha );
+
+ break;
+
+ case RENDER_MODE_SMOOTHING_GROUP:
+ {
+ // Render the non-smoothing group faces in white, yellow for the others.
+ CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
+ if ( pDoc )
+ {
+ int iGroup = pDoc->GetSmoothingGroupVisual();
+ if ( InSmoothingGroup( iGroup ) )
+ {
+ pColor[2] = 0;
+ }
+ }
+
+ Modulate( pColor, fShade );
+ break;
+ }
+
+ default:
+ assert(0);
+ break;
+ }
+}
+
+
+static bool ModeUsesTextureCoords(EditorRenderMode_t mode)
+{
+ return (
+ (mode == RENDER_MODE_TEXTURED) ||
+ (mode == RENDER_MODE_LIGHTMAP_GRID) ||
+ (mode == RENDER_MODE_TEXTURED_SHADED) ||
+ (mode == RENDER_MODE_LIGHT_PREVIEW2) ||
+ (mode == RENDER_MODE_LIGHT_PREVIEW_RAYTRACED)
+ );
+}
+
+//-----------------------------------------------------------------------------
+// Draws the face using the material system material
+//-----------------------------------------------------------------------------
+void CMapFace::DrawFace( Color &pColor, EditorRenderMode_t mode )
+{
+ // retrieve the coordinate frame to render into
+ // (most likely just the identity, unless we're animating)
+ VMatrix frame;
+ bool hasParent = GetTransformMatrix( frame );
+
+ // don't do this -- if you use the material system to rotate and/or translate
+ // this will cull the locally spaced object!! -- need to pass around a flag!
+#if 0
+ // A little culling....
+ float fEyeDot = DotProduct(plane.normal, ViewPoint);
+ if ((fEyeDot < plane.dist) && (mode != RENDER_MODE_WIREFRAME) && !hasParent &&
+ (m_uchAlpha == 255))
+ {
+ return;
+ }
+#endif
+
+
+ // don't draw no draws in ray tracced mode
+ if ( mode == RENDER_MODE_LIGHT_PREVIEW_RAYTRACED )
+ {
+ if ( m_nFaceFlags & FACE_FLAGS_NODRAW_IN_LPREVIEW )
+ return;
+ }
+
+
+ MaterialPrimitiveType_t type = (mode == RENDER_MODE_WIREFRAME) ?
+ MATERIAL_LINE_LOOP : MATERIAL_POLYGON;
+
+ CMeshBuilder meshBuilder;
+ CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+ meshBuilder.Begin( pMesh, type, nPoints );
+
+ for (int nPoint = 0; nPoint < nPoints; nPoint++)
+ {
+ if (ModeUsesTextureCoords(mode))
+ {
+ meshBuilder.TexCoord2f( 0, m_pTextureCoords[nPoint][0], m_pTextureCoords[nPoint][1] );
+ meshBuilder.TexCoord2f( 1, m_pLightmapCoords[nPoint][0], m_pLightmapCoords[nPoint][1]);
+ }
+
+ meshBuilder.Color4ubv( (byte*)&pColor );
+
+ // transform into absolute space
+ if ( hasParent )
+ {
+ Vector point;
+ VectorTransform( Points[nPoint], frame.As3x4(), point );
+ meshBuilder.Position3f(point[0], point[1], point[2]);
+ }
+ else
+ {
+ meshBuilder.Position3f(Points[nPoint][0], Points[nPoint][1], Points[nPoint][2]);
+ }
+
+ // FIXME: no smoothing group information
+ meshBuilder.Normal3fv(plane.normal.Base());
+ meshBuilder.TangentS3fv( m_pTangentAxes[nPoint].tangent.Base() );
+ meshBuilder.TangentT3fv( m_pTangentAxes[nPoint].binormal.Base() );
+
+ meshBuilder.AdvanceVertex();
+ }
+
+ meshBuilder.End();
+ pMesh->Draw();
+}
+
+
+//-----------------------------------------------------------------------------
+// Renders the grid on the face
+//-----------------------------------------------------------------------------
+void CMapFace::RenderGridIfCloseEnough( CRender3D* pRender )
+{
+ CMapFace *pThis = this;
+ RenderGridsIfCloseEnough( pRender, 1, &pThis );
+}
+
+
+//-----------------------------------------------------------------------------
+// renders the texture axes
+//-----------------------------------------------------------------------------
+void CMapFace::RenderTextureAxes( CRender3D* pRender )
+{
+ CMapFace *pThis = this;
+ RenderTextureAxes( pRender, 1, &pThis );
+}
+
+
+//-----------------------------------------------------------------------------
+// for sorting
+//-----------------------------------------------------------------------------
+bool CMapFace::ShouldRenderLast()
+{
+ if (!m_pTexture || !m_pTexture->GetMaterial())
+ return false;
+
+ return m_pTexture->GetMaterial()->IsTranslucent() || (m_uchAlpha != 255) ;
+}
+
+//-----------------------------------------------------------------------------
+// render texture axes
+//-----------------------------------------------------------------------------
+void CMapFace::RenderTextureAxes( CRender3D* pRender, int nCount, CMapFace **ppFaces )
+{
+ // Render the world axes.
+ pRender->PushRenderMode( RENDER_MODE_WIREFRAME );
+
+ CMeshBuilder meshBuilder;
+ CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+ meshBuilder.Begin( pMesh, MATERIAL_LINES, 2 * nCount );
+
+ Vector Center;
+ for ( int i = 0; i < nCount; ++i )
+ {
+ ppFaces[i]->GetCenter(Center);
+
+ meshBuilder.Color3ub(255, 255, 0);
+ meshBuilder.Position3f(Center[0], Center[1], Center[2]);
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Color3ub(255, 255, 0);
+ meshBuilder.Position3f(Center[0] + ppFaces[i]->texture.UAxis[0] * TEXTURE_AXIS_LENGTH,
+ Center[1] + ppFaces[i]->texture.UAxis[1] * TEXTURE_AXIS_LENGTH,
+ Center[2] + ppFaces[i]->texture.UAxis[2] * TEXTURE_AXIS_LENGTH);
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Color3ub(0, 255, 0);
+ meshBuilder.Position3f(Center[0], Center[1], Center[2]);
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.Color3ub(0, 255, 0);
+ meshBuilder.Position3f(Center[0] + ppFaces[i]->texture.VAxis[0] * TEXTURE_AXIS_LENGTH,
+ Center[1] + ppFaces[i]->texture.VAxis[1] * TEXTURE_AXIS_LENGTH,
+ Center[2] + ppFaces[i]->texture.VAxis[2] * TEXTURE_AXIS_LENGTH);
+ meshBuilder.AdvanceVertex();
+ }
+
+ meshBuilder.End();
+ pMesh->Draw();
+
+ pRender->PopRenderMode();
+}
+
+
+//-----------------------------------------------------------------------------
+// Render grids
+//-----------------------------------------------------------------------------
+void CMapFace::Render3DGrids( CRender3D *pRender, int nCount, CMapFace **ppFaces )
+{
+ // FIXME: Optimize this to render all of them in a single call
+ for ( int i = 0; i < nCount; ++i )
+ {
+ ppFaces[i]->Render3DGrid( pRender );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Render grids
+//-----------------------------------------------------------------------------
+void CMapFace::RenderGridsIfCloseEnough( CRender3D* pRender, int nCount, CMapFace **ppFaces )
+{
+ // If the 3D grid is enabled and we aren't picking,
+ // render the grid on this face.
+ if ( (!pRender->IsEnabled(RENDER_GRID)) || pRender->IsPicking() )
+ return;
+
+ Vector Maxs;
+ Vector Mins;
+ float fGridSize = pRender->GetGridDistance();
+
+ Vector viewPoint; pRender->GetCamera()->GetViewPoint( viewPoint );
+
+ CMapFace **ppFinalList = (CMapFace**)_alloca( nCount * sizeof(CMapFace*) );
+ int nFinalCount = 0;
+ for ( int i = 0; i < nCount; ++i )
+ {
+ ppFaces[i]->GetFaceBounds(Mins, Maxs);
+
+ for ( int j = 0; j < 3; j++)
+ {
+ Mins[j] -= fGridSize;
+ Maxs[j] += fGridSize;
+ }
+
+ // Only render the grid if the face is close enough to the camera.
+ if ( IsPointInBox(viewPoint, Mins, Maxs) )
+ {
+ ppFinalList[nFinalCount++] = ppFaces[i];
+ }
+ }
+
+ Render3DGrids( pRender, nFinalCount, ppFinalList );
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds a face's vertices to the meshbuilder
+//-----------------------------------------------------------------------------
+void CMapFace::AddFaceVertices( CMeshBuilder &meshBuilder, CRender3D* pRender, bool bRenderSelected, SelectionState_t faceSelectionState)
+{
+ Vector point;
+ VMatrix frame;
+ Color color;
+
+ bool bHasParent = GetTransformMatrix( frame );
+ ComputeColor( pRender, bRenderSelected, faceSelectionState, m_bIgnoreLighting, color );
+
+ for ( int nPoint = 0; nPoint < nPoints; nPoint++ )
+ {
+ if ( bHasParent )
+ {
+ // transform into absolute space
+ VectorTransform( Points[nPoint], frame.As3x4(), point );
+ meshBuilder.Position3fv( point.Base() );
+ }
+ else
+ {
+ meshBuilder.Position3fv( Points[nPoint].Base() );
+ }
+
+ meshBuilder.Normal3fv( plane.normal.Base() );
+ meshBuilder.Color4ubv( (byte*)&color );
+
+ meshBuilder.TexCoord2fv( 0, m_pTextureCoords[nPoint].Base() );
+ meshBuilder.TexCoord2fv( 1, m_pLightmapCoords[nPoint].Base() );
+ meshBuilder.TangentS3fv( m_pTangentAxes[nPoint].tangent.Base() );
+ meshBuilder.TangentT3fv( m_pTangentAxes[nPoint].binormal.Base() );
+
+ meshBuilder.AdvanceVertex();
+ }
+}
+
+
+struct MapFaceRender_t
+{
+ bool m_RenderSelected;
+ EditorRenderMode_t m_RenderMode;
+ IEditorTexture* m_pTexture;
+ CMapFace* m_pMapFace;
+ SelectionState_t m_FaceSelectionState;
+};
+
+typedef CUtlRBTree<MapFaceRender_t, int> FaceQueue_t;
+
+
+//-----------------------------------------------------------------------------
+// draws a list of faces in wireframe
+//-----------------------------------------------------------------------------
+void CMapFace::RenderWireframeFaces( CRender3D* pRender, int nCount, MapFaceRender_t **ppFaces )
+{
+
+ // Draw the texture axes
+ int nAxesCount = 0;
+ CMapFace **ppAxesFaces = (CMapFace**)_alloca( nCount * sizeof(CMapFace*) );
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( ppFaces[i]->m_FaceSelectionState != SELECT_NONE )
+ {
+ ppAxesFaces[ nAxesCount++ ] = ppFaces[i]->m_pMapFace;
+ }
+ }
+
+ if ( nAxesCount != 0 )
+ {
+ RenderTextureAxes( pRender, nAxesCount, ppAxesFaces );
+ }
+
+ if ( pRender->IsEnabled(RENDER_GRID) )
+ {
+ // Draw the grid
+ CMapFace **ppGridFaces = (CMapFace**)_alloca( nCount * sizeof(CMapFace*) );
+ for ( int i = 0; i < nCount; ++i )
+ {
+ ppGridFaces[i] = ppFaces[i]->m_pMapFace;
+ }
+
+ RenderGridsIfCloseEnough( pRender, nCount, ppGridFaces );
+ }
+
+}
+
+//-----------------------------------------------------------------------------
+// Draws a batch of faces.
+//-----------------------------------------------------------------------------
+void CMapFace::RenderFacesBatch( CMeshBuilder &meshBuilder, IMesh* pMesh, CRender3D* pRender, MapFaceRender_t **ppFaces, int nFaceCount, int nVertexCount, int nIndexCount, bool bWireframe )
+{
+ if ( bWireframe )
+ {
+ meshBuilder.Begin( pMesh, MATERIAL_LINES, nVertexCount, nIndexCount );
+ }
+ else
+ {
+ meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nVertexCount, nIndexCount );
+ }
+
+ int nFirstVertex = 0;
+
+ for ( int i = 0; i < nFaceCount; ++i )
+ {
+ CMapFace *pMapFace = ppFaces[i]->m_pMapFace;
+
+ pMapFace->AddFaceVertices( meshBuilder, pRender, ppFaces[i]->m_RenderSelected, ppFaces[i]->m_FaceSelectionState );
+
+ int nPoints = pMapFace->GetPointCount();
+
+ if ( bWireframe )
+ {
+ meshBuilder.FastIndex( nFirstVertex );
+ for ( int j = 1; j < nPoints; ++j )
+ {
+ meshBuilder.FastIndex( nFirstVertex + j );
+ meshBuilder.FastIndex( nFirstVertex + j );
+ }
+ meshBuilder.FastIndex( nFirstVertex );
+ }
+ else
+ {
+ for ( int j = 2; j < nPoints; ++j )
+ {
+ meshBuilder.FastIndex( nFirstVertex );
+ meshBuilder.FastIndex( nFirstVertex + j - 1 );
+ meshBuilder.FastIndex( nFirstVertex + j );
+ }
+ }
+
+ nFirstVertex += nPoints;
+ }
+
+ meshBuilder.End();
+ pMesh->Draw();
+}
+
+
+//-----------------------------------------------------------------------------
+// Draws a list of faces, breaking them up into batches if necessary.
+//-----------------------------------------------------------------------------
+void CMapFace::RenderFaces( CRender3D* pRender, int nCount, MapFaceRender_t **ppFaces )
+{
+ if ( nCount == 0 )
+ return;
+
+ bool bWireframe = ppFaces[0]->m_RenderMode == RENDER_MODE_WIREFRAME;
+
+ if ( RenderingModeIsTextured(ppFaces[0]->m_RenderMode))
+ {
+ pRender->BindTexture( ppFaces[0]->m_pTexture );
+ }
+
+ pRender->PushRenderMode( ppFaces[0]->m_RenderMode );
+
+ int nBatchStart = 0;
+ int nIndexCount = 0;
+ int nVertexCount = 0;
+
+ int nMaxVerts, nMaxIndices;
+
+ CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+
+ pRenderContext->GetMaxToRender( pMesh, true, &nMaxVerts, &nMaxIndices );
+
+ // Make sure we have enough for at least one triangle...
+ int nMinVerts = ppFaces[0]->m_pMapFace->GetPointCount();
+ int nMinIndices = max( nMinVerts*2, (nMinVerts-2)*3 );
+ if ( nMaxVerts < nMinVerts || nMaxIndices < nMinIndices )
+ {
+ pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices );
+ }
+
+ CMeshBuilder meshBuilder;
+ for ( int nFace = 0; nFace < nCount; nFace++ )
+ {
+ Assert( ppFaces[nFace]->m_RenderMode == ppFaces[0]->m_RenderMode );
+ Assert( ppFaces[nFace]->m_pTexture == ppFaces[0]->m_pTexture );
+
+ int newIndices, newVertices = ppFaces[nFace]->m_pMapFace->GetPointCount();
+
+ if( bWireframe )
+ {
+ newIndices = newVertices*2;
+ }
+ else
+ {
+ newIndices = (newVertices-2) * 3;
+ }
+
+ if ( ( ( nVertexCount + newVertices ) > nMaxVerts ) || ( ( nIndexCount + newIndices ) > nMaxIndices ) )
+ {
+ // If we hit this assert, there's a single face that's too big for the meshbuilder to handle!
+ Assert( ( nFace - nBatchStart ) > 0 );
+
+ // We have a full batch, render it.
+
+ RenderFacesBatch( meshBuilder, pMesh, pRender, &ppFaces[nBatchStart], nFace - nBatchStart, nVertexCount, nIndexCount, bWireframe );
+
+ pRenderContext->GetMaxToRender( pMesh, false, &nMaxVerts, &nMaxIndices );
+
+ nBatchStart = nFace;
+ nVertexCount = 0;
+ nIndexCount = 0;
+ }
+
+ nVertexCount += newVertices;
+ nIndexCount += newIndices;
+ }
+
+ // Render whatever is left over.
+ RenderFacesBatch( meshBuilder, pMesh, pRender, &ppFaces[nBatchStart], nCount - nBatchStart, nVertexCount, nIndexCount, bWireframe );
+
+ //render additional wireframe stuff
+ if ( bWireframe )
+ {
+ RenderWireframeFaces( pRender, nCount, ppFaces );
+ }
+
+ pRender->PopRenderMode();
+}
+
+
+//-----------------------------------------------------------------------------
+// draws a single face (displacement or normal)
+//-----------------------------------------------------------------------------
+void CMapFace::RenderFace3D( CRender3D* pRender, EditorRenderMode_t renderMode, bool renderSelected, SelectionState_t faceSelectionState )
+{
+ pRender->PushRenderMode( renderMode );
+
+ if ( HasDisp() && CMapDoc::GetActiveMapDoc() && CMapDoc::GetActiveMapDoc()->IsDispDraw3D() )
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ pDisp->Render3D( pRender, renderSelected, faceSelectionState );
+ }
+ else
+ {
+ Color color;
+ ComputeColor( pRender, renderSelected, faceSelectionState, m_bIgnoreLighting, color );
+ DrawFace( color, renderMode );
+ }
+
+ // Draw the texture axes
+ if( renderMode == RENDER_MODE_WIREFRAME )
+ {
+ if (faceSelectionState != SELECT_NONE)
+ RenderTextureAxes(pRender);
+
+ // Draw the grid
+ RenderGridIfCloseEnough( pRender );
+ }
+ else if ( m_pDetailObjects && Options.general.bShowDetailObjects )
+ {
+
+ // Only draw the detailed objects if the displacement/face is not currently selected.
+ pRender->AddTranslucentDeferredRendering( m_pDetailObjects );
+ }
+
+ pRender->PopRenderMode();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : mode -
+//-----------------------------------------------------------------------------
+static int SortVal(EditorRenderMode_t mode)
+{
+ if (mode == RENDER_MODE_WIREFRAME)
+ return 2;
+ if ( mode == RENDER_MODE_SELECTION_OVERLAY )
+ return 1;
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : s1 -
+// s2 -
+// Output :
+//-----------------------------------------------------------------------------
+static bool OpaqueFacesLessFunc( const MapFaceRender_t &s1, const MapFaceRender_t &s2 )
+{
+ // Render texture first, overlay second, wireframe 3rd
+ int nSort1 = SortVal(s1.m_RenderMode);
+ int nSort2 = SortVal(s2.m_RenderMode);
+ if (nSort1 < nSort2)
+ return true;
+ if (nSort1 > nSort2)
+ return false;
+
+ return s1.m_pTexture < s2.m_pTexture;
+}
+
+
+static FaceQueue_t g_OpaqueFaces(0, 0, OpaqueFacesLessFunc);
+static CUtlVector< FaceQueue_t * > g_OpaqueInstanceFaces;
+static FaceQueue_t *g_CurrentOpaqueFaces = &g_OpaqueFaces;
+
+
+//-----------------------------------------------------------------------------
+// Purpose: this function will add the face to the sorted current queue
+// Input : pMapFace - the face to be added
+// pTexture - the texture of the face
+// renderMode - what type of rendering mode
+// selected - if it is selected or not ( selected appears on top )
+// faceSelectionState - if the face is individual selected
+//-----------------------------------------------------------------------------
+void CMapFace::AddFaceToQueue( CMapFace* pMapFace, IEditorTexture* pTexture, EditorRenderMode_t renderMode, bool selected, SelectionState_t faceSelectionState )
+{
+ MapFaceRender_t newEntry;
+ newEntry.m_RenderMode = renderMode;
+ newEntry.m_pTexture = pTexture;
+ newEntry.m_RenderSelected = selected;
+ newEntry.m_pMapFace = pMapFace;
+ newEntry.m_FaceSelectionState = faceSelectionState;
+ g_CurrentOpaqueFaces->Insert( newEntry );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: this function will add a new face queue to the top of the list and
+// make it active
+//-----------------------------------------------------------------------------
+void CMapFace::PushFaceQueue( void )
+{
+ g_OpaqueInstanceFaces.AddToHead( new FaceQueue_t( 0, 0, OpaqueFacesLessFunc ) );
+
+ g_CurrentOpaqueFaces = g_OpaqueInstanceFaces.Head();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: this function will pop the top face queue off the list
+//-----------------------------------------------------------------------------
+void CMapFace::PopFaceQueue( void )
+{
+ Assert( g_OpaqueInstanceFaces.Count() > 0 );
+
+ FaceQueue_t *pHead = g_OpaqueInstanceFaces.Head();
+
+ g_OpaqueInstanceFaces.Remove( 0 );
+ delete pHead;
+
+ if ( g_OpaqueInstanceFaces.Count() )
+ {
+ g_CurrentOpaqueFaces = g_OpaqueInstanceFaces.Head();
+ }
+ else
+ {
+ g_CurrentOpaqueFaces = &g_OpaqueFaces;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// renders queued up opaque faces, sorted by material
+//-----------------------------------------------------------------------------
+void CMapFace::RenderOpaqueFaces( CRender3D* pRender )
+{
+ CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
+
+ MapFaceRender_t **ppMapFaces = (MapFaceRender_t**)_alloca( g_CurrentOpaqueFaces->Count() * sizeof( MapFaceRender_t* ) );
+ int nFaceCount = 0;
+
+ int nLastRenderMode = RENDER_MODE_NONE;
+ IEditorTexture *pLastTexture = NULL;
+
+ for ( int i = g_CurrentOpaqueFaces->FirstInorder(); i != g_CurrentOpaqueFaces->InvalidIndex(); i = g_CurrentOpaqueFaces->NextInorder(i) )
+ {
+ MapFaceRender_t& mapFace = ( *g_CurrentOpaqueFaces)[i];
+
+ if ( ( mapFace.m_RenderMode != nLastRenderMode ) || ( mapFace.m_pTexture != pLastTexture ) )
+ {
+ RenderFaces( pRender, nFaceCount, ppMapFaces );
+ nFaceCount = 0;
+ }
+
+ if ( mapFace.m_pMapFace->HasDisp() )
+ {
+ if ( RenderingModeIsTextured( mapFace.m_RenderMode ))
+ {
+ pRender->BindTexture( mapFace.m_pTexture );
+ }
+
+ mapFace.m_pMapFace->RenderFace3D( pRender, mapFace.m_RenderMode, mapFace.m_RenderSelected, mapFace.m_FaceSelectionState );
+ }
+ else
+ {
+ ppMapFaces[ nFaceCount++ ] = &mapFace;
+ nLastRenderMode = mapFace.m_RenderMode;
+ pLastTexture = mapFace.m_pTexture;
+ }
+ }
+
+ RenderFaces( pRender, nFaceCount, ppMapFaces );
+
+ g_CurrentOpaqueFaces->RemoveAll();
+}
+
+
+void CMapFace::Render2D(CRender2D *pRender)
+{
+ SelectionState_t eFaceSelectionState = GetSelectionState();
+ SelectionState_t eSolidSelectionState;
+ if (m_pParent != NULL)
+ {
+ eSolidSelectionState = m_pParent->GetSelectionState();
+ }
+ else
+ {
+ eSolidSelectionState = eFaceSelectionState;
+ }
+
+ bool bRenderSelected = ( eSolidSelectionState != SELECT_NONE );
+ bRenderSelected = bRenderSelected || ( ( eFaceSelectionState != SELECT_NONE ) && (CMapFace::m_bShowFaceSelection) );
+
+ Vector vViewNormal; pRender->GetCamera()->GetViewForward( vViewNormal );
+ Vector vNormal; GetFaceNormal( vNormal );
+
+ // if face is parallel to view axis, skip it
+ bool bIsParallel = ( fabs( vViewNormal.Dot( vNormal) ) < 0.0001f );
+
+ if ( HasDisp() && ( bIsParallel || bRenderSelected ) )
+ {
+ Vector mins,maxs;
+
+ GetRender2DBox( mins,maxs );
+
+ Vector2D pt,pt2;
+ pRender->TransformPoint(pt, mins );
+ pRender->TransformPoint(pt2, maxs );
+
+ int sizeX = abs(pt2.x-pt.x);
+ int sizeY = abs(pt2.y-pt.y);
+
+ bool bDrawDispMap = Options.view2d.bDrawModels && ( (sizeX+sizeY) > 50 || bRenderSelected );
+
+ if ( bDrawDispMap )
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ pDisp->Render2D( pRender, bRenderSelected, eFaceSelectionState );
+ return;
+ }
+ }
+
+ if ( !bIsParallel )
+ {
+ pRender->DrawPolyLine( nPoints, Points );
+ }
+}
+
+void CMapFace::RenderVertices(CRender *pRender)
+{
+ for ( int i=0; i< nPoints;i++ )
+ pRender->DrawHandle( Points[i] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Renders this face using the given 3D renderer.
+// Input : pRender - Renderer to draw with.
+//-----------------------------------------------------------------------------
+void CMapFace::Render3D( CRender3D *pRender )
+{
+ if (nPoints == 0)
+ {
+ return;
+ }
+
+ //
+ // Skip back faces unless rendering in wireframe.
+ //
+ EditorRenderMode_t eCurrentRenderMode = pRender->GetCurrentRenderMode();
+
+ if ( eCurrentRenderMode == RENDER_MODE_LIGHT_PREVIEW_RAYTRACED )
+ {
+ if ( m_nFaceFlags & FACE_FLAGS_NODRAW_IN_LPREVIEW )
+ return;
+ }
+
+ SelectionState_t eFaceSelectionState = GetSelectionState();
+ SelectionState_t eSolidSelectionState;
+ if (m_pParent != NULL)
+ {
+ eSolidSelectionState = m_pParent->GetSelectionState();
+ }
+ else
+ {
+ eSolidSelectionState = eFaceSelectionState;
+ }
+
+ if ( !Options.general.bShowNoDrawBrushes && eSolidSelectionState == SELECT_NONE && m_pTexture == g_Textures.GetNoDrawTexture() )
+ return;
+
+ //
+ // Draw the face.
+ //
+ bool renderSelected = ( ( eSolidSelectionState != SELECT_NONE ) );
+ renderSelected = renderSelected || ( ( eFaceSelectionState != SELECT_NONE ) && (CMapFace::m_bShowFaceSelection) );
+
+ if (pRender->DeferRendering())
+ {
+ AddFaceToQueue( this, m_pTexture, eCurrentRenderMode, renderSelected, eFaceSelectionState );
+ if ( ( renderSelected && pRender->NeedsOverlay() ) )
+ {
+ AddFaceToQueue( this, m_pTexture, RENDER_MODE_SELECTION_OVERLAY, renderSelected, eFaceSelectionState );
+ }
+
+ }
+ else
+ {
+ // Set up the texture to use
+ pRender->BindTexture( m_pTexture );
+
+ RenderFace3D( pRender, eCurrentRenderMode, renderSelected, eFaceSelectionState );
+ if ( ( renderSelected && pRender->NeedsOverlay() ) )
+ {
+ RenderFace3D( pRender, RENDER_MODE_SELECTION_OVERLAY, renderSelected, eFaceSelectionState );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Renders the world grid projected onto the given face.
+// Input : pFace - The face onto which the grid will be projected.
+//-----------------------------------------------------------------------------
+void CMapFace::Render3DGrid(CRender3D *pRender)
+{
+ //
+ // Determine the extents of this face.
+ //
+ Extents_t Extents;
+ float fDelta[3];
+ float fGridSpacing = pRender->GetGridSize();
+
+ GetFaceExtents(Extents);
+
+ fDelta[0] = Extents[EXTENTS_XMAX][0] - Extents[EXTENTS_XMIN][0];
+ fDelta[1] = Extents[EXTENTS_YMAX][1] - Extents[EXTENTS_YMIN][1];
+ fDelta[2] = Extents[EXTENTS_ZMAX][2] - Extents[EXTENTS_ZMIN][2];
+
+ //
+ // Render the grid lines with wireframe material.
+ //
+ pRender->PushRenderMode( RENDER_MODE_WIREFRAME );
+
+ CMeshBuilder meshBuilder;
+ CMatRenderContextPtr pRenderContext( MaterialSystemInterface() );
+ IMesh *pMesh = pRenderContext->GetDynamicMesh();
+
+ //
+ // For every dimension in which this face has a nonzero projection.
+ //
+ for (int nDim = 0; nDim < 3; nDim++)
+ {
+ if (fDelta[nDim] != 0)
+ {
+ Vector Normal;
+
+ Normal[0] = (float)((nDim % 3) == 0);
+ Normal[1] = (float)((nDim % 3) == 1);
+ Normal[2] = (float)((nDim % 3) == 2);
+
+ float fMin = Extents[nDim * 2][nDim];
+ float fMax = Extents[(nDim * 2) + 1][nDim];
+
+ float fStart = (float)(floor(fMin / fGridSpacing) * fGridSpacing);
+ float fEnd = (float)(ceil(fMax / fGridSpacing) * fGridSpacing);
+
+ float fGridPoint = fStart;
+
+ while (fGridPoint < fEnd)
+ {
+ int nPointsFound = 0;
+
+ //
+ // For every edge.
+ //
+ for (int nPoint = 0; nPoint < nPoints; nPoint++)
+ {
+ Vector PointFound[2];
+
+ //
+ // Get the start and end points of the edge.
+ //
+ Vector Point1 = Points[nPoint];
+
+ Vector Point2;
+ if (nPoint < nPoints - 1)
+ {
+ Point2 = Points[nPoint + 1];
+ }
+ else
+ {
+ Point2 = Points[0];
+ }
+
+ //
+ // If there is a projection of the normal vector along this edge.
+ //
+ if (Point2[nDim] != Point1[nDim])
+ {
+ //
+ // Solve for the point along this edge that intersects the grid line
+ // as a parameter from zero to one.
+ //
+ float fScale = (fGridPoint - Point1[nDim]) / (Point2[nDim] - Point1[nDim]);
+ if ((fScale >= 0) && (fScale <= 1))
+ {
+ PointFound[nPointsFound][0] = Point1[0] + (Point2[0] - Point1[0]) * fScale;
+ PointFound[nPointsFound][1] = Point1[1] + (Point2[1] - Point1[1]) * fScale;
+ PointFound[nPointsFound][2] = Point1[2] + (Point2[2] - Point1[2]) * fScale;
+
+ nPointsFound++;
+
+ if (nPointsFound == 2)
+ {
+ Vector RenderPoint;
+
+ meshBuilder.Begin( pMesh, MATERIAL_LINES, 1 );
+
+ VectorMA(PointFound[0], 0.2, plane.normal, RenderPoint);
+ meshBuilder.Position3f(RenderPoint[0], RenderPoint[1], RenderPoint[2]);
+ meshBuilder.Color3ub(Normal[0] * 255, Normal[1] * 255, Normal[2] * 255);
+ meshBuilder.AdvanceVertex();
+
+ VectorMA(PointFound[1], 0.2, plane.normal, RenderPoint);
+ meshBuilder.Position3f(RenderPoint[0], RenderPoint[1], RenderPoint[2]);
+ meshBuilder.Color3ub(Normal[0] * 255, Normal[1] * 255, Normal[2] * 255);
+ meshBuilder.AdvanceVertex();
+
+ meshBuilder.End();
+ pMesh->Draw();
+
+ nPointsFound = 0;
+ }
+ }
+ }
+ }
+
+ fGridPoint += fGridSpacing;
+ }
+ }
+ }
+
+ pRender->PopRenderMode();
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : pLoadInfo -
+// pFace -
+// Output : ChunkFileResult_t
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapFace::LoadDispInfoCallback(CChunkFile *pFile, CMapFace *pFace)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ // allocate a displacement (for the face)
+ EditDispHandle_t dispHandle = EditDispMgr()->Create();
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( dispHandle );
+
+ //
+ // load the displacement info and set relationships
+ //
+ ChunkFileResult_t eResult = pDisp->LoadVMF( pFile );
+ if( eResult == ChunkFile_Ok )
+ {
+ pDisp->SetParent( pFace );
+ pFace->SetDisp( dispHandle );
+
+ CMapWorld *pWorld = GetActiveWorld();
+ if( pWorld )
+ {
+ IWorldEditDispMgr *pDispMgr = pWorld->GetWorldEditDispManager();
+ if( pDispMgr )
+ {
+ pDispMgr->AddToWorld( dispHandle );
+ }
+ }
+ }
+
+ return( eResult );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handles key values when loading a VMF file.
+// Input : szKey - Key being handled.
+// szValue - Value of the key in the VMF file.
+// Output : Returns ChunkFile_Ok or an error if there was a parsing error.
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapFace::LoadKeyCallback(const char *szKey, const char *szValue, LoadFace_t *pLoadFace)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ CMapFace *pFace = pLoadFace->pFace;
+
+ if (!stricmp(szKey, "id"))
+ {
+ CChunkFile::ReadKeyValueInt(szValue, pFace->m_nFaceID);
+ }
+ else if (!stricmp(szKey, "rotation"))
+ {
+ pFace->texture.rotate = atof(szValue);
+ }
+ else if (!stricmp(szKey, "plane"))
+ {
+ int nRead = sscanf(szValue, "(%f %f %f) (%f %f %f) (%f %f %f)",
+ &pFace->plane.planepts[0][0], &pFace->plane.planepts[0][1], &pFace->plane.planepts[0][2],
+ &pFace->plane.planepts[1][0], &pFace->plane.planepts[1][1], &pFace->plane.planepts[1][2],
+ &pFace->plane.planepts[2][0], &pFace->plane.planepts[2][1], &pFace->plane.planepts[2][2]);
+
+ if (nRead != 9)
+ {
+ // TODO: need specific error message
+ return(ChunkFile_Fail);
+ }
+ }
+ else if (!stricmp(szKey, "material"))
+ {
+ strcpy(pLoadFace->szTexName, szValue);
+ }
+ else if (!stricmp(szKey, "uaxis"))
+ {
+ int nRead = sscanf(szValue, "[%f %f %f %f] %f",
+ &pFace->texture.UAxis[0], &pFace->texture.UAxis[1], &pFace->texture.UAxis[2], &pFace->texture.UAxis[3], &pFace->texture.scale[0]);
+
+ if (nRead != 5)
+ {
+ // TODO: need specific error message
+ return(ChunkFile_Fail);
+ }
+ }
+ else if (!stricmp(szKey, "vaxis"))
+ {
+ int nRead = sscanf(szValue, "[%f %f %f %f] %f",
+ &pFace->texture.VAxis[0], &pFace->texture.VAxis[1], &pFace->texture.VAxis[2], &pFace->texture.VAxis[3], &pFace->texture.scale[1]);
+
+ if (nRead != 5)
+ {
+ // TODO: need specific error message
+ return(ChunkFile_Fail);
+ }
+ }
+ else if (!stricmp(szKey, "lightmapscale"))
+ {
+ pFace->texture.nLightmapScale = atoi(szValue);
+ }
+ else if (!stricmp(szKey, "smoothing_groups"))
+ {
+ pFace->m_fSmoothingGroups = atoi(szValue);
+ }
+
+ return(ChunkFile_Ok);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Loads a face chunk from the VMF file.
+// Input : pFile - Chunk file being loaded.
+// Output : Returns ChunkFile_Ok or an error if there was a parsing error.
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapFace::LoadVMF(CChunkFile *pFile)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ //
+ // Set up handlers for the subchunks that we are interested in.
+ //
+ CChunkHandlerMap Handlers;
+ Handlers.AddHandler("dispinfo", (ChunkHandler_t)LoadDispInfoCallback, this);
+
+ //
+ // Read the keys and sub-chunks.
+ //
+ LoadFace_t LoadFace;
+ memset(&LoadFace, 0, sizeof(LoadFace));
+ LoadFace.pFace = this;
+
+ pFile->PushHandlers(&Handlers);
+ ChunkFileResult_t eResult = pFile->ReadChunk((KeyHandler_t)LoadKeyCallback, &LoadFace);
+ pFile->PopHandlers();
+
+ if (eResult == ChunkFile_Ok)
+ {
+ CalcPlane();
+ SetTexture(LoadFace.szTexName);
+ }
+
+ return(eResult);
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 CMapFace::OnAddToWorld(CMapWorld *pWorld)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ if (HasDisp())
+ {
+ //
+ // Add it to the world displacement list.
+ //
+ IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
+ if (pDispMgr != NULL)
+ {
+ pDispMgr->AddToWorld( m_DispHandle );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// 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 CMapFace::OnRemoveFromWorld(void)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ if (HasDisp())
+ {
+ //
+ // Add it to the world displacement list.
+ //
+ IWorldEditDispMgr *pDispMgr = GetActiveWorldEditDispManager();
+ if (pDispMgr != NULL)
+ {
+ pDispMgr->RemoveFromWorld( m_DispHandle );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : *pFile -
+// Output : ChunkFileResult_t
+//-----------------------------------------------------------------------------
+ChunkFileResult_t CMapFace::SaveVMF(CChunkFile *pFile, CSaveInfo *pSaveInfo)
+{
+ NormalizeTextureShifts();
+
+ //
+ // Check for duplicate plane points. All three plane points must be unique
+ // or it isn't a valid plane. Try to fix it if it isn't valid.
+ //
+ if (!CheckFace())
+ {
+ Fix();
+ }
+
+ ChunkFileResult_t eResult = pFile->BeginChunk("side");
+
+ char szBuf[512];
+
+ //
+ // Write our unique face ID.
+ //
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->WriteKeyValueInt("id", m_nFaceID);
+ }
+
+ //
+ // Write the plane information.
+ //
+ if (eResult == ChunkFile_Ok)
+ {
+ sprintf(szBuf, "(%g %g %g) (%g %g %g) (%g %g %g)",
+ (double)plane.planepts[0][0], (double)plane.planepts[0][1], (double)plane.planepts[0][2],
+ (double)plane.planepts[1][0], (double)plane.planepts[1][1], (double)plane.planepts[1][2],
+ (double)plane.planepts[2][0], (double)plane.planepts[2][1], (double)plane.planepts[2][2]);
+
+ eResult = pFile->WriteKeyValue("plane", szBuf);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ char szTexture[MAX_PATH];
+ strcpy(szTexture, texture.texture);
+ strupr(szTexture);
+
+ eResult = pFile->WriteKeyValue("material", szTexture);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ sprintf(szBuf, "[%g %g %g %g] %g", (double)texture.UAxis[0], (double)texture.UAxis[1], (double)texture.UAxis[2], (double)texture.UAxis[3], (double)texture.scale[0]);
+ eResult = pFile->WriteKeyValue("uaxis", szBuf);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ sprintf(szBuf, "[%g %g %g %g] %g", (double)texture.VAxis[0], (double)texture.VAxis[1], (double)texture.VAxis[2], (double)texture.VAxis[3], (double)texture.scale[1]);
+ eResult = pFile->WriteKeyValue("vaxis", szBuf);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->WriteKeyValueFloat("rotation", texture.rotate);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->WriteKeyValueFloat("lightmapscale", texture.nLightmapScale);
+ }
+
+ // Save smoothing group data.
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->WriteKeyValueInt("smoothing_groups", m_fSmoothingGroups );
+ }
+
+ //
+ // Write the displacement chunk.
+ //
+ if ((eResult == ChunkFile_Ok) && (HasDisp()))
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ eResult = pDisp->SaveVMF(pFile, pSaveInfo);
+ }
+
+ if (eResult == ChunkFile_Ok)
+ {
+ eResult = pFile->EndChunk();
+ }
+
+ return(eResult);
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Enables or disables the special rendering of selected faces.
+// Input : bShowSelection - true to enable, false to disable.
+//-----------------------------------------------------------------------------
+void CMapFace::SetShowSelection(bool bShowSelection)
+{
+ CMapFace::m_bShowFaceSelection = bShowSelection;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : nPoint -
+// u -
+// v -
+//-----------------------------------------------------------------------------
+void CMapFace::SetTextureCoords(int nPoint, float u, float v)
+{
+ if (nPoint < nPoints)
+ {
+ m_pTextureCoords[nPoint][0] = u;
+ m_pTextureCoords[nPoint][1] = v;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+size_t CMapFace::GetDataSize( void )
+{
+ // get base map class size
+ size_t size = sizeof( CMapFace );
+
+ //
+ // better approximate by added in verts, texture coordinates,
+ // and lightmap coordinates
+ //
+ size += ( sizeof( Vector ) * nPoints );
+ size += ( sizeof( Vector2D ) * ( nPoints * 2 ) );
+
+ // add displacement size if necessary
+ if( HasDisp() )
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ size += pDisp->GetSize();
+ }
+
+ return size;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns our bounds for 2D rendering. These bounds do not consider
+// any displacement information.
+// Input : boundMin -
+// boundMax -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CMapFace::GetRender2DBox( Vector& boundMin, Vector& boundMax )
+{
+ // valid face?
+ if( nPoints == 0 )
+ return false;
+
+ //
+ // find min and maximum points on face
+ //
+ VectorFill( boundMin, COORD_NOTINIT );
+ VectorFill( boundMax, -COORD_NOTINIT );
+ for( int i = 0; i < nPoints; i++ )
+ {
+ if( Points[i][0] < boundMin[0] ) { boundMin[0] = Points[i][0]; }
+ if( Points[i][1] < boundMin[1] ) { boundMin[1] = Points[i][1]; }
+ if( Points[i][2] < boundMin[2] ) { boundMin[2] = Points[i][2]; }
+
+ if( Points[i][0] > boundMax[0] ) { boundMax[0] = Points[i][0]; }
+ if( Points[i][1] > boundMax[1] ) { boundMax[1] = Points[i][1]; }
+ if( Points[i][2] > boundMax[2] ) { boundMax[2] = Points[i][2]; }
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Returns our bounds for frustum culling, including the bounds of
+// any displacement information.
+// Input : boundMin -
+// boundMax -
+// Output : Returns true on success, false on failure.
+//-----------------------------------------------------------------------------
+bool CMapFace::GetCullBox( Vector& boundMin, Vector& boundMax )
+{
+ // get the face bounds
+ if( !GetRender2DBox( boundMin, boundMax ) )
+ return false;
+
+ //
+ // add displacement to bounds
+ //
+ if( HasDisp() )
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+
+ Vector bbox[2];
+ pDisp->GetBoundingBox( bbox[0], bbox[1] );
+
+ for( int i = 0; i < 2; i++ )
+ {
+ if( bbox[i][0] < boundMin[0] ) { boundMin[0] = bbox[i][0]; }
+ if( bbox[i][1] < boundMin[1] ) { boundMin[1] = bbox[i][1]; }
+ if( bbox[i][2] < boundMin[2] ) { boundMin[2] = bbox[i][2]; }
+
+ if( bbox[i][0] > boundMax[0] ) { boundMax[0] = bbox[i][0]; }
+ if( bbox[i][1] > boundMax[1] ) { boundMax[1] = bbox[i][1]; }
+ if( bbox[i][2] > boundMax[2] ) { boundMax[2] = bbox[i][2]; }
+ }
+ }
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Purpose:
+// Input : HitPos -
+// Start -
+// End -
+// Output : Returns true if the ray intersected the face, false if not.
+//-----------------------------------------------------------------------------
+bool CMapFace::TraceLine(Vector &HitPos, Vector &HitNormal, Vector const &Start, Vector const &End )
+{
+ if (HasDisp())
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ return( pDisp->TraceLine( HitPos, HitNormal, Start, End ) );
+ }
+
+ //
+ // Find the point of intersection of the ray with the given plane.
+ //
+ float t = Start.Dot(plane.normal) - plane.dist;
+ t = t / -(End - Start).Dot(plane.normal);
+
+ HitPos = Start + (t * (End - Start));
+ HitNormal = plane.normal;
+ return(true);
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CMapFace::TraceLineInside( Vector &HitPos, Vector &HitNormal,
+ Vector const &Start, Vector const &End, bool bNoDisp )
+{
+ // if the face is displaced -- collide with that
+ if( HasDisp() && !bNoDisp )
+ {
+ CMapDisp *pDisp = EditDispMgr()->GetDisp( m_DispHandle );
+ return( pDisp->TraceLine( HitPos, HitNormal, Start, End ) );
+ }
+
+ //
+ // Find the point of intersection of the ray with the given plane.
+ //
+ float t = Start.Dot( plane.normal ) - plane.dist;
+ if ( -( End - Start ).Dot( plane.normal ) != 0.0f )
+ {
+ t = t / -( End - Start ).Dot( plane.normal );
+ }
+ HitPos = Start + ( t * ( End - Start ) );
+
+ //
+ // determine if the plane point lies behind all of the polygon planes (edges)
+ //
+ for( int ndxEdge = 0; ndxEdge < nPoints; ndxEdge++ )
+ {
+ // create the edge and cross it with the face plane normal
+ Vector edge;
+ edge = Points[(ndxEdge+1)%nPoints] - Points[ndxEdge];
+
+ PLANE edgePlane;
+ edgePlane.normal = edge.Cross( plane.normal );
+ VectorNormalize( edgePlane.normal );
+ edgePlane.dist = edgePlane.normal.Dot( Points[ndxEdge] );
+
+ // determine if the facing is correct
+ float dist = edgePlane.normal.Dot( Points[(ndxEdge+2)%nPoints] ) - edgePlane.dist;
+ if( dist > 0.0f )
+ {
+ // flip
+ edgePlane.normal.Negate();
+ edgePlane.dist = -edgePlane.dist;
+ }
+
+ // check to see if plane point lives behind the plane
+ dist = edgePlane.normal.Dot( HitPos ) - edgePlane.dist;
+ if( dist > 0.0f )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// NOTE: actually this could be calculated once for the face since only the
+// face normal is being used (no smoothing groups, etc.), but that may
+// change????
+//-----------------------------------------------------------------------------
+void CMapFace::CalcTangentSpaceAxes( void )
+{
+ // destroy old axes if need be
+ FreeTangentSpaceAxes();
+
+ // allocate memory for tangent space axes
+ if( !AllocTangentSpaceAxes( nPoints ) )
+ return;
+
+ //
+ // get the texture space axes
+ //
+ Vector4D& uVect = texture.UAxis;
+ Vector4D& vVect = texture.VAxis;
+
+ //
+ // calculate the tangent space per polygon point
+ //
+
+ for( int ptIndex = 0; ptIndex < nPoints; ptIndex++ )
+ {
+ // get the current tangent space axes
+ TangentSpaceAxes_t *pAxis = &m_pTangentAxes[ptIndex];
+
+ //
+ // create the axes
+ //
+ pAxis->binormal = vVect.AsVector3D();
+ VectorNormalize( pAxis->binormal );
+ CrossProduct( plane.normal, pAxis->binormal, pAxis->tangent );
+ VectorNormalize( pAxis->tangent );
+ CrossProduct( pAxis->tangent, plane.normal, pAxis->binormal );
+ VectorNormalize( pAxis->binormal );
+
+ //
+ // adjust tangent for "backwards" mapping if need be
+ //
+ Vector tmpVect;
+ CrossProduct( uVect.AsVector3D(), vVect.AsVector3D(), tmpVect );
+ if( DotProduct( plane.normal, tmpVect ) > 0.0f )
+ {
+ VectorScale( pAxis->tangent, -1.0f, pAxis->tangent );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CMapFace::AllocTangentSpaceAxes( int count )
+{
+ if( count < 1 )
+ return false;
+
+ m_pTangentAxes = new TangentSpaceAxes_t[count];
+ if( !m_pTangentAxes )
+ return false;
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapFace::FreeTangentSpaceAxes( void )
+{
+ if( m_pTangentAxes )
+ {
+ delete [] m_pTangentAxes;
+ m_pTangentAxes = NULL;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+int CMapFace::SmoothingGroupCount( void )
+{
+ int nCount = 0;
+ for ( int iGroup = 0; iGroup < SMOOTHING_GROUP_MAX_COUNT; ++iGroup )
+ {
+ if ( ( m_fSmoothingGroups & ( 1 << iGroup ) ) != 0 )
+ {
+ nCount++;
+ }
+ }
+
+ return nCount;
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapFace::AddSmoothingGroup( int iGroup )
+{
+ Assert( iGroup >= 0 );
+ Assert( iGroup < SMOOTHING_GROUP_MAX_COUNT );
+
+ m_fSmoothingGroups |= ( 1 << ( iGroup - 1 ) );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+void CMapFace::RemoveSmoothingGroup( int iGroup )
+{
+ Assert( iGroup >= 0 );
+ Assert( iGroup < SMOOTHING_GROUP_MAX_COUNT );
+
+ m_fSmoothingGroups &= ~( 1 << ( iGroup - 1 ) );
+}
+
+//-----------------------------------------------------------------------------
+//-----------------------------------------------------------------------------
+bool CMapFace::InSmoothingGroup( int iGroup )
+{
+ if ( ( m_fSmoothingGroups & ( 1 << ( iGroup - 1 ) ) ) != 0 )
+ return true;
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Performs an intersection of this list with another.
+// Input : IntersectWith - the list to intersect with.
+// In - the list of items that were in both lists
+// Out - the list of items that were in one list but not the other.
+//-----------------------------------------------------------------------------
+void CMapFaceList::Intersect(CMapFaceList &IntersectWith, CMapFaceList &In, CMapFaceList &Out)
+{
+ //
+ // See what we items have that are in the other list.
+ //
+ for (int i = 0; i < Count(); i++)
+ {
+ CMapFace *pFace = Element(i);
+
+ if (IntersectWith.Find(pFace) != -1)
+ {
+ if (In.Find(pFace) == -1)
+ {
+ In.AddToTail(pFace);
+ }
+ }
+ else
+ {
+ if (Out.Find(pFace) == -1)
+ {
+ Out.AddToTail(pFace);
+ }
+ }
+ }
+
+ //
+ // Now go the other way.
+ //
+ for (int i = 0; i < IntersectWith.Count(); i++)
+ {
+ CMapFace *pFace = IntersectWith.Element(i);
+
+ if (Find(pFace) != -1)
+ {
+ if (In.Find(pFace) == -1)
+ {
+ In.AddToTail(pFace);
+ }
+ }
+ else
+ {
+ if (Out.Find(pFace) == -1)
+ {
+ Out.AddToTail(pFace);
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Performs an intersection of this list with another.
+// Input : IntersectWith - the list to intersect with.
+// In - the list of items that were in both lists
+// Out - the list of items that were in one list but not the other.
+//-----------------------------------------------------------------------------
+void CMapFaceIDList::Intersect(CMapFaceIDList &IntersectWith, CMapFaceIDList &In, CMapFaceIDList &Out)
+{
+ //
+ // See what we items have that are in the other list.
+ //
+ for (int i = 0; i < Count(); i++)
+ {
+ int nFaceID = Element(i);
+ if (IntersectWith.Find(nFaceID) != -1)
+ {
+ if (In.Find(nFaceID) == -1)
+ {
+ In.AddToTail(nFaceID);
+ }
+ }
+ else
+ {
+ if (Out.Find(nFaceID) == -1)
+ {
+ Out.AddToTail(nFaceID);
+ }
+ }
+ }
+
+ //
+ // Now go the other way.
+ //
+ for (int i = 0; i < IntersectWith.Count(); i++)
+ {
+ int nFaceID = IntersectWith.Element(i);
+
+ if (Find(nFaceID) != -1)
+ {
+ if (In.Find(nFaceID) == -1)
+ {
+ In.AddToTail(nFaceID);
+ }
+ }
+ else
+ {
+ if (Out.Find(nFaceID) == -1)
+ {
+ Out.AddToTail(nFaceID);
+ }
+ }
+ }
+}
+
+void CMapFace::DoTransform(const VMatrix &matrix)
+{
+ SignalUpdate( EVTYPE_FACE_CHANGED );
+ if( nPoints < 3 )
+ {
+ Assert( nPoints > 2 );
+ return;
+ }
+
+ CMapDisp *pDisp = NULL;
+ Vector bbDispOld[2]; // Old bbox for the disp.
+ if( HasDisp() )
+ {
+ EditDispHandle_t handle = GetDisp();
+ pDisp = EditDispMgr()->GetDisp( handle );
+ if ( pDisp )
+ pDisp->GetBoundingBox( bbDispOld[0], bbDispOld[1] );
+ }
+
+ Vector oldPoint = Points[0];
+
+ // Transform the face points.
+ for (int i = 0; i < nPoints; i++)
+ {
+ TransformPoint( matrix, Points[i] );
+ }
+
+ bool bFlip = (matrix[0][0]*matrix[1][1]*matrix[2][2]) < 0;
+
+ if ( bFlip )
+ {
+ // mirror transformation would break CCW culling, so revert point order
+ PointsRevertOrder( Points, nPoints );
+ }
+
+ CalcPlaneFromFacePoints();
+
+ // ok, now apply all changes to texture & displacment too
+ VMatrix mTrans = matrix;
+
+ QAngle rotateAngles;
+ Vector moveDelta;
+ MatrixAngles( matrix.As3x4(), rotateAngles, moveDelta );
+
+ bool bIsLocking = Options.IsLockingTextures()!=0;
+ bool bIsMoving = moveDelta.LengthSqr() > 0.00001;
+
+ // erase move component from matrix
+ mTrans.SetTranslation( vec3_origin );
+
+ // check if new matrix is simple identity matrix
+ if ( mTrans.IsIdentity() )
+ {
+ if ( GetTexture() )
+ {
+ if ( bIsMoving && bIsLocking )
+ {
+ // Offset texture coordinates if we're moving and texture locking.
+ OffsetTexture(moveDelta);
+ }
+
+ // Recalculate the texture coordinates for this face.
+ CalcTextureCoords();
+
+ }
+
+ if ( pDisp )
+ {
+ pDisp->UpdateSurfData( this );
+
+ // Update the neighbors of displacements that intersect the old as well as the new bbox.
+ // Without this, there can be errors if you drag > 2 edges to interset each other, then
+ // move one of the intersectors (cloning can easily cause this case to happen).
+ Vector bbDispNew[2];
+ pDisp->GetBoundingBox( bbDispNew[0], bbDispNew[1] );
+
+ CMapDisp::UpdateNeighborsOfDispsIntersectingBox( bbDispOld[0], bbDispOld[1], 1.0 );
+ CMapDisp::UpdateNeighborsOfDispsIntersectingBox( bbDispNew[0], bbDispNew[1], 1.0 );
+ }
+
+ // Create any detail objects if appropriate
+ DetailObjects::BuildAnyDetailObjects(this);
+
+ return;
+ }
+
+ // ok, transformation is more complex then a simple move
+
+ if ( GetTexture() )
+ {
+ Vector vU = texture.UAxis.AsVector3D();
+ Vector vV = texture.VAxis.AsVector3D();
+
+ // store original length
+ float fScaleU = vU.Length();
+ float fScaleV = vV.Length();
+
+ if ( fScaleU <= 0 )
+ fScaleU = 1;
+
+ if ( fScaleV <=0 )
+ fScaleV = 1;
+
+ // transform UV axis
+ TransformPoint( mTrans, vU );
+ TransformPoint( mTrans, vV );
+
+ // get scaling factor for both axes
+ fScaleU = vU.Length()/fScaleU;
+ fScaleV = vV.Length()/fScaleV;
+
+ if ( fScaleU <= 0 )
+ fScaleU = 1;
+
+ if ( fScaleV <=0 )
+ fScaleV = 1;
+
+ // check is the transformation would destory the UV axis (both normals & perpendicular)
+ bool bUVAxisSameScale = fequal(fScaleV,1,0.0001) && fequal(fScaleU,1,0.0001);
+ bool bUVAxisPerpendicular = fequal(DotProduct( vU, vV ),0,0.0025);
+
+ // Rotate the U/V axes to keep them oriented the same relative
+ // to this face.
+
+ if ( bIsLocking && bUVAxisPerpendicular )
+ {
+ // make sure UV axes are normals & perpendicalur
+ texture.UAxis.AsVector3D() = vU/fScaleU;
+ texture.VAxis.AsVector3D() = vV/fScaleV;
+ }
+
+ if ( bUVAxisSameScale )
+ {
+ // scale is fine
+ if ( !bIsLocking )
+ {
+ // If we are not texture locking, recalculate the texture axes based on current
+ // texture alignment setting. This prevents the axes from becoming normal to the face.
+ InitializeTextureAxes(Options.GetTextureAlignment(), INIT_TEXTURE_AXES | INIT_TEXTURE_FORCE);
+ }
+ }
+ else // we stretch/scale axes
+ {
+ // operation changes scale of textures, check if we really want that:
+ bIsLocking = Options.IsScaleLockingTextures()!=0;
+
+ if ( bIsLocking )
+ {
+ texture.scale[0] *= fScaleU;
+ texture.scale[1] *= fScaleV;
+ }
+ }
+
+ if ( bIsMoving && bIsLocking )
+ {
+ // Offset texture coordinates if we're moving and texture locking.
+ OffsetTexture(moveDelta);
+ }
+
+ // Recalculate the texture coordinates for this face.
+ CalcTextureCoords();
+ }
+
+ // rotate the displacement field data - if any!
+ if( pDisp )
+ {
+ pDisp->DoTransform( mTrans );
+
+ // Update the neighbors of displacements that intersect the old as well as the new bbox.
+ // Without this, there can be errors if you drag > 2 edges to interset each other, then
+ // move one of the intersectors (cloning can easily cause this case to happen).
+ Vector bbDispNew[2];
+ pDisp->GetBoundingBox( bbDispNew[0], bbDispNew[1] );
+
+ CMapDisp::UpdateNeighborsOfDispsIntersectingBox( bbDispOld[0], bbDispOld[1], 1.0 );
+ CMapDisp::UpdateNeighborsOfDispsIntersectingBox( bbDispNew[0], bbDispNew[1], 1.0 );
+ }
+ // Create any detail objects if appropriate
+ DetailObjects::BuildAnyDetailObjects(this);
+}
+