mirror of
https://github.com/jorio/Pomme.git
synced 2024-05-28 12:41:36 +00:00
504 lines
14 KiB
C++
504 lines
14 KiB
C++
#include "QD3D.h"
|
|
#include "PommeDebug.h"
|
|
#include "Pomme.h"
|
|
#include "3DMFInternal.h"
|
|
|
|
// Comment out the line below to get debug output from the 3DMF parser on stdout
|
|
#define printf(...) do{}while(0)
|
|
|
|
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");
|
|
// Assert(containerEnd == 0, "stray endg");
|
|
// containerEnd = f.Tell(); // force while loop to stop
|
|
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
|
|
Assert(chunkSize == 0, "illegal txsu size");
|
|
break;
|
|
|
|
case 'txmm': // MipmapTexture
|
|
{
|
|
uint32_t internalTextureID;
|
|
if (knownTextures.find(chunkOffset) != knownTextures.end())
|
|
{
|
|
printf("Texture already seen!");
|
|
internalTextureID = knownTextures[chunkOffset];
|
|
f.Skip(chunkSize);
|
|
}
|
|
else
|
|
{
|
|
internalTextureID = Parse_txmm(chunkSize);
|
|
knownTextures[chunkOffset] = internalTextureID;
|
|
}
|
|
|
|
if (currentMesh)
|
|
{
|
|
Assert(!currentMesh->hasTexture, "txmm: current mesh already has a texture");
|
|
currentMesh->internalTextureID = internalTextureID;
|
|
currentMesh->hasTexture = true;
|
|
}
|
|
|
|
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));
|
|
auto jumpBackTo = f.Tell();
|
|
f.Goto(referenceTOC.at(target));
|
|
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>();
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
|
|
// 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");
|
|
|
|
currentMesh = Q3TriMeshData_New(numTriangles, numVertices);
|
|
|
|
__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
|
|
{
|
|
static_assert(sizeof(TQ3TriMeshTriangleData::pointIndices[0]) == 2);
|
|
Assert(false, "Meshes exceeding 65535 vertices are not supported");
|
|
//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)
|
|
{
|
|
printf("vertex UVs");
|
|
Assert(currentMesh->vertexUVs, "current mesh has no vertex UV array");
|
|
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 has no vertex normal array");
|
|
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
|
|
{
|
|
Assert(false, "per-vertex diffuse color not supported in Nanosaur");
|
|
#if 0
|
|
printf("vertex diffuse");
|
|
// Assert(positionInArray == 0, "PIA must be 0 for colors");
|
|
Assert(currentMesh->vertexNormals, "current mesh has no vertex color array");
|
|
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>();
|
|
}
|
|
#endif
|
|
}
|
|
else if (isTriangleAttribute && attributeType == kQ3AttributeTypeNormal) // face normals
|
|
{
|
|
printf("face normals (ignore)");
|
|
f.Skip(currentMesh->numTriangles * 3 * 4);
|
|
}
|
|
else
|
|
{
|
|
Assert(false, "unsupported combo");
|
|
}
|
|
}
|
|
|
|
uint32_t Q3MetaFileParser::Parse_txmm(uint32_t chunkSize)
|
|
{
|
|
Assert(chunkSize >= 8*4, "incorrect chunk header size");
|
|
|
|
uint32_t useMipmapping = f.Read<uint32_t>();
|
|
uint32_t pixelType = f.Read<uint32_t>();
|
|
uint32_t bitOrder = f.Read<uint32_t>();
|
|
uint32_t byteOrder = f.Read<uint32_t>();
|
|
uint32_t width = f.Read<uint32_t>();
|
|
uint32_t height = f.Read<uint32_t>();
|
|
uint32_t rowBytes = f.Read<uint32_t>();
|
|
uint32_t offset = f.Read<uint32_t>();
|
|
|
|
uint32_t imageSize = rowBytes * height;
|
|
if ((imageSize & 3) != 0)
|
|
imageSize = (imageSize & 0xFFFFFFFC) + 4;
|
|
|
|
Assert(chunkSize == 8*4 + imageSize, "incorrect chunk size");
|
|
|
|
|
|
Assert(!useMipmapping, "mipmapping not supported");
|
|
printf("%d*%d rb=%d", width, height, rowBytes);
|
|
|
|
static const char* pixelTypeDescriptions[] = { "RGB32", "ARGB32", "RGB16", "ARGB16", "RGB16_565", "RGB24" };
|
|
if (pixelType < kQ3PixelTypeRGB24)
|
|
printf(" %s", pixelTypeDescriptions[pixelType]);
|
|
else
|
|
printf(" UNKNOWN_PIXELTYPE");
|
|
|
|
Assert(offset == 0, "unsupported texture offset");
|
|
Assert(bitOrder == kQ3EndianBig, "unsupported bit order");
|
|
|
|
// 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;
|
|
|
|
|
|
|
|
uint32_t newTextureID = metaFile.numTextures;
|
|
|
|
__Q3EnlargeArray<TQ3Pixmap*>(metaFile.textures, metaFile.numTextures, 'TLST');
|
|
|
|
metaFile.textures[newTextureID] = __Q3Alloc<TQ3Pixmap>(1, 'PXMP');
|
|
TQ3Pixmap& texture = *metaFile.textures[newTextureID];
|
|
|
|
texture.glTextureName = 0;
|
|
texture.pixelType = pixelType;
|
|
texture.bitOrder = bitOrder;
|
|
texture.byteOrder = byteOrder;
|
|
texture.width = width;
|
|
texture.height = height;
|
|
texture.pixelSize = bytesPerPixel * 8;
|
|
texture.rowBytes = trimmedRowBytes;
|
|
texture.image = __Q3Alloc<uint8_t>(trimmedRowBytes * height, 'IMAG');
|
|
|
|
// Trim padding at end of rows
|
|
for (uint32_t y = 0; y < height; y++)
|
|
{
|
|
f.Read((Ptr) texture.image + y*texture.rowBytes, texture.rowBytes);
|
|
f.Skip(rowBytes - width * bytesPerPixel);
|
|
}
|
|
|
|
// Make every pixel little-endian (especially to avoid breaking 16-bit 1-5-5-5 ARGB textures)
|
|
if (byteOrder == kQ3EndianBig)
|
|
{
|
|
ByteswapInts(bytesPerPixel, width*height, texture.image);
|
|
texture.byteOrder = kQ3EndianLittle;
|
|
}
|
|
|
|
|
|
Q3Pixmap_ApplyEdgePadding(&texture);
|
|
|
|
return newTextureID;
|
|
}
|