Prepare the code for writing a new ray tracer

This commit is contained in:
Magnus Norddahl 2021-10-20 05:28:41 +02:00
parent e5816c7df4
commit fa1d2fb215
12 changed files with 970 additions and 1072 deletions

View file

@ -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

View file

@ -19,6 +19,7 @@
*/
#include "level/level.h"
#include "lightmap/lightmap.h"
//#include "rejectbuilder.h"
#include <memory>
@ -688,8 +689,10 @@ void FProcessor::BuildNodes()
void FProcessor::BuildLightmaps()
{
Level.SetupLights();
LMBuilder.CreateLightmaps(Level, Samples, LMDims);
LightmapsBuilt = true;
LightmapMesh = std::make_unique<LevelMesh>(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)
{

View file

@ -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 <zlib.h>
#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<LevelMesh> LightmapMesh;
};

View file

@ -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");

File diff suppressed because it is too large Load diff

View file

@ -31,30 +31,9 @@
#include "framework/tarray.h"
#include <mutex>
#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<uint16_t> mPixels;
std::vector<int> 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<LevelMesh> mesh;
LevelMesh* mesh = nullptr;
std::vector<std::unique_ptr<SurfaceLight>> surfaceLights;
std::vector<std::unique_ptr<LightmapTexture>> textures;
std::vector<TraceTask> traceTasks;
std::vector<LightProbeSample> lightProbes;
int tracedTexels = 0;
std::mutex mutex;

217
src/lightmap/pngwriter.cpp Normal file
View file

@ -0,0 +1,217 @@
#include "math/mathlib.h"
#include "pngwriter.h"
#include "framework/binfile.h"
#include "framework/templates.h"
#include "framework/halffloat.h"
#include <map>
#include <vector>
#include <algorithm>
#include <zlib.h>
#include <stdexcept>
#include <memory>
#include <cmath>
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 = &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<unsigned char> scanline_orig;
std::vector<unsigned char> 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<DataBuffer>((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<DataBuffer>(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;
}

78
src/lightmap/pngwriter.h Normal file
View file

@ -0,0 +1,78 @@
#pragma once
#include <string>
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<const unsigned char*>(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);
};

View file

@ -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 <map>
#include <vector>
#include <algorithm>
#include <zlib.h>
extern int Multisample;
extern int LightBounce;
extern float GridSize;
Raytracer::Raytracer()
{
}
Raytracer::~Raytracer()
{
}
void Raytracer::Raytrace(LevelMesh* level)
{
mesh = level;
}

16
src/lightmap/raytracer.h Normal file
View file

@ -0,0 +1,16 @@
#pragma once
class LevelMesh;
class Raytracer
{
public:
Raytracer();
~Raytracer();
void Raytrace(LevelMesh* level);
private:
LevelMesh* mesh = nullptr;
};

View file

@ -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 <map>
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<TriangleMeshShape>(&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<LightmapTexture>(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<uint8_t> 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>();
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<std::string, std::vector<uint32_t>> 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<uint8_t> 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<uint16_t> 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
}
}

View file

@ -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<uint16_t> mPixels;
std::vector<int> 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<std::unique_ptr<Surface>> surfaces;
std::vector<LightProbeSample> lightProbes;
std::vector<std::unique_ptr<LightmapTexture>> textures;
int samples = 16;
int textureWidth = 128;
int textureHeight = 128;
TArray<Vec3> MeshVertices;
TArray<int> 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);
};