diff --git a/src/bsp/Bsp.cpp b/src/bsp/Bsp.cpp index 60b7d2b2..619458d6 100644 --- a/src/bsp/Bsp.cpp +++ b/src/bsp/Bsp.cpp @@ -1732,7 +1732,7 @@ STRUCTCOUNT Bsp::delete_unused_hulls(bool noProgress) { void Bsp::delete_oob_nodes(int iNode, int16_t* parentBranch, vector& clipOrder, int oobFlags, bool* oobHistory, bool isFirstPass, int& removedNodes) { BSPNODE& node = nodes[iNode]; - const float oob_coord = 4096; + float oob_coord = g_limits.max_mapboundary; if (node.iPlane < 0) { return; @@ -1803,7 +1803,7 @@ void Bsp::delete_oob_nodes(int iNode, int16_t* parentBranch, vector& c void Bsp::delete_oob_clipnodes(int iNode, int16_t* parentBranch, vector& clipOrder, int oobFlags, bool* oobHistory, bool isFirstPass, int& removedNodes) { BSPCLIPNODE& node = clipnodes[iNode]; - const float oob_coord = 4096; + float oob_coord = g_limits.max_mapboundary; if (node.iPlane < 0) { return; @@ -1884,10 +1884,9 @@ void Bsp::delete_oob_clipnodes(int iNode, int16_t* parentBranch, vector clipOrder; @@ -1958,7 +1957,9 @@ void Bsp::delete_oob_data(int clipFlags) { } } - logf("Removed %d ents\n", ents.size() - newEnts.size()); + int deletedEnts = ents.size() - newEnts.size(); + if (deletedEnts) + logf(" Deleted %d entities\n", deletedEnts); ents = newEnts; uint8_t* oobFaces = new uint8_t[faceCount]; @@ -2003,7 +2004,6 @@ void Bsp::delete_oob_data(int clipFlags) { newFaces[outIdx++] = faces[i]; } } - logf("Wrote %d / %d faces\n", outIdx, faceCount - oobFaceCount); for (int i = 0; i < modelCount; i++) { BSPMODEL& model = models[i]; @@ -2020,9 +2020,6 @@ void Bsp::delete_oob_data(int clipFlags) { model.iFirstFace -= offset; model.nFaces -= countReduce; - - if (countReduce) - logf("Removed %d faces from model %d\n", countReduce, i); } for (int i = 0; i < nodeCount; i++) { @@ -2090,6 +2087,8 @@ void Bsp::delete_oob_data(int clipFlags) { vec3 buffer = vec3(64, 64, 128); // leave room for largest collision hull wall thickness worldmodel.nMins = mins - buffer; worldmodel.nMaxs = maxs + buffer; + + remove_unused_model_structures().print_delete_stats(1); } void Bsp::count_leaves(int iNode, int& leafCount) { @@ -2317,6 +2316,21 @@ void Bsp::deduplicate_models() { } } +float Bsp::calc_allocblock_usage() { + int total = 0; + + for (int i = 0; i < faceCount; i++) { + int size[2]; + GetFaceLightmapSize(this, i, size); + + total += size[0] * size[1]; + } + + const int allocBlockSize = 128 * 128; + + return total / (float)allocBlockSize; +} + void Bsp::allocblock_reduction() { int scaleCount = 0; @@ -2497,7 +2511,6 @@ bool Bsp::subdivide_face(int faceIdx) { } else if (node.firstFace <= faceIdx && node.firstFace + node.nFaces > faceIdx) { node.nFaces++; - logf("Add face to node %d\n", i); } } @@ -2536,92 +2549,138 @@ bool Bsp::subdivide_face(int faceIdx) { return true; } -void Bsp::fix_bad_surface_extents(bool scaleNotSubdivide, int maxTextureDim) { - int opcount = 0; +void Bsp::fix_bad_surface_extents_with_subdivide(int faceIdx) { + vector faces; + faces.push_back(faceIdx); + + int totalFaces = 1; + + while (faces.size()) { + int size[2]; + int i = faces[faces.size()-1]; + if (GetFaceLightmapSize(this, i, size)) { + faces.pop_back(); + continue; + } + + // adjust face indexes if about to split a face with a lower index + for (int i = 0; i < faces.size(); i++) { + if (faces[i] > i) { + faces[i]++; + } + } + + totalFaces++; + subdivide_face(i); + faces.push_back(i+1); + faces.push_back(i); + } + + logf("Subdivided into %d faces\n", totalFaces); +} + +void Bsp::fix_bad_surface_extents(bool scaleNotSubdivide, bool downscaleOnly, int maxTextureDim) { + int numSub = 0; + int numScale = 0; + int numShrink = 0; bool anySubdivides = true; - while (anySubdivides) { + if (scaleNotSubdivide) { + // create unique texinfos in case any are shared with both good and bad faces + for (int fa = 0; fa < faceCount; fa++) { + int faceIdx = fa; + BSPFACE& face = faces[faceIdx]; + BSPTEXTUREINFO& info = texinfos[face.iTextureInfo]; - anySubdivides = false; - for (int i = 0; i < modelCount; i++) { - BSPMODEL& model = models[i]; + if (info.nFlags & TEX_SPECIAL) { + continue; + } - if (model.nFaces == 0) + int size[2]; + if (GetFaceLightmapSize(this, faceIdx, size)) { continue; + } - for (int fa = 0; fa < model.nFaces; fa++) { - int faceIdx = model.iFirstFace + fa; - BSPFACE& face = faces[faceIdx]; - BSPTEXTUREINFO& info = texinfos[face.iTextureInfo]; + get_unique_texinfo(faceIdx); + } + } - if (info.nFlags & TEX_SPECIAL) { - continue; - } + while (anySubdivides) { + anySubdivides = false; + for (int fa = 0; fa < faceCount; fa++) { + int faceIdx = fa; + BSPFACE& face = faces[faceIdx]; + BSPTEXTUREINFO& info = texinfos[face.iTextureInfo]; - int size[2]; - if (GetFaceLightmapSize(this, faceIdx, size)) { - continue; - } + if (info.nFlags & TEX_SPECIAL) { + continue; + } - if (maxTextureDim >= 0) { - // first downscale the texture - if (downscale_texture(info.iMiptex, maxTextureDim)) { - if (GetFaceLightmapSize(this, faceIdx, size)) { - continue; - } + int size[2]; + if (GetFaceLightmapSize(this, faceIdx, size)) { + continue; + } - info = texinfos[face.iTextureInfo]; - } - } + if (maxTextureDim > 0 && downscale_texture(info.iMiptex, maxTextureDim)) { + // retry after downscaling + numShrink++; + fa--; + continue; + } - if (!scaleNotSubdivide) { - if (subdivide_face(faceIdx)) { - opcount++; - anySubdivides = true; - break; - } - } - else { - // TODO: don't scale up if texinfo is shared with a face that doesn't have bad extents - vec2 oldScale(1.0f / info.vS.length(), 1.0f / info.vT.length()); + if (downscaleOnly) { + continue; + } - bool scaledOk = false; - for (int i = 0; i < 128; i++) { - info.vS *= 0.5f; - info.vT *= 0.5f; + if (!scaleNotSubdivide) { + if (subdivide_face(faceIdx)) { + numSub++; + anySubdivides = true; + break; + } + // else scale the face because it was too skinny to be subdivided or something + } + + vec2 oldScale(1.0f / info.vS.length(), 1.0f / info.vT.length()); - if (GetFaceLightmapSize(this, faceIdx, size)) { - scaledOk = true; - break; - } - } + bool scaledOk = false; + for (int i = 0; i < 128; i++) { + info.vS *= 0.5f; + info.vT *= 0.5f; - if (!scaledOk) { - int32_t texOffset = ((int32_t*)textures)[info.iMiptex + 1]; - BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); - logf("Failed to fix face %s with scales %f %f\n", tex.szName, oldScale.x, oldScale.y); - } - else { - int32_t texOffset = ((int32_t*)textures)[info.iMiptex + 1]; - BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); - vec2 newScale(1.0f / info.vS.length(), 1.0f / info.vT.length()); - - vec3 center = get_face_center(faceIdx); - logf("Scaled up %s from %fx%f -> %fx%f (%d %d %d)\n", - tex.szName, oldScale.x, oldScale.y, newScale.x, newScale.y, - (int)center.x, (int)center.y, (int)center.z); - opcount++; - } + if (GetFaceLightmapSize(this, faceIdx, size)) { + scaledOk = true; + break; } } + + if (!scaledOk) { + int32_t texOffset = ((int32_t*)textures)[info.iMiptex + 1]; + BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); + logf("Failed to fix face %s with scales %f %f\n", tex.szName, oldScale.x, oldScale.y); + } + else { + int32_t texOffset = ((int32_t*)textures)[info.iMiptex + 1]; + BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); + vec2 newScale(1.0f / info.vS.length(), 1.0f / info.vT.length()); + + vec3 center = get_face_center(faceIdx); + logf("Scaled up %s from %fx%f -> %fx%f (%d %d %d)\n", + tex.szName, oldScale.x, oldScale.y, newScale.x, newScale.y, + (int)center.x, (int)center.y, (int)center.z); + numScale++; + } } } - if (scaleNotSubdivide) { - logf("Scaled up %d face textures\n", opcount); + if (numScale) { + logf("Scaled up %d face textures\n", numScale); } - else { - logf("Subdivided %d faces\n", opcount); + if (numSub) { + logf("Subdivided %d faces\n", numSub); + } + if (numShrink) { + logf("Downscaled %d textures\n", numShrink); } } @@ -2640,33 +2699,20 @@ vec3 Bsp::get_face_center(int faceIdx) { return centroid / (float)face.nEdges; } -bool Bsp::downscale_texture(int textureId, int maxDim) { - int32_t texOffset = ((int32_t*)textures)[textureId + 1]; - BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); - - if (tex.nOffsets[0] == 0) { - logf("Can't downscale WAD texture %s\n", tex.szName); +bool Bsp::downscale_texture(int textureId, int newWidth, int newHeight) { + if ((newWidth % 16 != 0) || (newHeight % 16 != 0) || newWidth <= 0 || newHeight <= 0) { + logf("Invalid downscale dimensions: %dx%d\n", newWidth, newHeight); return false; } + int32_t texOffset = ((int32_t*)textures)[textureId + 1]; + BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); + int oldWidth = tex.nWidth; int oldHeight = tex.nHeight; - float ratio = oldHeight / (float)oldWidth; - - while (tex.nWidth > 0 && (tex.nWidth > maxDim || tex.nHeight > maxDim || (tex.nHeight % 16) != 0)) { - tex.nWidth -= 16; - tex.nHeight = tex.nWidth * ratio; - } - - if (oldWidth == tex.nWidth) { - return false; - } - - if (tex.nWidth == 0) { - logf("Failed to downscale texture %s %dx%d to max dim %d\n", tex.szName, oldWidth, oldHeight, maxDim); - return false; - } + tex.nWidth = newWidth; + tex.nHeight = newHeight; int lastMipSize = (oldWidth >> 3) * (oldHeight >> 3); byte* pixels = (byte*)(textures + texOffset + tex.nOffsets[0]); @@ -2682,15 +2728,15 @@ bool Bsp::downscale_texture(int textureId, int maxDim) { oldHeights[i] = oldHeight >> (1 * i); newWidths[i] = tex.nWidth >> (1 * i); newHeights[i] = tex.nHeight >> (1 * i); - + if (i > 0) { - newOffset[i] = newOffset[i-1] + newWidths[i-1] * newHeights[i-1]; + newOffset[i] = newOffset[i - 1] + newWidths[i - 1] * newHeights[i - 1]; } else { newOffset[i] = sizeof(BSPMIPTEX); } } - byte* newPalette = (byte*)(textures + texOffset + newOffset[3] + newWidths[3]*newHeights[3] + 2); + byte* newPalette = (byte*)(textures + texOffset + newOffset[3] + newWidths[3] * newHeights[3] + 2); float srcScale = (float)oldWidth / tex.nWidth; @@ -2704,9 +2750,9 @@ bool Bsp::downscale_texture(int textureId, int maxDim) { int srcY = (int)(srcScale * y + 0.5f); for (int x = 0; x < newWidths[i]; x++) { - int srcX = (int)(srcScale*x + 0.5f); - - dstData[y* dstWidth + x] = srcData[srcY * srcWidth + srcX]; + int srcX = (int)(srcScale * x + 0.5f); + + dstData[y * dstWidth + x] = srcData[srcY * srcWidth + srcX]; } } } @@ -2715,23 +2761,16 @@ bool Bsp::downscale_texture(int textureId, int maxDim) { for (int i = 0; i < 4; i++) { tex.nOffsets[i] = newOffset[i]; } - + // scale up face texture coordinates float scale = tex.nWidth / (float)oldWidth; - /* - for (int i = 0; i < faceCount; i++) { - BSPFACE& face = faces[i]; - BSPTEXTUREINFO& info = texinfos[face.iTextureInfo]; - } - */ - for (int i = 0; i < faceCount; i++) { BSPFACE& face = faces[i]; if (texinfos[face.iTextureInfo].iMiptex != textureId) continue; - + // each affected face should have a unique texinfo because // the shift amount may be different for every face after scaling BSPTEXTUREINFO* info = get_unique_texinfo(i); @@ -2763,19 +2802,17 @@ bool Bsp::downscale_texture(int textureId, int maxDim) { // shrink texture lump int removedBytes = palette - newPalette; - logf("Removed %d bytes\n", removedBytes); byte* texEnd = newPalette + 256 * sizeof(COLOR3); int shiftBytes = (texEnd - textures) + removedBytes; - + memcpy(texEnd, texEnd + removedBytes, header.lump[LUMP_TEXTURES].nLength - shiftBytes); - for (int k = textureId+1; k < textureCount; k++) { + for (int k = textureId + 1; k < textureCount; k++) { ((int32_t*)textures)[k + 1] -= removedBytes; } for (int i = 0; i < textureCount; i++) { int32_t texOffset = ((int32_t*)textures)[i + 1]; BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); - logf("VALIDATE %s\n", tex.szName); } logf("Downscale %s %dx%d -> %dx%d\n", tex.szName, oldWidth, oldHeight, tex.nWidth, tex.nHeight); @@ -2783,48 +2820,79 @@ bool Bsp::downscale_texture(int textureId, int maxDim) { return true; } +bool Bsp::downscale_texture(int textureId, int maxDim) { + int32_t texOffset = ((int32_t*)textures)[textureId + 1]; + BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); + + if (tex.nOffsets[0] == 0) { + logf("Can't downscale WAD texture %s\n", tex.szName); + return false; + } + + int oldWidth = tex.nWidth; + int oldHeight = tex.nHeight; + int newWidth = tex.nWidth; + int newHeight = tex.nHeight; + + float ratio = oldHeight / (float)oldWidth; + + while (newWidth > 0 && (newWidth > maxDim || newHeight > maxDim || (newHeight % 16) != 0)) { + newWidth -= 16; + newHeight = newWidth * ratio; + } + + if (oldWidth == newWidth) { + return false; + } + + if (tex.nWidth == 0) { + logf("Failed to downscale texture %s %dx%d to max dim %d\n", tex.szName, oldWidth, oldHeight, maxDim); + return false; + } + + downscale_texture(textureId, newWidth, newHeight); +} + void Bsp::downscale_invalid_textures() { - const int MAX_PIXELS = 262144; // Half-Life limit + int count = 0; for (int i = 0; i < textureCount; i++) { int32_t texOffset = ((int32_t*)textures)[i + 1]; BSPMIPTEX& tex = *((BSPMIPTEX*)(textures + texOffset)); - if (tex.nWidth * tex.nHeight > MAX_PIXELS) { + if (tex.nOffsets[0] == 0) { + logf("Skipping WAD texture %s\n", tex.szName); + continue; + } + + if (tex.nWidth * tex.nHeight > g_limits.max_texturepixels) { int oldWidth = tex.nWidth; int oldHeight = tex.nHeight; + int newWidth = tex.nWidth; + int newHeight = tex.nHeight; float ratio = oldHeight / (float)oldWidth; - while (tex.nWidth > 16) { - tex.nWidth -= 16; - tex.nHeight = tex.nWidth * ratio; + while (newWidth > 16) { + newWidth -= 16; + newHeight = newWidth * ratio; - if (tex.nHeight % 16 != 0) { + if (newHeight % 16 != 0) { continue; } - if (tex.nWidth * tex.nHeight <= MAX_PIXELS) { + if (newWidth * newHeight <= g_limits.max_texturepixels) { break; } } - float scale = tex.nWidth / (float)oldWidth; - - for (int k = 0; k < texinfoCount; k++) { - BSPTEXTUREINFO& info = texinfos[k]; - if (info.iMiptex == i) { - info.vS *= scale; - info.vT *= scale; - info.shiftS = (info.shiftS * scale) - (oldWidth - tex.nWidth); - info.shiftT = (info.shiftT * scale) - (oldHeight - tex.nHeight); - } - } - - logf("Downscale %s %dx%d -> %dx%d\n", tex.szName, oldWidth, oldHeight, tex.nWidth, tex.nHeight); + downscale_texture(i, newWidth, newHeight); + count++; } } + + logf("Downscaled %d textures\n", count); } set Bsp::selectConnectedTexture(int modelId, int faceId) { @@ -3342,22 +3410,21 @@ bool sortModelInfos(const STRUCTUSAGE* a, const STRUCTUSAGE* b) { } bool Bsp::isValid() { - return modelCount < MAX_MAP_MODELS - && planeCount < MAX_MAP_PLANES - && vertCount < MAX_MAP_VERTS - && nodeCount < MAX_MAP_NODES - && texinfoCount < MAX_MAP_TEXINFOS - && faceCount < MAX_MAP_FACES - && clipnodeCount < MAX_MAP_CLIPNODES - && leafCount < MAX_MAP_LEAVES - && marksurfCount < MAX_MAP_MARKSURFS - && surfedgeCount < MAX_MAP_SURFEDGES - && edgeCount < MAX_MAP_SURFEDGES - && textureCount < MAX_MAP_TEXTURES - && lightDataLength < MAX_MAP_LIGHTDATA - && visDataLength < MAX_MAP_VISDATA - && ents.size() < MAX_MAP_ENTS; - + return modelCount < g_limits.max_models + && planeCount < g_limits.max_planes + && vertCount < g_limits.max_vertexes + && nodeCount < g_limits.max_nodes + && texinfoCount < g_limits.max_texinfos + && faceCount < g_limits.max_faces + && clipnodeCount < g_limits.max_clipnodes + && leafCount < g_limits.max_leaves + && marksurfCount < g_limits.max_marksurfaces + && surfedgeCount < g_limits.max_surfedges + && edgeCount < g_limits.max_edges + && textureCount < g_limits.max_textures + && lightDataLength < g_limits.max_lightdata + && visDataLength < g_limits.max_visdata + && ents.size() < g_limits.max_entities; } bool Bsp::validate() { @@ -3381,6 +3448,7 @@ bool Bsp::validate() { isValid = false; } } + int numBadExtent = 0; for (int i = 0; i < faceCount; i++) { if (faces[i].iPlane < 0 || faces[i].iPlane >= planeCount) { logf("Bad plane reference in face %d: %d / %d\n", i, faces[i].iPlane, planeCount); @@ -3400,6 +3468,16 @@ bool Bsp::validate() { logf("Bad lightmap offset in face %d: %d / %d\n", i, faces[i].nLightmapOffset, lightDataLength); isValid = false; } + + BSPTEXTUREINFO& info = texinfos[faces[i].iTextureInfo]; + int size[2]; + if (!(info.nFlags & TEX_SPECIAL) && !GetFaceLightmapSize(this, i, size)) { + numBadExtent++; + } + } + if (numBadExtent) { + logf("Bad Surface Extents on %d faces\n", numBadExtent); + isValid = false; } for (int i = 0; i < leafCount; i++) { @@ -3543,11 +3621,11 @@ void Bsp::print_info(bool perModelStats, int perModelLimit, int sortMode) { if (perModelStats) { g_sort_mode = sortMode; - if (planeCount >= MAX_MAP_PLANES || texinfoCount >= MAX_MAP_TEXINFOS || leafCount >= MAX_MAP_LEAVES || - modelCount >= MAX_MAP_MODELS || nodeCount >= MAX_MAP_NODES || vertCount >= MAX_MAP_VERTS || - faceCount >= MAX_MAP_FACES || clipnodeCount >= MAX_MAP_CLIPNODES || marksurfCount >= MAX_MAP_MARKSURFS || - surfedgeCount >= MAX_MAP_SURFEDGES || edgeCount >= MAX_MAP_EDGES || textureCount >= MAX_MAP_TEXTURES || - lightDataLength >= MAX_MAP_LIGHTDATA || visDataLength >= MAX_MAP_VISDATA) + if (planeCount >= g_limits.max_planes || texinfoCount >= g_limits.max_texinfos || leafCount >= g_limits.max_leaves || + modelCount >= g_limits.max_models || nodeCount >= g_limits.max_nodes || vertCount >= g_limits.max_vertexes || + faceCount >= g_limits.max_faces || clipnodeCount >= g_limits.max_clipnodes || marksurfCount >= g_limits.max_marksurfaces || + surfedgeCount >= g_limits.max_surfedges || edgeCount >= g_limits.max_edges || textureCount >= g_limits.max_textures || + lightDataLength >= g_limits.max_lightdata || visDataLength >= g_limits.max_visdata) { logf("Unable to show model stats while BSP limits are exceeded.\n"); return; @@ -3587,21 +3665,21 @@ void Bsp::print_info(bool perModelStats, int perModelLimit, int sortMode) { else { logf(" Data Type Current / Max Fullness\n"); logf("------------ ------------------- --------\n"); - print_stat("models", modelCount, MAX_MAP_MODELS, false); - print_stat("planes", planeCount, MAX_MAP_PLANES, false); - print_stat("vertexes", vertCount, MAX_MAP_VERTS, false); - print_stat("nodes", nodeCount, MAX_MAP_NODES, false); - print_stat("texinfos", texinfoCount, MAX_MAP_TEXINFOS, false); - print_stat("faces", faceCount, MAX_MAP_FACES, false); - print_stat("clipnodes", clipnodeCount, MAX_MAP_CLIPNODES, false); - print_stat("leaves", leafCount, MAX_MAP_LEAVES, false); - print_stat("marksurfaces", marksurfCount, MAX_MAP_MARKSURFS, false); - print_stat("surfedges", surfedgeCount, MAX_MAP_SURFEDGES, false); - print_stat("edges", edgeCount, MAX_MAP_SURFEDGES, false); - print_stat("textures", textureCount, MAX_MAP_TEXTURES, false); - print_stat("lightdata", lightDataLength, MAX_MAP_LIGHTDATA, true); - print_stat("visdata", visDataLength, MAX_MAP_VISDATA, true); - print_stat("entities", entCount, MAX_MAP_ENTS, false); + print_stat("models", modelCount, g_limits.max_models, false); + print_stat("planes", planeCount, g_limits.max_planes, false); + print_stat("vertexes", vertCount, g_limits.max_vertexes, false); + print_stat("nodes", nodeCount, g_limits.max_nodes, false); + print_stat("texinfos", texinfoCount, g_limits.max_texinfos, false); + print_stat("faces", faceCount, g_limits.max_faces, false); + print_stat("clipnodes", clipnodeCount, g_limits.max_clipnodes, false); + print_stat("leaves", leafCount, g_limits.max_leaves, false); + print_stat("marksurfaces", marksurfCount, g_limits.max_marksurfaces, false); + print_stat("surfedges", surfedgeCount, g_limits.max_surfedges, false); + print_stat("edges", edgeCount, g_limits.max_edges, false); + print_stat("textures", textureCount, g_limits.max_textures, false); + print_stat("lightdata", lightDataLength, g_limits.max_lightdata, true); + print_stat("visdata", visDataLength, g_limits.max_visdata, true); + print_stat("entities", entCount, g_limits.max_entities, false); } } @@ -5250,7 +5328,7 @@ void Bsp::dump_lightmap(int faceIdx, string outputPath) { } void Bsp::dump_lightmap_atlas(string outputPath) { - int lightmapWidth = MAX_SURFACE_EXTENT; + int lightmapWidth = g_limits.max_surface_extents; int lightmapsPerDim = ceil(sqrt(faceCount)); int atlasDim = lightmapsPerDim * lightmapWidth; @@ -5477,21 +5555,21 @@ void Bsp::update_lump_pointers() { lightDataLength = header.lump[LUMP_LIGHTING].nLength; visDataLength = header.lump[LUMP_VISIBILITY].nLength; - if (planeCount > MAX_MAP_PLANES) logf("Overflowed Planes !!!\n"); - if (texinfoCount > MAX_MAP_TEXINFOS) logf("Overflowed texinfos !!!\n"); - if (leafCount > MAX_MAP_LEAVES) logf("Overflowed leaves !!!\n"); - if (modelCount > MAX_MAP_MODELS) logf("Overflowed models !!!\n"); - if (texinfoCount > MAX_MAP_TEXINFOS) logf("Overflowed texinfos !!!\n"); - if (nodeCount > MAX_MAP_NODES) logf("Overflowed nodes !!!\n"); - if (vertCount > MAX_MAP_VERTS) logf("Overflowed verts !!!\n"); - if (faceCount > MAX_MAP_FACES) logf("Overflowed faces !!!\n"); - if (clipnodeCount > MAX_MAP_CLIPNODES) logf("Overflowed clipnodes !!!\n"); - if (marksurfCount > MAX_MAP_MARKSURFS) logf("Overflowed marksurfs !!!\n"); - if (surfedgeCount > MAX_MAP_SURFEDGES) logf("Overflowed surfedges !!!\n"); - if (edgeCount > MAX_MAP_EDGES) logf("Overflowed edges !!!\n"); - if (textureCount > MAX_MAP_TEXTURES) logf("Overflowed textures !!!\n"); - if (lightDataLength > MAX_MAP_LIGHTDATA) logf("Overflowed lightdata !!!\n"); - if (visDataLength > MAX_MAP_VISDATA) logf("Overflowed visdata !!!\n"); + if (planeCount > g_limits.max_planes) logf("Overflowed Planes !!!\n"); + if (texinfoCount > g_limits.max_texinfos) logf("Overflowed texinfos !!!\n"); + if (leafCount > g_limits.max_leaves) logf("Overflowed leaves !!!\n"); + if (modelCount > g_limits.max_models) logf("Overflowed models !!!\n"); + if (texinfoCount > g_limits.max_texinfos) logf("Overflowed texinfos !!!\n"); + if (nodeCount > g_limits.max_nodes) logf("Overflowed nodes !!!\n"); + if (vertCount > g_limits.max_vertexes) logf("Overflowed verts !!!\n"); + if (faceCount > g_limits.max_faces) logf("Overflowed faces !!!\n"); + if (clipnodeCount > g_limits.max_clipnodes) logf("Overflowed clipnodes !!!\n"); + if (marksurfCount > g_limits.max_marksurfaces) logf("Overflowed marksurfs !!!\n"); + if (surfedgeCount > g_limits.max_surfedges) logf("Overflowed surfedges !!!\n"); + if (edgeCount > g_limits.max_edges) logf("Overflowed edges !!!\n"); + if (textureCount > g_limits.max_textures) logf("Overflowed textures !!!\n"); + if (lightDataLength > g_limits.max_lightdata) logf("Overflowed lightdata !!!\n"); + if (visDataLength > g_limits.max_visdata) logf("Overflowed visdata !!!\n"); } void Bsp::replace_lump(int lumpIdx, void* newData, int newLength) { diff --git a/src/bsp/Bsp.h b/src/bsp/Bsp.h index af9eeec8..601e2769 100644 --- a/src/bsp/Bsp.h +++ b/src/bsp/Bsp.h @@ -191,21 +191,33 @@ class Bsp // showing between faces with different texture scales // scaleNotSubdivide:true = scale face textures to lower extents // scaleNotSubdivide:false = subdivide face textures to lower extents + // downscaleOnly:true = don't scale or subdivide anything, just downscale the textures // maxTextureDim = downscale textures first if they are larger than this (0 = disable) - void fix_bad_surface_extents(bool scaleNotSubdivide, int maxTextureDim); + void fix_bad_surface_extents(bool scaleNotSubdivide, bool downscaleOnly, int maxTextureDim); + + // subdivide a face until it has valid surface extents + void fix_bad_surface_extents_with_subdivide(int faceIdx); // reduces size of textures that exceed game limits and adjusts face scales accordingly void downscale_invalid_textures(); + void downscale_textures(int maxDim); + // downscales a texture to the maximum specified width/height // true if was downscaled bool downscale_texture(int textureId, int maxDim); + bool downscale_texture(int textureId, int newWidth, int newHeight); + vec3 get_face_center(int faceIdx); // scales up texture sizes on models that aren't used by visible entities void allocblock_reduction(); + // gets estimated number of allocblocks filled + // actual amount will vary because there is some wasted space when the engine generates lightmap atlases + float calc_allocblock_usage(); + // subdivides along the axis with the most texture pixels (for biggest surface extent reduction) bool subdivide_face(int faceIdx); diff --git a/src/bsp/BspMerger.cpp b/src/bsp/BspMerger.cpp index 0a38fb5a..0626a875 100644 --- a/src/bsp/BspMerger.cpp +++ b/src/bsp/BspMerger.cpp @@ -1650,7 +1650,7 @@ void BspMerger::merge_lighting(Bsp& mapA, Bsp& mapB) { // create a single full-bright lightmap to use for all faces, if one map has lighting but the other doesn't if (thisColorCount == 0 && otherColorCount != 0) { - thisColorCount = MAX_SURFACE_EXTENT * MAX_SURFACE_EXTENT; + thisColorCount = g_limits.max_surface_extents * g_limits.max_surface_extents; totalColorCount += thisColorCount; int sz = thisColorCount * sizeof(COLOR3); mapA.lumps[LUMP_LIGHTING] = new byte[sz]; @@ -1667,7 +1667,7 @@ void BspMerger::merge_lighting(Bsp& mapA, Bsp& mapB) { } } else if (thisColorCount != 0 && otherColorCount == 0) { - otherColorCount = MAX_SURFACE_EXTENT * MAX_SURFACE_EXTENT; + otherColorCount = g_limits.max_surface_extents * g_limits.max_surface_extents; totalColorCount += otherColorCount; otherRad = new COLOR3[otherColorCount]; freemem = true; diff --git a/src/bsp/bsplimits.h b/src/bsp/bsplimits.h index 43434a14..9c50bb8c 100644 --- a/src/bsp/bsplimits.h +++ b/src/bsp/bsplimits.h @@ -1,26 +1,9 @@ #define MAX_MAP_HULLS 4 #define MAX_MAP_COORD 131072 // stuff breaks past this point -#define MAX_MAP_MODELS 4096 -#define MAX_MAP_PLANES 65535 -#define MAX_MAP_VERTS 65535 -#define MAX_MAP_NODES 32768 -#define MAX_MAP_TEXINFOS 32767 -#define MAX_MAP_FACES 65535 // This ought to be 32768, otherwise faces(in world) can become invisible. --vluzacn -#define MAX_MAP_CLIPNODES 32767 -#define MAX_MAP_LEAVES 65536 -#define MAX_MAP_MARKSURFS 65536 -#define MAX_MAP_TEXDATA 0 -#define MAX_MAP_VISDATA (64 * ( 1024 * 1024 )) // 64 MB -#define MAX_MAP_ENTS 8192 -#define MAX_MAP_SURFEDGES 512000 -#define MAX_MAP_EDGES 256000 -#define MAX_MAP_TEXTURES 4096 -#define MAX_MAP_LIGHTDATA (64 * ( 1024 * 1024 )) // 64 MB #define MAX_TEXTURE_DIMENSION 1024 #define MAXTEXTURENAME 16 #define MIPLEVELS 4 -#define MAX_TEXTURE_SIZE ((MAX_TEXTURE_DIMENSION * MAX_TEXTURE_DIMENSION * sizeof(short) * 3) / 2) #define MAX_KEYS_PER_ENT 64 // just guessing #define MAX_KEY_LEN 256 // not sure if this includes the null char diff --git a/src/editor/AppSettings.cpp b/src/editor/AppSettings.cpp index 023d32f7..5113a338 100644 --- a/src/editor/AppSettings.cpp +++ b/src/editor/AppSettings.cpp @@ -32,6 +32,51 @@ void AppSettings::loadDefault() entreport_open = false; show_transform_axes = false; settings_tab = 0; + engine = ENGINE_HALF_LIFE; + + g_engine_limits[ENGINE_HALF_LIFE].max_surface_extents = 16; + g_engine_limits[ENGINE_HALF_LIFE].max_models = 512; + g_engine_limits[ENGINE_HALF_LIFE].max_planes = 32768; + g_engine_limits[ENGINE_HALF_LIFE].max_vertexes = 65535; + g_engine_limits[ENGINE_HALF_LIFE].max_nodes = 32767; + g_engine_limits[ENGINE_HALF_LIFE].max_faces = 65535; + g_engine_limits[ENGINE_HALF_LIFE].max_clipnodes = 32767; + g_engine_limits[ENGINE_HALF_LIFE].max_leaves = 32760; + g_engine_limits[ENGINE_HALF_LIFE].max_marksurfaces = 65535; + g_engine_limits[ENGINE_HALF_LIFE].max_surfedges = 512000; + g_engine_limits[ENGINE_HALF_LIFE].max_edges = 256000; + g_engine_limits[ENGINE_HALF_LIFE].max_textures = 512; + g_engine_limits[ENGINE_HALF_LIFE].max_lightdata = 48*1024*1024; + g_engine_limits[ENGINE_HALF_LIFE].max_visdata = 8*1024*1024; + g_engine_limits[ENGINE_HALF_LIFE].max_entdata = 2*1024*1024; + g_engine_limits[ENGINE_HALF_LIFE].max_entities = 8192; + g_engine_limits[ENGINE_HALF_LIFE].max_texinfos = 32767; + g_engine_limits[ENGINE_HALF_LIFE].max_allocblocks = 64; + g_engine_limits[ENGINE_HALF_LIFE].max_texturepixels = 262144; + g_engine_limits[ENGINE_HALF_LIFE].max_mapboundary = 4096; + + g_engine_limits[ENGINE_SVEN_COOP].max_surface_extents = 64; + g_engine_limits[ENGINE_SVEN_COOP].max_models = 4096; + g_engine_limits[ENGINE_SVEN_COOP].max_planes = 65535; + g_engine_limits[ENGINE_SVEN_COOP].max_vertexes = 65535; + g_engine_limits[ENGINE_SVEN_COOP].max_nodes = 32768; + g_engine_limits[ENGINE_SVEN_COOP].max_faces = 65535; + g_engine_limits[ENGINE_SVEN_COOP].max_clipnodes = 32768; + g_engine_limits[ENGINE_SVEN_COOP].max_leaves = 65536; + g_engine_limits[ENGINE_SVEN_COOP].max_marksurfaces = 65535; + g_engine_limits[ENGINE_SVEN_COOP].max_surfedges = 512000; + g_engine_limits[ENGINE_SVEN_COOP].max_edges = 256000; + g_engine_limits[ENGINE_SVEN_COOP].max_textures = 4096; + g_engine_limits[ENGINE_SVEN_COOP].max_lightdata = 64 * 1024 * 1024; + g_engine_limits[ENGINE_SVEN_COOP].max_visdata = 64 * 1024 * 1024; + g_engine_limits[ENGINE_SVEN_COOP].max_entdata = 2 * 1024 * 1024; + g_engine_limits[ENGINE_SVEN_COOP].max_entities = 8192; + g_engine_limits[ENGINE_SVEN_COOP].max_texinfos = 32767; + g_engine_limits[ENGINE_SVEN_COOP].max_allocblocks = 1024; + g_engine_limits[ENGINE_SVEN_COOP].max_texturepixels = 1048576; + g_engine_limits[ENGINE_SVEN_COOP].max_mapboundary = 32768; + + g_limits = g_engine_limits[ENGINE_HALF_LIFE]; render_flags = g_render_flags = RENDER_TEXTURES | RENDER_LIGHTMAPS | RENDER_SPECIAL | RENDER_ENTS | RENDER_SPECIAL_ENTS | RENDER_POINT_ENTS | RENDER_WIREFRAME | RENDER_ENT_CONNECTIONS @@ -94,6 +139,10 @@ void AppSettings::load() { else if (key == "fgd") { fgdPaths.push_back(val); } else if (key == "res") { resPaths.push_back(val); } else if (key == "savebackup") { g_settings.backUpMap = atoi(val.c_str()) != 0; } + else if (key == "engine") { + g_settings.engine = clamp(atoi(val.c_str()), 0, 1); + g_limits = g_engine_limits[g_settings.engine]; + } } g_settings.valid = true; @@ -184,4 +233,5 @@ void AppSettings::save() { file << "font_size=" << g_settings.fontSize << endl; file << "undo_levels=" << g_settings.undoLevels << endl; file << "savebackup=" << g_settings.backUpMap << endl; + file << "engine=" << g_settings.engine << endl; } \ No newline at end of file diff --git a/src/editor/AppSettings.h b/src/editor/AppSettings.h index 1925926f..8f97a52e 100644 --- a/src/editor/AppSettings.h +++ b/src/editor/AppSettings.h @@ -9,6 +9,7 @@ struct AppSettings { int windowY; int maximized; int fontSize; + int engine; std::string gamedir; std::string workingdir; bool valid; diff --git a/src/editor/BspRenderer.cpp b/src/editor/BspRenderer.cpp index 661dc021..418114e7 100644 --- a/src/editor/BspRenderer.cpp +++ b/src/editor/BspRenderer.cpp @@ -1802,6 +1802,8 @@ bool BspRenderer::pickModelPoly(vec3 start, vec3 dir, vec3 offset, int modelIdx, hullIdx = getBestClipnodeHull(modelIdx); } + // Nav mesh WIP code + /* if (clipnodesLoaded && (selectWorldClips || selectEntClips) && hullIdx != -1) { for (int i = 0; i < renderClipnodes[modelIdx].faceMaths[hullIdx].size(); i++) { FaceMath& faceMath = renderClipnodes[modelIdx].faceMaths[hullIdx][i]; @@ -1831,6 +1833,7 @@ bool BspRenderer::pickModelPoly(vec3 start, vec3 dir, vec3 offset, int modelIdx, } } } + */ return foundBetterPick; } diff --git a/src/editor/BspRenderer.h b/src/editor/BspRenderer.h index 6cc5aa04..49985ecf 100644 --- a/src/editor/BspRenderer.h +++ b/src/editor/BspRenderer.h @@ -29,7 +29,8 @@ enum RenderFlags { RENDER_ORIGIN = 128, RENDER_WORLD_CLIPNODES = 256, RENDER_ENT_CLIPNODES = 512, - RENDER_ENT_CONNECTIONS = 1024 + RENDER_ENT_CONNECTIONS = 1024, + RENDER_MAP_BOUNDARY = 2048 }; struct LightmapInfo { diff --git a/src/editor/Gui.cpp b/src/editor/Gui.cpp index 926c7625..69973e2e 100644 --- a/src/editor/Gui.cpp +++ b/src/editor/Gui.cpp @@ -23,6 +23,16 @@ float g_tooltip_delay = 0.6f; // time in seconds before showing a tooltip string iniPath = getConfigDir() + "imgui.ini"; +void tooltip(ImGuiContext& g, const char* text) { + if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted(text); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } +} + Gui::Gui(Renderer* app) { this->app = app; init(); @@ -416,11 +426,7 @@ void Gui::draw3dContextMenus() { command->execute(); app->pushUndoCommand(command); } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Create a copy of this BSP model and assign to this entity.\n\nThis lets you edit the model for this entity without affecting others."); - ImGui::EndTooltip(); - } + tooltip(g, "Create a copy of this BSP model and assign to this entity.\n\nThis lets you edit the model for this entity without affecting others."); } if (ImGui::MenuItem(app->movingEnt ? "Ungrab" : "Grab", "G")) { @@ -459,13 +465,13 @@ void Gui::draw3dContextMenus() { if (ImGui::BeginPopup("face_context")) { - if (ImGui::MenuItem("Copy texture", "Ctrl+C")) { + if (ImGui::MenuItem("Copy texture", "Ctrl+C", false, app->selectedFaces.size() == 1)) { copyTexture(); } if (ImGui::MenuItem("Paste texture", "Ctrl+V", false, copiedMiptex >= 0 && copiedMiptex < map->textureCount)) { pasteTexture(); } - if (ImGui::MenuItem("Select all of this texture")) { + if (ImGui::MenuItem("Select all of this texture", "", false, app->selectedFaces.size() == 1)) { if (!app->pickInfo.valid) { return; } @@ -484,13 +490,9 @@ void Gui::draw3dContextMenus() { logf("Selected %d faces\n", app->selectedFaces.size()); refreshSelectedFaces = true; } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Select every face in the map which has this texture."); - ImGui::EndTooltip(); - } + tooltip(g, "Select every face in the map which has this texture."); - if (ImGui::MenuItem("Select connected planar faces of this texture")) { + if (ImGui::MenuItem("Select connected planar faces of this texture", "", false, app->selectedFaces.size() == 1)) { if (!app->pickInfo.valid) { return; } @@ -506,42 +508,45 @@ void Gui::draw3dContextMenus() { logf("Selected %d faces\n", app->selectedFaces.size()); refreshSelectedFaces = true; } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Selects faces connected to this one which lie on the same plane and use the same texture"); - ImGui::EndTooltip(); - } + tooltip(g, "Selects faces connected to this one which lie on the same plane and use the same texture"); if (ImGui::MenuItem("Downscale texture")) { if (!app->pickInfo.valid) { return; } + Bsp* map = app->pickInfo.map; - BSPFACE& face = map->faces[app->pickInfo.faceIdx]; - BSPTEXTUREINFO& info = map->texinfos[face.iTextureInfo]; - int32_t texOffset = ((int32_t*)map->textures)[info.iMiptex + 1]; - BSPMIPTEX& tex = *((BSPMIPTEX*)(map->textures + texOffset)); + set downscaled; + + for (int i = 0; i < app->selectedFaces.size(); i++) { + BSPFACE& face = map->faces[app->pickInfo.faceIdx]; + BSPTEXTUREINFO& info = map->texinfos[face.iTextureInfo]; + + if (downscaled.count(info.iMiptex)) + continue; + + int32_t texOffset = ((int32_t*)map->textures)[info.iMiptex + 1]; + BSPMIPTEX& tex = *((BSPMIPTEX*)(map->textures + texOffset)); - int maxDim = max(tex.nWidth, tex.nHeight); + int maxDim = max(tex.nWidth, tex.nHeight); - int nextBestDim = 16; - if (maxDim > 512) { nextBestDim = 512; } - else if (maxDim > 256) { nextBestDim = 256; } - else if (maxDim > 128) { nextBestDim = 128; } - else if (maxDim > 64) { nextBestDim = 64; } - else if (maxDim > 32) { nextBestDim = 32; } - else if (maxDim > 32) { nextBestDim = 32; } + int nextBestDim = 16; + if (maxDim > 512) { nextBestDim = 512; } + else if (maxDim > 256) { nextBestDim = 256; } + else if (maxDim > 128) { nextBestDim = 128; } + else if (maxDim > 64) { nextBestDim = 64; } + else if (maxDim > 32) { nextBestDim = 32; } + else if (maxDim > 32) { nextBestDim = 32; } - map->downscale_texture(info.iMiptex, nextBestDim); + map->downscale_texture(info.iMiptex, nextBestDim); + downscaled.insert(info.iMiptex); + } + app->deselectFaces(); app->mapRenderers[0]->reload(); } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Reduces the dimensions of this texture down to the next power of 2"); - ImGui::EndTooltip(); - } + tooltip(g, "Reduces the dimensions of this texture down to the next power of 2."); if (ImGui::MenuItem("Subdivide")) { if (!app->pickInfo.valid) { @@ -549,21 +554,35 @@ void Gui::draw3dContextMenus() { } Bsp* map = app->pickInfo.map; - map->subdivide_face(app->pickInfo.faceIdx); + for (int i = 0; i < app->selectedFaces.size(); i++) { + map->subdivide_face(app->pickInfo.faceIdx); + } app->deselectFaces(); app->mapRenderers[0]->reload(); } + tooltip(g, "Split this face across the axis with the most texture pixels."); + + if (ImGui::MenuItem("Subdivide until valid")) { + if (!app->pickInfo.valid) { + return; + } + Bsp* map = app->pickInfo.map; + + for (int i = 0; i < app->selectedFaces.size(); i++) { + map->fix_bad_surface_extents_with_subdivide(app->selectedFaces[i]); + } + + app->deselectFaces(); + app->mapRenderers[0]->reload(); + } + tooltip(g, "Subdivide this face until it has valid surface extents."); ImGui::Separator(); - if (ImGui::MenuItem("Copy lightmap", "(WIP)")) { + if (ImGui::MenuItem("Copy lightmap", "(WIP)", false, app->selectedFaces.size() == 1)) { copyLightmap(); } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Only works for faces with matching sizes/extents,\nand the lightmap might get shifted."); - ImGui::EndTooltip(); - } + tooltip(g, "Only works for faces with matching sizes/extents,\nand the lightmap might get shifted."); if (ImGui::MenuItem("Paste lightmap", "", false, copiedLightmapFace >= 0 && copiedLightmapFace < map->faceCount)) { pasteLightmap(); @@ -784,6 +803,7 @@ void Gui::drawMenuBar() { if (ImGui::MenuItem("Reload", 0, false, !app->isLoading)) { app->reloadMaps(); + refresh(); } if (ImGui::MenuItem("Validate")) { for (int i = 0; i < app->mapRenderers.size(); i++) { @@ -900,22 +920,7 @@ void Gui::drawMenuBar() { app->pushUndoCommand(command); } - if (ImGui::BeginMenu("Porting tools", !app->isLoading)) { - if (ImGui::MenuItem("De-duplicate Models", 0, false, !app->isLoading && mapSelected)) { - map->deduplicate_models(); - - BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; - if (renderer) { - renderer->preRenderEnts(); - g_app->gui->refresh(); - } - } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Deletes BSP models that are identical to another model and updates entity model keyvalues accordingly."); - ImGui::EndTooltip(); - } - + if (ImGui::BeginMenu("Porting Tools", !app->isLoading)) { if (ImGui::MenuItem("AllocBlock Reduction", 0, false, !app->isLoading && mapSelected)) { map->allocblock_reduction(); @@ -925,58 +930,29 @@ void Gui::drawMenuBar() { g_app->gui->refresh(); } } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Scales up textures on invisible models. Manually scale up textures on visible models if you still get an error.\n"); - ImGui::EndTooltip(); - } - - if (ImGui::MenuItem("Fix Bad Surface Extents (subdivide)", 0, false, !app->isLoading && mapSelected)) { - map->fix_bad_surface_extents(false, 256); - - BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; - if (renderer) { - map->remove_unused_model_structures(); - renderer->reload(); - } - } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Subdivides faces until they have valid extents.\n"); - ImGui::EndTooltip(); - } + tooltip(g, "Scales up textures on invisible models, if any exist. Manually increase texture scales or downscale large textures to reduce AllocBlocks further.\n"); - if (ImGui::MenuItem("Fix Bad Surface Extents (scale)", 0, false, !app->isLoading && mapSelected)) { - map->fix_bad_surface_extents(true, 256); + if (ImGui::MenuItem("Apply Worldspawn Transform", 0, false, !app->isLoading && mapSelected)) { + if (map->ents[0]->hasKey("origin")) { + vec3 ori = map->ents[0]->getOrigin(); + logf("Moved worldspawn origin by %f %f %f\n", ori.x, ori.y, ori.z); + map->move(ori); + map->ents[0]->removeKeyvalue("origin"); - BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; - if (renderer) { - map->remove_unused_model_structures(); - renderer->reload(); + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + renderer->reload(); + g_app->gui->refresh(); + g_app->deselectObject(); + } } - } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Scales up face textures until they have valid extents.\n"); - ImGui::EndTooltip(); - } - - if (ImGui::MenuItem("Downscale invalid textures", 0, false, !app->isLoading && mapSelected)) { - map->downscale_invalid_textures(); - - BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; - if (renderer) { - renderer->preRenderFaces(); - g_app->gui->refresh(); + else { + logf("Transform the worldspawn origin first using the transform widget!\n"); } } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Shrinks textures that exceed the max texture size. Adjusts texture coordinates accordingly. WAD textures must be shrunk manually.\n"); - ImGui::EndTooltip(); - } + tooltip(g, "Moves BSP data by the amount set in the worldspawn origin keyvalue. Useful for adjusting OOB deletes or for alignining maps before merging."); - if (ImGui::BeginMenu("Delete OOB data", !app->isLoading && mapSelected)) { + if (ImGui::BeginMenu("Delete OOB Data", !app->isLoading && mapSelected)) { static const char* optionNames[10] = { "All Axes", @@ -992,7 +968,7 @@ void Gui::drawMenuBar() { }; static int clipFlags[10] = { - 0xffffffff, + 0xffffffff, OOB_CLIP_X | OOB_CLIP_X_NEG, OOB_CLIP_X, OOB_CLIP_X_NEG, @@ -1023,37 +999,125 @@ void Gui::drawMenuBar() { g_app->deselectObject(); } } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Deletes BSP data and entities outside of the max map boundary.\n"); - ImGui::EndTooltip(); - } + tooltip(g, "Deletes BSP data and entities outside of the " + "max map boundary.\n\n" + "This is useful for splitting maps for running in an engine with stricter map limits."); } ImGui::EndMenu(); } - if (ImGui::MenuItem("Apply worldspawn transform", 0, false, !app->isLoading && mapSelected)) { - if (map->ents[0]->hasKey("origin")) { - vec3 ori = map->ents[0]->getOrigin(); - logf("Moved worldspawn origin by %f %f %f\n", ori.x, ori.y, ori.z); - map->move(ori); - map->ents[0]->removeKeyvalue("origin"); + if (ImGui::MenuItem("De-duplicate Models", 0, false, !app->isLoading && mapSelected)) { + map->deduplicate_models(); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + renderer->preRenderEnts(); + g_app->gui->refresh(); + } + } + tooltip(g, "Deletes duplicated BSP models and updates entity model keyvalues accordingly. This lowers the model count and allows more game models to be precached."); + + if (ImGui::MenuItem("Downscale Invalid Textures", "(WIP)", false, !app->isLoading && mapSelected)) { + map->downscale_invalid_textures(); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + renderer->preRenderFaces(); + g_app->gui->refresh(); + reloadLimits(); + } + } + tooltip(g, "Shrinks textures that exceed the max texture size and adjusts texture coordinates accordingly. Does not work with WAD textures yet.\n"); + + if (ImGui::BeginMenu("Fix Bad Surface Extents", !app->isLoading && mapSelected)) { + if (ImGui::MenuItem("Shrink Textures (512)", 0, false, !app->isLoading && mapSelected)) { + map->fix_bad_surface_extents(false, true, 512); BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; if (renderer) { + map->remove_unused_model_structures(); renderer->reload(); - g_app->gui->refresh(); - g_app->deselectObject(); + reloadLimits(); } } - else { - logf("Transform the worldspawn origin first using the transform widget!\n"); + tooltip(g, "Downscales embedded textures on bad faces to a max resolution of 512x512 pixels. " + "This alone will likely not be enough to fix all faces with bad surface extents." + "You may also have to apply the Subdivide or Scale methods."); + + if (ImGui::MenuItem("Shrink Textures (256)", 0, false, !app->isLoading && mapSelected)) { + map->fix_bad_surface_extents(false, true, 256); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + map->remove_unused_model_structures(); + renderer->reload(); + reloadLimits(); + } } - } - if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { - ImGui::BeginTooltip(); - ImGui::TextUnformatted("Moves BSP data by the amount set in the worldspawn origin keyvalue\n"); - ImGui::EndTooltip(); + tooltip(g, "Downscales embedded textures on bad faces to a max resolution of 256x256 pixels. " + "This alone will likely not be enough to fix all faces with bad surface extents." + "You may also have to apply the Subdivide or Scale methods."); + + if (ImGui::MenuItem("Shrink Textures (128)", 0, false, !app->isLoading && mapSelected)) { + map->fix_bad_surface_extents(false, true, 128); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + map->remove_unused_model_structures(); + renderer->reload(); + reloadLimits(); + } + } + tooltip(g, "Downscales embedded textures on bad faces to a max resolution of 128x128 pixels. " + "This alone will likely not be enough to fix all faces with bad surface extents." + "You may also have to apply the Subdivide or Scale methods."); + + if (ImGui::MenuItem("Shrink Textures (64)", 0, false, !app->isLoading && mapSelected)) { + map->fix_bad_surface_extents(false, true, 64); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + map->remove_unused_model_structures(); + renderer->reload(); + reloadLimits(); + } + } + tooltip(g, "Downscales embedded textures to a max resolution of 64x64 pixels. " + "This alone will likely not be enough to fix all faces with bad surface extents." + "You may also have to apply the Subdivide or Scale methods."); + + ImGui::Separator(); + + if (ImGui::MenuItem("Scale", 0, false, !app->isLoading && mapSelected)) { + map->fix_bad_surface_extents(true, false, 0); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + map->remove_unused_model_structures(); + renderer->reload(); + reloadLimits(); + } + } + tooltip(g, "Scales up face textures until they have valid extents. The drawback to this method is shifted texture coordinates and lower apparent texture quality."); + + if (ImGui::MenuItem("Subdivide", 0, false, !app->isLoading && mapSelected)) { + map->fix_bad_surface_extents(false, false, 0); + + BspRenderer* renderer = mapSelected ? app->mapRenderers[app->pickInfo.mapIdx] : NULL; + if (renderer) { + map->remove_unused_model_structures(); + renderer->reload(); + reloadLimits(); + } + } + tooltip(g, "Subdivides faces until they have valid extents. The drawback to this method is reduced in-game performace from higher poly counts."); + + ImGui::MenuItem("", "WIP"); + tooltip(g, "Anything you choose here will break lightmaps. " + "Run the map through a RAD compiler to fix, and pray that the mapper didn't " + "customize compile settings much."); + + ImGui::EndMenu(); } ImGui::EndMenu(); @@ -2616,12 +2680,53 @@ void Gui::drawSettings() { shouldReloadFonts = true; } ImGui::DragInt("Undo Levels", &app->undoLevels, 0.05f, 0, 64); + ImGui::Checkbox("Verbose Logging", &g_verbose); + + // why not make a backup yourself? + /* ImGui::Checkbox("Make map backup", &g_settings.backUpMap); if (ImGui::IsItemHovered() && g.HoveredIdTimer > g_tooltip_delay) { ImGui::BeginTooltip(); ImGui::TextUnformatted("Creates a backup of the BSP file when saving for the first time."); } + */ + + + ImGui::Dummy(ImVec2(0, 8)); + + ImGui::Text("Engine"); + if (ImGui::IsItemHovered()) + { + ImGui::BeginTooltip(); + ImGui::PushTextWrapPos(ImGui::GetFontSize() * 35.0f); + ImGui::TextUnformatted("Map limits change depending on the engine selected here."); + ImGui::PopTextWrapPos(); + ImGui::EndTooltip(); + } + + if (ImGui::BeginCombo("##engine", g_settings.engine == ENGINE_HALF_LIFE ? "Half-Life" : "Sven Co-op")) + { + static int selected; + selected = g_settings.engine; + + bool changed = false; + if (ImGui::Selectable("Half-Life", selected == ENGINE_HALF_LIFE)) { + changed = g_settings.engine != ENGINE_HALF_LIFE; + g_settings.engine = ENGINE_HALF_LIFE; + } else if (ImGui::Selectable("Sven Co-op", selected == ENGINE_SVEN_COOP)) { + changed = g_settings.engine != ENGINE_SVEN_COOP; + g_settings.engine = ENGINE_SVEN_COOP; + } + + if (changed) { + g_limits = g_engine_limits[g_settings.engine]; + app->mapRenderers[0]->reload(); + reloadLimits(); + } + + ImGui::EndCombo(); + } } else if (settingsTab == 1) { for (int i = 0; i < numFgds; i++) { @@ -2690,6 +2795,7 @@ void Gui::drawSettings() { bool renderWorldClipnodes = g_render_flags & RENDER_WORLD_CLIPNODES; bool renderEntClipnodes = g_render_flags & RENDER_ENT_CLIPNODES; bool renderEntConnections = g_render_flags & RENDER_ENT_CONNECTIONS; + bool renderMapBoundary = g_render_flags & RENDER_MAP_BOUNDARY; ImGui::Text("Render Flags:"); @@ -2709,11 +2815,8 @@ void Gui::drawSettings() { if (ImGui::Checkbox("Origin", &renderOrigin)) { g_render_flags ^= RENDER_ORIGIN; } - if (ImGui::Checkbox("Entity Links", &renderEntConnections)) { - g_render_flags ^= RENDER_ENT_CONNECTIONS; - if (g_render_flags & RENDER_ENT_CONNECTIONS) { - app->updateEntConnections(); - } + if (ImGui::Checkbox("Map Boundary", &renderMapBoundary)) { + g_render_flags ^= RENDER_MAP_BOUNDARY; } ImGui::NextColumn(); @@ -2730,6 +2833,12 @@ void Gui::drawSettings() { if (ImGui::Checkbox("Special World Faces", &renderSpecial)) { g_render_flags ^= RENDER_SPECIAL; } + if (ImGui::Checkbox("Entity Links", &renderEntConnections)) { + g_render_flags ^= RENDER_ENT_CONNECTIONS; + if (g_render_flags & RENDER_ENT_CONNECTIONS) { + app->updateEntConnections(); + } + } ImGui::Columns(1); @@ -2911,21 +3020,22 @@ void Gui::drawLimits() { if (!loadedStats) { stats.clear(); - stats.push_back(calcStat("models", map->modelCount, MAX_MAP_MODELS, false)); - stats.push_back(calcStat("planes", map->planeCount, MAX_MAP_PLANES, false)); - stats.push_back(calcStat("vertexes", map->vertCount, MAX_MAP_VERTS, false)); - stats.push_back(calcStat("nodes", map->nodeCount, MAX_MAP_NODES, false)); - stats.push_back(calcStat("texinfos", map->texinfoCount, MAX_MAP_TEXINFOS, false)); - stats.push_back(calcStat("faces", map->faceCount, MAX_MAP_FACES, false)); - stats.push_back(calcStat("clipnodes", map->clipnodeCount, MAX_MAP_CLIPNODES, false)); - stats.push_back(calcStat("leaves", map->leafCount, MAX_MAP_LEAVES, false)); - stats.push_back(calcStat("marksurfaces", map->marksurfCount, MAX_MAP_MARKSURFS, false)); - stats.push_back(calcStat("surfedges", map->surfedgeCount, MAX_MAP_SURFEDGES, false)); - stats.push_back(calcStat("edges", map->edgeCount, MAX_MAP_EDGES, false)); - stats.push_back(calcStat("textures", map->textureCount, MAX_MAP_TEXTURES, false)); - stats.push_back(calcStat("lightdata", map->lightDataLength, MAX_MAP_LIGHTDATA, true)); - stats.push_back(calcStat("visdata", map->visDataLength, MAX_MAP_VISDATA, true)); - stats.push_back(calcStat("entities", map->ents.size(), MAX_MAP_ENTS, false)); + stats.push_back(calcStat("AllocBlock", map->calc_allocblock_usage(), g_limits.max_allocblocks, false)); + stats.push_back(calcStat("clipnodes", map->clipnodeCount, g_limits.max_clipnodes, false)); + stats.push_back(calcStat("nodes", map->nodeCount, g_limits.max_nodes, false)); + stats.push_back(calcStat("leaves", map->leafCount, g_limits.max_leaves, false)); + stats.push_back(calcStat("models", map->modelCount, g_limits.max_models, false)); + stats.push_back(calcStat("faces", map->faceCount, g_limits.max_faces, false)); + stats.push_back(calcStat("texinfos", map->texinfoCount, g_limits.max_texinfos, false)); + stats.push_back(calcStat("textures", map->textureCount, g_limits.max_textures, false)); + stats.push_back(calcStat("planes", map->planeCount, g_limits.max_planes, false)); + stats.push_back(calcStat("vertexes", map->vertCount, g_limits.max_vertexes, false)); + stats.push_back(calcStat("edges", map->edgeCount, g_limits.max_edges, false)); + stats.push_back(calcStat("surfedges", map->surfedgeCount, g_limits.max_surfedges, false)); + stats.push_back(calcStat("marksurfaces", map->marksurfCount, g_limits.max_marksurfaces, false)); + stats.push_back(calcStat("entdata", map->header.lump[LUMP_ENTITIES].nLength, g_limits.max_entdata, true)); + stats.push_back(calcStat("visdata", map->visDataLength, g_limits.max_visdata, true)); + stats.push_back(calcStat("lightdata", map->lightDataLength, g_limits.max_lightdata, true)); loadedStats = true; } @@ -2973,21 +3083,25 @@ void Gui::drawLimits() { } if (ImGui::BeginTabItem("Clipnodes")) { + loadedStats = false; drawLimitTab(map, SORT_CLIPNODES); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Nodes")) { + loadedStats = false; drawLimitTab(map, SORT_NODES); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Faces")) { + loadedStats = false; drawLimitTab(map, SORT_FACES); ImGui::EndTabItem(); } if (ImGui::BeginTabItem("Vertices")) { + loadedStats = false; drawLimitTab(map, SORT_VERTS); ImGui::EndTabItem(); } diff --git a/src/editor/Renderer.cpp b/src/editor/Renderer.cpp index 4c5f0c7f..44cb1a0a 100644 --- a/src/editor/Renderer.cpp +++ b/src/editor/Renderer.cpp @@ -293,9 +293,9 @@ void Renderer::renderLoop() { drawLine(debugPoint - vec3(0, 32, 0), debugPoint + vec3(0, 32, 0), { 0, 255, 0, 255 }); drawLine(debugPoint - vec3(0, 0, 32), debugPoint + vec3(0, 0, 32), { 0, 0, 255, 255 }); - if (pickInfo.valid && pickInfo.entIdx == 0) { + if ((g_render_flags & RENDER_MAP_BOUNDARY) && pickInfo.valid && pickInfo.entIdx == 0) { glDisable(GL_CULL_FACE); - drawBox(mapRenderers[0]->map->ents[0]->getOrigin() * -1, 8192, COLOR4(0, 255, 0, 64)); + drawBox(mapRenderers[0]->map->ents[0]->getOrigin() * -1, g_limits.max_mapboundary*2, COLOR4(0, 255, 0, 64)); glEnable(GL_CULL_FACE); } @@ -1548,12 +1548,12 @@ void Renderer::addMap(Bsp* map) { gui->checkValidHulls(); // Pick default map - if (!pickInfo.map) + //if (!pickInfo.map) { - pickInfo.modelIdx = -1; + pickInfo.modelIdx = 0; pickInfo.faceIdx = -1; - pickInfo.ent = NULL; - pickInfo.entIdx = -1; + pickInfo.ent = map->ents[0]; + pickInfo.entIdx = 0; pickInfo.mapIdx = 0; pickInfo.map = map; pickInfo.valid = true; diff --git a/src/globals.cpp b/src/globals.cpp index 03f62fc9..ea7d826d 100644 --- a/src/globals.cpp +++ b/src/globals.cpp @@ -11,4 +11,7 @@ mutex g_log_mutex; AppSettings g_settings; string g_config_dir = getConfigDir(); string g_settings_path = g_config_dir + "bspguy.cfg"; -Renderer* g_app = NULL; \ No newline at end of file +Renderer* g_app = NULL; + +MapLimits g_limits; +MapLimits g_engine_limits[ENGINE_TYPES]; \ No newline at end of file diff --git a/src/globals.h b/src/globals.h index e1297b75..4a155f55 100644 --- a/src/globals.h +++ b/src/globals.h @@ -5,6 +5,36 @@ #include #include "AppSettings.h" +enum engine_types { + ENGINE_HALF_LIFE, + ENGINE_SVEN_COOP, + ENGINE_TYPES +}; + +struct MapLimits { + int max_surface_extents; + + int max_models; + int max_planes; + int max_vertexes; + int max_nodes; + int max_texinfos; + int max_faces; + int max_clipnodes; + int max_leaves; + int max_marksurfaces; + int max_surfedges; + int max_edges; + int max_textures; + int max_lightdata; + int max_visdata; + int max_entities; + int max_entdata; + int max_allocblocks; + int max_texturepixels; + int max_mapboundary; // how far from the map origin you can play the game without weird glitches +}; + class Renderer; extern bool g_verbose; @@ -15,6 +45,8 @@ extern std::mutex g_log_mutex; extern AppSettings g_settings; extern Renderer* g_app; +extern MapLimits g_limits; +extern MapLimits g_engine_limits[ENGINE_TYPES]; extern std::string g_config_dir; extern std::string g_settings_path; diff --git a/src/qtools/rad.cpp b/src/qtools/rad.cpp index b58ace6f..fde658f1 100644 --- a/src/qtools/rad.cpp +++ b/src/qtools/rad.cpp @@ -3,6 +3,7 @@ #include "Bsp.h" #include "colors.h" #include "util.h" +#include "globals.h" void qrad_get_lightmap_flags(Bsp* bsp, int faceIdx, byte* luxelFlagsOut) { @@ -263,11 +264,11 @@ bool GetFaceLightmapSize(Bsp* bsp, int facenum, int size[2]) { size[1] = (maxs[1] - mins[1]); bool badSurfaceExtents = false; - if ((size[0] > MAX_SURFACE_EXTENT) || (size[1] > MAX_SURFACE_EXTENT) || size[0] < 0 || size[1] < 0) + if ((size[0] > g_limits.max_surface_extents) || (size[1] > g_limits.max_surface_extents) || size[0] < 0 || size[1] < 0) { //logf("Bad surface extents (%d x %d)\n", size[0], size[1]); - size[0] = min(size[0], MAX_SURFACE_EXTENT); - size[1] = min(size[1], MAX_SURFACE_EXTENT); + size[0] = min(size[0], g_limits.max_surface_extents); + size[1] = min(size[1], g_limits.max_surface_extents); badSurfaceExtents = true; } @@ -406,13 +407,13 @@ void CalcFaceExtents(Bsp* bsp, lightinfo_t* l) if (!(tex->nFlags & TEX_SPECIAL)) { - if ((l->texsize[0] > MAX_SURFACE_EXTENT) || (l->texsize[1] > MAX_SURFACE_EXTENT) + if ((l->texsize[0] > g_limits.max_surface_extents) || (l->texsize[1] > g_limits.max_surface_extents) || l->texsize[0] < 0 || l->texsize[1] < 0 //--vluzacn ) { //logf("Bad surface extents (%d x %d)\n", l->texsize[0], l->texsize[1]); - l->texsize[0] = min(l->texsize[0], MAX_SURFACE_EXTENT); - l->texsize[1] = min(l->texsize[1], MAX_SURFACE_EXTENT); + l->texsize[0] = min(l->texsize[0], g_limits.max_surface_extents); + l->texsize[1] = min(l->texsize[1], g_limits.max_surface_extents); } } } diff --git a/src/qtools/rad.h b/src/qtools/rad.h index 2504a73d..7f00c4c9 100644 --- a/src/qtools/rad.h +++ b/src/qtools/rad.h @@ -2,8 +2,7 @@ #include "bsptypes.h" -#define MAX_SINGLEMAP ((MAX_SURFACE_EXTENT+1)*(MAX_SURFACE_EXTENT+1)) -#define MAX_SURFACE_EXTENT 16 // if lightmap extent exceeds 16, the map will not be able to load in 'Software' renderer and HLDS. +#define MAX_SINGLEMAP ((g_limits.max_surface_extents+1)*(g_limits.max_surface_extents+1)) #define MAX_LUXELS 1600 // max pixels in a single lightmap #define TEXTURE_STEP 16 // this constant was previously defined in lightmap.cpp. --vluzacn #define TEX_SPECIAL 1 // sky or slime or null, no lightmap or 256 subdivision diff --git a/src/qtools/vis.cpp b/src/qtools/vis.cpp index 578e373a..e573bb3c 100644 --- a/src/qtools/vis.cpp +++ b/src/qtools/vis.cpp @@ -3,6 +3,8 @@ #include "util.h" #include "globals.h" +#define MAX_MAP_LEAVES 65536 // set this to the largest value in any engine + bool g_debug_shift = false; void printVisRow(byte* vis, int len, int offsetLeaf, int mask) {