From fa1d2fb21544c415a75fb2a15e8d311496e8f544 Mon Sep 17 00:00:00 2001 From: Magnus Norddahl Date: Wed, 20 Oct 2021 05:28:41 +0200 Subject: [PATCH] Prepare the code for writing a new ray tracer --- CMakeLists.txt | 4 + src/level/level.cpp | 11 +- src/level/level.h | 5 +- src/level/level_udmf.cpp | 6 +- src/lightmap/lightmap.cpp | 870 ++----------------------------------- src/lightmap/lightmap.h | 53 +-- src/lightmap/pngwriter.cpp | 217 +++++++++ src/lightmap/pngwriter.h | 78 ++++ src/lightmap/raytracer.cpp | 31 ++ src/lightmap/raytracer.h | 16 + src/lightmap/surfaces.cpp | 658 ++++++++++++++++++++-------- src/lightmap/surfaces.h | 93 +++- 12 files changed, 970 insertions(+), 1072 deletions(-) create mode 100644 src/lightmap/pngwriter.cpp create mode 100644 src/lightmap/pngwriter.h create mode 100644 src/lightmap/raytracer.cpp create mode 100644 src/lightmap/raytracer.h diff --git a/CMakeLists.txt b/CMakeLists.txt index a7de803..c05cc48 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -154,6 +154,8 @@ set( SOURCES src/nodebuilder/nodebuild_gl.cpp src/nodebuilder/nodebuild_utility.cpp src/nodebuilder/nodebuild_classify_nosse2.cpp + src/lightmap/pngwriter.cpp + src/lightmap/raytracer.cpp src/lightmap/lightmap.cpp src/lightmap/surfacelight.cpp src/lightmap/surfaces.cpp @@ -198,6 +200,8 @@ set( HEADERS src/framework/xs_Float.h src/framework/halffloat.h src/framework/binfile.h + src/lightmap/pngwriter.h + src/lightmap/raytracer.h src/lightmap/lightmap.h src/lightmap/surfacelight.h src/lightmap/surfaces.h diff --git a/src/level/level.cpp b/src/level/level.cpp index 7ee1716..fceb048 100644 --- a/src/level/level.cpp +++ b/src/level/level.cpp @@ -19,6 +19,7 @@ */ #include "level/level.h" +#include "lightmap/lightmap.h" //#include "rejectbuilder.h" #include @@ -688,8 +689,10 @@ void FProcessor::BuildNodes() void FProcessor::BuildLightmaps() { Level.SetupLights(); - LMBuilder.CreateLightmaps(Level, Samples, LMDims); - LightmapsBuilt = true; + LightmapMesh = std::make_unique(Level, Samples, LMDims); + DLightRaytracer raytracer; + raytracer.Raytrace(LightmapMesh.get()); + LightmapMesh->CreateTextures(); } void FProcessor::Write (FWadWriter &out) @@ -886,9 +889,9 @@ void FProcessor::Write (FWadWriter &out) out.CopyLump (Wad, Wad.FindMapLump ("BEHAVIOR", Lump)); out.CopyLump (Wad, Wad.FindMapLump ("SCRIPTS", Lump)); } - /*if (LightmapsBuilt) + /*if (LightmapMesh) { - LMBuilder.AddLightmapLump(out); + LightmapMesh->AddLightmapLump(out); }*/ if (Level.GLNodes != nullptr && !compressGL) { diff --git a/src/level/level.h b/src/level/level.h index 6402fc6..923617a 100644 --- a/src/level/level.h +++ b/src/level/level.h @@ -7,7 +7,7 @@ #include "framework/tarray.h" #include "nodebuilder/nodebuild.h" #include "blockmapbuilder/blockmapbuilder.h" -#include "lightmap/lightmap.h" +#include "lightmap/surfaces.h" #include #define DEFINE_SPECIAL(name, num, min, max, map) name = num, @@ -147,6 +147,5 @@ private: int Lump; bool NodesBuilt = false; - bool LightmapsBuilt = false; - LightmapBuilder LMBuilder; + std::unique_ptr LightmapMesh; }; diff --git a/src/level/level_udmf.cpp b/src/level/level_udmf.cpp index 7d8f689..5664737 100644 --- a/src/level/level_udmf.cpp +++ b/src/level/level_udmf.cpp @@ -887,10 +887,10 @@ void FProcessor::WriteUDMF(FWadWriter &out) } } - if (LightmapsBuilt) + if (LightmapMesh) { - LMBuilder.AddLightmapLump(out); - // LMBuilder.ExportMesh("level.obj"); + LightmapMesh->AddLightmapLump(out); + // LightmapMesh->ExportMesh("level.obj"); } out.CreateLabel("ENDMAP"); diff --git a/src/lightmap/lightmap.cpp b/src/lightmap/lightmap.cpp index b96a74d..2bed0c6 100644 --- a/src/lightmap/lightmap.cpp +++ b/src/lightmap/lightmap.cpp @@ -46,35 +46,28 @@ extern int Multisample; extern int LightBounce; -extern float GridSize; -LightmapBuilder::LightmapBuilder() +DLightRaytracer::DLightRaytracer() { } -LightmapBuilder::~LightmapBuilder() +DLightRaytracer::~DLightRaytracer() { } -void LightmapBuilder::CreateLightmaps(FLevel &doomMap, int sampleDistance, int textureSize) +void DLightRaytracer::Raytrace(LevelMesh* level) { - map = &doomMap; - samples = sampleDistance; - textureWidth = textureSize; - textureHeight = textureSize; - - mesh = std::make_unique(doomMap); + mesh = level; CreateSurfaceLights(); CreateTraceTasks(); - CreateLightProbes(); - SetupTaskProcessed("Tracing light probes", lightProbes.size()); - Worker::RunJob(lightProbes.size(), [=](int id) { + SetupTaskProcessed("Tracing light probes", mesh->lightProbes.size()); + Worker::RunJob(mesh->lightProbes.size(), [=](int id) { LightProbe(id); PrintTaskProcessed(); }); - printf("Probes traced: %i \n\n", (int)lightProbes.size()); + printf("Probes traced: %i \n\n", (int)mesh->lightProbes.size()); SetupTaskProcessed("Tracing surfaces", traceTasks.size()); Worker::RunJob(traceTasks.size(), [=](int id) { @@ -92,46 +85,12 @@ void LightmapBuilder::CreateLightmaps(FLevel &doomMap, int sampleDistance, int t }); printf("Texels traced: %i \n\n", tracedTexels); } - - for (auto &surf : mesh->surfaces) - { - FinishSurface(surf.get()); - } -} - -BBox LightmapBuilder::GetBoundsFromSurface(const Surface *surface) -{ - Vec3 low(M_INFINITY, M_INFINITY, M_INFINITY); - Vec3 hi(-M_INFINITY, -M_INFINITY, -M_INFINITY); - - BBox bounds; - bounds.Clear(); - - for (int i = 0; i < surface->numVerts; i++) - { - for (int j = 0; j < 3; j++) - { - if (surface->verts[i][j] < low[j]) - { - low[j] = surface->verts[i][j]; - } - if (surface->verts[i][j] > hi[j]) - { - hi[j] = surface->verts[i][j]; - } - } - } - - bounds.min = low; - bounds.max = hi; - - return bounds; } // Traces to the ceiling surface. Will emit light if the surface that was traced is a sky -bool LightmapBuilder::EmitFromCeiling(const Surface *surface, const Vec3 &origin, const Vec3 &normal, Vec3 &color) +bool DLightRaytracer::EmitFromCeiling(const Surface *surface, const Vec3 &origin, const Vec3 &normal, Vec3 &color) { - float attenuation = surface ? normal.Dot(map->GetSunDirection()) : 1.0f; + float attenuation = surface ? normal.Dot(mesh->map->GetSunDirection()) : 1.0f; if (attenuation <= 0) { @@ -139,7 +98,7 @@ bool LightmapBuilder::EmitFromCeiling(const Surface *surface, const Vec3 &origin return false; } - LevelTraceHit trace = mesh->Trace(origin, origin + (map->GetSunDirection() * 32768.0f)); + LevelTraceHit trace = mesh->Trace(origin, origin + (mesh->map->GetSunDirection() * 32768.0f)); if (trace.fraction == 1.0f) { @@ -157,7 +116,7 @@ bool LightmapBuilder::EmitFromCeiling(const Surface *surface, const Vec3 &origin return false; } - color += map->GetSunColor() * attenuation; + color += mesh->map->GetSunColor() * attenuation; return true; } @@ -175,7 +134,7 @@ static float radians(float degrees) } // Traces a line from the texel's origin to the sunlight direction and against all nearby thing lights -Vec3 LightmapBuilder::LightTexelSample(const Vec3 &origin, Surface *surface) +Vec3 DLightRaytracer::LightTexelSample(const Vec3 &origin, Surface *surface) { Plane plane; if (surface) @@ -184,9 +143,9 @@ Vec3 LightmapBuilder::LightTexelSample(const Vec3 &origin, Surface *surface) Vec3 color(0.0f, 0.0f, 0.0f); // check all thing lights - for (unsigned int i = 0; i < map->ThingLights.Size(); i++) + for (unsigned int i = 0; i < mesh->map->ThingLights.Size(); i++) { - ThingLight *tl = &map->ThingLights[i]; + ThingLight *tl = &mesh->map->ThingLights[i]; float originZ; if (!tl->bCeiling) @@ -263,7 +222,7 @@ Vec3 LightmapBuilder::LightTexelSample(const Vec3 &origin, Surface *surface) { SurfaceLight *surfaceLight = surfaceLights[i].get(); - float attenuation = surfaceLight->TraceSurface(mesh.get(), surface, origin); + float attenuation = surfaceLight->TraceSurface(mesh, surface, origin); if (attenuation > 0.0f) { color += surfaceLight->GetRGB() * surfaceLight->Intensity() * attenuation; @@ -274,104 +233,8 @@ Vec3 LightmapBuilder::LightTexelSample(const Vec3 &origin, Surface *surface) return color; } -// 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 LightmapBuilder::BuildSurfaceParams(Surface *surface) -{ - Plane *plane; - BBox bounds; - Vec3 roundedSize; - int i; - Plane::PlaneAxis axis; - Vec3 tCoords[2]; - Vec3 tOrigin; - int width; - int height; - float d; - - plane = &surface->plane; - bounds = GetBoundsFromSurface(surface); - - // round off dimentions - for (i = 0; i < 3; i++) - { - bounds.min[i] = samples * Math::Floor(bounds.min[i] / samples); - bounds.max[i] = samples * Math::Ceil(bounds.max[i] / samples); - - roundedSize[i] = (bounds.max[i] - bounds.min[i]) / samples + 1; - } - - tCoords[0].Clear(); - tCoords[1].Clear(); - - axis = plane->BestAxis(); - - switch (axis) - { - case Plane::AXIS_YZ: - width = (int)roundedSize.y; - height = (int)roundedSize.z; - tCoords[0].y = 1.0f / samples; - tCoords[1].z = 1.0f / samples; - break; - - case Plane::AXIS_XZ: - width = (int)roundedSize.x; - height = (int)roundedSize.z; - tCoords[0].x = 1.0f / samples; - tCoords[1].z = 1.0f / samples; - break; - - case Plane::AXIS_XY: - width = (int)roundedSize.x; - height = (int)roundedSize.y; - tCoords[0].x = 1.0f / samples; - tCoords[1].y = 1.0f / samples; - break; - } - - // clamp width - if (width > textureWidth) - { - tCoords[0] *= ((float)textureWidth / (float)width); - width = textureWidth; - } - - // clamp height - if (height > textureHeight) - { - tCoords[1] *= ((float)textureHeight / (float)height); - height = textureHeight; - } - - surface->lightmapCoords.resize(surface->numVerts * 2); - - surface->textureCoords[0] = tCoords[0]; - surface->textureCoords[1] = tCoords[1]; - - tOrigin = bounds.min; - - // project tOrigin and tCoords so they lie on the plane - d = (plane->Distance(bounds.min) - plane->d) / plane->Normal()[axis]; - tOrigin[axis] -= d; - - for (i = 0; i < 2; i++) - { - tCoords[i].Normalize(); - d = plane->Distance(tCoords[i]) / plane->Normal()[axis]; - tCoords[i][axis] -= d; - } - - surface->bounds = bounds; - surface->lightmapDims[0] = width; - surface->lightmapDims[1] = height; - surface->lightmapOrigin = tOrigin; - surface->lightmapSteps[0] = tCoords[0] * (float)samples; - surface->lightmapSteps[1] = tCoords[1] * (float)samples; -} - // Steps through each texel and traces a line to the world. -void LightmapBuilder::TraceSurface(Surface *surface, int offset) +void DLightRaytracer::TraceSurface(Surface *surface, int offset) { int sampleWidth = surface->lightmapDims[0]; int sampleHeight = surface->lightmapDims[1]; @@ -409,137 +272,6 @@ void LightmapBuilder::TraceSurface(Surface *surface, int offset) } } -void LightmapBuilder::FinishSurface(Surface *surface) -{ - int sampleWidth = surface->lightmapDims[0]; - int sampleHeight = surface->lightmapDims[1]; - Vec3 *colorSamples = surface->samples.data(); - - if (!surface->indirect.empty()) - { - Vec3 *indirect = surface->indirect.data(); - for (int i = 0; i < sampleHeight; i++) - { - for (int j = 0; j < sampleWidth; j++) - { - colorSamples[i * sampleWidth + j] += indirect[i * sampleWidth + j] * 0.5f; - } - } - } - - // SVE redraws the scene for lightmaps, so for optimizations, - // tell the engine to ignore this surface if completely black - bool bShouldLookupTexture = false; - for (int i = 0; i < sampleHeight; i++) - { - for (int j = 0; j < sampleWidth; j++) - { - const auto &c = colorSamples[i * sampleWidth + j]; - if (c.x > 0.0f || c.y > 0.0f || c.z > 0.0f) - { - bShouldLookupTexture = true; - break; - } - } - } - - if (bShouldLookupTexture == false) - { - surface->lightmapNum = -1; - } - else - { - int x = 0, y = 0; - uint16_t *currentTexture = AllocTextureRoom(surface, &x, &y); - - // calculate texture coordinates - for (int i = 0; i < surface->numVerts; i++) - { - Vec3 tDelta = surface->verts[i] - surface->bounds.min; - surface->lightmapCoords[i * 2 + 0] = (tDelta.Dot(surface->textureCoords[0]) + x + 0.5f) / (float)textureWidth; - surface->lightmapCoords[i * 2 + 1] = (tDelta.Dot(surface->textureCoords[1]) + y + 0.5f) / (float)textureHeight; - } - - surface->lightmapOffs[0] = x; - surface->lightmapOffs[1] = y; - -#if 1 - // store results to lightmap texture - float weights[9] = { 0.125f, 0.25f, 0.125f, 0.25f, 0.50f, 0.25f, 0.125f, 0.25f, 0.125f }; - for (int y = 0; y < sampleHeight; y++) - { - Vec3* src = &colorSamples[y * sampleWidth]; - for (int x = 0; x < sampleWidth; x++) - { - // gaussian blur with a 3x3 kernel - Vec3 color = { 0.0f }; - for (int yy = -1; yy <= 1; yy++) - { - int yyy = clamp(y + yy, 0, sampleHeight - 1) - y; - for (int xx = -1; xx <= 1; xx++) - { - int xxx = clamp(x + xx, 0, sampleWidth - 1); - color += src[yyy * sampleWidth + xxx] * weights[4 + xx + yy * 3]; - } - } - color *= 0.5f; - - // get texture offset - int offs = (((textureWidth * (y + surface->lightmapOffs[1])) + surface->lightmapOffs[0]) * 3); - - // convert RGB to bytes - currentTexture[offs + x * 3 + 0] = floatToHalf(colorSamples[y * sampleWidth + x].x); - currentTexture[offs + x * 3 + 1] = floatToHalf(colorSamples[y * sampleWidth + x].y); - currentTexture[offs + x * 3 + 2] = floatToHalf(colorSamples[y * sampleWidth + x].z); - } - } -#else - // store results to lightmap texture - for (int i = 0; i < sampleHeight; i++) - { - for (int j = 0; j < sampleWidth; j++) - { - // get texture offset - int offs = (((textureWidth * (i + surface->lightmapOffs[1])) + surface->lightmapOffs[0]) * 3); - - // convert RGB to bytes - currentTexture[offs + j * 3 + 0] = floatToHalf(colorSamples[i * sampleWidth + j].x); - currentTexture[offs + j * 3 + 1] = floatToHalf(colorSamples[i * sampleWidth + j].y); - currentTexture[offs + j * 3 + 2] = floatToHalf(colorSamples[i * sampleWidth + j].z); - } - } -#endif - } -} - -uint16_t *LightmapBuilder::AllocTextureRoom(Surface *surface, int *x, int *y) -{ - int width = surface->lightmapDims[0]; - int height = surface->lightmapDims[1]; - int numTextures = textures.size(); - - int k; - for (k = 0; k < numTextures; ++k) - { - if (textures[k]->MakeRoomForBlock(width, height, x, y)) - { - break; - } - } - - if (k == numTextures) - { - textures.push_back(std::make_unique(textureWidth, textureHeight)); - if (!textures[k]->MakeRoomForBlock(width, height, x, y)) - { - throw std::runtime_error("Lightmap allocation failed"); - } - } - - surface->lightmapNum = k; - return textures[surface->lightmapNum]->Pixels(); -} - static float RadicalInverse_VdC(uint32_t bits) { bits = (bits << 16u) | (bits >> 16u); @@ -575,7 +307,7 @@ static Vec3 ImportanceSampleGGX(Vec2 Xi, Vec3 N, float roughness) return Vec3::Normalize(sampleVec); } -void LightmapBuilder::TraceIndirectLight(Surface *surface, int offset) +void DLightRaytracer::TraceIndirectLight(Surface *surface, int offset) { int sampleWidth = surface->lightmapDims[0]; int sampleHeight = surface->lightmapDims[1]; @@ -648,21 +380,14 @@ void LightmapBuilder::TraceIndirectLight(Surface *surface, int offset) } } -void LightmapBuilder::CreateTraceTasks() +void DLightRaytracer::CreateTraceTasks() { for (size_t i = 0; i < mesh->surfaces.size(); i++) { Surface *surface = mesh->surfaces[i].get(); - BuildSurfaceParams(surface); - int sampleWidth = surface->lightmapDims[0]; int sampleHeight = surface->lightmapDims[1]; - surface->samples.resize(sampleWidth * sampleHeight); - - if (LightBounce > 0) - surface->indirect.resize(sampleWidth * sampleHeight); - int total = sampleWidth * sampleHeight; int count = (total + TraceTask::tasksize - 1) / TraceTask::tasksize; for (int j = 0; j < count; j++) @@ -670,72 +395,19 @@ void LightmapBuilder::CreateTraceTasks() } } -void LightmapBuilder::CreateLightProbes() -{ - float minX = std::floor(map->MinX / 65536.0f); - float minY = std::floor(map->MinY / 65536.0f); - float maxX = std::floor(map->MaxX / 65536.0f) + 1.0f; - float maxY = std::floor(map->MaxY / 65536.0f) + 1.0f; - - float halfGridSize = GridSize * 0.5f; - float doubleGridSize = GridSize * 2.0f; - - for (float y = minY; y < maxY; y += GridSize) - { - for (float x = minX; x < maxX; x += GridSize) - { - MapSubsectorEx* ssec = map->PointInSubSector((int)x, (int)y); - IntSector* sec = ssec ? map->GetSectorFromSubSector(ssec) : nullptr; - if (sec) - { - float z0 = sec->floorplane.zAt(x, y); - float z1 = sec->ceilingplane.zAt(x, y); - float delta = z1 - z0; - if (delta > doubleGridSize) - { - LightProbeSample p[3]; - p[0].Position = Vec3(x, y, z0 + halfGridSize); - p[1].Position = Vec3(x, y, z0 + (z1 - z0) * 0.5f); - p[2].Position = Vec3(x, y, z1 - halfGridSize); - - for (int i = 0; i < 3; i++) - { - lightProbes.push_back(p[i]); - } - } - else if (delta > 0.0f) - { - LightProbeSample probe; - probe.Position.x = x; - probe.Position.y = y; - probe.Position.z = z0 + (z1 - z0) * 0.5f; - lightProbes.push_back(probe); - } - } - } - } - - for (unsigned int i = 0; i < map->ThingLightProbes.Size(); i++) - { - LightProbeSample probe; - probe.Position = map->GetLightProbePosition(i); - lightProbes.push_back(probe); - } -} - -void LightmapBuilder::LightSurface(const int taskid) +void DLightRaytracer::LightSurface(const int taskid) { const TraceTask &task = traceTasks[taskid]; TraceSurface(mesh->surfaces[task.surface].get(), task.offset); } -void LightmapBuilder::LightIndirect(const int taskid) +void DLightRaytracer::LightIndirect(const int taskid) { const TraceTask &task = traceTasks[taskid]; TraceIndirectLight(mesh->surfaces[task.surface].get(), task.offset); } -void LightmapBuilder::CreateSurfaceLights() +void DLightRaytracer::CreateSurfaceLights() { for (size_t j = 0; j < mesh->surfaces.size(); ++j) { @@ -743,30 +415,30 @@ void LightmapBuilder::CreateSurfaceLights() if (surface->type >= ST_MIDDLESIDE && surface->type <= ST_LOWERSIDE) { - int lightdefidx = map->Sides[surface->typeIndex].lightdef; + int lightdefidx = mesh->map->Sides[surface->typeIndex].lightdef; if (lightdefidx != -1) { - auto surfaceLight = std::make_unique(map->SurfaceLights[lightdefidx], surface); + auto surfaceLight = std::make_unique(mesh->map->SurfaceLights[lightdefidx], surface); surfaceLight->Subdivide(16); surfaceLights.push_back(std::move(surfaceLight)); } } else if (surface->type == ST_FLOOR || surface->type == ST_CEILING) { - MapSubsectorEx *sub = &map->GLSubsectors[surface->typeIndex]; - IntSector *sector = map->GetSectorFromSubSector(sub); + MapSubsectorEx *sub = &mesh->map->GLSubsectors[surface->typeIndex]; + IntSector *sector = mesh->map->GetSectorFromSubSector(sub); if (sector && surface->numVerts > 0) { if (sector->floorlightdef != -1 && surface->type == ST_FLOOR) { - auto surfaceLight = std::make_unique(map->SurfaceLights[sector->floorlightdef], surface); + auto surfaceLight = std::make_unique(mesh->map->SurfaceLights[sector->floorlightdef], surface); surfaceLight->Subdivide(16); surfaceLights.push_back(std::move(surfaceLight)); } else if (sector->ceilinglightdef != -1 && surface->type == ST_CEILING) { - auto surfaceLight = std::make_unique(map->SurfaceLights[sector->ceilinglightdef], surface); + auto surfaceLight = std::make_unique(mesh->map->SurfaceLights[sector->ceilinglightdef], surface); surfaceLight->Subdivide(16); surfaceLights.push_back(std::move(surfaceLight)); } @@ -775,134 +447,12 @@ void LightmapBuilder::CreateSurfaceLights() } } -void LightmapBuilder::LightProbe(int id) +void DLightRaytracer::LightProbe(int id) { - lightProbes[id].Color = LightTexelSample(lightProbes[id].Position, nullptr); + mesh->lightProbes[id].Color = LightTexelSample(mesh->lightProbes[id].Position, nullptr); } -void LightmapBuilder::AddLightmapLump(FWadWriter &wadFile) -{ - const auto &surfaces = mesh->surfaces; - - // Calculate size of lump - int numTexCoords = 0; - int numSurfaces = 0; - for (size_t i = 0; i < mesh->surfaces.size(); i++) - { - if (surfaces[i]->lightmapNum != -1) - { - numTexCoords += surfaces[i]->numVerts; - numSurfaces++; - } - } - - int version = 0; - int headerSize = 5 * sizeof(uint32_t) + 2 * sizeof(uint16_t); - int surfacesSize = surfaces.size() * 5 * sizeof(uint32_t); - int texCoordsSize = numTexCoords * 2 * sizeof(float); - int texDataSize = textures.size() * textureWidth * textureHeight * 3 * 2; - int lightProbesSize = lightProbes.size() * 6 * sizeof(float); - int lumpSize = headerSize + lightProbesSize + surfacesSize + texCoordsSize + texDataSize; - - // Setup buffer - std::vector buffer(lumpSize); - BinFile lumpFile; - lumpFile.SetBuffer(buffer.data()); - - // Write header - lumpFile.Write32(version); - lumpFile.Write16(textureWidth); - lumpFile.Write16(textures.size()); - lumpFile.Write32(numSurfaces); - lumpFile.Write32(numTexCoords); - lumpFile.Write32(lightProbes.size()); - lumpFile.Write32(map->NumGLSubsectors); - - // Write light probes - for (const LightProbeSample& probe : lightProbes) - { - lumpFile.WriteFloat(probe.Position.x); - lumpFile.WriteFloat(probe.Position.y); - lumpFile.WriteFloat(probe.Position.z); - lumpFile.WriteFloat(probe.Color.x); - lumpFile.WriteFloat(probe.Color.y); - lumpFile.WriteFloat(probe.Color.z); - } - - // Write surfaces - int coordOffsets = 0; - for (size_t i = 0; i < surfaces.size(); i++) - { - if (surfaces[i]->lightmapNum == -1) - continue; - - lumpFile.Write32(surfaces[i]->type); - lumpFile.Write32(surfaces[i]->typeIndex); - lumpFile.Write32(surfaces[i]->controlSector ? (uint32_t)(surfaces[i]->controlSector - &map->Sectors[0]) : 0xffffffff); - lumpFile.Write32(surfaces[i]->lightmapNum); - lumpFile.Write32(coordOffsets); - coordOffsets += surfaces[i]->numVerts; - } - - // Write texture coordinates - for (size_t i = 0; i < surfaces.size(); i++) - { - if (surfaces[i]->lightmapNum == -1) - continue; - - int count = surfaces[i]->numVerts; - if (surfaces[i]->type == ST_FLOOR) - { - for (int j = count - 1; j >= 0; j--) - { - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2]); - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2 + 1]); - } - } - else if (surfaces[i]->type == ST_CEILING) - { - for (int j = 0; j < count; j++) - { - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2]); - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2 + 1]); - } - } - else - { - // zdray uses triangle strip internally, lump/gzd uses triangle fan - - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[0]); - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[1]); - - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[4]); - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[5]); - - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[6]); - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[7]); - - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[2]); - lumpFile.WriteFloat(surfaces[i]->lightmapCoords[3]); - } - } - - // Write lightmap textures - for (size_t i = 0; i < textures.size(); i++) - { - unsigned int count = (textureWidth * textureHeight) * 3; - uint16_t *pixels = textures[i]->Pixels(); - for (unsigned int j = 0; j < count; j++) - { - lumpFile.Write16(pixels[j]); - } - } - - // Compress and store in lump - ZLibOut zout(wadFile); - wadFile.StartWritingLump("LIGHTMAP"); - zout.Write(buffer.data(), lumpFile.BufferAt() - lumpFile.Buffer()); -} - -void LightmapBuilder::SetupTaskProcessed(const char *name, int total) +void DLightRaytracer::SetupTaskProcessed(const char *name, int total) { printf("-------------- %s ---------------\n", name); @@ -910,7 +460,7 @@ void LightmapBuilder::SetupTaskProcessed(const char *name, int total) progresstotal = total; } -void LightmapBuilder::PrintTaskProcessed() +void DLightRaytracer::PrintTaskProcessed() { std::unique_lock lock(mutex); @@ -923,361 +473,3 @@ void LightmapBuilder::PrintTaskProcessed() printf("%i%c done\r", (int)(remaining * 100.0f), '%'); } } - -class PNGWriter -{ -public: - static void save(const std::string &filename, int width, int height, int bytes_per_pixel, void *pixels) - { - PNGImage image; - image.width = width; - image.height = height; - image.bytes_per_pixel = bytes_per_pixel; - image.pixel_ratio = 1.0f; - image.data = pixels; - - FILE *file = fopen(filename.c_str(), "wb"); - if (file) - { - PNGWriter writer; - writer.file = file; - writer.image = ℑ - writer.write_magic(); - writer.write_headers(); - writer.write_data(); - writer.write_chunk("IEND", nullptr, 0); - fclose(file); - } - } - -private: - struct PNGImage - { - int width; - int height; - int bytes_per_pixel; - void *data; - float pixel_ratio; - }; - - struct DataBuffer - { - DataBuffer(int size) : size(size) { data = new uint8_t[size]; } - ~DataBuffer() { delete[] data; } - int size; - void *data; - }; - - const PNGImage *image; - FILE *file; - - class PNGCRC32 - { - public: - static unsigned long crc(const char name[4], const void *data, int len) - { - static PNGCRC32 impl; - - const unsigned char *buf = reinterpret_cast(data); - - unsigned int c = 0xffffffff; - - for (int n = 0; n < 4; n++) - c = impl.crc_table[(c ^ name[n]) & 0xff] ^ (c >> 8); - - for (int n = 0; n < len; n++) - c = impl.crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); - - return c ^ 0xffffffff; - } - - private: - unsigned int crc_table[256]; - - PNGCRC32() - { - for (unsigned int n = 0; n < 256; n++) - { - unsigned int c = n; - for (unsigned int k = 0; k < 8; k++) - { - if ((c & 1) == 1) - c = 0xedb88320 ^ (c >> 1); - else - c = c >> 1; - } - crc_table[n] = c; - } - } - }; - - void write_magic() - { - unsigned char png_magic[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; - write(png_magic, 8); - } - - void write_headers() - { - int ppm = (int)std::round(3800 * image->pixel_ratio); - int ppm_x = ppm; - int ppm_y = ppm; - - int width = image->width; - int height = image->height; - int bit_depth = image->bytes_per_pixel == 8 ? 16 : 8; - int color_type = 6; - int compression_method = 0; - int filter_method = 0; - int interlace_method = 0; - - unsigned char idhr[13]; - idhr[0] = (width >> 24) & 0xff; - idhr[1] = (width >> 16) & 0xff; - idhr[2] = (width >> 8) & 0xff; - idhr[3] = width & 0xff; - idhr[4] = (height >> 24) & 0xff; - idhr[5] = (height >> 16) & 0xff; - idhr[6] = (height >> 8) & 0xff; - idhr[7] = height & 0xff; - idhr[8] = bit_depth; - idhr[9] = color_type; - idhr[10] = compression_method; - idhr[11] = filter_method; - idhr[12] = interlace_method; - - //unsigned char srgb[1]; - //srgb[0] = 0; - - unsigned char phys[9]; - phys[0] = (ppm_x >> 24) & 0xff; - phys[1] = (ppm_x >> 16) & 0xff; - phys[2] = (ppm_x >> 8) & 0xff; - phys[3] = ppm_x & 0xff; - phys[4] = (ppm_y >> 24) & 0xff; - phys[5] = (ppm_y >> 16) & 0xff; - phys[6] = (ppm_y >> 8) & 0xff; - phys[7] = ppm_y & 0xff; - phys[8] = 1; // pixels per meter - - write_chunk("IHDR", idhr, 13); - - if (ppm != 0) - write_chunk("pHYs", phys, 9); - - //write_chunk("sRGB", srgb, 1); - } - - void write_data() - { - //int width = image->width; - int height = image->height; - int bytes_per_pixel = image->bytes_per_pixel; - int pitch = image->width * bytes_per_pixel; - - std::vector scanline_orig; - std::vector scanline_filtered; - scanline_orig.resize((image->width + 1) * bytes_per_pixel); - scanline_filtered.resize(image->width * bytes_per_pixel + 1); - - auto idat_uncompressed = std::make_shared(height * scanline_filtered.size()); - - for (int y = 0; y < height; y++) - { - // Grab scanline - memcpy(scanline_orig.data() + bytes_per_pixel, (uint8_t*)image->data + y * pitch, scanline_orig.size() - bytes_per_pixel); - - // Convert to big endian for 16 bit - if (bytes_per_pixel == 8) - { - for (size_t x = 0; x < scanline_orig.size(); x += 2) - { - std::swap(scanline_orig[x], scanline_orig[x + 1]); - } - } - - // Filter scanline - /* - scanline_filtered[0] = 0; // None filter type - for (int i = bytes_per_pixel; i < scanline_orig.size(); i++) - { - scanline_filtered[i - bytes_per_pixel + 1] = scanline_orig[i]; - } - */ - scanline_filtered[0] = 1; // Sub filter type - for (int i = bytes_per_pixel; i < scanline_orig.size(); i++) - { - unsigned char a = scanline_orig[i - bytes_per_pixel]; - unsigned char x = scanline_orig[i]; - scanline_filtered[i - bytes_per_pixel + 1] = x - a; - } - - // Output scanline - memcpy((uint8_t*)idat_uncompressed->data + y * scanline_filtered.size(), scanline_filtered.data(), scanline_filtered.size()); - } - - auto idat = std::make_unique(idat_uncompressed->size * 125 / 100); - idat->size = compress(idat.get(), idat_uncompressed.get(), false); - - write_chunk("IDAT", idat->data, (int)idat->size); - } - - void write_chunk(const char name[4], const void *data, int size) - { - unsigned char size_data[4]; - size_data[0] = (size >> 24) & 0xff; - size_data[1] = (size >> 16) & 0xff; - size_data[2] = (size >> 8) & 0xff; - size_data[3] = size & 0xff; - write(size_data, 4); - - write(name, 4); - - write(data, size); - unsigned int crc32 = PNGCRC32::crc(name, data, size); - - unsigned char crc32_data[4]; - crc32_data[0] = (crc32 >> 24) & 0xff; - crc32_data[1] = (crc32 >> 16) & 0xff; - crc32_data[2] = (crc32 >> 8) & 0xff; - crc32_data[3] = crc32 & 0xff; - write(crc32_data, 4); - } - - void write(const void *data, int size) - { - fwrite(data, size, 1, file); - } - - size_t compress(DataBuffer *out, const DataBuffer *data, bool raw) - { - if (data->size > (size_t)0xffffffff || out->size > (size_t)0xffffffff) - throw std::runtime_error("Data is too big"); - - const int window_bits = 15; - - int compression_level = 6; - int strategy = Z_DEFAULT_STRATEGY; - - z_stream zs; - memset(&zs, 0, sizeof(z_stream)); - int result = deflateInit2(&zs, compression_level, Z_DEFLATED, raw ? -window_bits : window_bits, 8, strategy); // Undocumented: if wbits is negative, zlib skips header check - if (result != Z_OK) - throw std::runtime_error("Zlib deflateInit failed"); - - zs.next_in = (unsigned char *)data->data; - zs.avail_in = (unsigned int)data->size; - zs.next_out = (unsigned char *)out->data; - zs.avail_out = (unsigned int)out->size; - - size_t outSize = 0; - try - { - int result = deflate(&zs, Z_FINISH); - if (result == Z_NEED_DICT) throw std::runtime_error("Zlib deflate wants a dictionary!"); - if (result == Z_DATA_ERROR) throw std::runtime_error("Zip data stream is corrupted"); - if (result == Z_STREAM_ERROR) throw std::runtime_error("Zip stream structure was inconsistent!"); - if (result == Z_MEM_ERROR) throw std::runtime_error("Zlib did not have enough memory to compress file!"); - if (result == Z_BUF_ERROR) throw std::runtime_error("Not enough data in buffer when Z_FINISH was used"); - if (result != Z_STREAM_END) throw std::runtime_error("Zlib deflate failed while compressing zip file!"); - outSize = zs.total_out; - } - catch (...) - { - deflateEnd(&zs); - throw; - } - deflateEnd(&zs); - - return outSize; - } -}; - -void LightmapBuilder::ExportMesh(std::string filename) -{ - mesh->Export(filename); - - int index = 0; - for (const auto &texture : textures) - { - int w = texture->Width(); - int h = texture->Height(); - uint16_t *p = texture->Pixels(); -#if 1 - std::vector buf(w * h * 4); - uint8_t *buffer = buf.data(); - for (int i = 0; i < w * h; i++) - { - buffer[i * 4] = (uint8_t)(int)clamp(halfToFloat(p[i * 3]) * 255.0f, 0.0f, 255.0f); - buffer[i * 4 + 1] = (uint8_t)(int)clamp(halfToFloat(p[i * 3 + 1]) * 255.0f, 0.0f, 255.0f); - buffer[i * 4 + 2] = (uint8_t)(int)clamp(halfToFloat(p[i * 3 + 2]) * 255.0f, 0.0f, 255.0f); - buffer[i * 4 + 3] = 0xff; - } - PNGWriter::save("lightmap" + std::to_string(index++) + ".png", w, h, 4, buffer); -#else - std::vector buf(w * h * 4); - uint16_t *buffer = buf.data(); - for (int i = 0; i < w * h; i++) - { - buffer[i * 4] = (uint16_t)(int)clamp(halfToFloat(p[i * 3]) * 65535.0f, 0.0f, 65535.0f); - buffer[i * 4 + 1] = (uint16_t)(int)clamp(halfToFloat(p[i * 3 + 1]) * 65535.0f, 0.0f, 65535.0f); - buffer[i * 4 + 2] = (uint16_t)(int)clamp(halfToFloat(p[i * 3 + 2]) * 65535.0f, 0.0f, 65535.0f); - buffer[i * 4 + 3] = 0xffff; - } - PNGWriter::save("lightmap" + std::to_string(index++) + ".png", w, h, 8, buffer); -#endif - } -} - -///////////////////////////////////////////////////////////////////////////// - -LightmapTexture::LightmapTexture(int width, int height) : textureWidth(width), textureHeight(height) -{ - mPixels.resize(width * height * 3); - allocBlocks.resize(width); -} - -bool LightmapTexture::MakeRoomForBlock(const int width, const int height, int *x, int *y) -{ - int bestRow1 = textureHeight; - - for (int i = 0; i <= textureWidth - width; i++) - { - int bestRow2 = 0; - - int j; - for (j = 0; j < width; j++) - { - if (allocBlocks[i + j] >= bestRow1) - { - break; - } - - if (allocBlocks[i + j] > bestRow2) - { - bestRow2 = allocBlocks[i + j]; - } - } - - // found a free block - if (j == width) - { - *x = i; - *y = bestRow1 = bestRow2; - } - } - - if (bestRow1 + height > textureHeight) - { - // no room - return false; - } - - // store row offset - for (int i = 0; i < width; i++) - { - allocBlocks[*x + i] = bestRow1 + height; - } - - return true; -} diff --git a/src/lightmap/lightmap.h b/src/lightmap/lightmap.h index d3780dc..939c654 100644 --- a/src/lightmap/lightmap.h +++ b/src/lightmap/lightmap.h @@ -31,30 +31,9 @@ #include "framework/tarray.h" #include -#define LIGHTCELL_SIZE 64 -#define LIGHTCELL_BLOCK_SIZE 16 - class FWadWriter; class SurfaceLight; -class LightmapTexture -{ -public: - LightmapTexture(int width, int height); - - bool MakeRoomForBlock(const int width, const int height, int *x, int *y); - - int Width() const { return textureWidth; } - int Height() const { return textureHeight; } - uint16_t *Pixels() { return mPixels.data(); } - -private: - int textureWidth; - int textureHeight; - std::vector mPixels; - std::vector allocBlocks; -}; - class TraceTask { public: @@ -67,55 +46,33 @@ public: static const int tasksize = 64; }; -class LightProbeSample +class DLightRaytracer { public: - Vec3 Position = Vec3(0.0f, 0.0f, 0.0f); - Vec3 Color = Vec3(0.0f, 0.0f, 0.0f); -}; + DLightRaytracer(); + ~DLightRaytracer(); -class LightmapBuilder -{ -public: - LightmapBuilder(); - ~LightmapBuilder(); - - void CreateLightmaps(FLevel &doomMap, int sampleDistance, int textureSize); - void AddLightmapLump(FWadWriter &wadFile); - void ExportMesh(std::string filename); + void Raytrace(LevelMesh* level); private: - BBox GetBoundsFromSurface(const Surface *surface); Vec3 LightTexelSample(const Vec3 &origin, Surface *surface); bool EmitFromCeiling(const Surface *surface, const Vec3 &origin, const Vec3 &normal, Vec3 &color); - void BuildSurfaceParams(Surface *surface); void TraceSurface(Surface *surface, int offset); void TraceIndirectLight(Surface *surface, int offset); - void FinishSurface(Surface *surface); void LightProbe(int probeid); void CreateTraceTasks(); void LightSurface(const int taskid); void LightIndirect(const int taskid); void CreateSurfaceLights(); - void CreateLightProbes(); void SetupTaskProcessed(const char *name, int total); void PrintTaskProcessed(); - uint16_t *AllocTextureRoom(Surface *surface, int *x, int *y); - - FLevel *map; - int samples = 16; - int textureWidth = 128; - int textureHeight = 128; - - std::unique_ptr mesh; + LevelMesh* mesh = nullptr; std::vector> surfaceLights; - std::vector> textures; std::vector traceTasks; - std::vector lightProbes; int tracedTexels = 0; std::mutex mutex; diff --git a/src/lightmap/pngwriter.cpp b/src/lightmap/pngwriter.cpp new file mode 100644 index 0000000..7643723 --- /dev/null +++ b/src/lightmap/pngwriter.cpp @@ -0,0 +1,217 @@ + +#include "math/mathlib.h" +#include "pngwriter.h" +#include "framework/binfile.h" +#include "framework/templates.h" +#include "framework/halffloat.h" +#include +#include +#include +#include +#include +#include +#include + +void PNGWriter::save(const std::string& filename, int width, int height, int bytes_per_pixel, void* pixels) +{ + PNGImage image; + image.width = width; + image.height = height; + image.bytes_per_pixel = bytes_per_pixel; + image.pixel_ratio = 1.0f; + image.data = pixels; + + FILE *file = fopen(filename.c_str(), "wb"); + if (file) + { + PNGWriter writer; + writer.file = file; + writer.image = ℑ + writer.write_magic(); + writer.write_headers(); + writer.write_data(); + writer.write_chunk("IEND", nullptr, 0); + fclose(file); + } +} + +void PNGWriter::write_magic() +{ + unsigned char png_magic[8] = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + write(png_magic, 8); +} + +void PNGWriter::write_headers() +{ + int ppm = (int)std::round(3800 * image->pixel_ratio); + int ppm_x = ppm; + int ppm_y = ppm; + + int width = image->width; + int height = image->height; + int bit_depth = image->bytes_per_pixel == 8 ? 16 : 8; + int color_type = 6; + int compression_method = 0; + int filter_method = 0; + int interlace_method = 0; + + unsigned char idhr[13]; + idhr[0] = (width >> 24) & 0xff; + idhr[1] = (width >> 16) & 0xff; + idhr[2] = (width >> 8) & 0xff; + idhr[3] = width & 0xff; + idhr[4] = (height >> 24) & 0xff; + idhr[5] = (height >> 16) & 0xff; + idhr[6] = (height >> 8) & 0xff; + idhr[7] = height & 0xff; + idhr[8] = bit_depth; + idhr[9] = color_type; + idhr[10] = compression_method; + idhr[11] = filter_method; + idhr[12] = interlace_method; + + //unsigned char srgb[1]; + //srgb[0] = 0; + + unsigned char phys[9]; + phys[0] = (ppm_x >> 24) & 0xff; + phys[1] = (ppm_x >> 16) & 0xff; + phys[2] = (ppm_x >> 8) & 0xff; + phys[3] = ppm_x & 0xff; + phys[4] = (ppm_y >> 24) & 0xff; + phys[5] = (ppm_y >> 16) & 0xff; + phys[6] = (ppm_y >> 8) & 0xff; + phys[7] = ppm_y & 0xff; + phys[8] = 1; // pixels per meter + + write_chunk("IHDR", idhr, 13); + + if (ppm != 0) + write_chunk("pHYs", phys, 9); + + //write_chunk("sRGB", srgb, 1); +} + +void PNGWriter::write_data() +{ + //int width = image->width; + int height = image->height; + int bytes_per_pixel = image->bytes_per_pixel; + int pitch = image->width * bytes_per_pixel; + + std::vector scanline_orig; + std::vector scanline_filtered; + scanline_orig.resize((image->width + 1) * bytes_per_pixel); + scanline_filtered.resize(image->width * bytes_per_pixel + 1); + + auto idat_uncompressed = std::make_shared((int)(height * scanline_filtered.size())); + + for (int y = 0; y < height; y++) + { + // Grab scanline + memcpy(scanline_orig.data() + bytes_per_pixel, (uint8_t*)image->data + y * pitch, scanline_orig.size() - bytes_per_pixel); + + // Convert to big endian for 16 bit + if (bytes_per_pixel == 8) + { + for (size_t x = 0; x < scanline_orig.size(); x += 2) + { + std::swap(scanline_orig[x], scanline_orig[x + 1]); + } + } + + // Filter scanline + /* + scanline_filtered[0] = 0; // None filter type + for (int i = bytes_per_pixel; i < scanline_orig.size(); i++) + { + scanline_filtered[i - bytes_per_pixel + 1] = scanline_orig[i]; + } + */ + scanline_filtered[0] = 1; // Sub filter type + for (int i = bytes_per_pixel; i < scanline_orig.size(); i++) + { + unsigned char a = scanline_orig[i - bytes_per_pixel]; + unsigned char x = scanline_orig[i]; + scanline_filtered[i - bytes_per_pixel + 1] = x - a; + } + + // Output scanline + memcpy((uint8_t*)idat_uncompressed->data + y * scanline_filtered.size(), scanline_filtered.data(), scanline_filtered.size()); + } + + auto idat = std::make_unique(idat_uncompressed->size * 125 / 100); + idat->size = (int)compress(idat.get(), idat_uncompressed.get(), false); + + write_chunk("IDAT", idat->data, (int)idat->size); +} + +void PNGWriter::write_chunk(const char name[4], const void *data, int size) +{ + unsigned char size_data[4]; + size_data[0] = (size >> 24) & 0xff; + size_data[1] = (size >> 16) & 0xff; + size_data[2] = (size >> 8) & 0xff; + size_data[3] = size & 0xff; + write(size_data, 4); + + write(name, 4); + + write(data, size); + unsigned int crc32 = PNGCRC32::crc(name, data, size); + + unsigned char crc32_data[4]; + crc32_data[0] = (crc32 >> 24) & 0xff; + crc32_data[1] = (crc32 >> 16) & 0xff; + crc32_data[2] = (crc32 >> 8) & 0xff; + crc32_data[3] = crc32 & 0xff; + write(crc32_data, 4); +} + +void PNGWriter::write(const void *data, int size) +{ + fwrite(data, size, 1, file); +} + +size_t PNGWriter::compress(DataBuffer *out, const DataBuffer *data, bool raw) +{ + if (data->size > (size_t)0xffffffff || out->size > (size_t)0xffffffff) + throw std::runtime_error("Data is too big"); + + const int window_bits = 15; + + int compression_level = 6; + int strategy = Z_DEFAULT_STRATEGY; + + z_stream zs; + memset(&zs, 0, sizeof(z_stream)); + int result = deflateInit2(&zs, compression_level, Z_DEFLATED, raw ? -window_bits : window_bits, 8, strategy); // Undocumented: if wbits is negative, zlib skips header check + if (result != Z_OK) + throw std::runtime_error("Zlib deflateInit failed"); + + zs.next_in = (unsigned char *)data->data; + zs.avail_in = (unsigned int)data->size; + zs.next_out = (unsigned char *)out->data; + zs.avail_out = (unsigned int)out->size; + + size_t outSize = 0; + try + { + int result = deflate(&zs, Z_FINISH); + if (result == Z_NEED_DICT) throw std::runtime_error("Zlib deflate wants a dictionary!"); + if (result == Z_DATA_ERROR) throw std::runtime_error("Zip data stream is corrupted"); + if (result == Z_STREAM_ERROR) throw std::runtime_error("Zip stream structure was inconsistent!"); + if (result == Z_MEM_ERROR) throw std::runtime_error("Zlib did not have enough memory to compress file!"); + if (result == Z_BUF_ERROR) throw std::runtime_error("Not enough data in buffer when Z_FINISH was used"); + if (result != Z_STREAM_END) throw std::runtime_error("Zlib deflate failed while compressing zip file!"); + outSize = zs.total_out; + } + catch (...) + { + deflateEnd(&zs); + throw; + } + deflateEnd(&zs); + + return outSize; +} diff --git a/src/lightmap/pngwriter.h b/src/lightmap/pngwriter.h new file mode 100644 index 0000000..97cba8e --- /dev/null +++ b/src/lightmap/pngwriter.h @@ -0,0 +1,78 @@ + +#pragma once + +#include + +class PNGWriter +{ +public: + static void save(const std::string& filename, int width, int height, int bytes_per_pixel, void* pixels); + + struct DataBuffer + { + DataBuffer(int size) : size(size) { data = new uint8_t[size]; } + ~DataBuffer() { delete[] data; } + int size; + void* data; + }; + +private: + struct PNGImage + { + int width; + int height; + int bytes_per_pixel; + void* data; + float pixel_ratio; + }; + + const PNGImage* image; + FILE* file; + + class PNGCRC32 + { + public: + static unsigned long crc(const char name[4], const void* data, int len) + { + static PNGCRC32 impl; + + const unsigned char* buf = reinterpret_cast(data); + + unsigned int c = 0xffffffff; + + for (int n = 0; n < 4; n++) + c = impl.crc_table[(c ^ name[n]) & 0xff] ^ (c >> 8); + + for (int n = 0; n < len; n++) + c = impl.crc_table[(c ^ buf[n]) & 0xff] ^ (c >> 8); + + return c ^ 0xffffffff; + } + + private: + unsigned int crc_table[256]; + + PNGCRC32() + { + for (unsigned int n = 0; n < 256; n++) + { + unsigned int c = n; + for (unsigned int k = 0; k < 8; k++) + { + if ((c & 1) == 1) + c = 0xedb88320 ^ (c >> 1); + else + c = c >> 1; + } + crc_table[n] = c; + } + } + }; + + void write_magic(); + void write_headers(); + void write_data(); + void write_chunk(const char name[4], const void* data, int size); + void write(const void* data, int size); + size_t compress(DataBuffer* out, const DataBuffer* data, bool raw); +}; diff --git a/src/lightmap/raytracer.cpp b/src/lightmap/raytracer.cpp new file mode 100644 index 0000000..4a4edb7 --- /dev/null +++ b/src/lightmap/raytracer.cpp @@ -0,0 +1,31 @@ + +#include "math/mathlib.h" +#include "surfaces.h" +#include "level/level.h" +#include "raytracer.h" +#include "surfacelight.h" +#include "worker.h" +#include "framework/binfile.h" +#include "framework/templates.h" +#include "framework/halffloat.h" +#include +#include +#include +#include + +extern int Multisample; +extern int LightBounce; +extern float GridSize; + +Raytracer::Raytracer() +{ +} + +Raytracer::~Raytracer() +{ +} + +void Raytracer::Raytrace(LevelMesh* level) +{ + mesh = level; +} diff --git a/src/lightmap/raytracer.h b/src/lightmap/raytracer.h new file mode 100644 index 0000000..ce91895 --- /dev/null +++ b/src/lightmap/raytracer.h @@ -0,0 +1,16 @@ + +#pragma once + +class LevelMesh; + +class Raytracer +{ +public: + Raytracer(); + ~Raytracer(); + + void Raytrace(LevelMesh* level); + +private: + LevelMesh* mesh = nullptr; +}; diff --git a/src/lightmap/surfaces.cpp b/src/lightmap/surfaces.cpp index a02a58c..657d0d4 100644 --- a/src/lightmap/surfaces.cpp +++ b/src/lightmap/surfaces.cpp @@ -26,17 +26,28 @@ // #include "math/mathlib.h" +#include "framework/templates.h" +#include "framework/halffloat.h" +#include "framework/binfile.h" #include "level/level.h" #include "surfaces.h" +#include "pngwriter.h" #include +extern float GridSize; + #ifdef _MSC_VER #pragma warning(disable: 4267) // warning C4267: 'argument': conversion from 'size_t' to 'int', possible loss of data #pragma warning(disable: 4244) // warning C4244: '=': conversion from '__int64' to 'int', possible loss of data #endif -LevelMesh::LevelMesh(FLevel &doomMap) +LevelMesh::LevelMesh(FLevel &doomMap, int sampleDistance, int textureSize) { + map = &doomMap; + samples = sampleDistance; + textureWidth = textureSize; + textureHeight = textureSize; + printf("------------- Building side surfaces -------------\n"); for (unsigned int i = 0; i < doomMap.Sides.Size(); i++) @@ -98,6 +109,335 @@ LevelMesh::LevelMesh(FLevel &doomMap) } CollisionMesh = std::make_unique(&MeshVertices[0], MeshVertices.Size(), &MeshElements[0], MeshElements.Size()); + + CreateLightProbes(doomMap); + + for (size_t i = 0; i < surfaces.size(); i++) + { + BuildSurfaceParams(surfaces[i].get()); + } +} + +// 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) +{ + Plane* plane; + BBox bounds; + Vec3 roundedSize; + int i; + Plane::PlaneAxis axis; + Vec3 tCoords[2]; + Vec3 tOrigin; + int width; + int height; + float d; + + plane = &surface->plane; + bounds = GetBoundsFromSurface(surface); + + // round off dimentions + for (i = 0; i < 3; i++) + { + bounds.min[i] = samples * Math::Floor(bounds.min[i] / samples); + bounds.max[i] = samples * Math::Ceil(bounds.max[i] / samples); + + roundedSize[i] = (bounds.max[i] - bounds.min[i]) / samples + 1; + } + + tCoords[0].Clear(); + tCoords[1].Clear(); + + axis = plane->BestAxis(); + + switch (axis) + { + case Plane::AXIS_YZ: + width = (int)roundedSize.y; + height = (int)roundedSize.z; + tCoords[0].y = 1.0f / samples; + tCoords[1].z = 1.0f / samples; + break; + + case Plane::AXIS_XZ: + width = (int)roundedSize.x; + height = (int)roundedSize.z; + tCoords[0].x = 1.0f / samples; + tCoords[1].z = 1.0f / samples; + break; + + case Plane::AXIS_XY: + width = (int)roundedSize.x; + height = (int)roundedSize.y; + tCoords[0].x = 1.0f / samples; + tCoords[1].y = 1.0f / samples; + break; + } + + // clamp width + if (width > textureWidth) + { + tCoords[0] *= ((float)textureWidth / (float)width); + width = textureWidth; + } + + // clamp height + if (height > textureHeight) + { + tCoords[1] *= ((float)textureHeight / (float)height); + height = textureHeight; + } + + surface->lightmapCoords.resize(surface->numVerts * 2); + + surface->textureCoords[0] = tCoords[0]; + surface->textureCoords[1] = tCoords[1]; + + tOrigin = bounds.min; + + // project tOrigin and tCoords so they lie on the plane + d = (plane->Distance(bounds.min) - plane->d) / plane->Normal()[axis]; + tOrigin[axis] -= d; + + for (i = 0; i < 2; i++) + { + tCoords[i].Normalize(); + d = plane->Distance(tCoords[i]) / plane->Normal()[axis]; + tCoords[i][axis] -= d; + } + + surface->bounds = bounds; + surface->lightmapDims[0] = width; + surface->lightmapDims[1] = height; + surface->lightmapOrigin = tOrigin; + surface->lightmapSteps[0] = tCoords[0] * (float)samples; + surface->lightmapSteps[1] = tCoords[1] * (float)samples; + + int sampleWidth = surface->lightmapDims[0]; + int sampleHeight = surface->lightmapDims[1]; + surface->samples.resize(sampleWidth * sampleHeight); + surface->indirect.resize(sampleWidth * sampleHeight); +} + +BBox LevelMesh::GetBoundsFromSurface(const Surface* surface) +{ + Vec3 low(M_INFINITY, M_INFINITY, M_INFINITY); + Vec3 hi(-M_INFINITY, -M_INFINITY, -M_INFINITY); + + BBox bounds; + bounds.Clear(); + + for (int i = 0; i < surface->numVerts; i++) + { + for (int j = 0; j < 3; j++) + { + if (surface->verts[i][j] < low[j]) + { + low[j] = surface->verts[i][j]; + } + if (surface->verts[i][j] > hi[j]) + { + hi[j] = surface->verts[i][j]; + } + } + } + + bounds.min = low; + bounds.max = hi; + + return bounds; +} + +void LevelMesh::CreateTextures() +{ + for (auto& surf : surfaces) + { + FinishSurface(surf.get()); + } +} + +void LevelMesh::FinishSurface(Surface* surface) +{ + int sampleWidth = surface->lightmapDims[0]; + int sampleHeight = surface->lightmapDims[1]; + Vec3* colorSamples = surface->samples.data(); + + if (!surface->indirect.empty()) + { + Vec3* indirect = surface->indirect.data(); + for (int i = 0; i < sampleHeight; i++) + { + for (int j = 0; j < sampleWidth; j++) + { + colorSamples[i * sampleWidth + j] += indirect[i * sampleWidth + j] * 0.5f; + } + } + } + + // SVE redraws the scene for lightmaps, so for optimizations, + // tell the engine to ignore this surface if completely black + bool bShouldLookupTexture = false; + for (int i = 0; i < sampleHeight; i++) + { + for (int j = 0; j < sampleWidth; j++) + { + const auto& c = colorSamples[i * sampleWidth + j]; + if (c.x > 0.0f || c.y > 0.0f || c.z > 0.0f) + { + bShouldLookupTexture = true; + break; + } + } + } + + if (bShouldLookupTexture == false) + { + surface->lightmapNum = -1; + } + else + { + int x = 0, y = 0; + uint16_t* currentTexture = AllocTextureRoom(surface, &x, &y); + + // calculate texture coordinates + for (int i = 0; i < surface->numVerts; i++) + { + Vec3 tDelta = surface->verts[i] - surface->bounds.min; + surface->lightmapCoords[i * 2 + 0] = (tDelta.Dot(surface->textureCoords[0]) + x + 0.5f) / (float)textureWidth; + surface->lightmapCoords[i * 2 + 1] = (tDelta.Dot(surface->textureCoords[1]) + y + 0.5f) / (float)textureHeight; + } + + surface->lightmapOffs[0] = x; + surface->lightmapOffs[1] = y; + +#if 1 + // store results to lightmap texture + float weights[9] = { 0.125f, 0.25f, 0.125f, 0.25f, 0.50f, 0.25f, 0.125f, 0.25f, 0.125f }; + for (int y = 0; y < sampleHeight; y++) + { + Vec3* src = &colorSamples[y * sampleWidth]; + for (int x = 0; x < sampleWidth; x++) + { + // gaussian blur with a 3x3 kernel + Vec3 color = { 0.0f }; + for (int yy = -1; yy <= 1; yy++) + { + int yyy = clamp(y + yy, 0, sampleHeight - 1) - y; + for (int xx = -1; xx <= 1; xx++) + { + int xxx = clamp(x + xx, 0, sampleWidth - 1); + color += src[yyy * sampleWidth + xxx] * weights[4 + xx + yy * 3]; + } + } + color *= 0.5f; + + // get texture offset + int offs = (((textureWidth * (y + surface->lightmapOffs[1])) + surface->lightmapOffs[0]) * 3); + + // convert RGB to bytes + currentTexture[offs + x * 3 + 0] = floatToHalf(colorSamples[y * sampleWidth + x].x); + currentTexture[offs + x * 3 + 1] = floatToHalf(colorSamples[y * sampleWidth + x].y); + currentTexture[offs + x * 3 + 2] = floatToHalf(colorSamples[y * sampleWidth + x].z); + } + } +#else + // store results to lightmap texture + for (int i = 0; i < sampleHeight; i++) + { + for (int j = 0; j < sampleWidth; j++) + { + // get texture offset + int offs = (((textureWidth * (i + surface->lightmapOffs[1])) + surface->lightmapOffs[0]) * 3); + + // convert RGB to bytes + currentTexture[offs + j * 3 + 0] = floatToHalf(colorSamples[i * sampleWidth + j].x); + currentTexture[offs + j * 3 + 1] = floatToHalf(colorSamples[i * sampleWidth + j].y); + currentTexture[offs + j * 3 + 2] = floatToHalf(colorSamples[i * sampleWidth + j].z); + } + } +#endif + } +} + +uint16_t* LevelMesh::AllocTextureRoom(Surface* surface, int* x, int* y) +{ + int width = surface->lightmapDims[0]; + int height = surface->lightmapDims[1]; + int numTextures = textures.size(); + + int k; + for (k = 0; k < numTextures; ++k) + { + if (textures[k]->MakeRoomForBlock(width, height, x, y)) + { + break; + } + } + + if (k == numTextures) + { + textures.push_back(std::make_unique(textureWidth, textureHeight)); + if (!textures[k]->MakeRoomForBlock(width, height, x, y)) + { + throw std::runtime_error("Lightmap allocation failed"); + } + } + + surface->lightmapNum = k; + return textures[surface->lightmapNum]->Pixels(); +} + +void LevelMesh::CreateLightProbes(FLevel& map) +{ + float minX = std::floor(map.MinX / 65536.0f); + float minY = std::floor(map.MinY / 65536.0f); + float maxX = std::floor(map.MaxX / 65536.0f) + 1.0f; + float maxY = std::floor(map.MaxY / 65536.0f) + 1.0f; + + float halfGridSize = GridSize * 0.5f; + float doubleGridSize = GridSize * 2.0f; + + for (float y = minY; y < maxY; y += GridSize) + { + for (float x = minX; x < maxX; x += GridSize) + { + MapSubsectorEx* ssec = map.PointInSubSector((int)x, (int)y); + IntSector* sec = ssec ? map.GetSectorFromSubSector(ssec) : nullptr; + if (sec) + { + float z0 = sec->floorplane.zAt(x, y); + float z1 = sec->ceilingplane.zAt(x, y); + float delta = z1 - z0; + if (delta > doubleGridSize) + { + LightProbeSample p[3]; + p[0].Position = Vec3(x, y, z0 + halfGridSize); + p[1].Position = Vec3(x, y, z0 + (z1 - z0) * 0.5f); + p[2].Position = Vec3(x, y, z1 - halfGridSize); + + for (int i = 0; i < 3; i++) + { + lightProbes.push_back(p[i]); + } + } + else if (delta > 0.0f) + { + LightProbeSample probe; + probe.Position.x = x; + probe.Position.y = y; + probe.Position.z = z0 + (z1 - z0) * 0.5f; + lightProbes.push_back(probe); + } + } + } + } + + for (unsigned int i = 0; i < map.ThingLightProbes.Size(); i++) + { + LightProbeSample probe; + probe.Position = map.GetLightProbePosition(i); + lightProbes.push_back(probe); + } } void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) @@ -500,6 +840,126 @@ bool LevelMesh::IsDegenerate(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2) return crosslengthsqr <= 1.e-6f; } +void LevelMesh::AddLightmapLump(FWadWriter& wadFile) +{ + // Calculate size of lump + int numTexCoords = 0; + int numSurfaces = 0; + for (size_t i = 0; i < surfaces.size(); i++) + { + if (surfaces[i]->lightmapNum != -1) + { + numTexCoords += surfaces[i]->numVerts; + numSurfaces++; + } + } + + int version = 0; + int headerSize = 5 * sizeof(uint32_t) + 2 * sizeof(uint16_t); + int surfacesSize = surfaces.size() * 5 * sizeof(uint32_t); + int texCoordsSize = numTexCoords * 2 * sizeof(float); + int texDataSize = textures.size() * textureWidth * textureHeight * 3 * 2; + int lightProbesSize = lightProbes.size() * 6 * sizeof(float); + int lumpSize = headerSize + lightProbesSize + surfacesSize + texCoordsSize + texDataSize; + + // Setup buffer + std::vector buffer(lumpSize); + BinFile lumpFile; + lumpFile.SetBuffer(buffer.data()); + + // Write header + lumpFile.Write32(version); + lumpFile.Write16(textureWidth); + lumpFile.Write16(textures.size()); + lumpFile.Write32(numSurfaces); + lumpFile.Write32(numTexCoords); + lumpFile.Write32(lightProbes.size()); + lumpFile.Write32(map->NumGLSubsectors); + + // Write light probes + for (const LightProbeSample& probe : lightProbes) + { + lumpFile.WriteFloat(probe.Position.x); + lumpFile.WriteFloat(probe.Position.y); + lumpFile.WriteFloat(probe.Position.z); + lumpFile.WriteFloat(probe.Color.x); + lumpFile.WriteFloat(probe.Color.y); + lumpFile.WriteFloat(probe.Color.z); + } + + // Write surfaces + int coordOffsets = 0; + for (size_t i = 0; i < surfaces.size(); i++) + { + if (surfaces[i]->lightmapNum == -1) + continue; + + lumpFile.Write32(surfaces[i]->type); + lumpFile.Write32(surfaces[i]->typeIndex); + lumpFile.Write32(surfaces[i]->controlSector ? (uint32_t)(surfaces[i]->controlSector - &map->Sectors[0]) : 0xffffffff); + lumpFile.Write32(surfaces[i]->lightmapNum); + lumpFile.Write32(coordOffsets); + coordOffsets += surfaces[i]->numVerts; + } + + // Write texture coordinates + for (size_t i = 0; i < surfaces.size(); i++) + { + if (surfaces[i]->lightmapNum == -1) + continue; + + int count = surfaces[i]->numVerts; + if (surfaces[i]->type == ST_FLOOR) + { + for (int j = count - 1; j >= 0; j--) + { + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2]); + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2 + 1]); + } + } + else if (surfaces[i]->type == ST_CEILING) + { + for (int j = 0; j < count; j++) + { + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2]); + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[j * 2 + 1]); + } + } + else + { + // zdray uses triangle strip internally, lump/gzd uses triangle fan + + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[0]); + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[1]); + + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[4]); + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[5]); + + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[6]); + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[7]); + + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[2]); + lumpFile.WriteFloat(surfaces[i]->lightmapCoords[3]); + } + } + + // Write lightmap textures + for (size_t i = 0; i < textures.size(); i++) + { + unsigned int count = (textureWidth * textureHeight) * 3; + uint16_t* pixels = textures[i]->Pixels(); + for (unsigned int j = 0; j < count; j++) + { + lumpFile.Write16(pixels[j]); + } + } + + // Compress and store in lump + ZLibOut zout(wadFile); + wadFile.StartWritingLump("LIGHTMAP"); + zout.Write(buffer.data(), lumpFile.BufferAt() - lumpFile.Buffer()); +} + void LevelMesh::Export(std::string filename) { // This is so ugly! I had nothing to do with it! ;) @@ -626,182 +1086,34 @@ void LevelMesh::Export(std::string filename) fclose(file); } -#if 0 - // Convert model mesh: - - auto zmodel = std::make_unique(); - - zmodel->Vertices.resize(MeshVertices.Size()); - for (unsigned int i = 0; i < MeshVertices.Size(); i++) + int index = 0; + for (const auto& texture : textures) { - ZModelVertex &vertex = zmodel->Vertices[i]; - vertex.Pos.X = MeshVertices[i].x; - vertex.Pos.Y = MeshVertices[i].z; - vertex.Pos.Z = MeshVertices[i].y; - vertex.BoneWeights.X = 0.0f; - vertex.BoneWeights.Y = 0.0f; - vertex.BoneWeights.Z = 0.0f; - vertex.BoneWeights.W = 0.0f; - vertex.BoneIndices.X = 0; - vertex.BoneIndices.Y = 0; - vertex.BoneIndices.Z = 0; - vertex.BoneIndices.W = 0; - vertex.Normal.X = 0.0f; - vertex.Normal.Y = 0.0f; - vertex.Normal.Z = 0.0f; - vertex.TexCoords.X = 0.0f; - vertex.TexCoords.Y = 0.0f; - } - - std::map> materialRanges; - - for (unsigned int surfidx = 0; surfidx < MeshElements.Size() / 3; surfidx++) - { - Surface *surface = surfaces[MeshSurfaces[surfidx]].get(); - for (int i = 0; i < 3; i++) + int w = texture->Width(); + int h = texture->Height(); + uint16_t* p = texture->Pixels(); +#if 1 + std::vector buf(w * h * 4); + uint8_t* buffer = buf.data(); + for (int i = 0; i < w * h; i++) { - int elementidx = surfidx * 3 + i; - int vertexidx = MeshElements[elementidx]; - int uvindex = MeshUVIndex[vertexidx]; - - ZModelVertex &vertex = zmodel->Vertices[vertexidx]; - vertex.Normal.X = surface->plane.Normal().x; - vertex.Normal.Y = surface->plane.Normal().z; - vertex.Normal.Z = surface->plane.Normal().y; - vertex.TexCoords.X = surface->uvs[uvindex].x; - vertex.TexCoords.Y = surface->uvs[uvindex].y; - vertex.TexCoords2.X = surface->lightmapCoords[uvindex * 2]; - vertex.TexCoords2.Y = surface->lightmapCoords[uvindex * 2 + 1]; - vertex.TexCoords2.Z = surface->lightmapNum; - - std::string matname = surface->material; - - size_t lastslash = matname.find_last_of('/'); - if (lastslash != std::string::npos) - matname = matname.substr(lastslash + 1); - - size_t lastdot = matname.find_last_of('.'); - if (lastdot != 0 && lastdot != std::string::npos) - matname = matname.substr(0, lastdot); - - for (auto &c : matname) - { - if (c >= 'A' && c <= 'Z') c = 'a' + (c - 'A'); - } - - matname = "materials/" + matname; - - materialRanges[matname].push_back(vertexidx); + buffer[i * 4] = (uint8_t)(int)clamp(halfToFloat(p[i * 3]) * 255.0f, 0.0f, 255.0f); + buffer[i * 4 + 1] = (uint8_t)(int)clamp(halfToFloat(p[i * 3 + 1]) * 255.0f, 0.0f, 255.0f); + buffer[i * 4 + 2] = (uint8_t)(int)clamp(halfToFloat(p[i * 3 + 2]) * 255.0f, 0.0f, 255.0f); + buffer[i * 4 + 3] = 0xff; } - } - - zmodel->Elements.reserve(MeshElements.Size()); - - for (const auto &it : materialRanges) - { - uint32_t startElement = (uint32_t)zmodel->Elements.size(); - for (uint32_t vertexidx : it.second) - zmodel->Elements.push_back(vertexidx); - uint32_t vertexCount = (uint32_t)zmodel->Elements.size() - startElement; - - ZModelMaterial mat; - mat.Name = it.first; - mat.Flags = 0; - mat.Renderstyle = 0; - mat.StartElement = startElement; - mat.VertexCount = vertexCount; - zmodel->Materials.push_back(mat); - } - - // Save mesh - - ZChunkStream zmdl, zdat; - - // zmdl - { - ZChunkStream &s = zmdl; - s.Uint32(zmodel->Version); - - s.Uint32(zmodel->Materials.size()); - for (const ZModelMaterial &mat : zmodel->Materials) + PNGWriter::save("lightmap" + std::to_string(index++) + ".png", w, h, 4, buffer); +#else + std::vector buf(w * h * 4); + uint16_t* buffer = buf.data(); + for (int i = 0; i < w * h; i++) { - s.String(mat.Name); - s.Uint32(mat.Flags); - s.Uint32(mat.Renderstyle); - s.Uint32(mat.StartElement); - s.Uint32(mat.VertexCount); + buffer[i * 4] = (uint16_t)(int)clamp(halfToFloat(p[i * 3]) * 65535.0f, 0.0f, 65535.0f); + buffer[i * 4 + 1] = (uint16_t)(int)clamp(halfToFloat(p[i * 3 + 1]) * 65535.0f, 0.0f, 65535.0f); + buffer[i * 4 + 2] = (uint16_t)(int)clamp(halfToFloat(p[i * 3 + 2]) * 65535.0f, 0.0f, 65535.0f); + buffer[i * 4 + 3] = 0xffff; } - - s.Uint32(zmodel->Bones.size()); - for (const ZModelBone &bone : zmodel->Bones) - { - s.String(bone.Name); - s.Uint32((uint32_t)bone.Type); - s.Uint32(bone.ParentBone); - s.Vec3f(bone.Pivot); - } - - s.Uint32(zmodel->Animations.size()); - for (const ZModelAnimation &anim : zmodel->Animations) - { - s.String(anim.Name); - s.Float(anim.Duration); - s.Vec3f(anim.AabbMin); - s.Vec3f(anim.AabbMax); - s.Uint32(anim.Bones.size()); - for (const ZModelBoneAnim &bone : anim.Bones) - { - s.FloatArray(bone.Translation.Timestamps); - s.Vec3fArray(bone.Translation.Values); - s.FloatArray(bone.Rotation.Timestamps); - s.QuaternionfArray(bone.Rotation.Values); - s.FloatArray(bone.Scale.Timestamps); - s.Vec3fArray(bone.Scale.Values); - } - s.Uint32(anim.Materials.size()); - for (const ZModelMaterialAnim &mat : anim.Materials) - { - s.FloatArray(mat.Translation.Timestamps); - s.Vec3fArray(mat.Translation.Values); - s.FloatArray(mat.Rotation.Timestamps); - s.QuaternionfArray(mat.Rotation.Values); - s.FloatArray(mat.Scale.Timestamps); - s.Vec3fArray(mat.Scale.Values); - } - } - - s.Uint32(zmodel->Attachments.size()); - for (const ZModelAttachment &attach : zmodel->Attachments) - { - s.String(attach.Name); - s.Uint32(attach.Bone); - s.Vec3f(attach.Position); - } - } - - // zdat - { - ZChunkStream &s = zdat; - - s.VertexArray(zmodel->Vertices); - s.Uint32Array(zmodel->Elements); - } - - FILE *file = fopen(filename.c_str(), "wb"); - if (file) - { - uint32_t chunkhdr[2]; - memcpy(chunkhdr, "ZMDL", 4); - chunkhdr[1] = zmdl.ChunkLength(); - fwrite(chunkhdr, 8, 1, file); - fwrite(zmdl.ChunkData(), zmdl.ChunkLength(), 1, file); - - memcpy(chunkhdr, "ZDAT", 4); - chunkhdr[1] = zdat.ChunkLength(); - fwrite(chunkhdr, 8, 1, file); - fwrite(zdat.ChunkData(), zdat.ChunkLength(), 1, file); - - fclose(file); - } + PNGWriter::save("lightmap" + std::to_string(index++) + ".png", w, h, 8, buffer); #endif + } } diff --git a/src/lightmap/surfaces.h b/src/lightmap/surfaces.h index 5c51ba3..6d07dc7 100644 --- a/src/lightmap/surfaces.h +++ b/src/lightmap/surfaces.h @@ -39,6 +39,7 @@ struct MapSubsectorEx; struct IntSector; struct IntSideDef; struct FLevel; +class FWadWriter; enum SurfaceType { @@ -84,17 +85,100 @@ struct LevelTraceHit float b, c; }; +class LightmapTexture +{ +public: + LightmapTexture(int width, int height) : textureWidth(width), textureHeight(height) + { + mPixels.resize(width * height * 3); + allocBlocks.resize(width); + } + + bool MakeRoomForBlock(const int width, const int height, int* x, int* y) + { + int bestRow1 = textureHeight; + + for (int i = 0; i <= textureWidth - width; i++) + { + int bestRow2 = 0; + + int j; + for (j = 0; j < width; j++) + { + if (allocBlocks[i + j] >= bestRow1) + { + break; + } + + if (allocBlocks[i + j] > bestRow2) + { + bestRow2 = allocBlocks[i + j]; + } + } + + // found a free block + if (j == width) + { + *x = i; + *y = bestRow1 = bestRow2; + } + } + + if (bestRow1 + height > textureHeight) + { + // no room + return false; + } + + // store row offset + for (int i = 0; i < width; i++) + { + allocBlocks[*x + i] = bestRow1 + height; + } + + return true; + } + + int Width() const { return textureWidth; } + int Height() const { return textureHeight; } + uint16_t* Pixels() { return mPixels.data(); } + +private: + int textureWidth; + int textureHeight; + std::vector mPixels; + std::vector allocBlocks; +}; + +class LightProbeSample +{ +public: + Vec3 Position = Vec3(0.0f, 0.0f, 0.0f); + Vec3 Color = Vec3(0.0f, 0.0f, 0.0f); +}; + class LevelMesh { public: - LevelMesh(FLevel &doomMap); + LevelMesh(FLevel &doomMap, int sampleDistance, int textureSize); + void CreateTextures(); + void AddLightmapLump(FWadWriter& wadFile); void Export(std::string filename); LevelTraceHit Trace(const Vec3 &startVec, const Vec3 &endVec); bool TraceAnyHit(const Vec3 &startVec, const Vec3 &endVec); + FLevel* map = nullptr; + std::vector> surfaces; + std::vector lightProbes; + + std::vector> textures; + + int samples = 16; + int textureWidth = 128; + int textureHeight = 128; TArray MeshVertices; TArray MeshUVIndex; @@ -106,8 +190,13 @@ private: 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); - void CreateSideSurfaces(FLevel &doomMap, IntSideDef *side); + void CreateLightProbes(FLevel& doomMap); + + void BuildSurfaceParams(Surface* surface); + BBox GetBoundsFromSurface(const Surface* surface); + void FinishSurface(Surface* surface); + uint16_t* AllocTextureRoom(Surface* surface, int* x, int* y); static bool IsDegenerate(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2); };