Split level mesh surface from lightmap tiles as we have multiple surfaces per tile

This commit is contained in:
Magnus Norddahl 2023-12-17 14:17:22 +01:00
commit 48b6a7ec46
19 changed files with 446 additions and 597 deletions

View file

@ -3012,18 +3012,12 @@ void MapLoader::InitLevelMesh(MapData* map)
Level->levelMesh = new DoomLevelMesh(*Level);
// Lightmap binding/loading
if (Level->lightmaps)
{
if (!LoadLightmap(map))
{
Level->levelMesh->PackLightmapAtlas();
}
}
LoadLightmap(map);
}
bool MapLoader::LoadLightmap(MapData* map)
{
if (!map->Size(ML_LIGHTMAP))
if (!Level->lightmaps || !map->Size(ML_LIGHTMAP))
return false;
FileReader fr;
@ -3031,65 +3025,34 @@ bool MapLoader::LoadLightmap(MapData* map)
return false;
int version = fr.ReadInt32();
if (version == 0)
{
Printf(PRINT_HIGH, "LoadLightmap: This is an old unsupported alpha version of the lightmap lump. Please rebuild the map with a newer version of zdray.\n");
return false;
}
if (version == 1)
if (version < 2)
{
Printf(PRINT_HIGH, "LoadLightmap: This is an old unsupported version of the lightmap lump. Please rebuild the map with a newer version of zdray.\n");
return false;
}
if (version != 2)
else if (version != 2)
{
Printf(PRINT_HIGH, "LoadLightmap: unsupported lightmap lump version\n");
return false;
}
uint32_t numSurfaces = fr.ReadUInt32();
uint32_t numTiles = fr.ReadUInt32();
uint32_t numTexPixels = fr.ReadUInt32();
uint32_t numTexCoords = fr.ReadUInt32();
uint32_t numTexCoords = fr.ReadUInt32(); // To do: remove from a future version of the format. We don't need this.
if (developer >= 5)
{
Printf("LoadLightmap: Surfaces: %u, Pixels: %u, UVs: %u\n", numSurfaces, numTexPixels, numTexCoords);
Printf("LoadLightmap: Tiles: %u, Pixels: %u, UVs: %u\n", numTiles, numTexPixels, numTexCoords);
}
if (numSurfaces == 0 || numTexCoords == 0 || numTexPixels == 0)
if (numTiles == 0 || numTexCoords == 0 || numTexPixels == 0)
return false;
bool errors = false;
int errors = 0;
// Load the surfaces we have lightmap data for
// Load the tiles we have lightmap data for
const int surfaceTypes = 5; // ST_CEILING, ST_FLOOR, etc.
TMap<int, TArray<DoomLevelMeshSurface*>> surfaceGroups[surfaceTypes]; // Let's assume there is only a handful of 3d floor surfaces matching the same typeIndex
auto findSurfaceIndex = [&](int type, int index, const sector_t* sec) {
const auto* surfaces = surfaceGroups[type - 1].CheckKey(index);
if (surfaces)
{
for (unsigned i = 0, count = surfaces->Size(); i < count; ++i)
{
const auto surface = (*surfaces)[i];
if (surface->ControlSector == sec)
{
return Level->levelMesh->StaticMesh->GetSurfaceIndex(surface);
}
}
}
return 0xffffffff;
};
auto getControlSector = [&](uint32_t index)
{
return index < Level->sectors.Size() ? &Level->sectors[index] : nullptr;
};
struct SurfaceEntry // V2 entries
struct TileEntry // V2 entries
{
uint32_t type, typeIndex;
uint32_t controlSector; // 0xFFFFFFFF is none
@ -3100,114 +3063,21 @@ bool MapLoader::LoadLightmap(MapData* map)
DoomLevelMeshSurface* targetSurface;
};
TMap<DoomLevelMeshSurface*, int> detectedSurfaces;
TArray<SurfaceEntry> zdraySurfaces;
zdraySurfaces.Reserve(numSurfaces);
auto submesh = static_cast<DoomLevelSubmesh*>(Level->levelMesh->StaticMesh.get());
for (auto& surface : submesh->Surfaces)
{
surface.NeedsUpdate = false; // let's consider everything valid until we make a mistake trying to change this surface
if (surface.Type > ST_NONE && surface.Type <= ST_FLOOR)
{
if (auto list = surfaceGroups[surface.Type - 1].CheckKey(surface.TypeIndex))
{
list->Push(&surface);
}
else
{
surfaceGroups[surface.Type - 1].InsertNew(surface.TypeIndex).Push(&surface);
}
}
}
TArray<TileEntry> tileEntries;
tileEntries.Reserve(numTiles);
uint32_t usedSurfaceIndex = 0;
for (uint32_t i = 0; i < numSurfaces; i++)
for (uint32_t i = 0; i < numTiles; i++)
{
SurfaceEntry surface;
surface.type = fr.ReadUInt32();
surface.typeIndex = fr.ReadUInt32();
surface.controlSector = fr.ReadUInt32();
surface.width = fr.ReadUInt16();
surface.height = fr.ReadUInt16();
surface.pixelsOffset = fr.ReadUInt32();
surface.uvCount = fr.ReadUInt32();
surface.uvOffset = fr.ReadUInt32();
auto controlSector = getControlSector(surface.controlSector);
// Check against the internal levelmesh
if (surface.type <= ST_NONE || surface.type > ST_FLOOR)
{
errors = true;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d uses invalid type %d\n", i, surface.type);
}
continue;
}
if (i >= submesh->Surfaces.Size())
{
errors = true;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d is out of bounds\n", i);
}
continue;
}
auto levelSurface = &submesh->Surfaces[i];
if (levelSurface->Type != surface.type || levelSurface->TypeIndex != surface.typeIndex || levelSurface->ControlSector != controlSector)
{
auto internalIndex = findSurfaceIndex(surface.type, surface.typeIndex, controlSector);
if (internalIndex < submesh->Surfaces.Size())
{
levelSurface = &submesh->Surfaces[internalIndex];
}
else
{
errors = true;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface %d mismatch. Couldn't find surface type:%d, typeindex:%d, controlsector:%d\n", i, surface.type, surface.typeIndex, surface.controlSector);
}
// TODO detailed printout
continue;
}
}
if (auto ptr = detectedSurfaces.CheckKey(levelSurface))
{
(*ptr)++;
if (developer >= 1)
{
Printf(PRINT_HIGH, "Lightmap lump surface index %d is referencing surface %d (ref count: %d). Surface type:%d, typeindex:%d, controlsector:%d\n", i, submesh->GetSurfaceIndex(levelSurface), *ptr, surface.type, surface.typeIndex, surface.controlSector);
}
}
else
{
levelSurface->AtlasTile.Width = surface.width;
levelSurface->AtlasTile.Height = surface.height;
surface.targetSurface = levelSurface;
detectedSurfaces.Insert(levelSurface, 1);
zdraySurfaces[usedSurfaceIndex++] = surface;
}
}
Level->levelMesh->PackLightmapAtlas();
zdraySurfaces.Resize(usedSurfaceIndex);
if (developer >= 1)
{
Printf("Lightmap contains %d surfaces out of which %d were successfully matched.\n", numSurfaces, zdraySurfaces.Size());
TileEntry& entry = tileEntries[i];
entry.type = fr.ReadUInt32();
entry.typeIndex = fr.ReadUInt32();
entry.controlSector = fr.ReadUInt32();
entry.width = fr.ReadUInt16();
entry.height = fr.ReadUInt16();
entry.pixelsOffset = fr.ReadUInt32();
entry.uvCount = fr.ReadUInt32();
entry.uvOffset = fr.ReadUInt32();
}
// Load pixels
@ -3216,184 +3086,81 @@ bool MapLoader::LoadLightmap(MapData* map)
uint8_t* data = (uint8_t*)&textureData[0];
fr.Read(data, numTexPixels * 3 * sizeof(uint16_t));
// Load texture coordinates
TArray<FVector2> zdrayUvs;
zdrayUvs.Resize(numTexCoords);
fr.Read(&zdrayUvs[0], numTexCoords * 2 * sizeof(float));
// Load texture coordinates
// TArray<FVector2> zdrayUvs;
// zdrayUvs.Resize(numTexCoords);
// fr.Read(&zdrayUvs[0], numTexCoords * 2 * sizeof(float));
// Load lightmap textures
auto submesh = Level->levelMesh->StaticMesh.get();
const auto textureSize = submesh->LMTextureSize;
// Start with empty lightmap textures
submesh->LMTextureData.Resize(submesh->LMTextureCount * textureSize * textureSize * 3);
memset(submesh->LMTextureData.Data(), 0, submesh->LMTextureData.Size() * sizeof(uint16_t));
auto pixels = &submesh->LMTextureData[0];
for (int i = 0, count = submesh->LMTextureData.Size(); i < count; i += 3)
// Create lookup for finding tiles
std::map<LightmapTileBinding, LightmapTile*> levelTiles;
for (LightmapTile& tile : submesh->LightmapTiles)
{
pixels[i] = floatToHalf(0.0);
pixels[i + 1] = floatToHalf(0.0);
pixels[i + 2] = floatToHalf(0.0);
levelTiles[tile.Binding] = &tile;
}
#if 0 // debug surface mapping
for (auto& surface : submesh->Surfaces)
// Bind tiles and copy their pixels to the texture
for (const TileEntry& entry : tileEntries)
{
int dstX = surface.AtlasTile.X;
int dstY = surface.AtlasTile.Y;
int dstPage = surface.AtlasTile.ArrayIndex;
LightmapTileBinding binding;
binding.Type = entry.type;
binding.TypeIndex = entry.typeIndex;
binding.ControlSector = entry.controlSector;
// copy pixels
uint16_t* dst = &submesh->LMTextureData[dstPage * textureSize * textureSize * 3];
uint32_t srcIndex = 0;
if (auto ptr = detectedSurfaces.CheckKey(&surface))
auto it = levelTiles.find(binding);
if (it == levelTiles.end())
{
for (int y = 0; y < surface.AtlasTile.Height; ++y)
{
for (int x = 0; x < surface.AtlasTile.Width; ++x)
{
uint32_t dstIndex = uint32_t(dstX + x + (dstY + y) * textureSize) * 3;
if (errors < 10 && developer >= 1)
Printf("Could not find lightmap tile in level mesh (type = %d, index = %d, control sector = %d)\n", entry.type, entry.typeIndex, entry.controlSector);
errors++;
continue;
}
dst[dstIndex] = floatToHalf(1.0);
dst[dstIndex + 1] = floatToHalf(0.0);
dst[dstIndex + 2] = floatToHalf(0.0);
}
LightmapTile* tile = it->second;
// To do: add transform info to the lump so that we can stretch the pixels as the lump lightmapper might be using fixed point coordinates that could cause alignment issues
if (tile->AtlasLocation.Width != entry.width || tile->AtlasLocation.Height != entry.height)
{
if (errors < 10 && developer >= 1)
Printf("Lightmap tile size mismatch (type = %d, index = %d, control sector = %d)\n", entry.type, entry.typeIndex, entry.controlSector);
errors++;
continue;
}
const uint16_t* src = textureData.Data() + entry.pixelsOffset;
uint16_t* dst = &submesh->LMTextureData[tile->AtlasLocation.ArrayIndex * textureSize * textureSize * 3];
int x = tile->AtlasLocation.X;
int y = tile->AtlasLocation.Y;
int w = tile->AtlasLocation.Width;
int h = tile->AtlasLocation.Height;
for (int yy = 0; yy < w; yy++)
{
const uint16_t* srcline = src + yy * w * 3;
uint16_t* dstline = dst + (x + yy * textureSize) * 3;
for (int xx = 0, end = w * 3; xx < end; xx++)
{
dstline[xx] = srcline[xx];
}
}
tile->NeedsUpdate = false;
}
if (errors > 0)
{
if (developer <= 0)
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces. Restart this level with 'developer 1' for further details.\nPerhaps you forget to rebuild lightmaps after modifying the map?\n");
else
{
for (int y = 0; y < surface.AtlasTile.Height; ++y)
{
for (int x = 0; x < surface.AtlasTile.Width; ++x)
{
uint32_t dstIndex = uint32_t(dstX + x + (dstY + y) * textureSize) * 3;
dst[dstIndex] = floatToHalf(0.0);
dst[dstIndex + 1] = floatToHalf(0.0);
dst[dstIndex + 2] = floatToHalf(0.0);
}
}
}
}
#endif
// Map surface pixels into atlas
int index = -1;
for (auto& surface : zdraySurfaces)
{
++index; // for debug output
auto& realSurface = *surface.targetSurface;
// calculate pixel positions
const uint32_t srcPixelOffset = surface.pixelsOffset;
const int dstX = realSurface.AtlasTile.X;
const int dstY = realSurface.AtlasTile.Y;
const int dstPage = realSurface.AtlasTile.ArrayIndex;
// Sanity checks
if (dstX < 0 || dstY < 0 || dstX + surface.width > textureSize || dstY + surface.height > textureSize || dstPage >= submesh->LMTextureCount)
{
errors = true;
if (developer >= 1)
{
Printf("Can't map lightmap surface %d pixels[%u] -> ((x:%d, y:%d), (x2:%d, y2:%d), page:%d)\n", index, srcPixelOffset, dstX, dstY, dstX + surface.width, dstY + surface.height, dstPage);
}
realSurface.NeedsUpdate = true;
continue;
}
if (realSurface.AtlasTile.Width != surface.width || realSurface.AtlasTile.Height != surface.height)
{
errors = true;
if (developer >= 1)
{
Printf("Surface size mismatch: Attempting to remap %dx%d to %dx%d pixel area.\n", surface.width, surface.height, realSurface.AtlasTile.Width, realSurface.AtlasTile.Height);
}
realSurface.NeedsUpdate = true;
continue;
}
if (developer >= 5)
{
Printf("Mapping lightmap surface pixels[%u] (count: %u) -> ((x:%d, y:%d), (x2:%d, y2:%d), page:%d) area: %u\n",
srcPixelOffset, surface.width * surface.height * 3,
dstX, dstY,
dstX + realSurface.AtlasTile.Width, dstY + realSurface.AtlasTile.Height,
dstPage,
realSurface.AtlasTile.Area() * 3);
}
// copy pixels
uint32_t srcIndex = 0;
uint16_t* src = &textureData[srcPixelOffset];
uint16_t* dst = &submesh->LMTextureData[realSurface.AtlasTile.ArrayIndex * textureSize * textureSize * 3];
int endY = realSurface.AtlasTile.Y + realSurface.AtlasTile.Height;
int endX = realSurface.AtlasTile.X + realSurface.AtlasTile.Width;
for (int y = realSurface.AtlasTile.Y; y < endY; ++y)
{
for (int x = realSurface.AtlasTile.X; x < endX; ++x)
{
uint32_t dstIndex = uint32_t(x + (y * textureSize)) * 3;
dst[dstIndex] = src[srcIndex++];
dst[dstIndex + 1] = src[srcIndex++];
dst[dstIndex + 2] = src[srcIndex++];
}
}
}
// Use UVs from the lightmap
for (auto& surface : zdraySurfaces)
{
auto& realSurface = *surface.targetSurface;
auto* vertices = &submesh->Mesh.Vertices[realSurface.MeshLocation.StartVertIndex];
auto* newUVs = &zdrayUvs[surface.uvOffset];
for (uint32_t i = 0; i < surface.uvCount; ++i)
{
FVector2 oldUv = FVector2(vertices[i].lu, vertices[i].lv);
if (developer >= 5)
{
Printf("Old UV: %.6f %.6f (w:%d, h:%d) (x:%d, y:%d), Lump UVs %.3f %.3f\n", vertices[i].lu, vertices[i].lv, realSurface.AtlasTile.Width, realSurface.AtlasTile.Height, realSurface.AtlasTile.X, realSurface.AtlasTile.Y, newUVs[i].X, newUVs[i].Y);
}
// Finish surface
vertices[i].lu = (newUVs[i].X + realSurface.AtlasTile.X) / textureSize;
vertices[i].lv = (newUVs[i].Y + realSurface.AtlasTile.Y) / textureSize;
if (developer >= 5)
{
if (abs(oldUv.X - vertices[i].lu) >= 0.0000001f || abs(oldUv.Y - vertices[i].lv) >= 0.0000001f)
{
Printf("New UV: %.6f %.6f\n", vertices[i].lu, vertices[i].lv);
}
}
}
}
if (developer >= 3)
{
int loadedSurfaces = 0;
for (auto& surface : submesh->Surfaces)
{
if (!surface.NeedsUpdate)
{
loadedSurfaces++;
}
}
Printf(PRINT_HIGH, "%d/%d surfaces were successfully loaded from lightmap.\n", loadedSurfaces, submesh->Surfaces.Size());
}
if (errors && developer <= 0)
{
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces. Restart this level with 'developer 1' for further details.\nPerhaps you forget to rebuild lightmaps after modifying the map?\n");
Printf(PRINT_HIGH, "Pre-calculated LIGHTMAP surfaces do not match current level surfaces.\nPerhaps you forget to rebuild lightmaps after modifying the map?\n");
}
return true;