source-engine/hammer/loadsave_rmf.cpp

927 lines
20 KiB
C++
Raw Permalink Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
// $NoKeywords: $
//=============================================================================//
#include "stdafx.h"
#include <io.h>
#include "hammer.h"
#include "MapEntity.h"
#include "MapFace.h"
#include "MapSolid.h"
#include "MapStudioModel.h"
#include "MapWorld.h"
#include "GlobalFunctions.h"
#include "VisGroup.h"
#include "MapDoc.h"
#include "MapDisp.h"
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
static CMapWorld *pLoadingWorld;
static float fThisVersion;
static BOOL bCorrupt;
class COldVisGroup
{
public:
char m_szName[128];
color32 m_rgbColor;
DWORD m_dwID;
bool m_bVisible;
};
float GetFileVersion() { return fThisVersion; }
static void WriteString(std::fstream& file, LPCTSTR pszString)
{
BYTE cLen = strlen(pszString)+1;
file.write((char*)&cLen, 1);
file.write(pszString, cLen);
}
static void ReadString(std::fstream& file, char * pszString)
{
BYTE cLen;
file.read((char *)&cLen, 1);
file.read(pszString, cLen);
}
//-----------------------------------------------------------------------------
// Purpose: Loads a solid face from RMF.
//-----------------------------------------------------------------------------
int CMapFace::SerializeRMF(std::fstream& file, BOOL fIsStoring)
{
int iSize;
if (fIsStoring)
{
//
// After 3.3 the alignment of vec4_t's changed. We never save the new format,
// since RMF is no longer being revved.
//
TEXTURE_33 OldTex33;
memset(&OldTex33, 0, sizeof(OldTex33));
memcpy(OldTex33.texture, texture.texture, sizeof(OldTex33.texture));
OldTex33.UAxis[0] = texture.UAxis[0];
OldTex33.UAxis[1] = texture.UAxis[1];
OldTex33.UAxis[2] = texture.UAxis[2];
OldTex33.UAxis[3] = texture.UAxis[3];
OldTex33.VAxis[0] = texture.VAxis[0];
OldTex33.VAxis[1] = texture.VAxis[1];
OldTex33.VAxis[2] = texture.VAxis[2];
OldTex33.VAxis[3] = texture.VAxis[3];
OldTex33.rotate = texture.rotate;
OldTex33.scale[0] = texture.scale[0];
OldTex33.scale[1] = texture.scale[1];
OldTex33.smooth = texture.smooth;
OldTex33.material = texture.material;
OldTex33.q2surface = texture.q2surface;
OldTex33.q2contents = texture.q2contents;
OldTex33.nLightmapScale = texture.nLightmapScale;
file.write((char *)&OldTex33, sizeof(OldTex33));
iSize = nPoints;
file.write((char *)&iSize, sizeof(int));
//
// Save face points. We don't serialize the Vectors directly because the memory
// layout changed with SSE optimizations.
//
float SavePoints[256][3];
for (int i = 0; i < iSize; i++)
{
SavePoints[i][0] = Points[i].x;
SavePoints[i][1] = Points[i].y;
SavePoints[i][2] = Points[i].z;
}
file.write((char *)SavePoints, nPoints * 3 * sizeof(float));
//
// Save plane points. We don't serialize the Vectors directly because the memory
// layout changed with SSE optimizations.
//
for (int i = 0; i < 3; i++)
{
SavePoints[i][0] = plane.planepts[i].x;
SavePoints[i][1] = plane.planepts[i].y;
SavePoints[i][2] = plane.planepts[i].z;
}
file.write((char *)SavePoints, 3 * 3 * sizeof(float));
}
else
{
// Pre-2.2 used a different texture structure format.
TEXTURE_21 OldTex;
memset(&OldTex, 0, sizeof(OldTex));
if (fThisVersion < 0.9f)
{
// Read the name
file.read(OldTex.texture, 16);
// Ensure name is ASCIIZ
OldTex.texture[16] = 0;
// Read the rest - skip the name
file.read((char *)&OldTex.rotate, sizeof(OldTex.rotate) + sizeof(OldTex.shift) + sizeof(OldTex.scale));
}
else if (fThisVersion < 1.2f)
{
// Didn't have smooth/material groups:
file.read((char *)&OldTex, 40);
file.read((char *)&OldTex, sizeof(OldTex.texture) - (MAX_PATH) + sizeof(OldTex.rotate) + sizeof(OldTex.shift) + sizeof(OldTex.scale));
}
else if (fThisVersion < 1.7f)
{
// No quake2 fields yet and smaller texture size.
file.read((char *)&OldTex, 40);
file.read((char *)&OldTex.rotate, sizeof(OldTex) - (sizeof(int) * 3) - MAX_PATH);
}
else if (fThisVersion < 1.8f)
{
// Texture name field changed from 40 to MAX_PATH in size.
file.read((char *)&OldTex, 40);
file.read((char *)&OldTex.rotate, sizeof(OldTex) - MAX_PATH);
}
else if (fThisVersion < 2.2f)
{
file.read((char *)&OldTex, sizeof(OldTex));
}
else
{
//
// After 3.3 the alignment of vec4_t's changed. We never save the new format,
// since RMF is no longer being revved.
//
TEXTURE_33 OldTex33;
memset(&OldTex33, 0, sizeof(OldTex33));
file.read((char *)&OldTex33, sizeof(OldTex33));
memcpy(texture.texture, OldTex33.texture, sizeof(texture.texture));
texture.UAxis[0] = OldTex33.UAxis[0];
texture.UAxis[1] = OldTex33.UAxis[1];
texture.UAxis[2] = OldTex33.UAxis[2];
texture.UAxis[3] = OldTex33.UAxis[3];
texture.VAxis[0] = OldTex33.VAxis[0];
texture.VAxis[1] = OldTex33.VAxis[1];
texture.VAxis[2] = OldTex33.VAxis[2];
texture.VAxis[3] = OldTex33.VAxis[3];
texture.rotate = OldTex33.rotate;
texture.scale[0] = OldTex33.scale[0];
texture.scale[1] = OldTex33.scale[1];
texture.smooth = OldTex33.smooth;
texture.material = OldTex33.material;
texture.q2surface = OldTex33.q2surface;
texture.q2contents = OldTex33.q2contents;
texture.nLightmapScale = OldTex33.nLightmapScale;
if (texture.nLightmapScale == 0)
{
texture.nLightmapScale = g_pGameConfig->GetDefaultLightmapScale();
}
}
// If reading from a pre-2.2 RMF file, copy the texture info from the old format.
if (fThisVersion < 2.2f)
{
memcpy(texture.texture, OldTex.texture, sizeof(texture.texture));
memcpy(texture.scale, OldTex.scale, sizeof(texture.scale));
texture.rotate = OldTex.rotate;
texture.smooth = OldTex.smooth;
texture.material = OldTex.material;
texture.q2surface = OldTex.q2surface;
texture.q2contents = OldTex.q2contents;
texture.UAxis[3] = OldTex.shift[0];
texture.VAxis[3] = OldTex.shift[1];
}
if (fThisVersion < 1.8f)
{
texture.texture[40] = 0;
}
//
// Reverse forward slashes if we are not using materials.
//
if (g_pGameConfig->GetTextureFormat() != tfVMT)
{
for (int i = strlen(texture.texture) - 1; i >= 0; i--)
{
if (texture.texture[i] == '/')
{
texture.texture[i] = '\\';
}
}
}
if (texture.texture[1] == ':')
{
char szBuf[MAX_PATH];
char *psz;
strcpy(szBuf, texture.texture);
psz = strstr(szBuf, "textures\\");
if (psz)
{
memset(texture.texture, 0, sizeof(texture.texture));
psz += strlen("textures\\");
strcpy(texture.texture, psz);
}
}
if (fThisVersion < 0.6f)
{
float light;
file.read((char*) &light, sizeof(light));
}
//
// Load the points into an array of float[3]'s and transfer them into
// an array of Vectors which will be used for face creation. We can't
// load directly into the Vectors because the memory layout changed
// when SSE optimizations were added.
//
float LoadPoints[256][3];
file.read((char *)&iSize, sizeof(int));
file.read((char *)&LoadPoints, iSize * 3 * sizeof(float));
Vector CreatePoints[256];
for (int i = 0; i < iSize; i++)
{
CreatePoints[i].x = LoadPoints[i][0];
CreatePoints[i].y = LoadPoints[i][1];
CreatePoints[i].z = LoadPoints[i][2];
//
// Negate Z for older RMF files.
//
if (fThisVersion < 0.5f)
{
CreatePoints[i].z = -CreatePoints[i].z;
}
}
if (fThisVersion < 2.2f)
{
CreateFace(CreatePoints, iSize);
}
//
// Load the plane points. We don't really need them, but they can fix the face if, somehow, it
// was saved without any points. RMF could have been smaller if we only saved these plane points.
//
if (fThisVersion >= 0.7f)
{
//
// Load the points into an array of float[3]'s and transfer them into
// the array of Vectors. We can't load directly into the Vectors because the memory
// layout changed when SSE optimizations were added.
//
float LoadPlanePoints[3][3];
file.read((char *)LoadPlanePoints, sizeof(LoadPlanePoints));
for (int i = 0; i < 3; i++)
{
plane.planepts[i].x = LoadPlanePoints[i][0];
plane.planepts[i].y = LoadPlanePoints[i][1];
plane.planepts[i].z = LoadPlanePoints[i][2];
}
CalcPlane();
// If reading from an older RMF file, set up the texture axes Quake-style.
if (fThisVersion < 2.2f)
{
InitializeTextureAxes(TEXTURE_ALIGN_QUAKE, INIT_TEXTURE_AXES | INIT_TEXTURE_FORCE);
}
}
if( fThisVersion < 2.2f )
{
SetTexture(texture.texture);
}
//
// version 3.4 -- added displacement info to faces
//
if( ( fThisVersion >= 3.4f ) && ( fThisVersion <= 3.6f ) )
{
bool bHasMapDisp;
if( fThisVersion >= 3.5f )
{
int nLoadHasMapDisp;
// check displacement mapping flag
file.read( ( char* )&nLoadHasMapDisp, sizeof( int ) );
bHasMapDisp = nLoadHasMapDisp != 0;
}
else
{
// check displacement mapping flag
file.read( ( char* )&bHasMapDisp, sizeof( bool ) );
}
if( bHasMapDisp )
{
EditDispHandle_t handle = EditDispMgr()->Create();
SetDisp( handle );
CMapDisp *pDisp = EditDispMgr()->GetDisp( handle );
pDisp->SetParent( this );
pDisp->SerializedLoadRMF( file, this, fThisVersion );
}
}
if (fThisVersion >= 2.2f)
{
CreateFace(CreatePoints, iSize);
SetTexture(texture.texture);
}
}
if (file.bad())
{
return(-1);
}
return(0);
}
int MDkeyvalue::SerializeRMF(std::fstream& file, BOOL fIsStoring)
{
// load/save a keyvalue
if( fIsStoring )
{
WriteString(file, szKey);
WriteString(file, szValue);
}
else
{
ReadString(file, szKey);
ReadString(file, szValue);
}
if( file.bad() )
return -1;
return 0;
}
int CMapSolid::SerializeRMF(std::fstream& file, BOOL fIsStoring)
{
int iRvl, iSize;
// load/save children
CMapClass::SerializeRMF(file, fIsStoring);
// load/save a brush
if(fIsStoring)
{
// serialize the Faces
iSize = Faces.GetCount();
file.write((char*) &iSize, sizeof(int));
for(int i = 0; i < iSize; i++)
{
iRvl = Faces[i].SerializeRMF(file, fIsStoring);
if(iRvl < 0)
return iRvl;
}
}
else
{
// There once was a bug that caused black solids. Fix it here.
if ((r == 0) && (g == 0) || (b == 0))
{
PickRandomColor();
}
// read Faces
file.read((char*) &iSize, sizeof(int));
Faces.SetCount(iSize);
for(int i = 0; i < iSize; i++)
{
// extract face
iRvl = Faces[i].SerializeRMF(file, fIsStoring);
if (iRvl < 0)
{
return(iRvl);
}
Faces[i].SetRenderColor(r, g, b);
Faces[i].SetParent(this);
}
CalcBounds();
//
// Set solid type based on texture name.
//
m_eSolidType = HL1SolidTypeFromTextureName(Faces[0].texture.texture);
}
if (file.bad())
{
return -1;
}
return 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : file -
// fIsStoring -
// Output : int
//-----------------------------------------------------------------------------
int CEditGameClass::SerializeRMF(std::fstream& file, BOOL fIsStoring)
{
int iSize, iRvl;
int iAngle = 0;
if (fIsStoring)
{
// save data
WriteString(file, GetClassName());
file.write((char*) &iAngle, sizeof(iAngle));
int nSpawnFlags = GetSpawnFlags();
file.write((char *)&nSpawnFlags, sizeof(nSpawnFlags));
//
// Write the number of keyvalues.
//
iSize = 0;
for ( int z=m_KeyValues.GetFirst(); z != m_KeyValues.GetInvalidIndex(); z=m_KeyValues.GetNext( z ) )
{
iSize++;
}
file.write((char*) &iSize, sizeof(int));
//
// Write the keyvalues.
//
for ( int z=m_KeyValues.GetFirst(); z != m_KeyValues.GetInvalidIndex(); z=m_KeyValues.GetNext( z ) )
{
MDkeyvalue KeyValue = m_KeyValues.GetKeyValue(z);
iRvl = KeyValue.SerializeRMF(file, fIsStoring);
if (iRvl < 0)
{
return iRvl;
}
}
//
// Save dummy timeline info.
//
BOOL bTimeline = FALSE;
int nTime = 0;
file.write((char*) &bTimeline, sizeof bTimeline);
file.write((char*) &nTime, sizeof nTime);
file.write((char*) &nTime, sizeof nTime);
}
else
{
char buf[128];
ReadString(file, buf);
file.read((char*) &iAngle, sizeof(iAngle));
int nSpawnFlags;
file.read((char *)&nSpawnFlags, sizeof(nSpawnFlags));
Assert(buf[0]);
CEditGameClass::SetClass(buf, true);
//
// Read the keyvalues.
//
file.read((char *) &iSize, sizeof(int));
for (int i = 0; i < iSize; i++ )
{
MDkeyvalue KeyValue;
iRvl = KeyValue.SerializeRMF(file, fIsStoring);
if (iRvl < 0)
{
return iRvl;
}
m_KeyValues.SetValue(KeyValue.szKey, KeyValue.szValue);
}
SetSpawnFlags(nSpawnFlags);
m_KeyValues.SetValue("classname", buf);
// backwards compatibility for old iAngle
if (iAngle)
{
ImportAngle(iAngle);
}
//
// Dummy timeline information - unused.
//
if (fThisVersion >= 1.5f)
{
BOOL bTimeline;
int nTime;
file.read((char*) &bTimeline, sizeof bTimeline);
file.read((char*) &nTime, sizeof nTime);
file.read((char*) &nTime, sizeof nTime);
}
}
return file.bad() ? -1 : 0;
}
int CMapClass::SerializeRMF(std::fstream& file, BOOL fIsStoring)
{
int iSize, iRvl;
if(fIsStoring)
{
// write type
WriteString(file, GetType());
//
// Write the visgroup ID (zero if none).
//
DWORD dwID = 0;
/*if (m_pVisGroup)
{
// visgroupfixme: how to handle saving RMF? save the first group??
dwID = m_pVisGroup->GetID();
}*/
file.write((char *)&dwID, sizeof(dwID));
//
// Write the object color.
//
file.write((char *)&r, sizeof(BYTE));
file.write((char *)&g, sizeof(BYTE));
file.write((char *)&b, sizeof(BYTE));
//
// Save children.
//
int nChildCount = 0;
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pChild = m_Children.Element(pos);
if (pChild->ShouldSerialize())
{
nChildCount++;
}
}
file.write((char *)&nChildCount, sizeof(int));
FOR_EACH_OBJ( m_Children, pos )
{
CMapClass *pChild = m_Children.Element(pos);
if (pChild->ShouldSerialize())
{
iRvl = pChild->SerializeRMF(file, fIsStoring);
if (iRvl < 0)
{
return iRvl;
}
}
}
}
else
{
// read our stuff
if(fThisVersion < 1.0f)
{
// kill group information .. unfortunate
file.read((char*) &iSize, sizeof(int));
file.seekg(iSize, std::ios::cur);
}
else
{
// just read the visgroup ID but ignore it
DWORD dwGroupID;
file.read((char*) &dwGroupID, sizeof(DWORD));
}
//
// Read the object color.
//
file.read((char *)&r, sizeof(BYTE));
file.read((char *)&g, sizeof(BYTE));
file.read((char *)&b, sizeof(BYTE));
// load children
file.read((char*) &iSize, sizeof(int));
for(int i = 0; i < iSize; i++)
{
char buf[128];
ReadString(file, buf);
CMapClass *pChild = CMapClassManager::CreateObject(buf);
if(!pChild)
{
bCorrupt = TRUE;
return -1;
}
iRvl = pChild->SerializeRMF(file, fIsStoring);
if(iRvl < 0)
return iRvl;
AddChild(pChild);
}
}
return file.bad() ? -1 : 0;
}
//-----------------------------------------------------------------------------
// Purpose:
// Input : file -
// fIsStoring -
// Output :
//-----------------------------------------------------------------------------
int CMapEntity::SerializeRMF(std::fstream &file, BOOL fIsStoring)
{
int iSize;
Vector Origin;
//
// Read/write base class.
//
CMapClass::SerializeRMF(file, fIsStoring);
CEditGameClass::SerializeRMF(file, fIsStoring);
if (fIsStoring)
{
// Write flags
file.write((char*) &flags, sizeof(flags));
// Write origin
GetOrigin(Origin);
file.write((char *)Origin.Base(), 3 * sizeof(float));
// Save padding for unused "complex" field
iSize = 0;
file.write((char*) &iSize, sizeof(int));
}
else
{
// Read flags
file.read((char *)&flags, sizeof(flags));
// Read origin
file.read((char *)Origin.Base(), 3 * sizeof(float));
SetOrigin(Origin);
if (IsClass())
{
// Known class. Determine flags based on the class.
flags = IsSolidClass() ? (flags & ~flagPlaceholder) : (flags | flagPlaceholder);
}
else
{
// Unknown class. Determine flags by looking for children (only solid ents have children at this point).
flags = (m_Children.Count() > 0) ? (flags & ~flagPlaceholder) : (flags | flagPlaceholder);
}
if (!(IsPlaceholder()))
{
CMapPoint::SetOrigin(Vector(0, 0, 0));
}
GetOrigin(Origin);
// import for previous to 0.5
if (fThisVersion < 0.5f)
{
Origin.z = -Origin.z;
}
// load unused "complex" field
file.read((char *)&iSize, sizeof(int));
SetOrigin(Origin);
//
// HACK: Set our class to NULL so that it is properly set from our "classname"
// key in PostloadWorld.
//
m_szClass[0] = '\0';
CalcBounds(TRUE);
}
if (file.bad())
{
return -1;
}
return 0;
}
int CMapWorld::SerializeRMF(std::fstream &file, BOOL fIsStoring)
{
float fVersion = 3.7f;
float fLastCompat = 0.3f;
int nSolids = 0;
int iSize;
pLoadingWorld = this;
bCorrupt = FALSE;
// load/save a world
if(fIsStoring)
{
// write version
file.write((char*) &fVersion, sizeof(fVersion));
file.write("RMF", 3);
// we don't save vis groups
iSize = 0;
file.write((char*) &iSize, sizeof(int));
// save children & local data
if(CMapClass::SerializeRMF(file, fIsStoring) == -1)
goto FatalError;
// save ceditgameclass
if(CEditGameClass::SerializeRMF(file, fIsStoring) == -1)
goto FatalError;
// save paths
iSize = m_Paths.Count();
file.write((char*) &iSize, sizeof(iSize));
FOR_EACH_OBJ( m_Paths, pos )
{
CMapPath *pPath = m_Paths.Element(pos);
pPath->SerializeRMF(file, TRUE);
}
if(file.bad())
goto FatalError;
}
else
{
// read & check version
file.read((char*) &fThisVersion, sizeof(fThisVersion));
if(fThisVersion < fLastCompat || fThisVersion > fVersion)
{
CString str;
str.Format("Oops! SerializeRMF() v%1.1f tried to load a file v%1.1f. Aborting.",
fVersion, fThisVersion);
AfxMessageBox(str);
return -1;
}
char buf[128];
if(fThisVersion >= 0.8f)
{
file.read(buf, 3);
if(strncmp(buf, "RMF", 3))
{
AfxMessageBox("Invalid file type.");
return -1;
}
}
// load groups
if (fThisVersion >= 1.0f)
{
file.read((char*) &iSize, sizeof(int));
for ( int i = 0; i < iSize; i++)
{
// just skip vis groups
COldVisGroup oldVisGroup;
file.read((char*) &oldVisGroup, sizeof(COldVisGroup));
}
}
m_Render2DBox.ResetBounds();
// make sure it's a CMapWorld
ReadString(file, buf);
if(strcmp(buf, GetType()))
{
AfxMessageBox("Invalid file type.");
return -1;
}
// load children & local data
if(CMapClass::SerializeRMF(file, fIsStoring) == -1)
goto FatalError;
// load ceditgameclass & CMapClass
if(CEditGameClass::SerializeRMF(file, fIsStoring) == -1)
goto FatalError;
if(fThisVersion < 1.0f)
{
const int old_group_bytes = 134;
file.read((char*) &iSize, sizeof(int));
file.seekg(old_group_bytes * iSize, std::ios::cur);
}
// load paths
if(fThisVersion >= 1.1f)
{
file.read((char*) &iSize, sizeof iSize);
for(int i = 0; i < iSize; i++)
{
CMapPath *pPath = new CMapPath;
pPath->SerializeRMF(file, FALSE);
if(pPath->GetNodeCount() == 0)
{
delete pPath;
continue; // no add dead paths
}
m_Paths.AddToTail(pPath);
}
}
// read camera
if(fThisVersion < 1.4f)
{
float unused[3];
file.read((char*) unused, sizeof(float)*3);
file.read((char*) unused, sizeof(float)*3);
}
if(file.bad())
goto FatalError;
PostloadWorld();
if (g_pGameConfig->GetTextureFormat() == tfVMT)
{
// do batch search and replace of textures from trans.txt if it exists.
char translationFilename[MAX_PATH];
Q_snprintf( translationFilename, sizeof( translationFilename ), "materials/trans.txt" );
FileHandle_t searchReplaceFP = fopen( translationFilename, "r" );
if( searchReplaceFP )
{
CMapDoc::GetActiveMapDoc()->BatchReplaceTextures( searchReplaceFP );
g_pFileSystem->Close( searchReplaceFP );
}
}
}
return nSolids;
FatalError:
CString str;
if(bCorrupt)
{
// file-is-corrupt error
str.Format("The file is corrupt.");
AfxMessageBox(str);
return -1;
}
// OS error.
str.Format("The OS reported an error %s the file: %s",
fIsStoring ? "saving" : "loading", strerror(errno));
AfxMessageBox(str);
return -1;
}