diff --git a/src/level/doomdata.h b/src/level/doomdata.h index 97dfd8f..86bab71 100644 --- a/src/level/doomdata.h +++ b/src/level/doomdata.h @@ -33,9 +33,9 @@ struct MapSideDef { short textureoffset; short rowoffset; - char toptexture[8]; - char bottomtexture[8]; - char midtexture[8]; + char toptexture[64/*8*/]; + char bottomtexture[64/*8*/]; + char midtexture[64/*8*/]; uint16_t sector; }; @@ -46,9 +46,9 @@ struct IntSideDef // the first 5 values are only used for binary format maps short textureoffset; short rowoffset; - char toptexture[8]; - char bottomtexture[8]; - char midtexture[8]; + char toptexture[64/*8*/]; + char bottomtexture[64/*8*/]; + char midtexture[64/*8*/]; int sector; int lightdef; @@ -95,8 +95,8 @@ struct MapSector { short floorheight; short ceilingheight; - char floorpic[8]; - char ceilingpic[8]; + char floorpic[64/*8*/]; + char ceilingpic[64/*8*/]; short lightlevel; short special; short tag; diff --git a/src/level/level_light.cpp b/src/level/level_light.cpp index 5326090..9ab5761 100644 --- a/src/level/level_light.cpp +++ b/src/level/level_light.cpp @@ -92,17 +92,17 @@ void FLevel::SetupLights() void FLevel::CheckSkySectors() { - char name[9]; + char name[65]; for (int i = 0; i < (int)Sectors.Size(); ++i) { //if (mapDef && mapDef->sunIgnoreTag != 0 && Sectors[i].data.tag == mapDef->sunIgnoreTag) // continue; - strncpy(name, Sectors[i].data.ceilingpic, 8); - name[8] = 0; + strncpy(name, Sectors[i].data.ceilingpic, 64); + name[64] = 0; - if (!strncmp(name, "F_SKY001", 8) || !strncmp(name, "F_SKY1", 8) || !strncmp(name, "F_SKY", 8)) + if (!strncmp(name, "F_SKY001", 64) || !strncmp(name, "F_SKY1", 64) || !strncmp(name, "F_SKY", 64)) { Sectors[i].skySector = true; } diff --git a/src/level/level_udmf.cpp b/src/level/level_udmf.cpp index 28d4013..d0ca20e 100644 --- a/src/level/level_udmf.cpp +++ b/src/level/level_udmf.cpp @@ -366,15 +366,15 @@ void FProcessor::ParseSidedef(IntSideDef *sd) if (stricmp(key, "texturetop") == 0) { - CopyUDMFString(sd->toptexture, 8, value); + CopyUDMFString(sd->toptexture, 64, value); } else if (stricmp(key, "texturemiddle") == 0) { - CopyUDMFString(sd->midtexture, 8, value); + CopyUDMFString(sd->midtexture, 64, value); } else if (stricmp(key, "texturebottom") == 0) { - CopyUDMFString(sd->bottomtexture, 8, value); + CopyUDMFString(sd->bottomtexture, 64, value); } else if (stricmp(key, "offsetx_mid") == 0) { @@ -413,11 +413,11 @@ void FProcessor::ParseSector(IntSector *sec) if (stricmp(key, "textureceiling") == 0) { - CopyUDMFString(sec->data.ceilingpic, 8, value); + CopyUDMFString(sec->data.ceilingpic, 64, value); } else if (stricmp(key, "texturefloor") == 0) { - CopyUDMFString(sec->data.floorpic, 8, value); + CopyUDMFString(sec->data.floorpic, 64, value); } else if (stricmp(key, "heightceiling") == 0) { @@ -886,6 +886,7 @@ void FProcessor::WriteUDMF(FWadWriter &out) if (LightmapsBuilt) { LMBuilder.AddLightmapLump(out); + //LMBuilder.ExportMesh("level.zmdl"); } out.CreateLabel("ENDMAP"); diff --git a/src/lightmap/lightmap.cpp b/src/lightmap/lightmap.cpp index 2ab262d..94511f3 100644 --- a/src/lightmap/lightmap.cpp +++ b/src/lightmap/lightmap.cpp @@ -37,6 +37,7 @@ #include <map> #include <vector> #include <algorithm> +#include <zlib.h> #ifdef _MSC_VER #pragma warning(disable: 4267) // warning C4267: 'argument': conversion from 'size_t' to 'int', possible loss of data @@ -948,6 +949,311 @@ void LightmapBuilder::PrintTaskProcessed() } } +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<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() + { + 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<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>(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 = 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<uint8_t> 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<uint16_t> 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) diff --git a/src/lightmap/lightmap.h b/src/lightmap/lightmap.h index 5d1a4f9..fbd5dcf 100644 --- a/src/lightmap/lightmap.h +++ b/src/lightmap/lightmap.h @@ -60,6 +60,8 @@ public: 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: @@ -89,6 +91,7 @@ public: void CreateLightmaps(FLevel &doomMap, int sampleDistance, int textureSize); void AddLightmapLump(FWadWriter &wadFile); + void ExportMesh(std::string filename); private: BBox GetBoundsFromSurface(const Surface *surface); diff --git a/src/lightmap/surfaces.cpp b/src/lightmap/surfaces.cpp index be2ace8..36d02b2 100644 --- a/src/lightmap/surfaces.cpp +++ b/src/lightmap/surfaces.cpp @@ -89,9 +89,9 @@ LevelMesh::LevelMesh(FLevel &doomMap) } if (!IsDegenerate(s->verts[1], s->verts[2], s->verts[3])) { - MeshElements.Push(pos + 1); - MeshElements.Push(pos + 2); MeshElements.Push(pos + 3); + MeshElements.Push(pos + 2); + MeshElements.Push(pos + 1); MeshSurfaces.Push(i); } } @@ -126,6 +126,9 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) int typeIndex = side - &doomMap.Sides[0]; + Vec2 dx(v2.x - v1.x, v2.y - v1.y); + float distance = std::sqrt(dx.Dot(dx)); + if (back) { for (unsigned int j = 0; j < front->x3dfloors.Size(); j++) @@ -145,7 +148,11 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) if (bothSides) continue; + float texWidth = 128.0f; + float texHeight = 128.0f; + auto surf = std::make_unique<Surface>(); + surf->material = "texture"; surf->type = ST_MIDDLESIDE; surf->typeIndex = typeIndex; surf->controlSector = xfloor; @@ -162,6 +169,18 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->plane.SetNormal(surf->verts[0], surf->verts[1], surf->verts[2]); surf->plane.SetDistance(surf->verts[0]); + float texZ = surf->verts[0].z; + + surf->uvs.resize(4); + surf->uvs[0].x = 0.0f; + surf->uvs[1].x = distance / texWidth; + surf->uvs[2].x = 0.0f; + surf->uvs[3].x = distance / texWidth; + surf->uvs[0].y = (surf->verts[0].z - texZ) / texHeight; + surf->uvs[1].y = (surf->verts[1].z - texZ) / texHeight; + surf->uvs[2].y = (surf->verts[2].z - texZ) / texHeight; + surf->uvs[3].y = (surf->verts[3].z - texZ) / texHeight; + surfaces.push_back(std::move(surf)); } @@ -180,7 +199,11 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) { if (side->bottomtexture[0] != '-') { + float texWidth = 128.0f; + float texHeight = 128.0f; + auto surf = std::make_unique<Surface>(); + surf->material = side->bottomtexture; surf->numVerts = 4; surf->verts.resize(4); @@ -199,6 +222,18 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->typeIndex = typeIndex; surf->controlSector = nullptr; + float texZ = surf->verts[0].z; + + surf->uvs.resize(4); + surf->uvs[0].x = 0.0f; + surf->uvs[1].x = distance / texWidth; + surf->uvs[2].x = 0.0f; + surf->uvs[3].x = distance / texWidth; + surf->uvs[0].y = (surf->verts[0].z - texZ) / texHeight; + surf->uvs[1].y = (surf->verts[1].z - texZ) / texHeight; + surf->uvs[2].y = (surf->verts[2].z - texZ) / texHeight; + surf->uvs[3].y = (surf->verts[3].z - texZ) / texHeight; + surfaces.push_back(std::move(surf)); } @@ -221,7 +256,11 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) if (side->toptexture[0] != '-' || bSky) { + float texWidth = 128.0f; + float texHeight = 128.0f; + auto surf = std::make_unique<Surface>(); + surf->material = side->toptexture; surf->numVerts = 4; surf->verts.resize(4); @@ -241,6 +280,18 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->bSky = bSky; surf->controlSector = nullptr; + float texZ = surf->verts[0].z; + + surf->uvs.resize(4); + surf->uvs[0].x = 0.0f; + surf->uvs[1].x = distance / texWidth; + surf->uvs[2].x = 0.0f; + surf->uvs[3].x = distance / texWidth; + surf->uvs[0].y = (surf->verts[0].z - texZ) / texHeight; + surf->uvs[1].y = (surf->verts[1].z - texZ) / texHeight; + surf->uvs[2].y = (surf->verts[2].z - texZ) / texHeight; + surf->uvs[3].y = (surf->verts[3].z - texZ) / texHeight; + surfaces.push_back(std::move(surf)); } @@ -252,7 +303,11 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) // middle seg if (back == nullptr) { + float texWidth = 128.0f; + float texHeight = 128.0f; + auto surf = std::make_unique<Surface>(); + surf->material = side->midtexture; surf->numVerts = 4; surf->verts.resize(4); @@ -271,6 +326,18 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) surf->typeIndex = typeIndex; surf->controlSector = nullptr; + float texZ = surf->verts[0].z; + + surf->uvs.resize(4); + surf->uvs[0].x = 0.0f; + surf->uvs[1].x = distance / texWidth; + surf->uvs[2].x = 0.0f; + surf->uvs[3].x = distance / texWidth; + surf->uvs[0].y = (surf->verts[0].z - texZ) / texHeight; + surf->uvs[1].y = (surf->verts[1].z - texZ) / texHeight; + surf->uvs[2].y = (surf->verts[2].z - texZ) / texHeight; + surf->uvs[3].y = (surf->verts[3].z - texZ) / texHeight; + surfaces.push_back(std::move(surf)); } } @@ -278,8 +345,10 @@ void LevelMesh::CreateSideSurfaces(FLevel &doomMap, IntSideDef *side) void LevelMesh::CreateFloorSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSector *sector, int typeIndex, bool is3DFloor) { auto surf = std::make_unique<Surface>(); + surf->material = sector->data.floorpic; surf->numVerts = sub->numlines; surf->verts.resize(surf->numVerts); + surf->uvs.resize(surf->numVerts); if (!is3DFloor) { @@ -298,6 +367,9 @@ void LevelMesh::CreateFloorSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSect surf->verts[j].x = v1.x; surf->verts[j].y = v1.y; surf->verts[j].z = surf->plane.zAt(surf->verts[j].x, surf->verts[j].y); + + surf->uvs[j].x = v1.x / 64.0f; + surf->uvs[j].y = v1.y / 64.0f; } surf->type = ST_FLOOR; @@ -310,8 +382,10 @@ void LevelMesh::CreateFloorSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSect void LevelMesh::CreateCeilingSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSector *sector, int typeIndex, bool is3DFloor) { auto surf = std::make_unique<Surface>(); + surf->material = sector->data.ceilingpic; surf->numVerts = sub->numlines; surf->verts.resize(surf->numVerts); + surf->uvs.resize(surf->numVerts); surf->bSky = sector->skySector; if (!is3DFloor) @@ -331,6 +405,9 @@ void LevelMesh::CreateCeilingSurface(FLevel &doomMap, MapSubsectorEx *sub, IntSe surf->verts[j].x = v1.x; surf->verts[j].y = v1.y; surf->verts[j].z = surf->plane.zAt(surf->verts[j].x, surf->verts[j].y); + + surf->uvs[j].x = v1.x / 64.0f; + surf->uvs[j].y = v1.y / 64.0f; } surf->type = ST_CEILING; @@ -422,3 +499,183 @@ bool LevelMesh::IsDegenerate(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2) float crosslengthsqr = crossx * crossx + crossy * crossy + crossz * crossz; return crosslengthsqr <= 1.e-6f; } + +void LevelMesh::Export(std::string filename) +{ + // Convert model mesh: + + auto zmodel = std::make_unique<ZModel>(); + + zmodel->Vertices.resize(MeshVertices.Size()); + for (unsigned int i = 0; i < MeshVertices.Size(); i++) + { + 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 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); + } + } + + 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) + { + s.String(mat.Name); + s.Uint32(mat.Flags); + s.Uint32(mat.Renderstyle); + s.Uint32(mat.StartElement); + s.Uint32(mat.VertexCount); + } + + 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); + } +} diff --git a/src/lightmap/surfaces.h b/src/lightmap/surfaces.h index 51578f6..4e3049f 100644 --- a/src/lightmap/surfaces.h +++ b/src/lightmap/surfaces.h @@ -29,6 +29,7 @@ #include <vector> #include <memory> +#include <string> #include "framework/tarray.h" #include "lightmap/collision.h" @@ -67,6 +68,8 @@ struct Surface int typeIndex; IntSector *controlSector; bool bSky; + std::vector<Vec2> uvs; + std::string material; }; struct LevelTraceHit @@ -85,6 +88,8 @@ class LevelMesh public: LevelMesh(FLevel &doomMap); + void Export(std::string filename); + LevelTraceHit Trace(const Vec3 &startVec, const Vec3 &endVec); bool TraceAnyHit(const Vec3 &startVec, const Vec3 &endVec); @@ -105,3 +110,174 @@ private: static bool IsDegenerate(const Vec3 &v0, const Vec3 &v1, const Vec3 &v2); }; + +///////////////////////////////////////////////////////////////////////////// + +struct ZModelVec2f +{ + float X, Y; +}; + +struct ZModelVec3f +{ + float X, Y, Z; +}; + +struct ZModelVec4ub +{ + uint8_t X, Y, Z, W; +}; + +struct ZModelQuaternionf +{ + float X, Y, Z, W; +}; + +struct ZModelVertex +{ + ZModelVec3f Pos; + ZModelVec4ub BoneWeights; + ZModelVec4ub BoneIndices; + ZModelVec3f Normal; + ZModelVec2f TexCoords; + ZModelVec3f TexCoords2; +}; + +struct ZModelMaterial +{ + std::string Name; + uint32_t Flags = 0; // Two-sided, depth test/write, what else? + uint32_t Renderstyle; + uint32_t StartElement = 0; + uint32_t VertexCount = 0; +}; + +template<typename Value> +struct ZModelTrack +{ + std::vector<float> Timestamps; + std::vector<Value> Values; +}; + +struct ZModelBoneAnim +{ + ZModelTrack<ZModelVec3f> Translation; + ZModelTrack<ZModelQuaternionf> Rotation; + ZModelTrack<ZModelVec3f> Scale; +}; + +struct ZModelMaterialAnim +{ + ZModelTrack<ZModelVec3f> Translation; + ZModelTrack<ZModelQuaternionf> Rotation; // Rotation center is texture center (0.5, 0.5) + ZModelTrack<ZModelVec3f> Scale; +}; + +struct ZModelAnimation +{ + std::string Name; // Name of animation + float Duration; // Length of this animation sequence in seconds + + ZModelVec3f AabbMin; // Animation bounds (for culling purposes) + ZModelVec3f AabbMax; + + std::vector<ZModelBoneAnim> Bones; // Animation tracks for each bone + std::vector<ZModelMaterialAnim> Materials; // Animation tracks for each material +}; + +enum class ZModelBoneType : uint32_t +{ + Normal, + BillboardSpherical, + BillboardCylindricalX, + BillboardCylindricalY, + BillboardCylindricalZ +}; + +struct ZModelBone +{ + std::string Name; + ZModelBoneType Type = ZModelBoneType::Normal; + int32_t ParentBone = -1; + ZModelVec3f Pivot; +}; + +struct ZModelAttachment +{ + std::string Name; + int32_t Bone = -1; + ZModelVec3f Position; +}; + +struct ZModel +{ + // ZMDL chunk + uint32_t Version = 1; + std::vector<ZModelMaterial> Materials; + std::vector<ZModelBone> Bones; + std::vector<ZModelAnimation> Animations; + std::vector<ZModelAttachment> Attachments; + + // ZDAT chunk + std::vector<ZModelVertex> Vertices; + std::vector<uint32_t> Elements; +}; + +struct ZChunkStream +{ + void Uint32(uint32_t v) { Write<uint32_t>(v); } + void Float(float v) { Write<float>(v); } + void Vec2f(const ZModelVec2f &v) { Write<ZModelVec2f>(v); } + void Vec3f(const ZModelVec3f &v) { Write<ZModelVec3f>(v); } + void Vec4ub(const ZModelVec4ub &v) { Write<ZModelVec4ub>(v); } + void Quaternionf(const ZModelQuaternionf &v) { Write<ZModelQuaternionf>(v); } + + void Uint32Array(const std::vector<uint32_t> &v) { WriteArray<uint32_t>(v); } + void FloatArray(const std::vector<float> &v) { WriteArray<float>(v); } + void Vec2fArray(const std::vector<ZModelVec2f> &v) { WriteArray<ZModelVec2f>(v); } + void Vec3fArray(const std::vector<ZModelVec3f> &v) { WriteArray<ZModelVec3f>(v); } + void Vec4ubArray(const std::vector<ZModelVec4ub> &v) { WriteArray<ZModelVec4ub>(v); } + void QuaternionfArray(const std::vector<ZModelQuaternionf> &v) { WriteArray<ZModelQuaternionf>(v); } + void VertexArray(const std::vector<ZModelVertex> &v) { WriteArray<ZModelVertex>(v); } + + void String(const std::string &v) + { + Write(v.c_str(), v.length() + 1); + } + + void StringArray(const std::vector<std::string> &v) + { + Uint32((uint32_t)v.size()); + for (const std::string &s : v) + String(s); + } + + const void *ChunkData() const { return buffer.data(); } + uint32_t ChunkLength() const { return (uint32_t)pos; } + +private: + template<typename Type> + void Write(const Type &v) + { + Write(&v, sizeof(Type)); + } + + template<typename Type> + void WriteArray(const std::vector<Type> &v) + { + Uint32((uint32_t)v.size()); + Write(v.data(), v.size() * sizeof(Type)); + } + + void Write(const void *data, size_t size) + { + if (pos + size > buffer.size()) + buffer.resize(buffer.size() * 2); + + memcpy(buffer.data() + pos, data, size); + pos += size; + } + + std::vector<uint8_t> buffer = std::vector<uint8_t>(16 * 1024 * 1024); + size_t pos = 0; +};