/* =========================================================================== IceTech GPL Source Code Copyright (C) 2026 Justin Marshall This file is part of the IceTech GPL Source Code (?IceTech Source Code?). IceTech Source Code is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. IceTech Source Code is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with IceTech Source Code. If not, see . In addition, the IceTech Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the IceTech Source Code. If not, please request a copy in writing from id Software at the address below. If you have questions concerning this license or the applicable additional terms, you may contact in writing Justin Marshall, justinmarshall20@gmail.com =========================================================================== */ #include "precompiled.h" #pragma hdrstop #ifdef QUAKE4 #include "../../quake4/idLib/Lib.h" #endif #ifdef QUAKE4 #ifdef _XENON #define PACIFIER_UPDATE session->PacifierUpdate() #else #define PACIFIER_UPDATE #endif #endif /* =============== FloatCRC =============== */ ID_INLINE unsigned int FloatCRC(float f) { return *(unsigned int*)&f; } /* =============== StringCRC =============== */ ID_INLINE unsigned int StringCRC(const char* str) { unsigned int i, crc; const unsigned char* ptr; crc = 0; ptr = reinterpret_cast(str); for (i = 0; str[i]; i++) { crc ^= str[i] << (i & 3); } return crc; } /* ================= ComputeAxisBase WARNING : special case behaviour of atan2(y,x) <-> atan(y/x) might not be the same everywhere when x == 0 rotation by (0,RotY,RotZ) assigns X to normal ================= */ static void ComputeAxisBase(const idVec3& normal, idVec3& texS, idVec3& texT) { float RotY, RotZ; idVec3 n; // do some cleaning n[0] = (idMath::Fabs(normal[0]) < 1e-6f) ? 0.0f : normal[0]; n[1] = (idMath::Fabs(normal[1]) < 1e-6f) ? 0.0f : normal[1]; n[2] = (idMath::Fabs(normal[2]) < 1e-6f) ? 0.0f : normal[2]; #ifdef QUAKE4 RotY = -idMath::ATan(n[2], idMath::Sqrt(n[1] * n[1] + n[0] * n[0])); RotZ = idMath::ATan(n[1], n[0]); #else RotY = -atan2(n[2], idMath::Sqrt(n[1] * n[1] + n[0] * n[0])); RotZ = atan2(n[1], n[0]); #endif // rotate (0,1,0) and (0,0,1) to compute texS and texT #ifdef QUAKE4 texS[0] = -idMath::Sin(RotZ); texS[1] = idMath::Cos(RotZ); texS[2] = 0; // the texT vector is along -Z ( T texture coordinates axis ) texT[0] = -idMath::Sin(RotY) * idMath::Cos(RotZ); texT[1] = -idMath::Sin(RotY) * idMath::Sin(RotZ); texT[2] = -idMath::Cos(RotY); #else texS[0] = -sin(RotZ); texS[1] = cos(RotZ); texS[2] = 0; // the texT vector is along -Z ( T texture coorinates axis ) texT[0] = -sin(RotY) * cos(RotZ); texT[1] = -sin(RotY) * sin(RotZ); texT[2] = -cos(RotY); #endif } /* ================= Quake 1 .map compatibility helpers Quake 1 and Valve 220 maps use the same outer entity/brush braces as the legacy Quake 3 parser, but their texture projection data must be interpreted as Quake axial vectors instead of the existing Quake 3 fallback texture matrix. The detector below enables this mode only for files that advertise Quake 1 or Valve 220 metadata, so the original Quake 3 fallback path remains unchanged. ================= */ static bool g_parseQuake1Map = false; typedef struct quake1MapTextureSizeCacheEntry_s { idStr name; int width; int height; } quake1MapTextureSizeCacheEntry_t; static idList g_quake1MapTextureSizeCache; /* ================= Quake1MapTokenToFloat ================= */ static bool Quake1MapTokenToFloat(const idToken& token, float& value) { char* end; const char* str; str = token.c_str(); if (!str || !str[0]) { return false; } value = (float)strtod(str, &end); return end != str && *end == '\0'; } /* ================= Quake1MapReadFloatToken idLexer may emit a negative number as either one token, "-1", or as a punctuation sign followed by a number, "-" "1". Quake .map texture scales commonly use negative values, so parse both forms from a token list. ================= */ static bool Quake1MapReadFloatToken(const idList& tokens, int& index, float& value) { float parsed; float sign; if (index >= tokens.Num()) { return false; } sign = 1.0f; if (tokens[index] == "-" || tokens[index] == "+") { if (tokens[index] == "-") { sign = -1.0f; } index++; if (index >= tokens.Num()) { return false; } } if (!Quake1MapTokenToFloat(tokens[index], parsed)) { return false; } value = sign * parsed; index++; return true; } /* ================= Quake1MapTokensAreFloatSuffix ================= */ static bool Quake1MapTokensAreFloatSuffix(const idList& tokens, int first, int minimumFloats) { int index, count; float value; index = first; count = 0; while (index < tokens.Num()) { if (!Quake1MapReadFloatToken(tokens, index, value)) { return false; } count++; } return count >= minimumFloats; } /* ================= JoinQuake1MapTextureName Quake texture names may begin with punctuation, for example +0WALL or *WATER. Collecting the whole texture-name token run avoids treating those prefixes as separate syntax. ================= */ static void JoinQuake1MapTextureName(const idList& tokens, int first, int count, idStr& material) { int i; material.Clear(); for (i = 0; i < count; i++) { material += tokens[first + i].c_str(); } } /* ================= FindQuake1MapStandardTextureParms Classic Quake faces end with: textureName offsetX offsetY rotation scaleX scaleY [optional numeric flags] Return the first token of that numeric suffix. ================= */ static int FindQuake1MapStandardTextureParms(const idList& tokens) { int i; for (i = 1; i < tokens.Num(); i++) { if (Quake1MapTokensAreFloatSuffix(tokens, i, 5)) { return i; } } return -1; } /* ================= ParseQuake1MapValve220Vector ================= */ static bool ParseQuake1MapValve220Vector(const idList& tokens, int& index, idVec3& axis, float& offset) { float v[4]; int i; if (index >= tokens.Num() || tokens[index] != "[") { return false; } index++; for (i = 0; i < 4; i++) { if (!Quake1MapReadFloatToken(tokens, index, v[i])) { return false; } } if (index >= tokens.Num() || tokens[index] != "]") { return false; } index++; axis[0] = v[0]; axis[1] = v[1]; axis[2] = v[2]; offset = v[3]; return true; } /* ================= Quake1MapTextureAxisFromPlane This is the historical dominant-axis basis used by Quake-family map compilers for the classic texture tuple. ================= */ static void Quake1MapTextureAxisFromPlane(const idVec3& normal, idVec3& texS, idVec3& texT) { static const float baseAxis[18][3] = { { 0, 0, 1 }, { 1, 0, 0 }, { 0, -1, 0 }, { 0, 0, -1 }, { 1, 0, 0 }, { 0, -1, 0 }, { 1, 0, 0 }, { 0, 1, 0 }, { 0, 0, -1 }, { -1, 0, 0 }, { 0, 1, 0 }, { 0, 0, -1 }, { 0, 1, 0 }, { 1, 0, 0 }, { 0, 0, -1 }, { 0, -1, 0 }, { 1, 0, 0 }, { 0, 0, -1 } }; int i, bestAxis; float best, dot; best = 0.0f; bestAxis = 0; for (i = 0; i < 6; i++) { dot = normal[0] * baseAxis[i * 3][0] + normal[1] * baseAxis[i * 3][1] + normal[2] * baseAxis[i * 3][2]; if (dot > best) { best = dot; bestAxis = i; } } for (i = 0; i < 3; i++) { texS[i] = baseAxis[bestAxis * 3 + 1][i]; texT[i] = baseAxis[bestAxis * 3 + 2][i]; } } /* ================= RotateQuake1MapTextureAxes ================= */ static void RotateQuake1MapTextureAxes(idVec3 axes[2], float rotate) { int i, sv, tv; float angle, sinv, cosv, ns, nt; angle = rotate * (3.14159265358979323846f / 180.0f); if (rotate == 0.0f) { sinv = 0.0f; cosv = 1.0f; } else if (rotate == 90.0f) { sinv = 1.0f; cosv = 0.0f; } else if (rotate == 180.0f) { sinv = 0.0f; cosv = -1.0f; } else if (rotate == 270.0f) { sinv = -1.0f; cosv = 0.0f; } else { #ifdef QUAKE4 sinv = idMath::Sin(angle); cosv = idMath::Cos(angle); #else sinv = sin(angle); cosv = cos(angle); #endif } if (axes[0][0]) { sv = 0; } else if (axes[0][1]) { sv = 1; } else { sv = 2; } if (axes[1][0]) { tv = 0; } else if (axes[1][1]) { tv = 1; } else { tv = 2; } for (i = 0; i < 2; i++) { ns = cosv * axes[i][sv] - sinv * axes[i][tv]; nt = sinv * axes[i][sv] + cosv * axes[i][tv]; axes[i][sv] = ns; axes[i][tv] = nt; } } /* ================= ClearQuake1MapTextureSizeCache ================= */ static void ClearQuake1MapTextureSizeCache() { g_quake1MapTextureSizeCache.Clear(); } /* ================= GetQuake1MapTextureDimensions Quake texture shifts and scales are stored in texture pixels. Convert them to Doom 3 brushDef UV space by normalizing S by the real texture width and T by the real texture height. renderSystem->LoadImage accepts names without an extension and resolves the actual image file. Missing textures fall back to 32x32, preserving the old hardcoded behavior for unresolved assets. ================= */ static bool GetQuake1MapTextureDimensions(const idStr& materialName, int& width, int& height) { int i; byte* pic; ID_TIME_T timestamp; quake1MapTextureSizeCacheEntry_t entry; for (i = 0; i < g_quake1MapTextureSizeCache.Num(); i++) { if (!g_quake1MapTextureSizeCache[i].name.Icmp(materialName)) { width = g_quake1MapTextureSizeCache[i].width; height = g_quake1MapTextureSizeCache[i].height; return width > 0 && height > 0; } } width = 0; height = 0; pic = NULL; timestamp = 0; if (renderSystem) { renderSystem->LoadImage(materialName.c_str(), &pic, &width, &height, ×tamp, false); } if (pic) { Mem_Free(pic); } if (width <= 0 || height <= 0) { width = 32; height = 32; } entry.name = materialName; entry.width = width; entry.height = height; g_quake1MapTextureSizeCache.Append(entry); return width > 0 && height > 0; } /* ================= GetQuake1MapTextureUnitScales ================= */ static void GetQuake1MapTextureUnitScales(const idStr& materialName, float& sUnitScale, float& tUnitScale) { int textureWidth, textureHeight; GetQuake1MapTextureDimensions(materialName, textureWidth, textureHeight); sUnitScale = 1.0f / (float)textureWidth; tUnitScale = 1.0f / (float)textureHeight; } /* ================= BuildQuake1MapTextureMatrix Convert Quake world-space texture vectors into the brushDef texture matrix basis used by idMapBrushSide::GetTextureVectors. Any component along the plane normal is folded into the constant offset because all points on the face share the same normal distance. ================= */ static void BuildQuake1MapTextureMatrix(const idPlane& plane, const idVec3& origin, const idVec3& sVecIn, float sOffsetIn, const idVec3& tVecIn, float tOffsetIn, idVec3 texMat[2]) { idVec3 normal, texX, texY, sVec, tVec; float sNormal, tNormal, planeDistance; int i; normal = plane.Normal(); ComputeAxisBase(normal, texX, texY); sNormal = sVecIn * normal; tNormal = tVecIn * normal; for (i = 0; i < 3; i++) { sVec[i] = sVecIn[i] - normal[i] * sNormal; tVec[i] = tVecIn[i] - normal[i] * tNormal; } planeDistance = -plane[3] + (origin * normal); texMat[0][0] = sVec * texX; texMat[0][1] = sVec * texY; texMat[0][2] = sOffsetIn + sNormal * planeDistance; texMat[1][0] = tVec * texX; texMat[1][1] = tVec * texY; texMat[1][2] = tOffsetIn + tNormal * planeDistance; } /* ================= SetQuake1MapMaterial ================= */ static void SetQuake1MapMaterial(idStr& out, const idStr& material) { // Keep the old id map convention for legacy texture names. out = "textures/"; out += material; } /* ================= ParseQuake1MapTextureInfo Supports both classic Quake 1 faces: TEXTURE offsetX offsetY rotation scaleX scaleY and Valve 220 faces: TEXTURE [ ux uy uz offsetX ] [ vx vy vz offsetY ] rotation scaleX scaleY ================= */ static bool ParseQuake1MapTextureInfo(const idList& tokens, const idPlane& plane, const idVec3& origin, idStr& materialOut, idVec3 texMat[2]) { idStr material; int i, j, index, parmIndex; float rotate, scale[2], shift[2], sUnitScale, tUnitScale; idVec3 axes[2]; if (tokens.Num() < 6) { return false; } for (i = 0; i < tokens.Num(); i++) { if (tokens[i] == "[") { JoinQuake1MapTextureName(tokens, 0, i, material); if (material.Length() == 0) { return false; } index = i; if (!ParseQuake1MapValve220Vector(tokens, index, axes[0], shift[0]) || !ParseQuake1MapValve220Vector(tokens, index, axes[1], shift[1]) || !Quake1MapReadFloatToken(tokens, index, rotate) || !Quake1MapReadFloatToken(tokens, index, scale[0]) || !Quake1MapReadFloatToken(tokens, index, scale[1])) { return false; } if (scale[0] == 0.0f) { scale[0] = 1.0f; } if (scale[1] == 0.0f) { scale[1] = 1.0f; } SetQuake1MapMaterial(materialOut, material); GetQuake1MapTextureUnitScales(materialOut, sUnitScale, tUnitScale); // Valve 220 stores the texture axes directly. The rotation token is an // editor compatibility field and should not be applied again here. for (j = 0; j < 3; j++) { axes[0][j] *= sUnitScale / scale[0]; axes[1][j] *= tUnitScale / scale[1]; } shift[0] *= sUnitScale; shift[1] *= tUnitScale; BuildQuake1MapTextureMatrix(plane, origin, axes[0], shift[0], axes[1], shift[1], texMat); return true; } } parmIndex = FindQuake1MapStandardTextureParms(tokens); if (parmIndex <= 0) { return false; } JoinQuake1MapTextureName(tokens, 0, parmIndex, material); index = parmIndex; if (material.Length() == 0 || !Quake1MapReadFloatToken(tokens, index, shift[0]) || !Quake1MapReadFloatToken(tokens, index, shift[1]) || !Quake1MapReadFloatToken(tokens, index, rotate) || !Quake1MapReadFloatToken(tokens, index, scale[0]) || !Quake1MapReadFloatToken(tokens, index, scale[1])) { return false; } if (scale[0] == 0.0f) { scale[0] = 1.0f; } if (scale[1] == 0.0f) { scale[1] = 1.0f; } SetQuake1MapMaterial(materialOut, material); GetQuake1MapTextureUnitScales(materialOut, sUnitScale, tUnitScale); Quake1MapTextureAxisFromPlane(plane.Normal(), axes[0], axes[1]); RotateQuake1MapTextureAxes(axes, rotate); for (i = 0; i < 3; i++) { axes[0][i] *= sUnitScale / scale[0]; axes[1][i] *= tUnitScale / scale[1]; } shift[0] *= sUnitScale; shift[1] *= tUnitScale; BuildQuake1MapTextureMatrix(plane, origin, axes[0], shift[0], axes[1], shift[1], texMat); return true; } /* ================= DetectQuake1MapFile This deliberately avoids classifying every old brace-only map as Quake 1, because that would change existing Quake 3 fallback behavior. Instead it requires common Quake 1/Valve220 signals: a wad key, mapversion 220, TrenchBroom texture metadata, or Valve 220 bracketed face axes. ================= */ static bool DetectQuake1MapFile(const idStr& fullName, bool osPath) { #ifdef QUAKE4 idAutoPtr lexer(LexerFactory::MakeLexer(LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES)); Lexer& detectSrc(*lexer); #else idLexer detectSrc(LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES); #endif idToken token; idToken value; detectSrc.LoadFile(fullName, osPath); if (!detectSrc.IsLoaded()) { return false; } // idTech map files advertise their format with a top-level Version token; // Quake 1 maps do not. if (detectSrc.CheckTokenString("Version")) { return false; } while (detectSrc.ReadToken(&token)) { if (token == "[") { return true; } if (!token.Icmp("wad") || !token.Icmp("_tb_textures")) { return true; } if (!token.Icmp("mapversion")) { if (detectSrc.ReadTokenOnLine(&value) && atoi(value.c_str()) == 220) { return true; } } } return false; } #ifdef QUAKE4 /* =============== idMapFile::~idMapFile =============== */ idMapFile::~idMapFile(void) { entities.DeleteContents(true); mExportEntities.DeleteContents(true); if (cvarSystem->GetCVarBool("developer")) { common->Printf("^2done with .map file ^0%s\n", name.c_str()); } } #endif /* ================= idMapBrushSide::GetTextureVectors ================= */ void idMapBrushSide::GetTextureVectors(idVec4 v[2]) const { int i; idVec3 texX, texY; ComputeAxisBase(plane.Normal(), texX, texY); for (i = 0; i < 2; i++) { v[i][0] = texX[0] * texMat[i][0] + texY[0] * texMat[i][1]; v[i][1] = texX[1] * texMat[i][0] + texY[1] * texMat[i][1]; v[i][2] = texX[2] * texMat[i][0] + texY[2] * texMat[i][1]; v[i][3] = texMat[i][2] + (origin * v[i].ToVec3()); } } /* ================= idMapPatch::Parse ================= */ #ifdef QUAKE4 idMapPatch* idMapPatch::Parse(Lexer& src, const idVec3& origin, bool patchDef3, int version) { TIME_THIS_SCOPE(__FUNCLINE__); #else idMapPatch* idMapPatch::Parse(idLexer & src, const idVec3 & origin, bool patchDef3, float version) { #endif float info[7]; idDrawVert* vert; idToken token; int i, j; if (!src.ExpectTokenString("{")) { return NULL; } // read the material (we had an implicit 'textures/' in the old format...) if (!src.ReadToken(&token)) { src.Error("idMapPatch::Parse: unexpected EOF"); return NULL; } // Parse it if (patchDef3) { if (!src.Parse1DMatrix(7, info)) { src.Error("idMapPatch::Parse: unable to Parse patchDef3 info"); return NULL; } } else { if (!src.Parse1DMatrix(5, info)) { src.Error("idMapPatch::Parse: unable to parse patchDef2 info"); return NULL; } } idMapPatch* patch = new idMapPatch(info[0], info[1]); patch->SetSize(info[0], info[1]); #ifdef QUAKE4 if (version < 2) { #else if (version < 2.0f) { #endif patch->SetMaterial("textures/" + token); } else { patch->SetMaterial(token); } if (patchDef3) { patch->SetHorzSubdivisions(info[2]); patch->SetVertSubdivisions(info[3]); patch->SetExplicitlySubdivided(true); } if (patch->GetWidth() < 0 || patch->GetHeight() < 0) { src.Error("idMapPatch::Parse: bad size"); delete patch; return NULL; } // these were written out in the wrong order, IMHO if (!src.ExpectTokenString("(")) { src.Error("idMapPatch::Parse: bad patch vertex data"); delete patch; return NULL; } for (j = 0; j < patch->GetWidth(); j++) { if (!src.ExpectTokenString("(")) { src.Error("idMapPatch::Parse: bad vertex row data"); delete patch; return NULL; } for (i = 0; i < patch->GetHeight(); i++) { float v[5]; if (!src.Parse1DMatrix(5, v)) { src.Error("idMapPatch::Parse: bad vertex column data"); delete patch; return NULL; } vert = &((*patch)[i * patch->GetWidth() + j]); vert->xyz[0] = v[0] - origin[0]; vert->xyz[1] = v[1] - origin[1]; vert->xyz[2] = v[2] - origin[2]; vert->st[0] = v[3]; vert->st[1] = v[4]; } if (!src.ExpectTokenString(")")) { delete patch; src.Error("idMapPatch::Parse: unable to parse patch control points"); return NULL; } } #ifdef QUAKE4 if (!src.ExpectTokenString(")") || !src.ExpectTokenString("}") || !src.ExpectTokenString("}")) { src.Error("idMapPatch::Parse: unable to parse patch control points, no closure"); delete patch; return NULL; } #else if (!src.ExpectTokenString(")")) { src.Error("idMapPatch::Parse: unable to parse patch control points, no closure"); delete patch; return NULL; } // read any key/value pairs while (src.ReadToken(&token)) { if (token == "}") { src.ExpectTokenString("}"); break; } if (token.type == TT_STRING) { idStr key = token; src.ExpectTokenType(TT_STRING, 0, &token); patch->epairs.Set(key, token); } } #endif return patch; } /* ============ idMapPatch::Write ============ */ bool idMapPatch::Write(idFile * fp, int primitiveNum, const idVec3 & origin) const { int i, j; const idDrawVert* v; if (GetExplicitlySubdivided()) { fp->WriteFloatString("// primitive %d\n{\n patchDef3\n {\n", primitiveNum); fp->WriteFloatString(" \"%s\"\n ( %d %d %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight(), GetHorzSubdivisions(), GetVertSubdivisions()); } else { fp->WriteFloatString("// primitive %d\n{\n patchDef2\n {\n", primitiveNum); fp->WriteFloatString(" \"%s\"\n ( %d %d 0 0 0 )\n", GetMaterial(), GetWidth(), GetHeight()); } fp->WriteFloatString(" (\n"); for (i = 0; i < GetWidth(); i++) { fp->WriteFloatString(" ( "); for (j = 0; j < GetHeight(); j++) { v = &verts[j * GetWidth() + i]; fp->WriteFloatString(" ( %f %f %f %f %f )", v->xyz[0] + origin[0], v->xyz[1] + origin[1], v->xyz[2] + origin[2], v->st[0], v->st[1]); } fp->WriteFloatString(" )\n"); } fp->WriteFloatString(" )\n }\n}\n"); return true; } #ifdef QUAKE4 /* =============== idMapPatch::AdjustOrigin =============== */ void idMapPatch::AdjustOrigin(idVec3 & delta) { TranslateSelf(delta); } #endif /* =============== idMapPatch::GetGeometryCRC =============== */ unsigned int idMapPatch::GetGeometryCRC(void) const { int i, j; unsigned int crc; crc = GetHorzSubdivisions() ^ GetVertSubdivisions(); for (i = 0; i < GetWidth(); i++) { for (j = 0; j < GetHeight(); j++) { crc ^= FloatCRC(verts[j * GetWidth() + i].xyz.x); crc ^= FloatCRC(verts[j * GetWidth() + i].xyz.y); crc ^= FloatCRC(verts[j * GetWidth() + i].xyz.z); } } crc ^= StringCRC(GetMaterial()); return crc; } /* ================= idMapBrush::Parse ================= */ #ifdef QUAKE4 idMapBrush* idMapBrush::Parse(Lexer & src, const idVec3 & origin, bool newFormat, int version) { TIME_THIS_SCOPE(__FUNCLINE__); #else idMapBrush* idMapBrush::Parse(idLexer & src, const idVec3 & origin, bool newFormat, float version) { #endif int i; idVec3 planepts[3]; idToken token; idList sides; idMapBrushSide* side; idDict epairs; if (!src.ExpectTokenString("{")) { return NULL; } do { if (!src.ReadToken(&token)) { src.Error("idMapBrush::Parse: unexpected EOF"); sides.DeleteContents(true); return NULL; } if (token == "}") { break; } // here we may have to jump over brush epairs ( only used in editor ) do { // if token is a brace if (token == "(") { break; } // the token should be a key string for a key/value pair if (token.type != TT_STRING) { src.Error("idMapBrush::Parse: unexpected %s, expected ( or epair key string", token.c_str()); sides.DeleteContents(true); return NULL; } idStr key = token; if (!src.ReadTokenOnLine(&token) || token.type != TT_STRING) { src.Error("idMapBrush::Parse: expected epair value string not found"); sides.DeleteContents(true); return NULL; } epairs.Set(key, token); // try to read the next key if (!src.ReadToken(&token)) { src.Error("idMapBrush::Parse: unexpected EOF"); sides.DeleteContents(true); return NULL; } } while (1); src.UnreadToken(&token); side = new idMapBrushSide(); sides.Append(side); if (newFormat) { if (!src.Parse1DMatrix(4, side->plane.ToFloatPtr())) { src.Error("idMapBrush::Parse: unable to read brush side plane definition"); sides.DeleteContents(true); return NULL; } } else { // read the three point plane definition if (!src.Parse1DMatrix(3, planepts[0].ToFloatPtr()) || !src.Parse1DMatrix(3, planepts[1].ToFloatPtr()) || !src.Parse1DMatrix(3, planepts[2].ToFloatPtr())) { src.Error("idMapBrush::Parse: unable to read brush side plane definition"); sides.DeleteContents(true); return NULL; } planepts[0] -= origin; planepts[1] -= origin; planepts[2] -= origin; side->plane.FromPoints(planepts[0], planepts[1], planepts[2]); } // read the texture matrix // this is odd, because the texmat is 2D relative to default planar texture axis if (!src.Parse2DMatrix(2, 3, side->texMat[0].ToFloatPtr())) { src.Error("idMapBrush::Parse: unable to read brush side texture matrix"); sides.DeleteContents(true); return NULL; } side->origin = origin; // read the material if (!src.ReadTokenOnLine(&token)) { src.Error("idMapBrush::Parse: unable to read brush side material"); sides.DeleteContents(true); return NULL; } // we had an implicit 'textures/' in the old format... #ifdef QUAKE4 if (version < 2) { #else if (version < 2.0f) { #endif side->material = "textures/" + token; } else { side->material = token; } #ifdef QUAKE4 // Make sure the material is parsed while the map is loading. declManager->FindMaterial(token); // Quake 4 removed the trailing Q2 override tokens in version 3 maps. if (version < 3) { #endif // Q2 allowed override of default flags and values, but we don't any more if (src.ReadTokenOnLine(&token)) { if (src.ReadTokenOnLine(&token)) { if (src.ReadTokenOnLine(&token)) { } } } #ifdef QUAKE4 } #endif } while (1); if (!src.ExpectTokenString("}")) { sides.DeleteContents(true); return NULL; } idMapBrush* brush = new idMapBrush(); for (i = 0; i < sides.Num(); i++) { brush->AddSide(sides[i]); } brush->epairs = epairs; return brush; } /* ================= idMapBrush::ParseQ3 ================= */ #ifdef QUAKE4 idMapBrush* idMapBrush::ParseQ3(Lexer & src, const idVec3 & origin) { #else idMapBrush* idMapBrush::ParseQ3(idLexer & src, const idVec3 & origin) { #endif if (g_parseQuake1Map) { int i; idVec3 planepts[3]; idToken token; idList textureTokens; idList sides; idMapBrushSide* side; idDict epairs; do { if (src.CheckTokenString("}")) { break; } side = new idMapBrushSide(); sides.Append(side); // read the three point plane definition if (!src.Parse1DMatrix(3, planepts[0].ToFloatPtr()) || !src.Parse1DMatrix(3, planepts[1].ToFloatPtr()) || !src.Parse1DMatrix(3, planepts[2].ToFloatPtr())) { src.Error("idMapBrush::ParseQ3: unable to read Quake 1 brush side plane definition"); sides.DeleteContents(true); return NULL; } planepts[0] -= origin; planepts[1] -= origin; planepts[2] -= origin; side->plane.FromPoints(planepts[0], planepts[1], planepts[2]); side->origin = origin; // Read the rest of the face line. Quake texture names may begin with // punctuation, so keep the whole line and identify the numeric suffix. textureTokens.Clear(); while (src.ReadTokenOnLine(&token)) { textureTokens.Append(token); } if (!ParseQuake1MapTextureInfo(textureTokens, side->plane, origin, side->material, side->texMat)) { src.Error("idMapBrush::ParseQ3: unable to read Quake 1 brush side texture info"); sides.DeleteContents(true); return NULL; } } while (1); idMapBrush* brush = new idMapBrush(); for (i = 0; i < sides.Num(); i++) { brush->AddSide(sides[i]); } brush->epairs = epairs; return brush; } int i, shift[2], rotate; float scale[2]; idVec3 planepts[3]; idToken token; idList sides; idMapBrushSide* side; idDict epairs; do { if (src.CheckTokenString("}")) { break; } side = new idMapBrushSide(); sides.Append(side); // read the three point plane definition if (!src.Parse1DMatrix(3, planepts[0].ToFloatPtr()) || !src.Parse1DMatrix(3, planepts[1].ToFloatPtr()) || !src.Parse1DMatrix(3, planepts[2].ToFloatPtr())) { src.Error("idMapBrush::ParseQ3: unable to read brush side plane definition"); sides.DeleteContents(true); return NULL; } planepts[0] -= origin; planepts[1] -= origin; planepts[2] -= origin; side->plane.FromPoints(planepts[0], planepts[1], planepts[2]); // read the material if (!src.ReadTokenOnLine(&token)) { src.Error("idMapBrush::ParseQ3: unable to read brush side material"); sides.DeleteContents(true); return NULL; } // we have an implicit 'textures/' in the old format side->material = "textures/" + token; // read the texture shift, rotate and scale shift[0] = src.ParseInt(); shift[1] = src.ParseInt(); rotate = src.ParseInt(); scale[0] = src.ParseFloat(); scale[1] = src.ParseFloat(); side->texMat[0] = idVec3(0.03125f, 0.0f, 0.0f); side->texMat[1] = idVec3(0.0f, 0.03125f, 0.0f); side->origin = origin; // Q2 allowed override of default flags and values, but we don't any more if (src.ReadTokenOnLine(&token)) { if (src.ReadTokenOnLine(&token)) { if (src.ReadTokenOnLine(&token)) { } } } } while (1); idMapBrush* brush = new idMapBrush(); for (i = 0; i < sides.Num(); i++) { brush->AddSide(sides[i]); } brush->epairs = epairs; return brush; } /* ============ idMapBrush::Write ============ */ bool idMapBrush::Write(idFile * fp, int primitiveNum, const idVec3 & origin) const { int i; idMapBrushSide* side; fp->WriteFloatString("// primitive %d\n{\n brushDef3\n {\n", primitiveNum); // write brush epairs for (i = 0; i < epairs.GetNumKeyVals(); i++) { fp->WriteFloatString(" \"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); } // write brush sides for (i = 0; i < GetNumSides(); i++) { side = GetSide(i); fp->WriteFloatString(" ( %f %f %f %f ) ", side->plane[0], side->plane[1], side->plane[2], side->plane[3]); #ifdef QUAKE4 fp->WriteFloatString("( ( %f %f %f ) ( %f %f %f ) ) \"%s\"\n", #else fp->WriteFloatString("( ( %f %f %f ) ( %f %f %f ) ) \"%s\" 0 0 0\n", #endif side->texMat[0][0], side->texMat[0][1], side->texMat[0][2], side->texMat[1][0], side->texMat[1][1], side->texMat[1][2], side->material.c_str()); } fp->WriteFloatString(" }\n}\n"); return true; } /* =============== idMapBrush::GetGeometryCRC =============== */ unsigned int idMapBrush::GetGeometryCRC(void) const { int i, j; idMapBrushSide* mapSide; unsigned int crc; crc = 0; for (i = 0; i < GetNumSides(); i++) { mapSide = GetSide(i); for (j = 0; j < 4; j++) { crc ^= FloatCRC(mapSide->GetPlane()[j]); } crc ^= StringCRC(mapSide->GetMaterial()); } return crc; } #ifdef QUAKE4 /* =============== SarrusDet =============== */ static float SarrusDet(const idVec3 & a, const idVec3 & b, const idVec3 & c) { return (float)a[0] * (float)b[1] * (float)c[2] + (float)b[0] * (float)c[1] * (float)a[2] + (float)c[0] * (float)a[1] * (float)b[2] - (float)c[0] * (float)b[1] * (float)a[2] - (float)a[1] * (float)b[0] * (float)c[2] - (float)a[0] * (float)b[2] * (float)c[1]; } /* =============== idMapBrush::AdjustOrigin =============== */ void idMapBrush::AdjustOrigin(idVec3 & delta) { for (int i = 0; i < GetNumSides(); i++) { idMapBrushSide* mapSide = GetSide(i); mapSide->SetPlane(mapSide->GetPlane().Translate(delta)); idVec3 texS, texT; ComputeAxisBase(mapSide->GetPlane().Normal(), texS, texT); float tx = delta * texS; float ty = delta * texT; idVec3 M[3]; M[0][0] = tx; M[0][1] = 1.0f + tx; M[0][2] = tx; M[1][0] = ty; M[1][1] = ty; M[1][2] = 1.0f + ty; M[2][0] = 1.0f; M[2][1] = 1.0f; M[2][2] = 1.0f; idVec3 tm[2]; mapSide->GetTextureMatrix(tm[0], tm[1]); idVec3 D[2]; D[0][0] = tm[0][2]; D[0][1] = tm[0][0] + tm[0][2]; D[0][2] = tm[0][1] + tm[0][2]; D[1][0] = tm[1][2]; D[1][1] = tm[1][0] + tm[1][2]; D[1][2] = tm[1][1] + tm[1][2]; float det = SarrusDet(M[0], M[1], M[2]); if (det != 0.0f) { tm[0][0] = SarrusDet(D[0], M[1], M[2]) / det; tm[0][1] = SarrusDet(M[0], D[0], M[2]) / det; tm[0][2] = SarrusDet(M[0], M[1], D[0]) / det; tm[1][0] = SarrusDet(D[1], M[1], M[2]) / det; tm[1][1] = SarrusDet(M[0], D[1], M[2]) / det; tm[1][2] = SarrusDet(M[0], M[1], D[1]) / det; mapSide->SetTextureMatrix(tm); } } } #endif /* ================ idMapEntity::Parse ================ */ #ifdef QUAKE4 idMapEntity* idMapEntity::Parse(Lexer & src, bool worldSpawn, int version) { TIME_THIS_SCOPE(__FUNCLINE__); #else idMapEntity* idMapEntity::Parse(idLexer & src, bool worldSpawn, float version) { #endif idToken token; idMapEntity* mapEnt; idMapPatch* mapPatch; idMapBrush* mapBrush; bool worldent; idVec3 origin; double v1, v2, v3; if (!src.ReadToken(&token)) { return NULL; } if (token != "{") { src.Error("idMapEntity::Parse: { not found, found %s", token.c_str()); return NULL; } mapEnt = new idMapEntity(); if (worldSpawn) { mapEnt->primitives.Resize(1024, 256); } origin.Zero(); worldent = false; do { if (!src.ReadToken(&token)) { src.Error("idMapEntity::Parse: EOF without closing brace"); return NULL; } if (token == "}") { break; } if (token == "{") { // parse a brush or patch if (!src.ReadToken(&token)) { src.Error("idMapEntity::Parse: unexpected EOF"); return NULL; } if (worldent) { origin.Zero(); } // if is it a brush: brush, brushDef, brushDef2, brushDef3 if (token.Icmpn("brush", 5) == 0) { mapBrush = idMapBrush::Parse(src, origin, (!token.Icmp("brushDef2") || !token.Icmp("brushDef3")), version); if (!mapBrush) { return NULL; } mapEnt->AddPrimitive(mapBrush); } // if is it a patch: patchDef2, patchDef3 else if (token.Icmpn("patch", 5) == 0) { mapPatch = idMapPatch::Parse(src, origin, !token.Icmp("patchDef3"), version); if (!mapPatch) { return NULL; } mapEnt->AddPrimitive(mapPatch); } // assume it's a brush in Q3 or older style else { src.UnreadToken(&token); mapBrush = idMapBrush::ParseQ3(src, origin); if (!mapBrush) { return NULL; } mapEnt->AddPrimitive(mapBrush); } } else { idStr key, value; // parse a key / value pair key = token; src.ReadTokenOnLine(&token); value = token; // strip trailing spaces that sometimes get accidentally // added in the editor value.StripTrailingWhitespace(); key.StripTrailingWhitespace(); mapEnt->epairs.Set(key, value); if (!idStr::Icmp(key, "origin")) { // scanf into doubles, then assign, so it is idVec size independent v1 = v2 = v3 = 0; sscanf(value, "%lf %lf %lf", &v1, &v2, &v3); origin.x = v1; origin.y = v2; origin.z = v3; } else if (!idStr::Icmp(key, "classname") && !idStr::Icmp(value, "worldspawn")) { worldent = true; } } } while (1); return mapEnt; } /* ============ idMapEntity::Write ============ */ bool idMapEntity::Write(idFile * fp, int entityNum) const { int i; idMapPrimitive* mapPrim; idVec3 origin; fp->WriteFloatString("// entity %d\n{\n", entityNum); // write entity epairs for (i = 0; i < epairs.GetNumKeyVals(); i++) { fp->WriteFloatString("\"%s\" \"%s\"\n", epairs.GetKeyVal(i)->GetKey().c_str(), epairs.GetKeyVal(i)->GetValue().c_str()); } epairs.GetVector("origin", "0 0 0", origin); // write pritimives for (i = 0; i < GetNumPrimitives(); i++) { mapPrim = GetPrimitive(i); switch (mapPrim->GetType()) { case idMapPrimitive::TYPE_BRUSH: static_cast(mapPrim)->Write(fp, i, origin); break; case idMapPrimitive::TYPE_PATCH: static_cast(mapPrim)->Write(fp, i, origin); break; } } fp->WriteFloatString("}\n"); return true; } /* =============== idMapEntity::RemovePrimitiveData =============== */ void idMapEntity::RemovePrimitiveData() { primitives.DeleteContents(true); } /* =============== idMapEntity::GetGeometryCRC =============== */ unsigned int idMapEntity::GetGeometryCRC(void) const { int i; unsigned int crc; idMapPrimitive* mapPrim; crc = 0; for (i = 0; i < GetNumPrimitives(); i++) { mapPrim = GetPrimitive(i); switch (mapPrim->GetType()) { case idMapPrimitive::TYPE_BRUSH: crc ^= static_cast(mapPrim)->GetGeometryCRC(); #ifdef QUAKE4 if (epairs.GetString("model")) { crc ^= StringCRC(epairs.GetString("model")); } #endif break; case idMapPrimitive::TYPE_PATCH: crc ^= static_cast(mapPrim)->GetGeometryCRC(); #ifdef QUAKE4 if (epairs.GetString("model")) { crc ^= StringCRC(epairs.GetString("model")); } #endif break; } } return crc; } /* =============== idMapFile::Parse =============== */ bool idMapFile::Parse(const char* filename, bool ignoreRegion, bool osPath) { // no string concatenation for epairs and allow path names for materials #ifdef QUAKE4 idAutoPtr lexer(LexerFactory::MakeLexer(LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES)); Lexer& src(*lexer); TIME_THIS_SCOPE(__FUNCLINE__); #else idLexer src(LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES); #endif idToken token; idStr fullName; idMapEntity* mapEnt; #ifndef QUAKE4 int i, j, k; #endif name = filename; name.StripFileExtension(); fullName = name; hasPrimitiveData = false; g_parseQuake1Map = false; ClearQuake1MapTextureSizeCache(); #ifdef QUAKE4 mHasExportEntities = false; mHasFuncGroups = false; mHasBeenResolved = false; #endif if (!ignoreRegion) { // try loading a .reg file first fullName.SetFileExtension("reg"); src.LoadFile(fullName, osPath); } if (!src.IsLoaded()) { #ifdef QUAKE4 PACIFIER_UPDATE; #endif // now try a .map file fullName.SetFileExtension("map"); src.LoadFile(fullName, osPath); if (!src.IsLoaded()) { // didn't get anything at all return false; } } version = OLD_MAP_VERSION; fileTime = src.GetFileTime(); entities.DeleteContents(true); g_parseQuake1Map = DetectQuake1MapFile(fullName, osPath); if (src.CheckTokenString("Version")) { src.ReadTokenOnLine(&token); #ifdef QUAKE4 version = token.GetIntValue(); #else version = token.GetFloatValue(); #endif } while (1) { #ifdef QUAKE4 PACIFIER_UPDATE; #endif mapEnt = idMapEntity::Parse(src, (entities.Num() == 0), version); if (!mapEnt) { break; } #ifdef QUAKE4 if (!mHasFuncGroups && !idStr::Icmp(mapEnt->epairs.GetString("classname"), "func_group")) { mHasFuncGroups = true; } #endif entities.Append(mapEnt); } #ifdef QUAKE4 PACIFIER_UPDATE; ParseExport(filename, osPath); #endif SetGeometryCRC(); #ifdef QUAKE4 if (cvarSystem->GetCVarBool("developer")) { common->Printf("^2loaded .map file ^0%s\n", filename); } mHasBeenResolved = false; #else // if the map has a worldspawn if (entities.Num()) { // "removeEntities" "classname" can be set in the worldspawn to remove all entities with the given classname const idKeyValue* removeEntities = entities[0]->epairs.MatchPrefix("removeEntities", NULL); while (removeEntities) { RemoveEntities(removeEntities->GetValue()); removeEntities = entities[0]->epairs.MatchPrefix("removeEntities", removeEntities); } // "overrideMaterial" "material" can be set in the worldspawn to reset all materials idStr material; if (entities[0]->epairs.GetString("overrideMaterial", "", material)) { for (i = 0; i < entities.Num(); i++) { mapEnt = entities[i]; for (j = 0; j < mapEnt->GetNumPrimitives(); j++) { idMapPrimitive* mapPrimitive = mapEnt->GetPrimitive(j); switch (mapPrimitive->GetType()) { case idMapPrimitive::TYPE_BRUSH: { idMapBrush* mapBrush = static_cast(mapPrimitive); for (k = 0; k < mapBrush->GetNumSides(); k++) { mapBrush->GetSide(k)->SetMaterial(material); } break; } case idMapPrimitive::TYPE_PATCH: { static_cast(mapPrimitive)->SetMaterial(material); break; } } } } } // force all entities to have a name key/value pair if (entities[0]->epairs.GetBool("forceEntityNames")) { for (i = 1; i < entities.Num(); i++) { mapEnt = entities[i]; if (!mapEnt->epairs.FindKey("name")) { mapEnt->epairs.Set("name", va("%s%d", mapEnt->epairs.GetString("classname", "forcedName"), i)); } } } // move the primitives of any func_group entities to the worldspawn if (entities[0]->epairs.GetBool("moveFuncGroups")) { for (i = 1; i < entities.Num(); i++) { mapEnt = entities[i]; if (idStr::Icmp(mapEnt->epairs.GetString("classname"), "func_group") == 0) { entities[0]->primitives.Append(mapEnt->primitives); mapEnt->primitives.Clear(); } } } } #endif hasPrimitiveData = true; g_parseQuake1Map = false; ClearQuake1MapTextureSizeCache(); return true; } #ifdef QUAKE4 /* =============== idMapFile::Resolve =============== */ void idMapFile::Resolve(void) { int i, j, k; idMapEntity* mapEnt; if (!hasPrimitiveData) { return; } // if the map has a worldspawn if (entities.Num()) { // "removeEntities" "classname" can be set in the worldspawn to remove all entities with the given classname const idKeyValue* removeEntities = entities[0]->epairs.MatchPrefix("removeEntities", NULL); while (removeEntities) { RemoveEntities(removeEntities->GetValue()); removeEntities = entities[0]->epairs.MatchPrefix("removeEntities", removeEntities); } // "overrideMaterial" "material" can be set in the worldspawn to reset all materials idStr material; if (entities[0]->epairs.GetString("overrideMaterial", "", material)) { for (i = 0; i < entities.Num(); i++) { mapEnt = entities[i]; for (j = 0; j < mapEnt->GetNumPrimitives(); j++) { idMapPrimitive* mapPrimitive = mapEnt->GetPrimitive(j); switch (mapPrimitive->GetType()) { case idMapPrimitive::TYPE_BRUSH: { idMapBrush* mapBrush = static_cast(mapPrimitive); for (k = 0; k < mapBrush->GetNumSides(); k++) { mapBrush->GetSide(k)->SetMaterial(material); } break; } case idMapPrimitive::TYPE_PATCH: { static_cast(mapPrimitive)->SetMaterial(material); break; } } } } } // force all entities to have a name key/value pair if (entities[0]->epairs.GetBool("forceEntityNames")) { for (i = 1; i < entities.Num(); i++) { mapEnt = entities[i]; if (!mapEnt->epairs.FindKey("name")) { mapEnt->epairs.Set("name", va("%s%d", mapEnt->epairs.GetString("classname", "forcedName"), i)); } } } // move the primitives of any func_group entities to the worldspawn for (i = 1; i < entities.Num(); i++) { mapEnt = entities[i]; if (idStr::Icmp(mapEnt->epairs.GetString("classname"), "func_group") == 0) { idVec3 delta; mapEnt->epairs.GetVector("origin", "0 0 0", delta); for (j = 0; j < mapEnt->primitives.Num(); j++) { idMapPrimitive* mapPrim = mapEnt->primitives[j]; mapPrim->AdjustOrigin(delta); } entities[0]->primitives.Append(mapEnt->primitives); mapEnt->primitives.Clear(); } } RemoveEntities("func_group"); } mHasBeenResolved = true; if (cvarSystem->GetCVarBool("developer")) { common->Printf("^2idMapFile::Resolve has been run on ^0%s^2 so no func_groups exist.\n", name.c_str()); } } #endif /* ============ idMapFile::Write ============ */ #ifdef QUAKE4 bool idMapFile::Write(const char* fileName, const char* ext, bool fromBasePath, bool exportOnly) { #else bool idMapFile::Write(const char* fileName, const char* ext, bool fromBasePath) { #endif int i; idStr qpath; idFile* fp; #ifdef QUAKE4 bool funcGroupFound = false; #endif qpath = fileName; qpath.SetFileExtension(ext); idLib::common->Printf("writing %s...\n", qpath.c_str()); if (fromBasePath) { fp = idLib::fileSystem->OpenFileWrite(qpath, "fs_devpath"); } else { fp = idLib::fileSystem->OpenExplicitFileWrite(qpath); } if (!fp) { idLib::common->Warning("Couldn't open %s\n", qpath.c_str()); return false; } #ifdef QUAKE4 fp->WriteFloatString("Version %d\n", CURRENT_MAP_VERSION); if (exportOnly) { for (i = 0; i < entities.Num(); i++) { if (entities[i]->epairs.GetInt("export")) { entities[i]->Write(fp, i); } } } else { for (i = 0; i < entities.Num(); i++) { entities[i]->Write(fp, i); if (!idStr::Icmp(entities[i]->epairs.GetString("classname"), "func_group")) { funcGroupFound = true; } } } if (mHasFuncGroups && !funcGroupFound && cvarSystem->GetCVarBool("developer")) { common->Warning("^5Did not find any ^2func_groups^0 during save, but there were ^2func_groups^0 found during loading. Was this intended?\n"); } #else fp->WriteFloatString("Version %f\n", (float)CURRENT_MAP_VERSION); for (i = 0; i < entities.Num(); i++) { entities[i]->Write(fp, i); } #endif idLib::fileSystem->CloseFile(fp); return true; } /* =============== idMapFile::SetGeometryCRC =============== */ void idMapFile::SetGeometryCRC(void) { int i; geometryCRC = 0; for (i = 0; i < entities.Num(); i++) { geometryCRC ^= entities[i]->GetGeometryCRC(); } } /* =============== idMapFile::AddEntity =============== */ int idMapFile::AddEntity(idMapEntity * mapEnt) { int ret = entities.Append(mapEnt); return ret; } /* =============== idMapFile::FindEntity =============== */ idMapEntity* idMapFile::FindEntity(const char* name) { for (int i = 0; i < entities.Num(); i++) { idMapEntity* ent = entities[i]; if (idStr::Icmp(ent->epairs.GetString("name"), name) == 0) { return ent; } } return NULL; } /* =============== idMapFile::RemoveEntity =============== */ void idMapFile::RemoveEntity(idMapEntity * mapEnt) { entities.Remove(mapEnt); delete mapEnt; } /* =============== idMapFile::RemoveEntity =============== */ void idMapFile::RemoveEntities(const char* classname) { for (int i = 0; i < entities.Num(); i++) { idMapEntity* ent = entities[i]; if (idStr::Icmp(ent->epairs.GetString("classname"), classname) == 0) { delete entities[i]; entities.RemoveIndex(i); i--; } } } /* =============== idMapFile::RemoveAllEntities =============== */ void idMapFile::RemoveAllEntities() { entities.DeleteContents(true); hasPrimitiveData = false; } /* =============== idMapFile::RemovePrimitiveData =============== */ void idMapFile::RemovePrimitiveData() { for (int i = 0; i < entities.Num(); i++) { idMapEntity* ent = entities[i]; ent->RemovePrimitiveData(); } hasPrimitiveData = false; #ifdef QUAKE4 if (cvarSystem->GetCVarBool("developer")) { common->Printf("^2Removed all primitive data from ^0%s\n", name.c_str()); } #endif } /* =============== idMapFile::NeedsReload =============== */ bool idMapFile::NeedsReload() { if (name.Length()) { ID_TIME_T time = (ID_TIME_T)-1; if (idLib::fileSystem->ReadFile(name, NULL, &time) > 0) { return (time > fileTime); } } return true; } #ifdef QUAKE4 bool idMapFile::WriteExport(const char* fileName, bool fromBasePath) { int i, j; idDict newPairs; for (i = 0; i < entities.Num(); i++) { idMapEntity* ent = entities[i]; if (ent->epairs.GetInt("export") == 1) { /* for ( j = 0; j < ent->epairs.GetNumKeyVals(); j++) { const idKeyValue *kv = ent->epairs.GetKeyVal(j); if (kv->key.CmpPrefix("export_") == 0) { ent->epairs.Delete(kv->key.c_str()); j--; continue; } } */ newPairs.Clear(); for (j = 0; j < ent->epairs.GetNumKeyVals(); j++) { const idKeyValue* kv = ent->epairs.GetKeyVal(j); if (kv->GetKey().CmpPrefix("export_") != 0) { char newName[1024]; sprintf(newName, "export_%s", kv->GetKey().c_str()); if (!ent->epairs.GetString(newName, 0)) { // don't overwrite an existing export newPairs.Set(newName, kv->GetValue().c_str()); } } } ent->epairs.Copy(newPairs); } } return Write(fileName, "export", fromBasePath, true); } bool idMapFile::ParseExport(const char* filename, bool osPath) { // no string concatenation for epairs and allow path names for materials // RAVEN BEGIN // jsinger: Done this way to reduce the amount of code to change. // The auto pointer will delete the lexer when this method exits. idAutoPtr lexer(LexerFactory::MakeLexer(LEXFL_NOSTRINGCONCAT | LEXFL_NOSTRINGESCAPECHARS | LEXFL_ALLOWPATHNAMES)); Lexer& src(*lexer); // RAVEN END idToken token; idStr fullName; idMapEntity* mapEnt; int i, j; int numMerged, numNotMerged, numAdded, numRemoved; numMerged = numNotMerged = numAdded = numRemoved = 0; mHasExportEntities = false; g_parseQuake1Map = false; ClearQuake1MapTextureSizeCache(); fullName = filename; fullName.StripFileExtension(); fullName.SetFileExtension("export"); src.LoadFile(fullName, osPath); if (!src.IsLoaded()) { // didn't get anything at all return false; } version = OLD_MAP_VERSION; fileTime = src.GetFileTime(); mExportEntities.DeleteContents(true); if (src.CheckTokenString("Version")) { src.ReadTokenOnLine(&token); version = token.GetIntValue(); } while (1) { mapEnt = idMapEntity::Parse(src, false, version); if (!mapEnt) { break; } mExportEntities.Append(mapEnt); } if (mExportEntities.Num()) { mHasExportEntities = true; } for (i = 0; i < mExportEntities.Num(); i++) { idMapEntity* ent = mExportEntities[i]; idMapEntity* mapEnt; const char* name; name = ent->epairs.GetString("name"); mapEnt = FindEntity(name); if (!mapEnt) { // check to see if we have an export_name name = ent->epairs.GetString("export_name", 0); if (!name || ent->epairs.GetInt("original")) { // this is a new entity ent->epairs.SetInt("export", 2); // 2 = when re-exporting, don't duplicated the export_ fields ent->epairs.SetInt("merged", 1); ent->epairs.SetInt("original", 0); // 0 = now the entity in map is always treated as master entities.Append(ent); mExportEntities.RemoveIndex(i); i--; numAdded++; continue; } else { mapEnt = FindEntity(name); } } if (!mapEnt) { // this export entity has been removed from the original map numNotMerged++; } else { // exists in both, do a merge! mapEnt->epairs.SetInt("export", 2); // 2 = when re-exporting, don't duplicated the export_ fields mapEnt->epairs.SetInt("merged", 1); for (j = 0; j < ent->epairs.GetNumKeyVals(); j++) { char origName[1024]; const idKeyValue* exportKV = ent->epairs.GetKeyVal(j); const idKeyValue* origKV, * mapKV; if (exportKV->GetKey().CmpPrefix("export_") != 0) { sprintf(origName, "export_%s", exportKV->GetKey().c_str()); origKV = ent->epairs.FindKey(origName); mapKV = mapEnt->epairs.FindKey(exportKV->GetKey().c_str()); if (origKV && mapKV) { // keys exist in all spots, compare if (origKV->GetValue() == exportKV->GetValue()) { // export hasn't change } else if (mapKV->GetValue() == origKV->GetValue()) { // map is same as the orig, yet export has changed, so bring it over mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); } } else if (origKV) { // map has removed this key, so we shouldn't merge it } else if (mapKV) { // map has added this key, so we'll ignore the exported value, which has also been added } else { // this was added to the export, so merge it in mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); } } else { mapEnt->epairs.Set(exportKV->GetKey().c_str(), exportKV->GetValue().c_str()); } } numMerged++; } } // check for any entities that have been marked as exported in the original map but have been removed from the export file for (i = 0; i < entities.Num(); i++) { idMapEntity* ent = entities[i]; if (ent->epairs.GetInt("export")) { if (!ent->epairs.GetInt("merged")) { entities.RemoveIndex(i); numRemoved++; i--; } else { ent->epairs.Delete("merged"); } } } common->Printf("Export Merge: %d added, %d merged, %d not merged, %d removed\n", numAdded, numMerged, numNotMerged, numRemoved); mExportEntities.DeleteContents(true); return true; } #endif