Import QD3D/3DMF code from Nanosaur's new renderer

This commit is contained in:
Iliyas Jorio 2021-02-18 21:12:34 +01:00
parent ad669a3fd4
commit 00b4a7e72b
8 changed files with 2407 additions and 3 deletions

View File

@ -51,13 +51,29 @@ set(POMME_SOURCES
${POMME_SRCDIR}/Utilities/StringUtils.h
${POMME_SRCDIR}/Utilities/structpack.cpp
${POMME_SRCDIR}/Utilities/structpack.h
${POMME_SRCDIR}/Video/Cinepak.cpp
${POMME_SRCDIR}/Video/Cinepak.h
${POMME_SRCDIR}/Video/moov.cpp
$<$<BOOL:${WIN32}>:${POMME_SRCDIR}/Platform/Windows/PommeWindows.cpp>
$<$<BOOL:${WIN32}>:${POMME_SRCDIR}/Platform/Windows/PommeWindows.h>
)
if (NOT(POMME_NO_VIDEO))
list(APPEND POMME_SOURCES
${POMME_SRCDIR}/Video/Cinepak.cpp
${POMME_SRCDIR}/Video/Cinepak.h
${POMME_SRCDIR}/Video/moov.cpp
)
endif()
if (NOT(POMME_NO_QD3D))
list(APPEND POMME_SOURCES
${POMME_SRCDIR}/QD3D/3DMFInternal.h
${POMME_SRCDIR}/QD3D/3DMFParser.cpp
${POMME_SRCDIR}/QD3D/QD3D.cpp
${POMME_SRCDIR}/QD3D/QD3D.h
${POMME_SRCDIR}/QD3D/QD3DMath.cpp
${POMME_SRCDIR}/QD3D/QD3DMath.h
)
endif()
add_library(${PROJECT_NAME} ${POMME_SOURCES})
find_package(SDL2 REQUIRED)
@ -79,10 +95,12 @@ if(MSVC)
target_compile_options(${PROJECT_NAME} PRIVATE
/EHa
/W4
/wd4068 # ignore unrecognized pragmas
/wd4100 # unreferenced formal parameter
/wd4201 # nonstandard extension
/wd4244 # conversion from double to float
/wd4458 # declaration of variable hides class member
/MP
)
else()
target_compile_options(${PROJECT_NAME} PRIVATE
@ -91,6 +109,7 @@ else()
-Wshadow
-Wno-multichar
-Wno-unused-parameter
-Wno-unknown-pragmas
-fexceptions
)
endif()

View File

@ -74,6 +74,45 @@ The following portions are derivate works from
--------
The following portions are derivative works from parts of
[Quesa](https://github.com/jwwalker/Quesa) by the Quesa Developers
(BSD license):
- QD3DMath.h
- QD3DMath.cpp
> Copyright (c) 1999-2020, Quesa Developers. All rights reserved.
>
> For the current release of Quesa, please see:
> https://github.com/jwwalker/Quesa
>
> Redistribution and use in source and binary forms, with or without
> modification, are permitted provided that the following conditions are met:
>
> - Redistributions of source code must retain the above copyright notice, this
> list of conditions and the following disclaimer.
>
> - Redistributions in binary form must reproduce the above copyright notice,
> this list of conditions and the following disclaimer in the documentation
> and/or other materials provided with the distribution.
>
> - Neither the name of Quesa nor the names of its contributors may be used to
> endorse or promote products derived from this software without specific
> prior written permission.
>
> THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
> AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
> IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
> ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
> LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
> CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
> SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
> INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
> CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
> ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
> POSSIBILITY OF SUCH DAMAGE.
--------
Portions copied from [gulrak/filesystem](https://github.com/gulrak/filesystem)
by Steffen Schümann (MIT license):
- filesystem_implementation.hpp

157
src/QD3D/3DMFInternal.h Normal file
View File

@ -0,0 +1,157 @@
#pragma once
#ifdef __cplusplus
#include <vector>
#include <istream>
#include <map>
#include <cstring>
#include <QD3D/QD3D.h>
#include "Utilities/BigEndianIStream.h"
class Q3MetaFileParser
{
public:
Q3MetaFileParser(std::istream& input, TQ3MetaFile& dest);
void Parse3DMF();
private:
// Returns fourcc of parsed chunk
uint32_t Parse1Chunk();
// Parse trimesh chunk
void Parse_tmsh(uint32_t chunkSize);
// Parse attribute array chunk
void Parse_atar(uint32_t chunkSize);
// Parse mipmap texture chunk
uint32_t Parse_txmm(uint32_t chunkSize);
TQ3MetaFile& metaFile;
std::istream& baseStream;
Pomme::BigEndianIStream f;
int currentDepth = 0;
TQ3TriMeshData* currentMesh;
std::map<uint32_t, uint64_t> referenceTOC;
std::map<std::streampos, uint32_t> knownTextures;
};
//-----------------------------------------------------------------------------
// Memory allocator
#define ALLOCATOR_HEADER_BYTES 16
struct __Q3AllocatorCookie
{
uint32_t classID;
size_t blockSize; // including header cookie
};
static_assert(sizeof(__Q3AllocatorCookie) <= ALLOCATOR_HEADER_BYTES);
template<typename T>
static T* __Q3Alloc(size_t count, uint32_t classID)
{
size_t totalBytes = ALLOCATOR_HEADER_BYTES + count*sizeof(T);
uint8_t* block = (uint8_t*) calloc(totalBytes, 1);
__Q3AllocatorCookie* cookie = (__Q3AllocatorCookie*) block;
cookie->classID = classID;
cookie->blockSize = totalBytes;
return (T*) (block + ALLOCATOR_HEADER_BYTES);
}
static __Q3AllocatorCookie* __Q3GetCookie(const void* sourcePayload, uint32_t classID)
{
if (!sourcePayload)
throw std::runtime_error("__Q3GetCookie: got null pointer");
uint8_t* block = ((uint8_t*) sourcePayload) - ALLOCATOR_HEADER_BYTES;
__Q3AllocatorCookie* cookie = (__Q3AllocatorCookie*) block;
if (classID != cookie->classID)
throw std::runtime_error("__Q3GetCookie: incorrect cookie");
return cookie;
}
static void __Q3Dispose(void* object, uint32_t classID)
{
if (!object)
return;
auto cookie = __Q3GetCookie(object, classID);
memset(cookie, 0, cookie->blockSize);
cookie->classID = 'DEAD';
free(cookie);
}
template<typename T>
static T* __Q3Realloc(T* sourcePayload, size_t minCount, uint32_t classID)
{
if (!sourcePayload)
{
return __Q3Alloc<T>(minCount * 2, classID);
}
__Q3AllocatorCookie* sourceCookie = __Q3GetCookie(sourcePayload, classID);
size_t currentBytes = sourceCookie->blockSize - ALLOCATOR_HEADER_BYTES;
size_t wantedBytes = minCount * sizeof(T);
if (wantedBytes < currentBytes)
{
return sourcePayload;
}
T* newPayload = __Q3Alloc<T>(minCount * 2, classID);
memcpy(newPayload, sourcePayload, currentBytes);
__Q3Dispose(sourcePayload, classID);
return newPayload;
}
template<typename T>
static void __Q3EnlargeArray(T*& sourcePayload, int& size, uint32_t classID)
{
size++;
sourcePayload = __Q3Realloc(sourcePayload, size, classID);
}
template<typename T>
static T* __Q3Copy(const T* sourcePayload, uint32_t classID)
{
if (!sourcePayload)
return nullptr;
__Q3AllocatorCookie* sourceCookie = __Q3GetCookie(sourcePayload, classID);
uint8_t* block = (uint8_t*) calloc(sourceCookie->blockSize, 1);
memcpy(block, sourceCookie, sourceCookie->blockSize);
return (T*) (block + ALLOCATOR_HEADER_BYTES);
}
template<typename T>
static void __Q3DisposeArray(T** arrayPtr, uint32_t classID)
{
if (*arrayPtr)
{
__Q3Dispose(*arrayPtr, classID);
*arrayPtr = nullptr;
}
}
#endif // __cplusplus

503
src/QD3D/3DMFParser.cpp Normal file
View File

@ -0,0 +1,503 @@
#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;
}

342
src/QD3D/QD3D.cpp Normal file
View File

@ -0,0 +1,342 @@
#include "Pomme.h"
#include "PommeFiles.h"
#include "QD3D.h"
#include "3DMFInternal.h"
#define EDGE_PADDING_REPEAT 8
static void Assert(bool condition, const char* message)
{
if (!condition)
{
throw std::runtime_error(message);
}
}
TQ3MetaFile* Q3MetaFile_Load3DMF(const FSSpec* spec)
{
short refNum;
OSErr err;
err = FSpOpenDF(spec, fsRdPerm, &refNum);
if (err != noErr)
return nullptr;
TQ3MetaFile* metaFile = __Q3Alloc<TQ3MetaFile>(1, '3DMF');
auto& fileStream = Pomme::Files::GetStream(refNum);
Q3MetaFileParser(fileStream, *metaFile).Parse3DMF();
FSClose(refNum);
return metaFile;
}
void Q3MetaFile_Dispose(TQ3MetaFile* metaFile)
{
__Q3GetCookie(metaFile, '3DMF');
for (int i = 0; i < metaFile->numTextures; i++)
{
if (metaFile->textures[i]->glTextureName)
glDeleteTextures(1, &metaFile->textures[i]->glTextureName);
Q3Pixmap_Dispose(metaFile->textures[i]);
}
for (int i = 0; i < metaFile->numMeshes; i++)
Q3TriMeshData_Dispose(metaFile->meshes[i]);
for (int i = 0; i < metaFile->numTopLevelGroups; i++)
__Q3Dispose(metaFile->topLevelGroups[i].meshes, 'GMSH');
__Q3Dispose(metaFile->textures, 'TLST');
__Q3Dispose(metaFile->meshes, 'MLST');
__Q3Dispose(metaFile->topLevelGroups, 'GLST');
__Q3Dispose(metaFile, '3DMF');
}
#pragma mark -
template<typename T>
static void _EdgePadding(
T* const pixelData,
const int width,
const int height,
const int rowBytes,
const T alphaMask)
{
Assert(rowBytes % sizeof(T) == 0, "EdgePadding: rowBytes is not a multiple of pixel bytesize");
const int rowAdvance = rowBytes / sizeof(T);
T* const firstRow = pixelData;
T* const lastRow = firstRow + (height-1) * rowAdvance;
for (int i = 0; i < EDGE_PADDING_REPEAT; i++)
{
// Dilate horizontally, row by row
for (T* row = firstRow; row <= lastRow; row += rowAdvance)
{
// Expand east
for (int x = 0; x < width-1; x++)
if (!row[x])
row[x] = row[x+1] & ~alphaMask;
// Expand west
for (int x = width-1; x > 0; x--)
if (!row[x])
row[x] = row[x-1] & ~alphaMask;
}
// Dilate vertically, column by column
for (int x = 0; x < width; x++)
{
// Expand south
for (T* row = firstRow; row < lastRow; row += rowAdvance)
if (!row[x])
row[x] = row[x + rowAdvance] & ~alphaMask;
// Expand north
for (T* row = lastRow; row > firstRow; row -= rowAdvance)
if (!row[x])
row[x] = row[x - rowAdvance] & ~alphaMask;
}
}
}
void Q3Pixmap_ApplyEdgePadding(TQ3Pixmap* pm)
{
switch (pm->pixelType)
{
case kQ3PixelTypeARGB16:
Assert(pm->rowBytes >= pm->width * 2, "EdgePadding ARGB16: incorrect rowBytes");
_EdgePadding<uint16_t>(
(uint16_t *) pm->image,
pm->width,
pm->height,
pm->rowBytes,
pm->byteOrder==kQ3EndianBig? 0x0080: 0x8000);
break;
case kQ3PixelTypeARGB32:
Assert(pm->rowBytes >= pm->width * 4, "EdgePadding ARGB32: incorrect rowBytes");
_EdgePadding<uint32_t>(
(uint32_t *) pm->image,
pm->width,
pm->height,
pm->rowBytes,
pm->byteOrder==kQ3EndianBig? 0x000000FF: 0xFF000000);
break;
case kQ3PixelTypeRGB16:
case kQ3PixelTypeRGB16_565:
case kQ3PixelTypeRGB24:
case kQ3PixelTypeRGB32:
// Unnecessary to apply edge padding here because there's no alpha channel
break;
default:
Assert(false, "EdgePadding: pixel type unsupported");
break;
}
}
void Q3Pixmap_Dispose(TQ3Pixmap* pixmap)
{
__Q3Dispose(pixmap->image, 'IMAG');
__Q3Dispose(pixmap, 'PXMP');
}
#pragma mark -
TQ3TriMeshData* Q3TriMeshData_New(int numTriangles, int numPoints)
{
TQ3TriMeshData* mesh = __Q3Alloc<TQ3TriMeshData>(1, 'MESH');
mesh->numTriangles = numTriangles;
mesh->numPoints = numPoints;
mesh->points = __Q3Alloc<TQ3Point3D>(numPoints, 'TMpt');
mesh->triangles = __Q3Alloc<TQ3TriMeshTriangleData>(numTriangles, 'TMtr');
mesh->vertexNormals = __Q3Alloc<TQ3Vector3D>(numPoints, 'TMvn');
mesh->vertexUVs = __Q3Alloc<TQ3Param2D>(numPoints, 'TMuv');
mesh->vertexColors = nullptr;
mesh->diffuseColor = {1, 1, 1, 1};
mesh->hasTexture = false;
mesh->textureHasTransparency = false;
for (int i = 0; i < numPoints; i++)
{
mesh->vertexNormals[i] = {0, 1, 0};
mesh->vertexUVs[i] = {.5f, .5f};
// triMeshData->vertexColors[i] = {1, 1, 1, 1};
}
return mesh;
}
TQ3TriMeshData* Q3TriMeshData_Duplicate(const TQ3TriMeshData* source)
{
TQ3TriMeshData* mesh = __Q3Copy(source, 'MESH');
mesh->points = __Q3Copy(source->points, 'TMpt');
mesh->triangles = __Q3Copy(source->triangles, 'TMtr');
mesh->vertexNormals = __Q3Copy(source->vertexNormals, 'TMvn');
mesh->vertexColors = __Q3Copy(source->vertexColors, 'TMvc');
mesh->vertexUVs = __Q3Copy(source->vertexUVs, 'TMuv');
return mesh;
}
void Q3TriMeshData_Dispose(TQ3TriMeshData* mesh)
{
__Q3DisposeArray(&mesh->points, 'TMpt');
__Q3DisposeArray(&mesh->triangles, 'TMtr');
__Q3DisposeArray(&mesh->vertexNormals, 'TMvn');
__Q3DisposeArray(&mesh->vertexColors, 'TMvc');
__Q3DisposeArray(&mesh->vertexUVs, 'TMuv');
__Q3Dispose(mesh, 'MESH');
}
void Q3TriMeshData_SubdivideTriangles(TQ3TriMeshData* mesh)
{
struct Edge
{
int a;
int b;
int midpoint;
};
std::map<uint32_t, Edge> edges;
const int oldNumTriangles = mesh->numTriangles;
const int oldNumPoints = mesh->numPoints;
auto triangleEdges = new Edge*[oldNumTriangles * 3];
int numDistinctEdges = 0;
//-------------------------------------------------------------------------
// Prep edge records (so we have edge count too)
for (int t = 0; t < mesh->numTriangles; t++)
{
auto& triangle = mesh->triangles[t];
for (int e = 0; e < 3; e++)
{
int edgeP0 = triangle.pointIndices[e];
int edgeP1 = triangle.pointIndices[(e+1) % 3];
if (edgeP0 > edgeP1)
{
int swap = edgeP0;
edgeP0 = edgeP1;
edgeP1 = swap;
}
uint32_t edgeHash = (edgeP0 << 16) | edgeP1;
auto foundEdge = edges.find(edgeHash);
if (foundEdge != edges.end())
{
triangleEdges[t*3 + e] = &foundEdge->second;
}
else
{
edges[edgeHash] = { edgeP0, edgeP1, -1 };
triangleEdges[t*3 + e] = &edges[edgeHash];
numDistinctEdges++;
}
}
}
//-------------------------------------------------------------------------
// Reallocate mesh
int numPointsWritten = oldNumPoints;
int numTrianglesWritten = oldNumTriangles;
mesh->numTriangles *= 4;
mesh->numPoints += numDistinctEdges;
mesh->points = __Q3Realloc(mesh->points, mesh->numPoints, 'TMpt');
mesh->vertexUVs = __Q3Realloc(mesh->vertexUVs, mesh->numPoints, 'TMuv');
mesh->vertexNormals = __Q3Realloc(mesh->vertexNormals, mesh->numPoints, 'TMvn');
if (mesh->vertexColors)
mesh->vertexColors = __Q3Realloc(mesh->vertexColors, mesh->numPoints, 'TMvc');
mesh->triangles = __Q3Realloc(mesh->triangles, mesh->numTriangles, 'TMtr');
//-------------------------------------------------------------------------
// Create edge midpoints
for (auto& edgeKV: edges)
{
auto& edge = edgeKV.second;
edge.midpoint = numPointsWritten;
numPointsWritten++;
int M = edge.midpoint;
int A = edge.a;
int B = edge.b;
mesh->points[M].x = (mesh->points[A].x + mesh->points[B].x) / 2.0f;
mesh->points[M].y = (mesh->points[A].y + mesh->points[B].y) / 2.0f;
mesh->points[M].z = (mesh->points[A].z + mesh->points[B].z) / 2.0f;
mesh->vertexNormals[M].x = (mesh->vertexNormals[A].x + mesh->vertexNormals[B].x) / 2.0f;
mesh->vertexNormals[M].y = (mesh->vertexNormals[A].y + mesh->vertexNormals[B].y) / 2.0f;
mesh->vertexNormals[M].z = (mesh->vertexNormals[A].z + mesh->vertexNormals[B].z) / 2.0f;
mesh->vertexUVs[M].u = (mesh->vertexUVs[A].u + mesh->vertexUVs[B].u) / 2.0f;
mesh->vertexUVs[M].v = (mesh->vertexUVs[A].v + mesh->vertexUVs[B].v) / 2.0f;
if (mesh->vertexColors)
{
mesh->vertexColors[M].r = (mesh->vertexColors[A].r + mesh->vertexColors[B].r) / 2.0f;
mesh->vertexColors[M].g = (mesh->vertexColors[A].g + mesh->vertexColors[B].g) / 2.0f;
mesh->vertexColors[M].b = (mesh->vertexColors[A].b + mesh->vertexColors[B].b) / 2.0f;
mesh->vertexColors[M].a = (mesh->vertexColors[A].a + mesh->vertexColors[B].a) / 2.0f;
}
}
//-------------------------------------------------------------------------
// Create new triangles
for (int t = 0; t < oldNumTriangles; t++)
{
auto& triangle = mesh->triangles[t];
/*
Original triangle: ABC.
Create 4 new triangles.
B
+
/ \
/ \
/ \
>+ . . . +<
/ . . \
/ . . \
/ . . \
+-------+-------+
A ^ C
*/
uint16_t A = triangle.pointIndices[0];
uint16_t B = triangle.pointIndices[1];
uint16_t C = triangle.pointIndices[2];
uint16_t A2B = triangleEdges[t*3 + 0]->midpoint;
uint16_t B2C = triangleEdges[t*3 + 1]->midpoint;
uint16_t C2A = triangleEdges[t*3 + 2]->midpoint;
mesh->triangles[numTrianglesWritten++] = {{A2B, B, B2C} };
mesh->triangles[numTrianglesWritten++] = {{B2C, C, C2A} };
mesh->triangles[numTrianglesWritten++] = {{C2A, A, A2B} };
mesh->triangles[t] = {{A2B, B2C, C2A} }; // replace original triangle
}
//-------------------------------------------------------------------------
// Make sure we didn't break the mesh
delete[] triangleEdges;
Assert(numTrianglesWritten == 4*oldNumTriangles, "unexpected number of triangles written");
Assert(numPointsWritten == oldNumPoints + (int)edges.size(), "unexpected number of points written");
}

345
src/QD3D/QD3D.h Normal file
View File

@ -0,0 +1,345 @@
#pragma once
#ifdef __cplusplus
extern "C"
{
#endif
#include "PommeTypes.h"
#include <SDL_opengl.h>
#include <stdbool.h>
typedef float TQ3Float32;
typedef uint32_t TQ3Uns32;
typedef int32_t TQ3Int32;
typedef int32_t TQ3ObjectType;
// junk types
typedef void* TQ3TransformObject;
typedef void* TQ3Object;
typedef void* TQ3GroupPosition;
typedef void* TQ3GeometryObject;
typedef void* TQ3ViewObject;
typedef void* TQ3SurfaceShaderObject;
typedef void* TQ3StorageObject;
typedef void* TQ3ShaderObject;
typedef void* TQ3FileObject;
typedef void* TQ3TriMeshAttributeData;
typedef enum
{
kQ3False = 0,
kQ3True = 1,
kQ3BooleanSize32 = 0xFFFFFFFF
} TQ3Boolean;
typedef enum
{
kQ3Off = 0,
kQ3On = 1,
kQ3SwitchSize32 = 0xFFFFFFFF
} TQ3Switch;
typedef enum
{
kQ3Failure = 0,
kQ3Success = 1,
kQ3StatusSize32 = 0xFFFFFFFF
} TQ3Status;
enum TQ3AttributeTypes
{
kQ3AttributeTypeNone = 0, // N/A
kQ3AttributeTypeSurfaceUV = 1, // TQ3Param2D
kQ3AttributeTypeShadingUV = 2, // TQ3Param2D
kQ3AttributeTypeNormal = 3, // TQ3Vector3D
kQ3AttributeTypeAmbientCoefficient = 4, // float
kQ3AttributeTypeDiffuseColor = 5, // TQ3ColorRGB
kQ3AttributeTypeSpecularColor = 6, // TQ3ColorRGB
kQ3AttributeTypeSpecularControl = 7, // float
kQ3AttributeTypeTransparencyColor = 8, // TQ3ColorRGB
kQ3AttributeTypeSurfaceTangent = 9, // TQ3Tangent2D
kQ3AttributeTypeHighlightState = 10, // TQ3Switch
kQ3AttributeTypeSurfaceShader = 11, // TQ3SurfaceShaderObject
kQ3AttributeTypeEmissiveColor = 12, // TQ3ColorRGB
kQ3AttributeTypeNumTypes = 13, // N/A
kQ3AttributeTypeSize32 = 0xFFFFFFFF
};
typedef enum
{
/// 8 bits for red, green, and blue. High-order byte ignored.
kQ3PixelTypeRGB32 = 0,
/// 8 bits for alpha, red, green, and blue.
kQ3PixelTypeARGB32 = 1,
/// 5 bits for red, green, and blue. High-order bit ignored.
kQ3PixelTypeRGB16 = 2,
/// 1 bit for alpha. 5 bits for red, green, and blue.
kQ3PixelTypeARGB16 = 3,
/// 5 bits for red, 6 bits for green, 5 bits for blue.
kQ3PixelTypeRGB16_565 = 4,
/// 8 bits for red, green, and blue. No alpha byte.
kQ3PixelTypeRGB24 = 5,
kQ3PixelTypeUnknown = 200,
kQ3PixelTypeSize32 = 0xFFFFFFFF
} TQ3PixelType;
typedef enum TQ3InterpolationStyle
{
kQ3InterpolationStyleNone = 0,
kQ3InterpolationStyleVertex = 1,
kQ3InterpolationStylePixel = 2,
kQ3InterpolationSize32 = 0xFFFFFFFF
} TQ3InterpolationStyle;
typedef enum TQ3BackfacingStyle
{
kQ3BackfacingStyleBoth = 0,
kQ3BackfacingStyleRemove = 1,
kQ3BackfacingStyleFlip = 2,
kQ3BackfacingStyleRemoveFront = 3,
kQ3BackfacingStyleSize32 = 0xFFFFFFFF
} TQ3BackfacingStyle;
typedef enum TQ3FillStyle
{
kQ3FillStyleFilled = 0,
kQ3FillStyleEdges = 1,
kQ3FillStylePoints = 2,
kQ3FillStyleSize32 = 0xFFFFFFFF
} TQ3FillStyle;
typedef enum TQ3FogMode
{
kQ3FogModeLinear = 0,
kQ3FogModeExponential = 1,
kQ3FogModeExponentialSquared = 2,
kQ3FogModeAlpha = 3,
kQ3FogModePlaneBasedLinear = 4,
kQ3FogModeSize32 = 0xFFFFFFFF
} TQ3FogMode;
typedef enum
{
kQ3EndianBig = 0,
kQ3EndianLittle = 1,
kQ3EndianSize32 = 0xFFFFFFFF
} TQ3Endian;
typedef struct TQ3Param2D
{
float u;
float v;
} TQ3Param2D;
typedef struct TQ3Point2D
{
float x;
float y;
} TQ3Point2D;
typedef struct TQ3Area
{
TQ3Point2D min;
TQ3Point2D max;
} TQ3Area;
typedef struct TQ3Point3D
{
float x;
float y;
float z;
} TQ3Point3D;
typedef struct TQ3Vector2D
{
float x;
float y;
} TQ3Vector2D;
typedef struct TQ3Vector3D
{
float x;
float y;
float z;
} TQ3Vector3D;
typedef struct TQ3RationalPoint3D
{
float x;
float y;
float w;
} TQ3RationalPoint3D;
typedef struct TQ3RationalPoint4D
{
float x;
float y;
float z;
float w;
} TQ3RationalPoint4D;
typedef struct TQ3ColorARGB
{
float a;
float r;
float g;
float b;
} TQ3ColorARGB;
typedef struct TQ3ColorRGBA
{
float r;
float g;
float b;
float a;
} TQ3ColorRGBA;
typedef struct TQ3ColorRGB
{
float r;
float g;
float b;
} TQ3ColorRGB;
typedef struct TQ3Vertex3D
{
TQ3Point3D point;
} TQ3Vertex3D;
typedef struct TQ3BoundingBox
{
TQ3Point3D min;
TQ3Point3D max;
TQ3Boolean isEmpty;
} TQ3BoundingBox;
typedef struct TQ3Matrix3x3
{
float value[3][3];
} TQ3Matrix3x3;
typedef struct TQ3Matrix4x4
{
float value[4][4];
} TQ3Matrix4x4;
typedef struct TQ3PlaneEquation
{
TQ3Vector3D normal;
float constant;
} TQ3PlaneEquation;
// WARNING: this structure differs from QD3D (indices were originally 32-bit)
typedef struct TQ3TriMeshTriangleData
{
uint16_t pointIndices[3];
} TQ3TriMeshTriangleData;
// This structure differs from QD3D.
typedef struct TQ3TriMeshData
{
int numTriangles;
TQ3TriMeshTriangleData *triangles;
int numPoints;
TQ3Point3D *points;
TQ3Vector3D *vertexNormals;
TQ3Param2D *vertexUVs; // automatically allocated by constructor
TQ3ColorRGBA *vertexColors; // may be null if mesh doesn't need per-vertex colors (Nanosaur never does)
TQ3BoundingBox bBox;
bool hasTexture;
bool textureHasTransparency; // TODO
int internalTextureID;
uint32_t glTextureName;
bool hasVertexColors;
TQ3ColorRGBA diffuseColor;
} TQ3TriMeshData;
// This structure does not exist in QD3D.
typedef struct TQ3TriMeshFlatGroup
{
int numMeshes;
TQ3TriMeshData** meshes;
} TQ3TriMeshFlatGroup;
typedef struct TQ3FogStyleData
{
TQ3Switch state;
TQ3FogMode mode;
float fogStart;
float fogEnd;
float density;
TQ3ColorRGBA color;
} TQ3FogStyleData;
typedef struct TQ3CameraPlacement
{
TQ3Point3D cameraLocation;
TQ3Point3D pointOfInterest;
TQ3Vector3D upVector;
} TQ3CameraPlacement;
// WARNING: this structure differs from QD3D.
typedef struct TQ3Pixmap
{
uint8_t *image;
uint32_t width;
uint32_t height;
uint32_t rowBytes;
uint32_t pixelSize;
uint32_t pixelType;
uint32_t bitOrder;
uint32_t byteOrder;
uint32_t glTextureName;
} TQ3Pixmap;
// WARNING: this structure does not exist in QD3D.
typedef struct TQ3MetaFile
{
int numTextures;
TQ3Pixmap **textures;
int numMeshes;
TQ3TriMeshData **meshes;
int numTopLevelGroups;
TQ3TriMeshFlatGroup *topLevelGroups;
} TQ3MetaFile;
#pragma mark -
TQ3MetaFile* Q3MetaFile_Load3DMF(const FSSpec* spec);
void Q3MetaFile_Dispose(TQ3MetaFile* the3DMFFile);
#pragma mark -
void Q3Pixmap_ApplyEdgePadding(TQ3Pixmap*);
void Q3Pixmap_Dispose(TQ3Pixmap*);
#pragma mark -
TQ3TriMeshData* Q3TriMeshData_New(int numTriangles, int numPoints);
TQ3TriMeshData* Q3TriMeshData_Duplicate(const TQ3TriMeshData* source);
void Q3TriMeshData_Dispose(TQ3TriMeshData*);
void Q3TriMeshData_SubdivideTriangles(TQ3TriMeshData* src);
#ifdef __cplusplus
}
#endif

384
src/QD3D/QD3DMath.cpp Normal file
View File

@ -0,0 +1,384 @@
/*
Adapted from Quesa's math routines.
Original copyright notice below:
Copyright (c) 1999-2020, Quesa Developers. All rights reserved.
For the current release of Quesa, please see:
<https://github.com/jwwalker/Quesa>
Redistribution and use in source and binary forms, with or without modification, are permitted
provided that the following conditions are met:
o Redistributions of source code must retain the above copyright notice, this list of conditions
and the following disclaimer.
o Redistributions in binary form must reproduce the above copyright notice, this list of conditions
and the following disclaimer in the documentation and/or other materials provided with the
distribution.
o Neither the name of Quesa nor the names of its contributors may be used to endorse or promote
products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR
IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT