mirror of
https://github.com/jmarshall23/DoomRTX.git
synced 2026-08-12 08:11:10 +02:00
2126 lines
51 KiB
C++
2126 lines
51 KiB
C++
/*
|
|
===========================================================================
|
|
|
|
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 <http://www.gnu.org/licenses/>.
|
|
|
|
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<const unsigned char*>(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<quake1MapTextureSizeCacheEntry_t> 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<idToken>& 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<idToken>& 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<idToken>& 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<idToken>& 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<idToken>& 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<idToken>& 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> 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<idMapBrushSide*> 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<idToken> textureTokens;
|
|
idList<idMapBrushSide*> 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<idMapBrushSide*> 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<idMapBrush*>(mapPrim)->Write(fp, i, origin);
|
|
break;
|
|
case idMapPrimitive::TYPE_PATCH:
|
|
static_cast<idMapPatch*>(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<idMapBrush*>(mapPrim)->GetGeometryCRC();
|
|
#ifdef QUAKE4
|
|
if (epairs.GetString("model")) {
|
|
crc ^= StringCRC(epairs.GetString("model"));
|
|
}
|
|
#endif
|
|
break;
|
|
case idMapPrimitive::TYPE_PATCH:
|
|
crc ^= static_cast<idMapPatch*>(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> 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<idMapBrush*>(mapPrimitive);
|
|
for (k = 0; k < mapBrush->GetNumSides(); k++) {
|
|
mapBrush->GetSide(k)->SetMaterial(material);
|
|
}
|
|
break;
|
|
}
|
|
case idMapPrimitive::TYPE_PATCH: {
|
|
static_cast<idMapPatch*>(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<idMapBrush*>(mapPrimitive);
|
|
for (k = 0; k < mapBrush->GetNumSides(); k++) {
|
|
mapBrush->GetSide(k)->SetMaterial(material);
|
|
}
|
|
break;
|
|
}
|
|
case idMapPrimitive::TYPE_PATCH: {
|
|
static_cast<idMapPatch*>(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> 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
|