Pomme/src/QD3D/3DMFParser.cpp

562 lines
16 KiB
C++

#include "QD3D.h"
#include "PommeDebug.h"
#include "Pomme.h"
#include "3DMFInternal.h"
#if !(POMME_DEBUG_3DMF)
#define printf(...) do{}while(0)
#endif
class Q3MetaFile_EarlyEOFException : public std::exception
{
public:
const char *what() const noexcept override
{
return "Early EOF in 3DMF";
}
};
static void Assert(bool condition, const char* message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
template<typename T>
static void ReadTriangleVertexIndices(Pomme::BigEndianIStream& f, int numTriangles, TQ3TriMeshData* currentMesh)
{
for (int i = 0; i < numTriangles; i++)
{
T v0 = f.Read<T>();
T v1 = f.Read<T>();
T v2 = f.Read<T>();
currentMesh->triangles[i] = {v0, v1, v2};
}
}
Q3MetaFileParser::Q3MetaFileParser(std::istream& theBaseStream, TQ3MetaFile& dest)
: metaFile(dest)
, baseStream(theBaseStream)
, f(baseStream)
, currentMesh(nullptr)
{
}
uint32_t Q3MetaFileParser::Parse1Chunk()
{
Assert(currentDepth >= 0, "depth underflow");
//-----------------------------------------------------------
// Get current chunk offset, type, size
uint32_t chunkOffset = f.Tell();
uint32_t chunkType = f.Read<uint32_t>();
uint32_t chunkSize = f.Read<uint32_t>();
// std::string myChunk = Pomme::FourCCString(chunkType);
// const char* myChunkC = myChunk.c_str();
printf("\n%d-%08x ", currentDepth, chunkOffset);
for (int i = 0; i < 1 + currentDepth - (chunkType=='endg'?1:0); i++)
printf("\t");
printf("%s\t", Pomme::FourCCString(chunkType).c_str());
fflush(stdout);
//-----------------------------------------------------------
// Process current chunk
switch (chunkType)
{
case 0: // Happens in Diloph_Fin.3df at 0x00014233 -- signals early EOF? or corrupted file? either way, stop parsing.
throw Q3MetaFile_EarlyEOFException();
case 'cntr': // Container
{
if (currentDepth == 1)
__Q3EnlargeArray<TQ3TriMeshFlatGroup>(metaFile.topLevelGroups, metaFile.numTopLevelGroups, 'GLST');
currentDepth++;
auto limit = f.Tell() + (std::streamoff) chunkSize;
while (f.Tell() != limit)
Parse1Chunk();
currentDepth--;
currentMesh = nullptr;
break;
}
case 'bgng':
if (currentDepth == 1)
__Q3EnlargeArray<TQ3TriMeshFlatGroup>(metaFile.topLevelGroups, metaFile.numTopLevelGroups, 'GLST');
currentDepth++;
f.Skip(chunkSize); // bgng itself typically contains dspg, dgst
while ('endg' != Parse1Chunk())
;
currentDepth--;
currentMesh = nullptr;
break;
case 'endg':
Assert(chunkSize == 0, "illegal endg size");
break;
case 'tmsh': // TriMesh
{
Assert(!currentMesh, "nested meshes not supported");
Parse_tmsh(chunkSize);
Assert(currentMesh, "currentMesh wasn't get set by Parse_tmsh?");
if (metaFile.numTopLevelGroups == 0)
__Q3EnlargeArray<TQ3TriMeshFlatGroup>(metaFile.topLevelGroups, metaFile.numTopLevelGroups, 'GLST');
TQ3TriMeshFlatGroup* group = &metaFile.topLevelGroups[metaFile.numTopLevelGroups-1];
__Q3EnlargeArray(group->meshes, group->numMeshes, 'GMSH');
group->meshes[group->numMeshes-1] = currentMesh;
break;
}
case 'atar': // AttributeArray
Parse_atar(chunkSize);
break;
case 'attr': // AttributeSet
Assert(chunkSize == 0, "illegal attr size");
break;
case 'kdif': // Diffuse Color
Assert(chunkSize == 12, "illegal kdif size");
Assert(currentMesh, "stray kdif");
{
static_assert(sizeof(float) == 4);
currentMesh->diffuseColor.r = f.Read<float>();
currentMesh->diffuseColor.g = f.Read<float>();
currentMesh->diffuseColor.b = f.Read<float>();
}
break;
case 'kxpr': // Transparency Color
Assert(chunkSize == 12, "illegal kxpr size");
Assert(currentMesh, "stray kxpr");
{
static_assert(sizeof(float) == 4);
float r = f.Read<float>();
float g = f.Read<float>();
float b = f.Read<float>();
float a = r;
printf("%.3f %.3f %.3f\t", r, g, b);
Assert(r == g && g == b, "kxpr: expecting all components to be equal");
currentMesh->diffuseColor.a = a;
}
break;
case 'txsu': // TextureShader
{
uint32_t internalTextureID;
Assert(chunkSize == 0, "illegal txsu size");
if (knownTextures.find(chunkOffset) != knownTextures.end())
{
// We've seen this 'txsu' before. We're here because a 'rfrn' refers to it again.
// Don't create a new texture for it.
printf("Already seen this txsu.");
internalTextureID = knownTextures[chunkOffset];
// TODO: Just skip to end of container
}
else
{
internalTextureID = metaFile.numTextures;
__Q3EnlargeArray(metaFile.textures, metaFile.numTextures, 'TXSU');
knownTextures[chunkOffset] = internalTextureID;
}
if (currentMesh)
{
Assert(currentMesh->internalTextureID < 0, "txmm: current mesh already has a texture");
Assert(currentMesh->texturingMode == kQ3TexturingModeOff, "txmm: current mesh already has a texturing mode");
currentMesh->internalTextureID = internalTextureID;
currentMesh->texturingMode = kQ3TexturingModeInvalid; // set texturing mode to invalid because we don't know if the texture is opaque yet
}
break;
}
case 'txmm': // MipmapTexture (after a txsu)
case 'txpm': // PixmapTexture (after a txsu)
if (GetCurrentTextureShader().pixmap)
{
printf("Pixmap already set for this txsu\n");
f.Skip(chunkSize);
}
else
{
GetCurrentTextureShader().pixmap = ParsePixmap(chunkType, chunkSize);
}
break;
case 'shdr': // UV clamp/wrap (after a txsu)
Assert(chunkSize == 8, "illegal shdr size");
GetCurrentTextureShader().boundaryU = (TQ3ShaderUVBoundary) f.Read<uint32_t>();
GetCurrentTextureShader().boundaryV = (TQ3ShaderUVBoundary) f.Read<uint32_t>();
break;
case 'rfrn': // Reference (into TOC)
{
Assert(chunkSize == 4, "illegal rfrn size");
uint32_t target = f.Read<uint32_t>();
printf("TOC#%d -----> %08lx", target, referenceTOC.at(target).offset);
auto jumpBackTo = f.Tell();
f.Goto(referenceTOC.at(target).offset);
Parse1Chunk();
f.Goto(jumpBackTo);
break;
}
case 'toc ':
// Already read TOC at beginning
f.Skip(chunkSize);
break;
default:
throw std::runtime_error("unrecognized 3DMF chunk");
}
return chunkType;
}
void Q3MetaFileParser::Parse3DMF()
{
baseStream.seekg(0, std::ios::end);
std::streampos fileLength = baseStream.tellg();
baseStream.seekg(0, std::ios::beg);
Assert(f.Read<uint32_t>() == '3DMF', "Not a 3DMF file");
Assert(f.Read<uint32_t>() == 16, "Bad header length");
uint16_t versionMajor = f.Read<uint16_t>();
uint16_t versionMinor = f.Read<uint16_t>();
Assert(versionMajor == 1 && (versionMinor == 5 || versionMinor == 6), "Unsupported 3DMF version");
uint32_t flags = f.Read<uint32_t>();
Assert(flags == 0, "Database or Stream aren't supported");
uint64_t tocOffset = f.Read<uint64_t>();
printf("Version %d.%d tocOffset %08lx\n", versionMajor, versionMinor, tocOffset);
// Read TOC
if (tocOffset != 0)
{
Pomme::StreamPosGuard rewindAfterTOC(baseStream);
f.Goto(tocOffset);
Assert('toc ' == f.Read<uint32_t>(), "Expecting toc magic here");
f.Skip(4); //uint32_t tocSize = f.Read<uint32_t>();
f.Skip(8); //uint64_t nextToc = f.Read<uint64_t>();
f.Skip(4); //uint32_t refSeed = f.Read<uint32_t>();
f.Skip(4); //uint32_t typeSeed = f.Read<uint32_t>();
uint32_t tocEntryType = f.Read<uint32_t>();
uint32_t tocEntrySize = f.Read<uint32_t>();
uint32_t nEntries = f.Read<uint32_t>();
Assert(tocEntryType == 1, "only QD3D 1.5 3DMF TOCs are recognized");
Assert(tocEntrySize == 16, "incorrect tocEntrySize");
for (uint32_t i = 0; i < nEntries;i++)
{
uint32_t refID = f.Read<uint32_t>();
uint64_t objLocation = f.Read<uint64_t>();
uint32_t objType = f.Read<uint32_t>();
printf("TOC: refID %d '%s' at %08lx\n", refID, Pomme::FourCCString(objType).c_str(), objLocation);
referenceTOC[refID] = {objLocation, objType};
}
}
// Chunk Loop
try
{
while (f.Tell() != fileLength)
{
Parse1Chunk();
}
}
catch (Q3MetaFile_EarlyEOFException&)
{
// Stop parsing
printf("Early EOF");
}
printf("\n");
}
void Q3MetaFileParser::Parse_tmsh(uint32_t chunkSize)
{
Assert(chunkSize >= 52, "Illegal tmsh size");
Assert(!currentMesh, "current mesh already set");
uint32_t numTriangles = f.Read<uint32_t>();
f.Skip(4); // numTriangleAttributes (u32) -- don't care how many attribs there are, we'll read them in as we go
uint32_t numEdges = f.Read<uint32_t>();
uint32_t numEdgeAttributes = f.Read<uint32_t>();
uint32_t numVertices = f.Read<uint32_t>();
f.Skip(4); // numVertexAttributes (u32) -- don't care how many attribs there are, we'll read them in as we go
printf("%d tris, %d vertices\t", numTriangles, numVertices);
Assert(0 == numEdges, "edges are not supported");
Assert(0 == numEdgeAttributes, "edge attributes are not supported");
// Allocate the mesh.
// Don't allocate vertex UVs, colors or normals yet. We'll allocate them later if the mesh needs them.
currentMesh = Q3TriMeshData_New(numTriangles, numVertices, 0);
__Q3EnlargeArray(metaFile.meshes, metaFile.numMeshes, 'MLST');
metaFile.meshes[metaFile.numMeshes-1] = currentMesh;
// Triangles
if (numVertices <= 0xFF)
{
ReadTriangleVertexIndices<uint8_t>(f, numTriangles, currentMesh);
}
else if (numVertices <= 0xFFFF)
{
ReadTriangleVertexIndices<uint16_t>(f, numTriangles, currentMesh);
}
else
{
ReadTriangleVertexIndices<uint32_t>(f, numTriangles, currentMesh);
}
// Ensure all vertex indices are in the expected range
for (uint32_t i = 0; i < numTriangles; i++)
{
for (auto index : currentMesh->triangles[i].pointIndices)
{
Assert(index < numVertices, "3DMF parser: vertex index out of range");
}
}
// Edges
// (not supported yet)
// Vertices
for (uint32_t i = 0; i < numVertices; i++)
{
float x = f.Read<float>();
float y = f.Read<float>();
float z = f.Read<float>();
//printf("%f %f %f\n", vertexX, vertexY, vertexZ);
currentMesh->points[i] = {x, y, z};
}
// Bounding box
{
float xMin = f.Read<float>();
float yMin = f.Read<float>();
float zMin = f.Read<float>();
float xMax = f.Read<float>();
float yMax = f.Read<float>();
float zMax = f.Read<float>();
uint32_t emptyFlag = f.Read<uint32_t>();
currentMesh->bBox.min = {xMin, yMin, zMin};
currentMesh->bBox.max = {xMax, yMax, zMax};
currentMesh->bBox.isEmpty = emptyFlag? kQ3True: kQ3False;
//printf("%f %f %f - %f %f %f (empty? %d)\n", xMin, yMin, zMin, xMax, yMax, zMax, emptyFlag);
}
}
void Q3MetaFileParser::Parse_atar(uint32_t chunkSize)
{
Assert(chunkSize >= 20, "Illegal atar size");
Assert(currentMesh, "no current mesh");
uint32_t attributeType = f.Read<uint32_t>();
Assert(0 == f.Read<uint32_t>(), "expected zero here");
uint32_t positionOfArray = f.Read<uint32_t>();
uint32_t positionInArray = f.Read<uint32_t>(); // what's that?
uint32_t attributeUseFlag = f.Read<uint32_t>();
Assert(attributeType >= 1 && attributeType < kQ3AttributeTypeNumTypes, "illegal attribute type");
Assert(positionOfArray <= 2, "illegal position of array");
Assert(attributeUseFlag <= 1, "unrecognized attribute use flag");
bool isTriangleAttribute = positionOfArray == 0;
bool isVertexAttribute = positionOfArray == 2;
Assert(isTriangleAttribute || isVertexAttribute, "only face or vertex attributes are supported");
if (isVertexAttribute &&
(attributeType == kQ3AttributeTypeShadingUV || attributeType == kQ3AttributeTypeSurfaceUV))
{
printf("vertex UVs");
Assert(!currentMesh->vertexUVs, "current mesh already had a vertex UV array");
currentMesh->vertexUVs = __Q3Alloc<TQ3Param2D>(currentMesh->numPoints, 'TMuv');
for (int i = 0; i < currentMesh->numPoints; i++)
{
float u = f.Read<float>();
float v = f.Read<float>();
currentMesh->vertexUVs[i] = {u, 1-v};
}
}
else if (isVertexAttribute && attributeType == kQ3AttributeTypeNormal)
{
printf("vertex normals");
Assert(positionInArray == 0, "PIA must be 0 for normals");
Assert(!currentMesh->vertexNormals, "current mesh already had a vertex normal array");
currentMesh->vertexNormals = __Q3Alloc<TQ3Vector3D>(currentMesh->numPoints, 'TMvn');
currentMesh->hasVertexNormals = true;
for (int i = 0; i < currentMesh->numPoints; i++)
{
currentMesh->vertexNormals[i].x = f.Read<float>();
currentMesh->vertexNormals[i].y = f.Read<float>();
currentMesh->vertexNormals[i].z = f.Read<float>();
}
}
else if (isVertexAttribute && attributeType == kQ3AttributeTypeDiffuseColor) // used in Bugdom's Global_Models2.3dmf
{
printf("vertex diffuse");
// Assert(positionInArray == 0, "PIA must be 0 for colors");
Assert(!currentMesh->vertexColors, "current mesh already had a vertex color array");
currentMesh->vertexColors = __Q3Alloc<TQ3ColorRGBA>(currentMesh->numPoints, 'TMvc');
currentMesh->hasVertexColors = true;
for (int i = 0; i < currentMesh->numPoints; i++)
{
currentMesh->vertexColors[i].r = f.Read<float>();
currentMesh->vertexColors[i].g = f.Read<float>();
currentMesh->vertexColors[i].b = f.Read<float>();
currentMesh->vertexColors[i].a = 1.0f;
}
}
else if (isTriangleAttribute && attributeType == kQ3AttributeTypeNormal) // face normals
{
printf("face normals (ignore)");
f.Skip(currentMesh->numTriangles * 3 * 4);
}
else
{
Assert(false, "unsupported combo");
}
}
TQ3Pixmap* Q3MetaFileParser::ParsePixmap(uint32_t chunkType, uint32_t chunkSize)
{
size_t chunkHeaderSize = chunkType == 'txmm'? 8*4: 7*4;
Assert(chunkSize >= chunkHeaderSize, "incorrect chunk header size");
uint32_t pixelType;
uint32_t bitOrder;
uint32_t byteOrder;
uint32_t width;
uint32_t height;
uint32_t rowBytes;
if (chunkType == 'txmm')
{
uint32_t useMipmapping = f.Read<uint32_t>();
pixelType = f.Read<uint32_t>();
bitOrder = f.Read<uint32_t>();
byteOrder = f.Read<uint32_t>();
width = f.Read<uint32_t>();
height = f.Read<uint32_t>();
rowBytes = f.Read<uint32_t>();
uint32_t offset = f.Read<uint32_t>();
Assert(!useMipmapping, "mipmapping not supported");
Assert(offset == 0, "unsupported texture offset");
}
else if (chunkType == 'txpm')
{
width = f.Read<uint32_t>();
height = f.Read<uint32_t>();
rowBytes = f.Read<uint32_t>();
f.Skip(4); //pixelSize = f.Read<uint32_t>();
pixelType = f.Read<uint32_t>();
bitOrder = f.Read<uint32_t>();
byteOrder = f.Read<uint32_t>();
}
else
{
Assert(false, "ParsePixmap: Illegal chunkType");
return nullptr;
}
uint32_t imageSize = rowBytes * height;
if ((imageSize & 3) != 0)
imageSize = (imageSize & 0xFFFFFFFC) + 4;
Assert(chunkSize == chunkHeaderSize + imageSize, "incorrect chunk size");
Assert(bitOrder == kQ3EndianBig, "unsupported bit order");
#if POMME_DEBUG_3DMF
printf("%d*%d rb=%d", width, height, rowBytes);
switch (pixelType)
{
case kQ3PixelTypeRGB32: printf(" RGB32"); break;
case kQ3PixelTypeARGB32: printf(" ARGB32"); break;
case kQ3PixelTypeRGB16: printf(" RGB16"); break;
case kQ3PixelTypeARGB16: printf(" ARGB16"); break;
case kQ3PixelTypeRGB16_565: printf(" RGB16_565"); break;
case kQ3PixelTypeRGB24: printf(" RGB24"); break;
case kQ3PixelTypeRGBA32: printf(" RGBA32"); break;
default: printf(" UnknownPixelType"); break;
}
#endif
// Find bytes per pixel
int bytesPerPixel = 0;
if (pixelType == kQ3PixelTypeRGB16 || pixelType == kQ3PixelTypeARGB16)
bytesPerPixel = 2;
else if (pixelType == kQ3PixelTypeRGB32 || pixelType == kQ3PixelTypeARGB32)
bytesPerPixel = 4;
else
Assert(false, "unrecognized pixel type");
int trimmedRowBytes = bytesPerPixel * width;
TQ3Pixmap* pixmap = __Q3Alloc<TQ3Pixmap>(1, 'PXMP');
pixmap->pixelType = pixelType;
pixmap->bitOrder = bitOrder;
pixmap->byteOrder = byteOrder;
pixmap->width = width;
pixmap->height = height;
pixmap->pixelSize = bytesPerPixel * 8;
pixmap->rowBytes = trimmedRowBytes;
pixmap->image = __Q3Alloc<uint8_t>(trimmedRowBytes * height, 'IMAG');
// Trim padding at end of rows
for (uint32_t y = 0; y < height; y++)
{
f.Read((Ptr) pixmap->image + y*pixmap->rowBytes, pixmap->rowBytes);
f.Skip(rowBytes - width * bytesPerPixel);
}
// Convert to native endianness (especially to avoid breaking 16-bit 1-5-5-5 ARGB textures)
if (byteOrder != kQ3EndianNative)
{
ByteswapInts(bytesPerPixel, width*height, pixmap->image);
pixmap->byteOrder = kQ3EndianNative;
}
Q3Pixmap_ApplyEdgePadding(pixmap);
return pixmap;
}
TQ3TextureShader& Q3MetaFileParser::GetCurrentTextureShader()
{
Assert(metaFile.numTextures > 0, "txmm/txpm: no txsu opened");
return metaFile.textures[metaFile.numTextures - 1];
}