#include "templates.h" #include "doom_levelmesh.h" #include "doom_levelsubmesh.h" #include "g_levellocals.h" #include "texturemanager.h" #include "playsim/p_lnspec.h" #include "c_dispatch.h" #include "g_levellocals.h" #include "a_dynlight.h" #include "hw_renderstate.h" #include "common/rendering/vulkan/accelstructs/vk_lightmap.h" #include "common/rendering/vulkan/accelstructs/halffloat.h" static bool RequireLevelMesh() { if (level.levelMesh) return true; Printf("No level mesh. Perhaps your level has no lightmap loaded?\n"); return false; } static bool RequireLightmap() { if (!RequireLevelMesh()) return false; if (level.lightmaps) return true; Printf("Lightmap is not enabled in this level.\n"); return false; } ADD_STAT(lightmap) { FString out; DoomLevelMesh* levelMesh = level.levelMesh; if (!levelMesh || !level.lightmaps) { out.Format("No lightmap"); return out; } uint32_t atlasPixelCount = levelMesh->StaticMesh->AtlasPixelCount(); auto stats = levelMesh->GatherSurfacePixelStats(); out.Format("Surfaces: %u (sky: %u, awaiting updates: %u)\nSurface pixel area to update: %u\nSurface pixel area: %u\nAtlas pixel area: %u\nAtlas efficiency: %.4f%%", stats.surfaces.total, stats.surfaces.sky, std::max(stats.surfaces.dirty - stats.surfaces.sky, (uint32_t)0), stats.pixels.dirty, stats.pixels.total, atlasPixelCount, float(stats.pixels.total) / float(atlasPixelCount) * 100.0f ); return out; } CCMD(dumplevelmesh) { if (!RequireLevelMesh()) return; level.levelMesh->DumpMesh(FString("levelmesh.obj"), FString("levelmesh.mtl")); Printf("Level mesh exported.\n"); } CCMD(invalidatelightmap) { if (!RequireLightmap()) return; int count = 0; for (auto& surface : static_cast(level.levelMesh->StaticMesh.get())->Surfaces) { if (!surface.NeedsUpdate) ++count; surface.NeedsUpdate = true; } Printf("Marked %d out of %d surfaces for update.\n", count, level.levelMesh->StaticMesh->GetSurfaceCount()); } void PrintSurfaceInfo(const DoomLevelMeshSurface* surface) { if (!RequireLevelMesh()) return; auto gameTexture = surface->texture; Printf("Surface %d (%p)\n Type: %d, TypeIndex: %d, ControlSector: %d\n", surface->Submesh->GetSurfaceIndex(surface), surface, surface->Type, surface->TypeIndex, surface->ControlSector ? surface->ControlSector->Index() : -1); Printf(" Atlas page: %d, x:%d, y:%d\n", surface->AtlasTile.ArrayIndex, surface->AtlasTile.X, surface->AtlasTile.Y); Printf(" Pixels: %dx%d (area: %d)\n", surface->AtlasTile.Width, surface->AtlasTile.Height, surface->Area()); Printf(" Sample dimension: %d\n", surface->sampleDimension); Printf(" Needs update?: %d\n", surface->NeedsUpdate); Printf(" Always update?: %d\n", surface->AlwaysUpdate); Printf(" Sector group: %d\n", surface->sectorGroup); Printf(" Texture: '%s'\n", gameTexture ? gameTexture->GetName().GetChars() : ""); Printf(" Alpha: %f\n", surface->alpha); } FVector3 RayDir(FAngle angle, FAngle pitch) { auto pc = float(pitch.Cos()); return FVector3{ pc * float(angle.Cos()), pc * float(angle.Sin()), -float(pitch.Sin()) }; } DVector3 RayDir(DAngle angle, DAngle pitch) { auto pc = pitch.Cos(); return DVector3{ pc * (angle.Cos()), pc * (angle.Sin()), -(pitch.Sin()) }; } CCMD(surfaceinfo) { if (!RequireLevelMesh()) return; auto pov = players[consoleplayer].mo; if (!pov) { Printf("players[consoleplayer].mo is null.\n"); return; } auto posXYZ = FVector3(pov->Pos()); posXYZ.Z = float(players[consoleplayer].viewz); auto angle = pov->Angles.Yaw; auto pitch = pov->Angles.Pitch; const auto surface = (DoomLevelMeshSurface*)level.levelMesh->Trace(posXYZ, FVector3(RayDir(angle, pitch)), 32000.0f); if (surface) { PrintSurfaceInfo(surface); } else { Printf("No surface was hit.\n"); } } EXTERN_CVAR(Float, lm_scale); ///////////////////////////////////////////////////////////////////////////// DoomLevelMesh::DoomLevelMesh(FLevelLocals& doomMap) { SunColor = doomMap.SunColor; // TODO keep only one copy? SunDirection = doomMap.SunDirection; StaticMesh = std::make_unique(); DynamicMesh = std::make_unique(); static_cast(StaticMesh.get())->CreateStatic(doomMap); static_cast(DynamicMesh.get())->CreateDynamic(doomMap); } void DoomLevelMesh::BeginFrame(FLevelLocals& doomMap) { static_cast(DynamicMesh.get())->UpdateDynamic(doomMap, static_cast(StaticMesh.get())->LMTextureCount); } bool DoomLevelMesh::TraceSky(const FVector3& start, FVector3 direction, float dist) { FVector3 end = start + direction * dist; auto surface = Trace(start, direction, dist); return surface && surface->bSky; } void DoomLevelMesh::PackLightmapAtlas() { static_cast(StaticMesh.get())->PackLightmapAtlas(0); } int DoomLevelMesh::AddSurfaceLights(const LevelMeshSurface* surface, LevelMeshLight* list, int listMaxSize) { const DoomLevelMeshSurface* doomsurf = static_cast(surface); FLightNode* node = nullptr; if (doomsurf->Type == ST_FLOOR || doomsurf->Type == ST_CEILING) { node = doomsurf->Subsector->section->lighthead; } else if (doomsurf->Type == ST_MIDDLESIDE || doomsurf->Type == ST_UPPERSIDE || doomsurf->Type == ST_LOWERSIDE) { bool isPolyLine = !!(doomsurf->Side->Flags & WALLF_POLYOBJ); if (isPolyLine) { subsector_t* subsector = level.PointInRenderSubsector((doomsurf->Side->V1()->fPos() + doomsurf->Side->V2()->fPos()) * 0.5); node = subsector->section->lighthead; } else if (!doomsurf->ControlSector) { node = doomsurf->Side->lighthead; } else // 3d floor needs light from the sidedef on the other side { int otherside = doomsurf->Side->linedef->sidedef[0] == doomsurf->Side ? 1 : 0; node = doomsurf->Side->linedef->sidedef[otherside]->lighthead; } } if (!node) return 0; int listpos = 0; while (node && listpos < listMaxSize) { FDynamicLight* light = node->lightsource; if (light && light->Trace()) { DVector3 pos = light->Pos; //light->PosRelative(portalgroup); LevelMeshLight& meshlight = list[listpos++]; meshlight.Origin = { (float)pos.X, (float)pos.Y, (float)pos.Z }; meshlight.RelativeOrigin = meshlight.Origin; meshlight.Radius = (float)light->GetRadius(); meshlight.Intensity = (float)light->target->Alpha; if (light->IsSpot()) { meshlight.InnerAngleCos = (float)light->pSpotInnerAngle->Cos(); meshlight.OuterAngleCos = (float)light->pSpotOuterAngle->Cos(); DAngle negPitch = -*light->pPitch; DAngle Angle = light->target->Angles.Yaw; double xzLen = negPitch.Cos(); meshlight.SpotDir.X = float(-Angle.Cos() * xzLen); meshlight.SpotDir.Y = float(-Angle.Sin() * xzLen); meshlight.SpotDir.Z = float(-negPitch.Sin()); } else { meshlight.InnerAngleCos = -1.0f; meshlight.OuterAngleCos = -1.0f; meshlight.SpotDir.X = 0.0f; meshlight.SpotDir.Y = 0.0f; meshlight.SpotDir.Z = 0.0f; } meshlight.Color.X = light->GetRed() * (1.0f / 255.0f); meshlight.Color.Y = light->GetGreen() * (1.0f / 255.0f); meshlight.Color.Z = light->GetBlue() * (1.0f / 255.0f); if (light->Sector) meshlight.SectorGroup = static_cast(StaticMesh.get())->sectorGroup[light->Sector->Index()]; else meshlight.SectorGroup = 0; } node = node->nextLight; } return listpos; } void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilename) const { DoomLevelSubmesh* submesh = static_cast(StaticMesh.get()); auto f = fopen(objFilename.GetChars(), "w"); fprintf(f, "# DoomLevelMesh debug export\n"); fprintf(f, "# MeshVertices: %u, MeshElements: %u, Surfaces: %u\n", submesh->Mesh.Vertices.Size(), submesh->Mesh.Elements.Size(), submesh->Surfaces.Size()); fprintf(f, "mtllib %s\n", mtlFilename.GetChars()); double scale = 1 / 10.0; for (const auto& v : submesh->Mesh.Vertices) { fprintf(f, "v %f %f %f\n", v.x * scale, v.y * scale, v.z * scale); } for (const auto& v : submesh->Mesh.Vertices) { fprintf(f, "vt %f %f\n", v.lu, v.lv); } auto name = [](DoomLevelMeshSurfaceType type) -> const char* { switch (type) { case ST_CEILING: return "ceiling"; case ST_FLOOR: return "floor"; case ST_LOWERSIDE: return "lowerside"; case ST_UPPERSIDE: return "upperside"; case ST_MIDDLESIDE: return "middleside"; case ST_NONE: return "none"; default: break; } return "error"; }; uint32_t lastSurfaceIndex = -1; bool useErrorMaterial = false; int highestUsedAtlasPage = -1; for (unsigned i = 0, count = submesh->Mesh.Elements.Size(); i + 2 < count; i += 3) { auto index = submesh->Mesh.SurfaceIndexes[i / 3]; if (index != lastSurfaceIndex) { lastSurfaceIndex = index; if (unsigned(index) >= submesh->Surfaces.Size()) { fprintf(f, "o Surface[%d] (bad index)\n", index); fprintf(f, "usemtl error\n"); useErrorMaterial = true; } else { const auto& surface = submesh->Surfaces[index]; fprintf(f, "o Surface[%d] %s %d%s\n", index, name(surface.Type), surface.TypeIndex, surface.bSky ? " sky" : ""); fprintf(f, "usemtl lightmap%d\n", surface.AtlasTile.ArrayIndex); if (surface.AtlasTile.ArrayIndex > highestUsedAtlasPage) { highestUsedAtlasPage = surface.AtlasTile.ArrayIndex; } } } // fprintf(f, "f %d %d %d\n", MeshElements[i] + 1, MeshElements[i + 1] + 1, MeshElements[i + 2] + 1); fprintf(f, "f %d/%d %d/%d %d/%d\n", submesh->Mesh.Elements[i + 0] + 1, submesh->Mesh.Elements[i + 0] + 1, submesh->Mesh.Elements[i + 1] + 1, submesh->Mesh.Elements[i + 1] + 1, submesh->Mesh.Elements[i + 2] + 1, submesh->Mesh.Elements[i + 2] + 1); } fclose(f); // material f = fopen(mtlFilename.GetChars(), "w"); fprintf(f, "# DoomLevelMesh debug export\n"); if (useErrorMaterial) { fprintf(f, "# Surface indices that are referenced, but do not exists in the 'Surface' array\n"); fprintf(f, "newmtl error\nKa 1 0 0\nKd 1 0 0\nKs 1 0 0\n"); } for (int page = 0; page <= highestUsedAtlasPage; ++page) { fprintf(f, "newmtl lightmap%d\n", page); fprintf(f, "Ka 1 1 1\nKd 1 1 1\nKs 0 0 0\n"); fprintf(f, "map_Ka lightmap%d.png\n", page); fprintf(f, "map_Kd lightmap%d.png\n", page); } fclose(f); }