Some mesh export functionality and name size changes, supposedly

This commit is contained in:
Magnus Norddahl 2021-09-24 16:54:33 +02:00
parent 28d7bb586f
commit 82c0837928
7 changed files with 762 additions and 19 deletions

View file

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

View file

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

View file

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

View file

@ -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 = &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)

View file

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

View file

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

View file

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