diff --git a/CMakeLists.txt b/CMakeLists.txt index f3a2da2..ae6a3b0 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -8,7 +8,7 @@ include( CheckCXXCompilerFlag ) project( ZDRay ) -SET( CMAKE_CXX_STANDARD 14 ) +SET( CMAKE_CXX_STANDARD 17 ) IF( NOT CMAKE_BUILD_TYPE ) SET( CMAKE_BUILD_TYPE Debug CACHE STRING @@ -196,6 +196,7 @@ set( SOURCES src/lightmap/glsl_vert.h src/lightmap/glsl_frag_resolve.h src/lightmap/renderdoc_app.h + src/lightmap/portal.h src/math/mat.cpp src/math/plane.cpp src/math/angle.cpp diff --git a/src/level/doomdata.h b/src/level/doomdata.h index 3406f58..a39518e 100644 --- a/src/level/doomdata.h +++ b/src/level/doomdata.h @@ -7,6 +7,7 @@ #include "math/mathlib.h" #include #include +#include #undef MIN #undef MAX #undef min @@ -70,6 +71,8 @@ struct IntSideDef inline int GetSampleDistanceBottom() const { return sampleDistanceBottom ? sampleDistanceBottom : sampleDistance; } TArray props; + + inline int GetSectorGroup() const; }; struct MapLineDef @@ -107,6 +110,8 @@ struct IntLineDef TArray ids; IntSector *frontsector = nullptr, *backsector = nullptr; + + inline int GetSectorGroup() const; }; struct MapSector @@ -158,6 +163,8 @@ struct IntSector TArray lines; TArray portals; + int group = 0; + // Utility functions inline const char* GetTextureName(int plane) const { return plane != PLANE_FLOOR ? data.ceilingpic : data.floorpic; } @@ -173,6 +180,16 @@ struct IntSector } }; +inline int IntLineDef::GetSectorGroup() const +{ + return frontsector ? frontsector->group : (backsector ? backsector->group : 0); +} + +inline int IntSideDef::GetSectorGroup() const +{ + return line ? line->GetSectorGroup() : 0; +} + struct MapSubsector { uint16_t numlines; @@ -329,6 +346,21 @@ struct ThingLight IntSector *sector; MapSubsectorEx *ssect; + // Portal related functionality + std::optional relativePosition; + int sectorGroup = 0; + + // Portal aware position + vec3 LightRelativeOrigin() const + { + if (relativePosition) + { + return *relativePosition; + } + return LightOrigin(); + } + + // Absolute X, Y, Z position of the light vec3 LightOrigin() const { float originZ; diff --git a/src/level/level.cpp b/src/level/level.cpp index 060cd76..d3391a4 100644 --- a/src/level/level.cpp +++ b/src/level/level.cpp @@ -691,6 +691,49 @@ void FLevel::PostLoadInitialization() } } } + + // Discover sector groups (graph islands) + { + int groupId = 0; + std::vector candidates; + + auto canPass = [&](IntLineDef* line) { + // Further conditions can be added to further split the map into groups + return line->special == 0 || (line->special != Line_SetPortal && line->special != Line_Horizon); + }; + + for (auto& sector : Sectors) + { + if (!sector.group) + { + sector.group = ++groupId; + candidates.push_back(§or); + + while (!candidates.empty()) + { + auto* sector = candidates[candidates.size() - 1]; + candidates.pop_back(); + + for (const auto& line : sector->lines) + { + if (canPass(line)) + { + if (line->frontsector && !line->frontsector->group) + { + line->frontsector->group = groupId; + candidates.push_back(line->frontsector); + } + if (line->backsector && !line->backsector->group) + { + line->backsector->group = groupId; + candidates.push_back(line->backsector); + } + } + } + } + } + } + } } void FProcessor::BuildNodes() diff --git a/src/level/level_light.cpp b/src/level/level_light.cpp index 6e16283..756a0f8 100644 --- a/src/level/level_light.cpp +++ b/src/level/level_light.cpp @@ -290,6 +290,7 @@ void FLevel::CreateLights() thingLight.sector = GetSectorFromSubSector(thingLight.ssect); thingLight.origin.x = x; thingLight.origin.y = y; + thingLight.sectorGroup = thingLight.sector->group; ThingLights.Push(thingLight); } diff --git a/src/lightmap/glsl_frag.h b/src/lightmap/glsl_frag.h index d163785..0edd66b 100644 --- a/src/lightmap/glsl_frag.h +++ b/src/lightmap/glsl_frag.h @@ -52,14 +52,16 @@ struct LightInfo { vec3 Origin; float Padding0; + vec3 RelativeOrigin; + float Padding1; float Radius; float Intensity; float InnerAngleCos; float OuterAngleCos; vec3 SpotDir; - float Padding1; - vec3 Color; float Padding2; + vec3 Color; + float Padding3; }; layout(set = 0, binding = 1) buffer SurfaceIndexBuffer { uint surfaceIndices[]; }; @@ -91,6 +93,7 @@ vec2 Hammersley(uint i, uint N); float RadicalInverse_VdC(uint bits); bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax); +bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax); int TraceFirstHitTriangle(vec3 origin, float tmin, vec3 dir, float tmax); int TraceFirstHitTriangleT(vec3 origin, float tmin, vec3 dir, float tmax, out float t); @@ -117,10 +120,10 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light) { const float minDistance = 0.01; vec3 incoming = vec3(0.0); - float dist = distance(light.Origin, origin); + float dist = distance(light.RelativeOrigin, origin); if (dist > minDistance && dist < light.Radius) { - vec3 dir = normalize(light.Origin - origin); + vec3 dir = normalize(light.RelativeOrigin - origin); float distAttenuation = max(1.0 - (dist / light.Radius), 0.0); float angleAttenuation = 1.0f; @@ -139,12 +142,13 @@ vec3 TraceLight(vec3 origin, vec3 normal, LightInfo light) float attenuation = distAttenuation * angleAttenuation * spotAttenuation; if (attenuation > 0.0) { - if (!TraceAnyHit(origin, minDistance, dir, dist)) + if(TracePoint(origin, light.Origin, minDistance, dir, dist)) { incoming.rgb += light.Color * (attenuation * light.Intensity); } } } + return incoming; } @@ -236,6 +240,7 @@ int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, } else { + t = tmax; return -1; } } @@ -466,6 +471,8 @@ int TraceFirstHitTriangleNoPortal(vec3 origin, float tmin, vec3 dir, float tmax, return hit.triangle; } } + + tparam = tracedist; return -1; } @@ -512,4 +519,48 @@ bool TraceAnyHit(vec3 origin, float tmin, vec3 dir, float tmax) return TraceFirstHitTriangle(origin, tmin, dir, tmax) >= 0; } +bool TracePoint(vec3 origin, vec3 target, float tmin, vec3 dir, float tmax) +{ + int primitiveID; + float t; + while(true) + { + t = tmax; + primitiveID = TraceFirstHitTriangleNoPortal(origin, tmin, dir, tmax, t); + + origin += dir * t; + tmax -= t; + + if(primitiveID < 0) + { + // We didn't hit anything + break; + } + + SurfaceInfo surface = surfaces[surfaceIndices[primitiveID]]; + + if(surface.PortalIndex == 0) + { + break; + } + + if(dot(surface.Normal, dir) >= 0.0) + { + continue; + } + + mat4 transformationMatrix = portals[surface.PortalIndex].Transformation; + origin = (transformationMatrix * vec4(origin, 1.0)).xyz; + dir = (transformationMatrix * vec4(dir, 0.0)).xyz; + +#if defined(USE_RAYQUERY) +#else + origin += dir * tmin; + tmax -= tmin; +#endif + } + + return distance(origin, target) <= 1.0; +} + )glsl"; diff --git a/src/lightmap/gpuraytracer.cpp b/src/lightmap/gpuraytracer.cpp index 11f1777..35c6223 100644 --- a/src/lightmap/gpuraytracer.cpp +++ b/src/lightmap/gpuraytracer.cpp @@ -216,6 +216,7 @@ void GPURaytracer::RenderAtlasImage(size_t pageIndex) for (ThingLight* light : surface->LightList) { lightinfo->Origin = light->LightOrigin(); + lightinfo->RelativeOrigin = light->LightRelativeOrigin(); lightinfo->Radius = light->LightRadius(); lightinfo->Intensity = light->intensity; lightinfo->InnerAngleCos = light->innerAngleCos; @@ -1015,14 +1016,11 @@ std::vector GPURaytracer::CreatePortalInfo() portals.push_back(noPortal); } - for (const auto& surface : mesh->surfaces) + for (const auto& portal : mesh->portals) { - if (surface->portalIndex >= 0) - { - PortalInfo info; - info.Transformation = mesh->portals[surface->portalIndex]->transformation; - portals.push_back(info); - } + PortalInfo info; + info.Transformation = portal->transformation; + portals.push_back(info); } return portals; diff --git a/src/lightmap/gpuraytracer.h b/src/lightmap/gpuraytracer.h index 3fd480e..25b03f6 100644 --- a/src/lightmap/gpuraytracer.h +++ b/src/lightmap/gpuraytracer.h @@ -50,16 +50,20 @@ struct LightInfo { vec3 Origin; float Padding0; + vec3 RelativeOrigin; + float Padding1; float Radius; float Intensity; float InnerAngleCos; float OuterAngleCos; vec3 SpotDir; - float Padding1; - vec3 Color; float Padding2; + vec3 Color; + float Padding3; }; +static_assert(sizeof(LightInfo) == sizeof(float) * 20); + struct CollisionNodeBufferHeader { int root; diff --git a/src/lightmap/levelmesh.cpp b/src/lightmap/levelmesh.cpp index d4b1f4c..7372f63 100644 --- a/src/lightmap/levelmesh.cpp +++ b/src/lightmap/levelmesh.cpp @@ -60,6 +60,13 @@ LevelMesh::LevelMesh(FLevel &doomMap, int sampleDistance, int textureSize) printf("Surfaces total: %i\n\n", (int)surfaces.size()); + // Update sector group of the surfacesaa + for (auto& surface : surfaces) + { + surface->sectorGroup = surface->type == ST_CEILING || surface->type == ST_FLOOR ? + doomMap.GetSectorFromSubSector(&doomMap.GLSubsectors[surface->typeIndex])->group : (doomMap.Sides[surface->typeIndex].GetSectorGroup()); + } + printf("Building level mesh...\n\n"); for (size_t i = 0; i < surfaces.size(); i++) @@ -112,66 +119,12 @@ LevelMesh::LevelMesh(FLevel &doomMap, int sampleDistance, int textureSize) } printf("Finding smoothing groups...\n\n"); - - for (size_t i = 0; i < surfaces.size(); i++) - { - // Is this surface in the same plane as an existing smoothing group? - int smoothingGroupIndex = -1; - for (size_t j = 0; j < smoothingGroups.size(); j++) - { - float direction = std::abs(dot(smoothingGroups[j].Normal(), surfaces[i]->plane.Normal())); - if (direction >= 0.9999f && direction <= 1.001f) - { - float dist = std::abs(smoothingGroups[j].Distance(surfaces[i]->plane.Normal() * surfaces[i]->plane.d)); - if (dist <= 0.01f) - { - smoothingGroupIndex = (int)j; - break; - } - } - } - - // Surface is in a new plane. Create a smoothing group for it - if (smoothingGroupIndex == -1) - { - smoothingGroupIndex = smoothingGroups.size(); - smoothingGroups.push_back(surfaces[i]->plane); - } - - surfaces[i]->smoothingGroupIndex = smoothingGroupIndex; - } - - printf("Created %d smoothing groups for %d surfaces\n\n", (int)smoothingGroups.size(), (int)surfaces.size()); - + BuildSmoothingGroups(doomMap); printf("Building collision data...\n\n"); Collision = std::make_unique(MeshVertices.Data(), MeshVertices.Size(), MeshElements.Data(), MeshElements.Size()); - printf("Building light list...\n\n"); - - for (ThingLight& light : map->ThingLights) - { - SphereShape sphere; - sphere.center = light.LightOrigin(); - sphere.radius = light.LightRadius(); - - for (int triangleIndex : TriangleMeshShape::find_all_hits(Collision.get(), &sphere)) - { - Surface* surface = surfaces[MeshSurfaces[triangleIndex]].get(); - bool found = false; - for (ThingLight* light2 : surface->LightList) - { - if (light2 == &light) - { - found = true; - break; - } - } - if (!found) - surface->LightList.push_back(&light); - } - } - + BuildLightLists(doomMap); /* std::map lightStats; for (auto& surface : surfaces) @@ -182,6 +135,119 @@ LevelMesh::LevelMesh(FLevel &doomMap, int sampleDistance, int textureSize) */ } +void LevelMesh::BuildSmoothingGroups(FLevel& doomMap) +{ + for (size_t i = 0; i < surfaces.size(); i++) + { + // Is this surface in the same plane as an existing smoothing group? + int smoothingGroupIndex = -1; + + auto surface = surfaces[i].get(); + + for (size_t j = 0; j < smoothingGroups.size(); j++) + { + if (surface->sectorGroup == smoothingGroups[j].sectorGroup) + { + float direction = std::abs(dot(smoothingGroups[j].plane.Normal(), surface->plane.Normal())); + if (direction >= 0.9999f && direction <= 1.001f) + { + float dist = std::abs(smoothingGroups[j].plane.Distance(surface->plane.Normal() * surface->plane.d)); + if (dist <= 0.01f) + { + smoothingGroupIndex = (int)j; + break; + } + } + } + } + + // Surface is in a new plane. Create a smoothing group for it + if (smoothingGroupIndex == -1) + { + smoothingGroupIndex = smoothingGroups.size(); + + SmoothingGroup group; + group.plane = surface->plane; + group.sectorGroup = surface->sectorGroup; + smoothingGroups.push_back(group); + } + + surface->smoothingGroupIndex = smoothingGroupIndex; + } + + printf("Created %d smoothing groups for %d surfaces\n\n", (int)smoothingGroups.size(), (int)surfaces.size()); +} + +void LevelMesh::PropagateLight(FLevel& doomMap, ThingLight *light) +{ + if (lightPropagationRecursiveDepth > 32) + { + return; + } + + SphereShape sphere; + sphere.center = light->LightRelativeOrigin(); + sphere.radius = light->LightRadius(); + lightPropagationRecursiveDepth++; + std::set portalsToErase; + for (int triangleIndex : TriangleMeshShape::find_all_hits(Collision.get(), &sphere)) + { + Surface* surface = surfaces[MeshSurfaces[triangleIndex]].get(); + + // skip any surface which isn't physically connected to the sector group in which the light resides + if (light->sectorGroup == surface->sectorGroup) + { + if (surface->portalIndex >= 0) + { + auto portal = portals[surface->portalIndex].get(); + + if (touchedPortals.insert(*portal).second) + { + auto fakeLight = std::make_unique(*light); + + fakeLight->relativePosition.emplace(portal->TransformPosition(light->LightRelativeOrigin())); + fakeLight->sectorGroup = portal->targetSectorGroup; + + PropagateLight(doomMap, fakeLight.get()); + portalsToErase.insert(*portal); + portalLights.push_back(std::move(fakeLight)); + } + } + + // Add light to the list if it isn't already there + bool found = false; + for (ThingLight* light2 : surface->LightList) + { + if (light2 == light) + { + found = true; + break; + } + } + if (!found) + surface->LightList.push_back(light); + } + } + + for (auto& portal : portalsToErase) + { + touchedPortals.erase(portal); + } + + lightPropagationRecursiveDepth--; +} + +void LevelMesh::BuildLightLists(FLevel& doomMap) +{ + for (unsigned i = 0; i < map->ThingLights.Size(); ++i) + { + printf("Building light lists: %u / %u\r", i, map->ThingLights.Size()); + PropagateLight(doomMap, &map->ThingLights[i]); + } + + printf("Building light lists: %u / %u\n", map->ThingLights.Size(), map->ThingLights.Size()); +} + // Determines a lightmap block in which to map to the lightmap texture. // Width and height of the block is calcuated and steps are computed to determine where each texel will be positioned on the surface void LevelMesh::BuildSurfaceParams(Surface* surface) @@ -504,10 +570,21 @@ int LevelMesh::CreateLinePortal(FLevel& doomMap, const IntLineDef& srcLine, cons // printf("Portal translation: %.3f %.3f %.3f\n", translation.x, translation.y, translation.z); portal->transformation = mat4::translate(translation); + portal->sourceSectorGroup = srcLine.GetSectorGroup(); + portal->targetSectorGroup = dstLine.GetSectorGroup(); } - portals.push_back(std::move(portal)); - return int(portals.size() - 1); + // Deduplicate portals + auto it = portalCache.find(*portal); + + if (it == portalCache.end()) + { + int id = int(portals.size()); + portalCache.emplace(*portal, id); + portals.push_back(std::move(portal)); + return id; + } + return it->second; } int LevelMesh::CheckAndMakePortal(FLevel& doomMap, MapSubsectorEx* sub, IntSector* sector, int typeIndex, int plane) @@ -588,10 +665,21 @@ int LevelMesh::CreatePlanePortal(FLevel& doomMap, const IntLineDef& srcLine, con // printf("Portal translation: %.3f %.3f %.3f\n", translation.x, translation.y, translation.z); portal->transformation = mat4::translate(translation); + portal->sourceSectorGroup = srcLine.GetSectorGroup(); + portal->targetSectorGroup = dstLine.GetSectorGroup(); } - portals.push_back(std::move(portal)); - return int(portals.size() - 1); + // Deduplicate portals + auto it = portalCache.find(*portal); + + if (it == portalCache.end()) + { + int id = int(portals.size()); + portalCache.emplace(*portal, id); + portals.push_back(std::move(portal)); + return id; + } + return it->second; } void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) diff --git a/src/lightmap/levelmesh.h b/src/lightmap/levelmesh.h index 63fb0d2..9c3add9 100644 --- a/src/lightmap/levelmesh.h +++ b/src/lightmap/levelmesh.h @@ -31,12 +31,15 @@ #include #include #include +#include +#include #include "framework/tarray.h" #include "framework/halffloat.h" #include "lightmaptexture.h" #include "math/mathlib.h" #include "collision.h" +#include "portal.h" #include "dp_rect_pack/dp_rect_pack.h" @@ -60,11 +63,6 @@ enum SurfaceType ST_FLOOR }; -struct Portal -{ - mat4 transformation = mat4::identity(); -}; - struct Surface { // Surface geometry @@ -86,6 +84,9 @@ struct Surface // Portal int portalDestinationIndex = -1; // line or sector index int portalIndex = -1; + + // Sector group + int sectorGroup = 0; // Touching light sources std::vector LightList; @@ -117,6 +118,12 @@ struct Surface int smoothingGroupIndex = -1; }; +struct SmoothingGroup +{ + Plane plane = Plane(0, 0, 1, 0); + int sectorGroup = 0; +}; + class LevelMesh { public: @@ -132,7 +139,7 @@ public: std::vector> textures; - std::vector smoothingGroups; + std::vector smoothingGroups; std::vector> portals; @@ -148,6 +155,14 @@ public: std::unique_ptr Collision; private: + // Portal to portals[] index + std::map portalCache; + + // Portal lights + std::vector> portalLights; + std::set touchedPortals; + int lightPropagationRecursiveDepth = 0; + void CreateSubsectorSurfaces(FLevel &doomMap); void CreateCeilingSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSector *sector, int typeIndex, bool is3DFloor); void CreateFloorSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSector *sector, int typeIndex, bool is3DFloor); @@ -155,6 +170,12 @@ private: void BuildSurfaceParams(Surface* surface); BBox GetBoundsFromSurface(const Surface* surface); + + void BuildLightLists(FLevel &doomMap); + void PropagateLight(FLevel& doomMap, ThingLight* thing); + + void BuildSmoothingGroups(FLevel& doomMap); + void BlurSurfaces(); void FinishSurface(RectPacker& packer, Surface* surface); diff --git a/src/lightmap/portal.h b/src/lightmap/portal.h new file mode 100644 index 0000000..1adcbe9 --- /dev/null +++ b/src/lightmap/portal.h @@ -0,0 +1,73 @@ +#pragma once + +#include "math/mathlib.h" + +struct Portal +{ + mat4 transformation = mat4::identity(); + int sourceSectorGroup = 0; + int targetSectorGroup = 0; + + inline vec3 TransformPosition(const vec3& pos) const + { + auto v = transformation * vec4(pos, 1.0); + return vec3(v.x, v.y, v.z); + } + + inline vec3 TransformRotation(const vec3& dir) const + { + auto v = transformation * vec4(dir, 0.0); + return vec3(v.x, v.y, v.z); + } + + // Checks only transformation + inline bool IsInverseTransformationPortal(const Portal& portal) const + { + auto diff = portal.TransformPosition(TransformPosition(vec3(0))); + return abs(diff.x) < 0.001 && abs(diff.y) < 0.001 && abs(diff.z) < 0.001; + } + + // Checks only transformation + inline bool IsEqualTransformationPortal(const Portal& portal) const + { + auto diff = portal.TransformPosition(vec3(0)) - TransformPosition(vec3(0)); + return (abs(diff.x) < 0.001 && abs(diff.y) < 0.001 && abs(diff.z) < 0.001); + } + + + // Checks transformation, source and destiantion sector groups + inline bool IsEqualPortal(const Portal& portal) const + { + return sourceSectorGroup == portal.sourceSectorGroup && targetSectorGroup == portal.targetSectorGroup && IsEqualTransformationPortal(portal); + } + + // Checks transformation, source and destiantion sector groups + inline bool IsInversePortal(const Portal& portal) const + { + return sourceSectorGroup == portal.targetSectorGroup && targetSectorGroup == portal.sourceSectorGroup && IsInverseTransformationPortal(portal); + } + + inline void DumpInfo() + { + auto v = TransformPosition(vec3(0)); + printf("Portal offset: %.3f %.3f %.3f\n\tsource group:\t%d\n\ttarget group:\t%d", v.x, v.y, v.z, sourceSectorGroup, targetSectorGroup); + } +}; + +// for use with std::set to recursively go through portals and skip returning portals +struct RecursivePortalComparator +{ + bool operator()(const Portal& a, const Portal& b) const + { + return !a.IsInversePortal(b) && std::memcmp(&a.transformation, &b.transformation, sizeof(mat4)) < 0; + } +}; + +// for use with std::map to reject portals which have the same effect for light rays +struct IdenticalPortalComparator +{ + bool operator()(const Portal& a, const Portal& b) const + { + return !a.IsEqualPortal(b) && std::memcmp(&a.transformation, &b.transformation, sizeof(mat4)) < 0; + } +};