//========= Copyright © 1996-2005, 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 //#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[]={ "playerclip", "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(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 = rint(texture.UAxis[nDim]); if (fabs(texture.UAxis[nDim] - nValue) < TEXTURE_AXIS_ROUND_EPSILON) { texture.UAxis[nDim] = nValue; } nValue = 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->GetMappingWidth() != 0) { texture.UAxis[3] = fmod(texture.UAxis[3], m_pTexture->GetMappingWidth()); } if (m_pTexture->GetMappingHeight() != 0) { texture.VAxis[3] = fmod(texture.VAxis[3], m_pTexture->GetMappingHeight()); } } //----------------------------------------------------------------------------- // 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->GetMappingHeight(); 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->GetMappingWidth(); break; } // Center the texture on the face. case TEXTURE_JUSTIFY_CENTER: { texture.UAxis[3] = -Center[0] + (m_pTexture->GetMappingWidth() / 2); texture.VAxis[3] = -Center[1] + (m_pTexture->GetMappingHeight() / 2); break; } // Scale the texture to exactly fit the face. case TEXTURE_JUSTIFY_FIT: { // Calculate the appropriate scale. if (m_pTexture && m_pTexture->GetMappingWidth() && m_pTexture->GetMappingHeight()) { texture.scale[0] = (BottomRight[0] - TopLeft[0]) / m_pTexture->GetMappingWidth(); texture.scale[1] = (BottomRight[1] - TopLeft[1]) / m_pTexture->GetMappingHeight(); } 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 &tri_list ) { // create a fan if (! (m_nFaceFlags & FACE_FLAGS_NOSHADOW )) for(int i=2;inumpoints); 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->GetMappingWidth() / pTexture->GetMappingWidth(); float flYFactor = (float)m_pTexture->GetMappingHeight() / pTexture->GetMappingHeight(); 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->GetPreviewImageWidth() == 0 || m_pTexture->GetPreviewImageHeight() == 0 || m_pTexture->GetMappingWidth() == 0 || m_pTexture->GetMappingHeight() == 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->GetMappingWidth() ) texCoord[0] = s / ( float )m_pTexture->GetMappingWidth(); else texCoord[0] = 0.0; if ( m_pTexture->GetMappingHeight() ) texCoord[1] = t / ( float )m_pTexture->GetMappingHeight(); 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->GetMappingWidth()) m_pTextureCoords[i][0] = s / (float)m_pTexture->GetMappingWidth(); else m_pTextureCoords[i][0] = 0.0f; if (m_pTexture->GetMappingHeight()) m_pTextureCoords[i][1] = t / (float)m_pTexture->GetMappingHeight(); 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 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; float d = -(End - Start).Dot(plane.normal); if ( d == 0.0f ) return false; t = t / d; 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); }