#include "templates.h" #include "doom_levelmesh.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" VSMatrix GetPlaneTextureRotationMatrix(FGameTexture* gltexture, const sector_t* sector, int plane); void GetTexCoordInfo(FGameTexture* tex, FTexCoordInfo* tci, side_t* side, int texpos); 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 = TexMan.GameByIndex(surface->texture.GetIndex()); 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' (id=%d)\n", gameTexture ? gameTexture->GetName().GetChars() : "", surface->texture.GetIndex()); 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); } void DoomLevelMesh::BindLightmapSurfacesToGeometry(FLevelLocals& doomMap) { static_cast(StaticMesh.get())->BindLightmapSurfacesToGeometry(doomMap); } void DoomLevelMesh::DisableLightmaps() { static_cast(StaticMesh.get())->DisableLightmaps(); } void DoomLevelMesh::DumpMesh(const FString& objFilename, const FString& mtlFilename) const { static_cast(StaticMesh.get())->DumpMesh(objFilename, mtlFilename); } 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 DoomLevelSubmesh::CreateStatic(FLevelLocals& doomMap) { MeshVertices.Clear(); MeshElements.Clear(); LightmapSampleDistance = doomMap.LightmapSampleDistance; BuildSectorGroups(doomMap); for (unsigned int i = 0; i < doomMap.sides.Size(); i++) { bool isPolyLine = !!(doomMap.sides[i].Flags & WALLF_POLYOBJ); if (!isPolyLine) CreateSideSurfaces(doomMap, &doomMap.sides[i]); } CreateSubsectorSurfaces(doomMap); CreateIndexes(); SetupLightmapUvs(doomMap); BuildTileSurfaceLists(); UpdateCollision(); } void DoomLevelSubmesh::CreateDynamic(FLevelLocals& doomMap) { LightmapSampleDistance = doomMap.LightmapSampleDistance; BuildSectorGroups(doomMap); } void DoomLevelSubmesh::UpdateDynamic(FLevelLocals& doomMap, int lightmapStartIndex) { Surfaces.Clear(); MeshVertices.Clear(); MeshElements.Clear(); MeshSurfaceIndexes.Clear(); // Look for polyobjects for (unsigned int i = 0; i < doomMap.lines.Size(); i++) { side_t* side = doomMap.lines[i].sidedef[0]; bool isPolyLine = !!(side->Flags & WALLF_POLYOBJ); if (isPolyLine) { // Make sure we have a lightmap array on the polyobj sidedef if (side->lightmap.Size() == 0) { auto array = std::make_unique(4); memset(array.get(), 0, sizeof(DoomLevelMeshSurface*)); side->lightmap = TArrayView(array.get(), 4); PolyLMSurfaces.Push(std::move(array)); } CreateSideSurfaces(doomMap, side); } } CreateIndexes(); SetupLightmapUvs(doomMap); BuildTileSurfaceLists(); UpdateCollision(); PackLightmapAtlas(lightmapStartIndex); BindLightmapSurfacesToGeometry(doomMap); } void DoomLevelSubmesh::CreateIndexes() { for (size_t i = 0; i < Surfaces.Size(); i++) { DoomLevelMeshSurface& s = Surfaces[i]; int numVerts = s.numVerts; unsigned int pos = s.startVertIndex; FFlatVertex* verts = &MeshVertices[pos]; s.startElementIndex = MeshElements.Size(); s.numElements = 0; if (s.Type == ST_CEILING) { for (int j = 2; j < numVerts; j++) { if (!IsDegenerate(verts[0].fPos(), verts[j - 1].fPos(), verts[j].fPos())) { MeshElements.Push(pos); MeshElements.Push(pos + j - 1); MeshElements.Push(pos + j); MeshSurfaceIndexes.Push((int)i); s.numElements += 3; } } } else if (s.Type == ST_FLOOR) { for (int j = 2; j < numVerts; j++) { if (!IsDegenerate(verts[0].fPos(), verts[j - 1].fPos(), verts[j].fPos())) { MeshElements.Push(pos + j); MeshElements.Push(pos + j - 1); MeshElements.Push(pos); MeshSurfaceIndexes.Push((int)i); s.numElements += 3; } } } else if (s.Type == ST_MIDDLESIDE || s.Type == ST_UPPERSIDE || s.Type == ST_LOWERSIDE) { if (!IsDegenerate(verts[0].fPos(), verts[2].fPos(), verts[1].fPos())) { MeshElements.Push(pos + 0); MeshElements.Push(pos + 1); MeshElements.Push(pos + 2); MeshSurfaceIndexes.Push((int)i); s.numElements += 3; } if (!IsDegenerate(verts[0].fPos(), verts[2].fPos(), verts[3].fPos())) { MeshElements.Push(pos + 0); MeshElements.Push(pos + 2); MeshElements.Push(pos + 3); MeshSurfaceIndexes.Push((int)i); s.numElements += 3; } } } } void DoomLevelSubmesh::BuildSectorGroups(const FLevelLocals& doomMap) { int groupIndex = 0; TArray queue; sectorGroup.Resize(doomMap.sectors.Size()); memset(sectorGroup.Data(), 0, sectorGroup.Size() * sizeof(int)); for (int i = 0, count = doomMap.sectors.Size(); i < count; ++i) { auto* sector = &doomMap.sectors[i]; auto& currentSectorGroup = sectorGroup[sector->Index()]; if (currentSectorGroup == 0) { currentSectorGroup = ++groupIndex; queue.Push(sector); while (queue.Size() > 0) { auto* sector = queue.Last(); queue.Pop(); for (auto& line : sector->Lines) { auto otherSector = line->frontsector == sector ? line->backsector : line->frontsector; if (otherSector && otherSector != sector) { auto& id = sectorGroup[otherSector->Index()]; if (id == 0) { id = groupIndex; queue.Push(otherSector); } } } } } } if (developer >= 5) { Printf("DoomLevelMesh::BuildSectorGroups created %d groups.", groupIndex); } } void DoomLevelSubmesh::CreatePortals() { std::map transformationIndices; // TODO use the list of portals from the level to avoids duplicates? transformationIndices.emplace(LevelMeshPortal{}, 0); // first portal is an identity matrix for (auto& surface : Surfaces) { bool hasPortal = [&]() { if (surface.Type == ST_FLOOR || surface.Type == ST_CEILING) { return !surface.Subsector->sector->GetPortalDisplacement(surface.Type == ST_FLOOR ? sector_t::floor : sector_t::ceiling).isZero(); } else if (surface.Type == ST_MIDDLESIDE) { return surface.Side->linedef->isLinePortal(); } return false; // It'll take eternity to get lower/upper side portals into the ZDoom family. }(); if (hasPortal) { auto transformation = [&]() { VSMatrix matrix; matrix.loadIdentity(); if (surface.Type == ST_FLOOR || surface.Type == ST_CEILING) { auto d = surface.Subsector->sector->GetPortalDisplacement(surface.Type == ST_FLOOR ? sector_t::floor : sector_t::ceiling); matrix.translate((float)d.X, (float)d.Y, 0.0f); } else if(surface.Type == ST_MIDDLESIDE) { auto sourceLine = surface.Side->linedef; if (sourceLine->isLinePortal()) { auto targetLine = sourceLine->getPortalDestination(); if (targetLine && sourceLine->frontsector && targetLine->frontsector) { double z = 0; // auto xy = surface.Side->linedef->getPortalDisplacement(); // Works only for static portals... ugh auto sourceXYZ = DVector2((sourceLine->v1->fX() + sourceLine->v2->fX()) / 2, (sourceLine->v2->fY() + sourceLine->v1->fY()) / 2); auto targetXYZ = DVector2((targetLine->v1->fX() + targetLine->v2->fX()) / 2, (targetLine->v2->fY() + targetLine->v1->fY()) / 2); // floor or ceiling alignment auto alignment = surface.Side->linedef->GetLevel()->linePortals[surface.Side->linedef->portalindex].mAlign; if (alignment != PORG_ABSOLUTE) { int plane = alignment == PORG_FLOOR ? 1 : 0; auto& sourcePlane = plane ? sourceLine->frontsector->floorplane : sourceLine->frontsector->ceilingplane; auto& targetPlane = plane ? targetLine->frontsector->floorplane : targetLine->frontsector->ceilingplane; auto tz = targetPlane.ZatPoint(targetXYZ); auto sz = sourcePlane.ZatPoint(sourceXYZ); z = tz - sz; } matrix.rotate((float)sourceLine->getPortalAngleDiff().Degrees(), 0.0f, 0.0f, 1.0f); matrix.translate((float)(targetXYZ.X - sourceXYZ.X), (float)(targetXYZ.Y - sourceXYZ.Y), (float)z); } } } return matrix; }(); LevelMeshPortal portal; portal.transformation = transformation; portal.sourceSectorGroup = surface.sectorGroup; portal.targetSectorGroup = [&]() { if (surface.Type == ST_FLOOR || surface.Type == ST_CEILING) { auto plane = surface.Type == ST_FLOOR ? sector_t::floor : sector_t::ceiling; auto portalDestination = surface.Subsector->sector->GetPortal(plane)->mDestination; if (portalDestination) { return sectorGroup[portalDestination->Index()]; } } else if (surface.Type == ST_MIDDLESIDE) { auto targetLine = surface.Side->linedef->getPortalDestination(); auto sector = targetLine->frontsector ? targetLine->frontsector : targetLine->backsector; if (sector) { return sectorGroup[sector->Index()]; } } return 0; }(); auto& index = transformationIndices[portal]; if (index == 0) // new transformation was created { index = Portals.Size(); Portals.Push(portal); } surface.portalIndex = index; } else { surface.portalIndex = 0; } } } void DoomLevelSubmesh::BindLightmapSurfacesToGeometry(FLevelLocals& doomMap) { // Link surfaces for (auto& surface : Surfaces) { surface.Vertices = &MeshVertices[surface.startVertIndex]; if (surface.Type == ST_FLOOR || surface.Type == ST_CEILING) { surface.Subsector = &doomMap.subsectors[surface.TypeIndex]; if (surface.Subsector->firstline && surface.Subsector->firstline->sidedef) surface.Subsector->firstline->sidedef->sector->HasLightmaps = true; SetSubsectorLightmap(&surface); } else { surface.Side = &doomMap.sides[surface.TypeIndex]; SetSideLightmap(&surface); } } } void DoomLevelSubmesh::SetSubsectorLightmap(DoomLevelMeshSurface* surface) { if (!surface->ControlSector) { int index = surface->Type == ST_CEILING ? 1 : 0; surface->Subsector->lightmap[index][0] = surface; } else { int index = surface->Type == ST_CEILING ? 0 : 1; const auto& ffloors = surface->Subsector->sector->e->XFloor.ffloors; for (unsigned int i = 0; i < ffloors.Size(); i++) { if (ffloors[i]->model == surface->ControlSector) { surface->Subsector->lightmap[index][i + 1] = surface; } } } } void DoomLevelSubmesh::SetSideLightmap(DoomLevelMeshSurface* surface) { if (!surface->ControlSector) { if (surface->Type == ST_UPPERSIDE) { surface->Side->lightmap[0] = surface; } else if (surface->Type == ST_MIDDLESIDE) { surface->Side->lightmap[1] = surface; surface->Side->lightmap[2] = surface; } else if (surface->Type == ST_LOWERSIDE) { surface->Side->lightmap[3] = surface; } } else { const auto& ffloors = surface->Side->sector->e->XFloor.ffloors; for (unsigned int i = 0; i < ffloors.Size(); i++) { if (ffloors[i]->model == surface->ControlSector) { surface->Side->lightmap[4 + i] = surface; } } } } void DoomLevelSubmesh::CreateLinePortalSurface(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v2Bottom = (float)front->floorplane.ZatPoint(v2); FFlatVertex verts[4]; verts[0].x = verts[2].x = v1.X; verts[0].y = verts[2].y = v1.Y; verts[1].x = verts[3].x = v2.X; verts[1].y = verts[3].y = v2.Y; verts[0].z = v1Bottom; verts[1].z = v2Bottom; verts[2].z = v1Top; verts[3].z = v2Top; DoomLevelMeshSurface surf; surf.Submesh = this; surf.Type = ST_MIDDLESIDE; surf.TypeIndex = side->Index(); surf.bSky = front->GetTexture(sector_t::floor) == skyflatnum || front->GetTexture(sector_t::ceiling) == skyflatnum; surf.sampleDimension = side->textures[side_t::mid].LightmapSampleDistance; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; MeshVertices.Push(verts[0]); MeshVertices.Push(verts[1]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); surf.sectorGroup = sectorGroup[front->Index()]; surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::top, v1Top, v1Bottom, v2Top, v2Bottom); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::CreateSideSurfaces(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; sector_t* back = (side->linedef->frontsector == front) ? side->linedef->backsector : side->linedef->frontsector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v2Bottom = (float)front->floorplane.ZatPoint(v2); if (side->linedef->getPortal() && side->linedef->frontsector == front) { CreateLinePortalSurface(doomMap, side); } else if (side->linedef->special == Line_Horizon && front != back) { CreateLineHorizonSurface(doomMap, side); } else if (!back) { CreateFrontWallSurface(doomMap, side); } else { if (side->textures[side_t::mid].texture.isValid()) { CreateMidWallSurface(doomMap, side); } Create3DFloorWallSurfaces(doomMap, side); float v1TopBack = (float)back->ceilingplane.ZatPoint(v1); float v1BottomBack = (float)back->floorplane.ZatPoint(v1); float v2TopBack = (float)back->ceilingplane.ZatPoint(v2); float v2BottomBack = (float)back->floorplane.ZatPoint(v2); if (v1Bottom < v1BottomBack || v2Bottom < v2BottomBack) { CreateBottomWallSurface(doomMap, side); } if (v1Top > v1TopBack || v2Top > v2TopBack) { CreateTopWallSurface(doomMap, side); } } } void DoomLevelSubmesh::CreateLineHorizonSurface(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v2Bottom = (float)front->floorplane.ZatPoint(v2); DoomLevelMeshSurface surf; surf.Submesh = this; surf.Type = ST_MIDDLESIDE; surf.TypeIndex = side->Index(); surf.bSky = front->GetTexture(sector_t::floor) == skyflatnum || front->GetTexture(sector_t::ceiling) == skyflatnum; surf.sampleDimension = side->textures[side_t::mid].LightmapSampleDistance; FFlatVertex verts[4]; verts[0].x = verts[2].x = v1.X; verts[0].y = verts[2].y = v1.Y; verts[1].x = verts[3].x = v2.X; verts[1].y = verts[3].y = v2.Y; verts[0].z = v1Bottom; verts[1].z = v2Bottom; verts[2].z = v1Top; verts[3].z = v2Top; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; MeshVertices.Push(verts[0]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); MeshVertices.Push(verts[1]); surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); surf.sectorGroup = sectorGroup[front->Index()]; surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::top, v1Top, v1Bottom, v2Top, v2Bottom); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::CreateFrontWallSurface(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; /* bool isPolyLine = !!(side->Flags & WALLF_POLYOBJ); if (isPolyLine) { subsector_t* subsector = level.PointInRenderSubsector((side->V1()->fPos() + side->V2()->fPos()) * 0.5); front = subsector->sector; } */ FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v2Bottom = (float)front->floorplane.ZatPoint(v2); FFlatVertex verts[4]; verts[0].x = verts[2].x = v1.X; verts[0].y = verts[2].y = v1.Y; verts[1].x = verts[3].x = v2.X; verts[1].y = verts[3].y = v2.Y; verts[0].z = v1Bottom; verts[1].z = v2Bottom; verts[2].z = v1Top; verts[3].z = v2Top; DoomLevelMeshSurface surf; surf.Submesh = this; surf.bSky = false; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; surf.bSky = false; MeshVertices.Push(verts[0]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); MeshVertices.Push(verts[1]); surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); surf.Type = ST_MIDDLESIDE; surf.TypeIndex = side->Index(); surf.sampleDimension = side->textures[side_t::mid].LightmapSampleDistance; surf.ControlSector = nullptr; surf.sectorGroup = sectorGroup[front->Index()]; surf.texture = side->textures[side_t::mid].texture; surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::top, v1Top, v1Bottom, v2Top, v2Bottom); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::CreateMidWallSurface(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v2Bottom = (float)front->floorplane.ZatPoint(v2); FFlatVertex verts[4]; verts[0].x = verts[2].x = v1.X; verts[0].y = verts[2].y = v1.Y; verts[1].x = verts[3].x = v2.X; verts[1].y = verts[3].y = v2.Y; const auto& texture = side->textures[side_t::mid].texture; if ((side->Flags & WALLF_WRAP_MIDTEX) || (side->linedef->flags & WALLF_WRAP_MIDTEX)) { verts[0].z = v1Bottom; verts[1].z = v2Bottom; verts[2].z = v1Top; verts[3].z = v2Top; } else { int offset = 0; auto gameTexture = TexMan.GetGameTexture(texture); float mid1Top = (float)(gameTexture->GetDisplayHeight() / side->textures[side_t::mid].yScale); float mid2Top = (float)(gameTexture->GetDisplayHeight() / side->textures[side_t::mid].yScale); float mid1Bottom = 0; float mid2Bottom = 0; float yTextureOffset = (float)(side->textures[side_t::mid].yOffset / gameTexture->GetScaleY()); if (side->linedef->flags & ML_DONTPEGBOTTOM) { yTextureOffset += (float)side->sector->planes[sector_t::floor].TexZ; } else { yTextureOffset += (float)(side->sector->planes[sector_t::ceiling].TexZ - gameTexture->GetDisplayHeight() / side->textures[side_t::mid].yScale); } verts[0].z = min(max(yTextureOffset + mid1Bottom, v1Bottom), v1Top); verts[1].z = min(max(yTextureOffset + mid2Bottom, v2Bottom), v2Top); verts[2].z = max(min(yTextureOffset + mid1Top, v1Top), v1Bottom); verts[3].z = max(min(yTextureOffset + mid2Top, v2Top), v2Bottom); } // mid texture DoomLevelMeshSurface surf; surf.Submesh = this; surf.bSky = false; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; surf.bSky = false; surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); if (side->linedef->sidedef[0] != side) { surf.plane = -surf.plane; surf.plane.W = -surf.plane.W; } MeshVertices.Push(verts[0]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); MeshVertices.Push(verts[1]); surf.Type = ST_MIDDLESIDE; surf.TypeIndex = side->Index(); surf.sampleDimension = side->textures[side_t::mid].LightmapSampleDistance; surf.ControlSector = nullptr; surf.sectorGroup = sectorGroup[front->Index()]; surf.texture = texture; surf.alpha = float(side->linedef->alpha); surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::top, verts[2].z, verts[0].z, verts[3].z, verts[1].z); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::Create3DFloorWallSurfaces(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; sector_t* back = (side->linedef->frontsector == front) ? side->linedef->backsector : side->linedef->frontsector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v2Bottom = (float)front->floorplane.ZatPoint(v2); for (unsigned int j = 0; j < front->e->XFloor.ffloors.Size(); j++) { F3DFloor* xfloor = front->e->XFloor.ffloors[j]; // Don't create a line when both sectors have the same 3d floor bool bothSides = false; for (unsigned int k = 0; k < back->e->XFloor.ffloors.Size(); k++) { if (back->e->XFloor.ffloors[k] == xfloor) { bothSides = true; break; } } if (bothSides) continue; DoomLevelMeshSurface surf; surf.Submesh = this; surf.Type = ST_MIDDLESIDE; surf.TypeIndex = side->Index(); surf.ControlSector = xfloor->model; surf.bSky = false; surf.sampleDimension = side->textures[side_t::mid].LightmapSampleDistance; float blZ = (float)xfloor->model->floorplane.ZatPoint(v1); float brZ = (float)xfloor->model->floorplane.ZatPoint(v2); float tlZ = (float)xfloor->model->ceilingplane.ZatPoint(v1); float trZ = (float)xfloor->model->ceilingplane.ZatPoint(v2); FFlatVertex verts[4]; verts[0].x = verts[2].x = v2.X; verts[0].y = verts[2].y = v2.Y; verts[1].x = verts[3].x = v1.X; verts[1].y = verts[3].y = v1.Y; verts[0].z = brZ; verts[1].z = blZ; verts[2].z = trZ; verts[3].z = tlZ; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; MeshVertices.Push(verts[0]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); MeshVertices.Push(verts[1]); surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); surf.sectorGroup = sectorGroup[front->Index()]; surf.texture = side->textures[side_t::mid].texture; surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::top, tlZ, blZ, trZ, brZ); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } } void DoomLevelSubmesh::CreateTopWallSurface(FLevelLocals& doomMap, side_t* side) { sector_t* front = side->sector; sector_t* back = (side->linedef->frontsector == front) ? side->linedef->backsector : side->linedef->frontsector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Top = (float)front->ceilingplane.ZatPoint(v1); float v2Top = (float)front->ceilingplane.ZatPoint(v2); float v1TopBack = (float)back->ceilingplane.ZatPoint(v1); float v2TopBack = (float)back->ceilingplane.ZatPoint(v2); bool bSky = IsTopSideSky(front, back, side); if (!bSky && !IsTopSideVisible(side)) return; FFlatVertex verts[4]; verts[0].x = verts[2].x = v1.X; verts[0].y = verts[2].y = v1.Y; verts[1].x = verts[3].x = v2.X; verts[1].y = verts[3].y = v2.Y; verts[0].z = v1TopBack; verts[1].z = v2TopBack; verts[2].z = v1Top; verts[3].z = v2Top; DoomLevelMeshSurface surf; surf.Submesh = this; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; MeshVertices.Push(verts[0]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); MeshVertices.Push(verts[1]); surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); surf.Type = ST_UPPERSIDE; surf.TypeIndex = side->Index(); surf.bSky = bSky; surf.sampleDimension = side->textures[side_t::top].LightmapSampleDistance; surf.ControlSector = nullptr; surf.sectorGroup = sectorGroup[front->Index()]; surf.texture = side->textures[side_t::top].texture; surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::top, v1Top, v1TopBack, v2Top, v2TopBack); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::CreateBottomWallSurface(FLevelLocals& doomMap, side_t* side) { if (!IsBottomSideVisible(side)) return; sector_t* front = side->sector; sector_t* back = (side->linedef->frontsector == front) ? side->linedef->backsector : side->linedef->frontsector; FVector2 v1 = ToFVector2(side->V1()->fPos()); FVector2 v2 = ToFVector2(side->V2()->fPos()); float v1Bottom = (float)front->floorplane.ZatPoint(v1); float v2Bottom = (float)front->floorplane.ZatPoint(v2); float v1BottomBack = (float)back->floorplane.ZatPoint(v1); float v2BottomBack = (float)back->floorplane.ZatPoint(v2); FFlatVertex verts[4]; verts[0].x = verts[2].x = v1.X; verts[0].y = verts[2].y = v1.Y; verts[1].x = verts[3].x = v2.X; verts[1].y = verts[3].y = v2.Y; verts[0].z = v1Bottom; verts[1].z = v2Bottom; verts[2].z = v1BottomBack; verts[3].z = v2BottomBack; DoomLevelMeshSurface surf; surf.Submesh = this; surf.startVertIndex = MeshVertices.Size(); surf.numVerts = 4; MeshVertices.Push(verts[0]); MeshVertices.Push(verts[2]); MeshVertices.Push(verts[3]); MeshVertices.Push(verts[1]); surf.plane = ToPlane(verts[0].fPos(), verts[1].fPos(), verts[2].fPos(), verts[3].fPos()); surf.Type = ST_LOWERSIDE; surf.TypeIndex = side->Index(); surf.bSky = false; surf.sampleDimension = side->textures[side_t::bottom].LightmapSampleDistance; surf.ControlSector = nullptr; surf.sectorGroup = sectorGroup[front->Index()]; surf.texture = side->textures[side_t::bottom].texture; surf.AlwaysUpdate = !!(front->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = front->lightlevel; SetSideTextureUVs(surf, side, side_t::bottom, v1BottomBack, v1Bottom, v2BottomBack, v2Bottom); MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::SetSideTextureUVs(DoomLevelMeshSurface& surface, side_t* side, side_t::ETexpart texpart, float v1TopZ, float v1BottomZ, float v2TopZ, float v2BottomZ) { FFlatVertex* uvs = &MeshVertices[surface.startVertIndex]; if (surface.texture.isValid()) { const auto gtxt = TexMan.GetGameTexture(surface.texture); FTexCoordInfo tci; GetTexCoordInfo(gtxt, &tci, side, texpart); float startU = tci.FloatToTexU(tci.TextureOffset((float)side->GetTextureXOffset(texpart)) + tci.TextureOffset((float)side->GetTextureXOffset(texpart))); float endU = startU + tci.FloatToTexU(side->TexelLength); uvs[0].u = startU; uvs[1].u = endU; uvs[2].u = startU; uvs[3].u = endU; // To do: the ceiling version is apparently used in some situation related to 3d floors (rover->top.isceiling) //float offset = tci.RowOffset((float)side->GetTextureYOffset(texpart)) + tci.RowOffset((float)side->GetTextureYOffset(texpart)) + (float)side->sector->GetPlaneTexZ(sector_t::ceiling); float offset = tci.RowOffset((float)side->GetTextureYOffset(texpart)) + tci.RowOffset((float)side->GetTextureYOffset(texpart)) + (float)side->sector->GetPlaneTexZ(sector_t::floor); uvs[0].v = tci.FloatToTexV(offset - v1BottomZ); uvs[1].v = tci.FloatToTexV(offset - v2BottomZ); uvs[2].v = tci.FloatToTexV(offset - v1TopZ); uvs[3].v = tci.FloatToTexV(offset - v2TopZ); } else { uvs[0].u = 0.0f; uvs[0].v = 0.0f; uvs[1].u = 0.0f; uvs[1].v = 0.0f; uvs[2].u = 0.0f; uvs[2].v = 0.0f; uvs[3].u = 0.0f; uvs[3].v = 0.0f; } } void DoomLevelSubmesh::CreateFloorSurface(FLevelLocals &doomMap, subsector_t *sub, sector_t *sector, sector_t *controlSector, int typeIndex) { DoomLevelMeshSurface surf; surf.Submesh = this; secplane_t plane; if (!controlSector) { plane = sector->floorplane; surf.bSky = IsSkySector(sector, sector_t::floor); } else { plane = controlSector->ceilingplane; plane.FlipVert(); surf.bSky = false; } surf.numVerts = sub->numlines; surf.startVertIndex = MeshVertices.Size(); surf.texture = (controlSector ? controlSector : sector)->planes[sector_t::floor].Texture; FGameTexture* txt = TexMan.GetGameTexture(surf.texture); float w = txt->GetDisplayWidth(); float h = txt->GetDisplayHeight(); VSMatrix mat = GetPlaneTextureRotationMatrix(txt, sector, sector_t::floor); MeshVertices.Resize(surf.startVertIndex + surf.numVerts); FFlatVertex* verts = &MeshVertices[surf.startVertIndex]; for (int j = 0; j < surf.numVerts; j++) { seg_t* seg = &sub->firstline[j]; FVector2 v1 = ToFVector2(seg->v1->fPos()); FVector2 uv = (mat * FVector4(v1.X / 64.f, -v1.Y / 64.f, 0.f, 1.f)).XY(); // The magic 64.f and negative Y is based on SetFlatVertex verts[j].x = v1.X; verts[j].y = v1.Y; verts[j].z = (float)plane.ZatPoint(v1); verts[j].u = uv.X; verts[j].v = uv.Y; } surf.Type = ST_FLOOR; surf.TypeIndex = typeIndex; surf.sampleDimension = (controlSector ? controlSector : sector)->planes[sector_t::floor].LightmapSampleDistance; surf.ControlSector = controlSector; surf.plane = FVector4((float)plane.Normal().X, (float)plane.Normal().Y, (float)plane.Normal().Z, -(float)plane.D); surf.sectorGroup = sectorGroup[sector->Index()]; surf.AlwaysUpdate = !!(sector->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = sector->lightlevel; MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::CreateCeilingSurface(FLevelLocals& doomMap, subsector_t* sub, sector_t* sector, sector_t* controlSector, int typeIndex) { DoomLevelMeshSurface surf; surf.Submesh = this; secplane_t plane; if (!controlSector) { plane = sector->ceilingplane; surf.bSky = IsSkySector(sector, sector_t::ceiling); } else { plane = controlSector->floorplane; plane.FlipVert(); surf.bSky = false; } surf.numVerts = sub->numlines; surf.startVertIndex = MeshVertices.Size(); surf.texture = (controlSector ? controlSector : sector)->planes[sector_t::ceiling].Texture; FGameTexture* txt = TexMan.GetGameTexture(surf.texture); float w = txt->GetDisplayWidth(); float h = txt->GetDisplayHeight(); VSMatrix mat = GetPlaneTextureRotationMatrix(txt, sector, sector_t::ceiling); MeshVertices.Resize(surf.startVertIndex + surf.numVerts); FFlatVertex* verts = &MeshVertices[surf.startVertIndex]; for (int j = 0; j < surf.numVerts; j++) { seg_t* seg = &sub->firstline[j]; FVector2 v1 = ToFVector2(seg->v1->fPos()); FVector2 uv = (mat * FVector4(v1.X / 64.f, -v1.Y / 64.f, 0.f, 1.f)).XY(); // The magic 64.f and negative Y is based on SetFlatVertex verts[j].x = v1.X; verts[j].y = v1.Y; verts[j].z = (float)plane.ZatPoint(v1); verts[j].u = uv.X; verts[j].v = uv.Y; } surf.Type = ST_CEILING; surf.TypeIndex = typeIndex; surf.sampleDimension = (controlSector ? controlSector : sector)->planes[sector_t::ceiling].LightmapSampleDistance; surf.ControlSector = controlSector; surf.plane = FVector4((float)plane.Normal().X, (float)plane.Normal().Y, (float)plane.Normal().Z, -(float)plane.D); surf.sectorGroup = sectorGroup[sector->Index()]; surf.AlwaysUpdate = !!(sector->Flags & SECF_LM_DYNAMIC); SurfaceUniforms uniforms = DefaultUniforms(); uniforms.uLightLevel = sector->lightlevel; MeshSurfaceUniforms.Push(uniforms); Surfaces.Push(surf); } void DoomLevelSubmesh::CreateSubsectorSurfaces(FLevelLocals &doomMap) { for (unsigned int i = 0; i < doomMap.subsectors.Size(); i++) { subsector_t *sub = &doomMap.subsectors[i]; if (sub->numlines < 3) { continue; } sector_t *sector = sub->sector; if (!sector) continue; CreateFloorSurface(doomMap, sub, sector, nullptr, i); CreateCeilingSurface(doomMap, sub, sector, nullptr, i); for (unsigned int j = 0; j < sector->e->XFloor.ffloors.Size(); j++) { CreateFloorSurface(doomMap, sub, sector, sector->e->XFloor.ffloors[j]->model, i); CreateCeilingSurface(doomMap, sub, sector, sector->e->XFloor.ffloors[j]->model, i); } } } bool DoomLevelSubmesh::IsTopSideSky(sector_t* frontsector, sector_t* backsector, side_t* side) { return IsSkySector(frontsector, sector_t::ceiling) && IsSkySector(backsector, sector_t::ceiling); } bool DoomLevelSubmesh::IsTopSideVisible(side_t* side) { auto tex = TexMan.GetGameTexture(side->GetTexture(side_t::top), true); return tex && tex->isValid(); } bool DoomLevelSubmesh::IsBottomSideVisible(side_t* side) { auto tex = TexMan.GetGameTexture(side->GetTexture(side_t::bottom), true); return tex && tex->isValid(); } bool DoomLevelSubmesh::IsSkySector(sector_t* sector, int plane) { // plane is either sector_t::ceiling or sector_t::floor return sector->GetTexture(plane) == skyflatnum; } bool DoomLevelSubmesh::IsDegenerate(const FVector3 &v0, const FVector3 &v1, const FVector3 &v2) { // A degenerate triangle has a zero cross product for two of its sides. float ax = v1.X - v0.X; float ay = v1.Y - v0.Y; float az = v1.Z - v0.Z; float bx = v2.X - v0.X; float by = v2.Y - v0.Y; float bz = v2.Z - v0.Z; float crossx = ay * bz - az * by; float crossy = az * bx - ax * bz; float crossz = ax * by - ay * bx; float crosslengthsqr = crossx * crossx + crossy * crossy + crossz * crossz; return crosslengthsqr <= 1.e-6f; } void DoomLevelSubmesh::DumpMesh(const FString& objFilename, const FString& mtlFilename) const { auto f = fopen(objFilename.GetChars(), "w"); fprintf(f, "# DoomLevelMesh debug export\n"); fprintf(f, "# MeshVertices: %u, MeshElements: %u, Surfaces: %u\n", MeshVertices.Size(), MeshElements.Size(), Surfaces.Size()); fprintf(f, "mtllib %s\n", mtlFilename.GetChars()); double scale = 1 / 10.0; for (const auto& v : MeshVertices) { fprintf(f, "v %f %f %f\n", v.x * scale, v.y * scale, v.z * scale); } for (const auto& v : MeshVertices) { 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_UNKNOWN: return "unknown"; default: break; } return "error"; }; uint32_t lastSurfaceIndex = -1; bool useErrorMaterial = false; int highestUsedAtlasPage = -1; for (unsigned i = 0, count = MeshElements.Size(); i + 2 < count; i += 3) { auto index = MeshSurfaceIndexes[i / 3]; if(index != lastSurfaceIndex) { lastSurfaceIndex = index; if (unsigned(index) >= Surfaces.Size()) { fprintf(f, "o Surface[%d] (bad index)\n", index); fprintf(f, "usemtl error\n"); useErrorMaterial = true; } else { const auto& surface = 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", MeshElements[i + 0] + 1, MeshElements[i + 0] + 1, MeshElements[i + 1] + 1, MeshElements[i + 1] + 1, MeshElements[i + 2] + 1, MeshElements[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); } void DoomLevelSubmesh::SetupLightmapUvs(FLevelLocals& doomMap) { LMTextureSize = 1024; // TODO cvar for (auto& surface : Surfaces) { BuildSurfaceParams(LMTextureSize, LMTextureSize, surface); } } void DoomLevelSubmesh::PackLightmapAtlas(int lightmapStartIndex) { std::vector sortedSurfaces; sortedSurfaces.reserve(Surfaces.Size()); for (auto& surface : Surfaces) { sortedSurfaces.push_back(&surface); } std::sort(sortedSurfaces.begin(), sortedSurfaces.end(), [](LevelMeshSurface* a, LevelMeshSurface* b) { return a->AtlasTile.Height != b->AtlasTile.Height ? a->AtlasTile.Height > b->AtlasTile.Height : a->AtlasTile.Width > b->AtlasTile.Width; }); RectPacker packer(LMTextureSize, LMTextureSize, RectPacker::Spacing(0)); for (LevelMeshSurface* surf : sortedSurfaces) { int sampleWidth = surf->AtlasTile.Width; int sampleHeight = surf->AtlasTile.Height; auto result = packer.insert(sampleWidth, sampleHeight); int x = result.pos.x, y = result.pos.y; surf->AtlasTile.X = x; surf->AtlasTile.Y = y; surf->AtlasTile.ArrayIndex = lightmapStartIndex + (int)result.pageIndex; // calculate final texture coordinates for (int i = 0; i < (int)surf->numVerts; i++) { auto& vertex = MeshVertices[surf->startVertIndex + i]; vertex.lu = (vertex.lu + x) / (float)LMTextureSize; vertex.lv = (vertex.lv + y) / (float)LMTextureSize; vertex.lindex = (float)surf->AtlasTile.ArrayIndex; } } LMTextureCount = (int)packer.getNumPages(); } BBox DoomLevelSubmesh::GetBoundsFromSurface(const LevelMeshSurface& surface) const { constexpr float M_INFINITY = 1e30f; // TODO cleanup FVector3 low(M_INFINITY, M_INFINITY, M_INFINITY); FVector3 hi(-M_INFINITY, -M_INFINITY, -M_INFINITY); for (int i = int(surface.startVertIndex); i < int(surface.startVertIndex) + surface.numVerts; i++) { for (int j = 0; j < 3; j++) { if (MeshVertices[i].fPos()[j] < low[j]) { low[j] = MeshVertices[i].fPos()[j]; } if (MeshVertices[i].fPos()[j] > hi[j]) { hi[j] = MeshVertices[i].fPos()[j]; } } } BBox bounds; bounds.Clear(); bounds.min = low; bounds.max = hi; return bounds; } DoomLevelSubmesh::PlaneAxis DoomLevelSubmesh::BestAxis(const FVector4& p) { float na = fabs(float(p.X)); float nb = fabs(float(p.Y)); float nc = fabs(float(p.Z)); // figure out what axis the plane lies on if (na >= nb && na >= nc) { return AXIS_YZ; } else if (nb >= na && nb >= nc) { return AXIS_XZ; } return AXIS_XY; } void DoomLevelSubmesh::BuildSurfaceParams(int lightMapTextureWidth, int lightMapTextureHeight, LevelMeshSurface& surface) { BBox bounds = GetBoundsFromSurface(surface); surface.bounds = bounds; if (surface.sampleDimension <= 0) { surface.sampleDimension = LightmapSampleDistance; } surface.sampleDimension = uint16_t(max(int(roundf(float(surface.sampleDimension) / max(1.0f / 4, float(lm_scale)))), 1)); { // Round to nearest power of two uint32_t n = uint16_t(surface.sampleDimension); n |= n >> 1; n |= n >> 2; n |= n >> 4; n |= n >> 8; n = (n + 1) >> 1; surface.sampleDimension = uint16_t(n) ? uint16_t(n) : uint16_t(0xFFFF); } // round off dimensions FVector3 roundedSize; for (int i = 0; i < 3; i++) { bounds.min[i] = surface.sampleDimension * (floor(bounds.min[i] / surface.sampleDimension) - 1); bounds.max[i] = surface.sampleDimension * (ceil(bounds.max[i] / surface.sampleDimension) + 1); roundedSize[i] = (bounds.max[i] - bounds.min[i]) / surface.sampleDimension; } FVector3 tCoords[2] = { FVector3(0.0f, 0.0f, 0.0f), FVector3(0.0f, 0.0f, 0.0f) }; PlaneAxis axis = BestAxis(surface.plane); int width; int height; switch (axis) { default: case AXIS_YZ: width = (int)roundedSize.Y; height = (int)roundedSize.Z; tCoords[0].Y = 1.0f / surface.sampleDimension; tCoords[1].Z = 1.0f / surface.sampleDimension; break; case AXIS_XZ: width = (int)roundedSize.X; height = (int)roundedSize.Z; tCoords[0].X = 1.0f / surface.sampleDimension; tCoords[1].Z = 1.0f / surface.sampleDimension; break; case AXIS_XY: width = (int)roundedSize.X; height = (int)roundedSize.Y; tCoords[0].X = 1.0f / surface.sampleDimension; tCoords[1].Y = 1.0f / surface.sampleDimension; break; } // clamp width if (width > lightMapTextureWidth - 2) { tCoords[0] *= ((float)(lightMapTextureWidth - 2) / (float)width); width = (lightMapTextureWidth - 2); } // clamp height if (height > lightMapTextureHeight - 2) { tCoords[1] *= ((float)(lightMapTextureHeight - 2) / (float)height); height = (lightMapTextureHeight - 2); } surface.translateWorldToLocal = bounds.min; surface.projLocalToU = tCoords[0]; surface.projLocalToV = tCoords[1]; for (int i = 0; i < surface.numVerts; i++) { FVector3 tDelta = MeshVertices[surface.startVertIndex + i].fPos() - surface.translateWorldToLocal; MeshVertices[surface.startVertIndex + i].lu = (tDelta | surface.projLocalToU); MeshVertices[surface.startVertIndex + i].lv = (tDelta | surface.projLocalToV); } // project tCoords so they lie on the plane const FVector4& plane = surface.plane; float d = ((bounds.min | FVector3(plane.X, plane.Y, plane.Z)) - plane.W) / plane[axis]; //d = (plane->PointToDist(bounds.min)) / plane->Normal()[axis]; for (int i = 0; i < 2; i++) { tCoords[i].MakeUnit(); d = (tCoords[i] | FVector3(plane.X, plane.Y, plane.Z)) / plane[axis]; //d = dot(tCoords[i], plane->Normal()) / plane->Normal()[axis]; tCoords[i][axis] -= d; } surface.AtlasTile.Width = width; surface.AtlasTile.Height = height; } SurfaceUniforms DoomLevelSubmesh::DefaultUniforms() { SurfaceUniforms surfaceUniforms = {}; surfaceUniforms.uFogColor = toFVector4(PalEntry(0xffffffff)); surfaceUniforms.uDesaturationFactor = 0.0f; surfaceUniforms.uAlphaThreshold = 0.5f; surfaceUniforms.uAddColor = toFVector4(PalEntry(0)); surfaceUniforms.uObjectColor = toFVector4(PalEntry(0xffffffff)); surfaceUniforms.uObjectColor2 = toFVector4(PalEntry(0)); surfaceUniforms.uTextureBlendColor = toFVector4(PalEntry(0)); surfaceUniforms.uTextureAddColor = toFVector4(PalEntry(0)); surfaceUniforms.uTextureModulateColor = toFVector4(PalEntry(0)); surfaceUniforms.uLightDist = 0.0f; surfaceUniforms.uLightFactor = 0.0f; surfaceUniforms.uFogDensity = 0.0f; surfaceUniforms.uLightLevel = 255.0f;// -1.0f; surfaceUniforms.uInterpolationFactor = 0; surfaceUniforms.uVertexColor = { 1.0f, 1.0f, 1.0f, 1.0f }; surfaceUniforms.uGlowTopColor = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uGlowBottomColor = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uGlowTopPlane = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uGlowBottomPlane = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uGradientTopPlane = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uGradientBottomPlane = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uSplitTopPlane = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uSplitBottomPlane = { 0.0f, 0.0f, 0.0f, 0.0f }; surfaceUniforms.uDynLightColor = { 0.0f, 0.0f, 0.0f, 1.0f }; surfaceUniforms.uDetailParms = { 0.0f, 0.0f, 0.0f, 0.0f }; #ifdef NPOT_EMULATION surfaceUniforms.uNpotEmulation = { 0,0,0,0 }; #endif surfaceUniforms.uClipSplit.X = -1000000.f; surfaceUniforms.uClipSplit.Y = 1000000.f; return surfaceUniforms; }