Split level mesh surface from lightmap tiles as we have multiple surfaces per tile
This commit is contained in:
parent
421b8532ff
commit
48b6a7ec46
19 changed files with 446 additions and 597 deletions
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue