2021-02-18 20:12:34 +00:00
|
|
|
#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++)
|
2021-06-24 16:52:31 +00:00
|
|
|
Q3Pixmap_Dispose(metaFile->textures[i].pixmap);
|
2021-02-18 20:12:34 +00:00
|
|
|
|
|
|
|
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');
|
|
|
|
|
2021-06-24 16:52:31 +00:00
|
|
|
__Q3Dispose(metaFile->textures, 'TXSU');
|
2021-02-18 20:12:34 +00:00
|
|
|
__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,
|
2022-09-02 22:53:11 +00:00
|
|
|
pm->byteOrder==kQ3EndianNative? 0x8000: 0x0080);
|
2021-02-18 20:12:34 +00:00
|
|
|
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,
|
2022-09-02 22:53:11 +00:00
|
|
|
pm->byteOrder==kQ3EndianNative? 0xFF000000: 0x000000FF);
|
2021-02-18 20:12:34 +00:00
|
|
|
break;
|
|
|
|
|
2023-01-21 13:22:14 +00:00
|
|
|
case kQ3PixelTypeRGBA32:
|
|
|
|
Assert(pm->rowBytes >= pm->width * 4, "EdgePadding RGBA32: incorrect rowBytes");
|
|
|
|
_EdgePadding<uint32_t>(
|
|
|
|
(uint32_t *) pm->image,
|
|
|
|
pm->width,
|
|
|
|
pm->height,
|
|
|
|
pm->rowBytes,
|
|
|
|
pm->byteOrder==kQ3EndianNative? 0x000000FF: 0xFF000000);
|
|
|
|
break;
|
|
|
|
|
2021-02-18 20:12:34 +00:00
|
|
|
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 -
|
|
|
|
|
2021-05-02 13:37:15 +00:00
|
|
|
TQ3TriMeshData* Q3TriMeshData_New(int numTriangles, int numPoints, int featureFlags)
|
2021-02-18 20:12:34 +00:00
|
|
|
{
|
|
|
|
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->diffuseColor = {1, 1, 1, 1};
|
2021-02-21 09:54:57 +00:00
|
|
|
mesh->texturingMode = kQ3TexturingModeOff;
|
|
|
|
mesh->internalTextureID = -1;
|
2021-02-21 14:02:23 +00:00
|
|
|
mesh->bBox = {{0,0,0}, {0,0,0}, kQ3True}; // empty
|
2021-02-18 20:12:34 +00:00
|
|
|
|
2021-05-02 13:37:15 +00:00
|
|
|
if (featureFlags & kQ3TriMeshDataFeatureVertexUVs)
|
2021-02-18 20:12:34 +00:00
|
|
|
{
|
2021-05-02 13:37:15 +00:00
|
|
|
mesh->vertexUVs = __Q3Alloc<TQ3Param2D>(numPoints, 'TMuv');
|
|
|
|
for (int i = 0; i < numPoints; i++)
|
|
|
|
mesh->vertexUVs[i] = {.5f, .5f};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mesh->vertexUVs = nullptr;
|
2021-02-21 14:02:23 +00:00
|
|
|
}
|
|
|
|
|
2021-05-02 13:37:15 +00:00
|
|
|
if (featureFlags & kQ3TriMeshDataFeatureVertexNormals)
|
2021-02-21 14:02:23 +00:00
|
|
|
{
|
2021-05-02 13:37:15 +00:00
|
|
|
mesh->hasVertexNormals = true;
|
|
|
|
mesh->vertexNormals = __Q3Alloc<TQ3Vector3D>(numPoints, 'TMvn');
|
2021-02-21 14:02:23 +00:00
|
|
|
for (int i = 0; i < numPoints; i++)
|
2021-05-02 13:37:15 +00:00
|
|
|
mesh->vertexNormals[i] = {0, 1, 0};
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
mesh->vertexNormals = nullptr;
|
|
|
|
mesh->hasVertexNormals = false;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (featureFlags & kQ3TriMeshDataFeatureVertexColors)
|
|
|
|
{
|
2021-05-02 13:36:55 +00:00
|
|
|
mesh->hasVertexColors = true;
|
2021-05-02 13:37:15 +00:00
|
|
|
mesh->vertexColors = __Q3Alloc<TQ3ColorRGBA>(numPoints, 'TMvc');
|
|
|
|
for (int i = 0; i < numPoints; i++)
|
|
|
|
mesh->vertexColors[i] = {1, 1, 1, 1};
|
2021-05-02 13:36:55 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2021-05-02 13:37:15 +00:00
|
|
|
mesh->vertexColors = nullptr;
|
2021-05-02 13:36:55 +00:00
|
|
|
mesh->hasVertexColors = false;
|
2021-02-18 20:12:34 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
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
|
|
|
|
{
|
2023-10-19 17:21:59 +00:00
|
|
|
uint32_t a;
|
|
|
|
uint32_t b;
|
|
|
|
uint32_t midpoint;
|
2021-02-18 20:12:34 +00:00
|
|
|
};
|
|
|
|
|
|
|
|
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++)
|
|
|
|
{
|
2023-10-19 17:21:59 +00:00
|
|
|
uint32_t edgeP0 = triangle.pointIndices[e];
|
|
|
|
uint32_t edgeP1 = triangle.pointIndices[(e+1) % 3];
|
2021-02-18 20:12:34 +00:00
|
|
|
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
|
|
|
|
{
|
2023-10-19 17:21:59 +00:00
|
|
|
edges[edgeHash] = { edgeP0, edgeP1, (uint32_t) -1 };
|
2021-02-18 20:12:34 +00:00
|
|
|
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++;
|
|
|
|
|
2023-10-19 17:21:59 +00:00
|
|
|
uint32_t M = edge.midpoint;
|
|
|
|
uint32_t A = edge.a;
|
|
|
|
uint32_t B = edge.b;
|
2021-02-18 20:12:34 +00:00
|
|
|
|
|
|
|
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
|
|
|
|
*/
|
|
|
|
|
2023-10-19 17:21:59 +00:00
|
|
|
uint32_t A = triangle.pointIndices[0];
|
|
|
|
uint32_t B = triangle.pointIndices[1];
|
|
|
|
uint32_t C = triangle.pointIndices[2];
|
|
|
|
uint32_t A2B = triangleEdges[t*3 + 0]->midpoint;
|
|
|
|
uint32_t B2C = triangleEdges[t*3 + 1]->midpoint;
|
|
|
|
uint32_t C2A = triangleEdges[t*3 + 2]->midpoint;
|
2021-02-18 20:12:34 +00:00
|
|
|
|
|
|
|
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");
|
|
|
|
}
|