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
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include "QD3DMath.h"
#include <stdexcept>
void Q3Point3D_To3DTransformArray(
const TQ3Point3D *inPoints3D,
const TQ3Matrix4x4 *matrix4x4,
TQ3Point3D *outPoints3D,
TQ3Uns32 numPoints)
{
TQ3Uns32 i;
// In the common case of the last column of the matrix being (0, 0, 0, 1),
// we can avoid some divisions and conditionals inside the loop.
if ( (matrix4x4->value[3][3] == 1.0f) &&
(matrix4x4->value[0][3] == 0.0f) &&
(matrix4x4->value[1][3] == 0.0f) &&
(matrix4x4->value[2][3] == 0.0f) )
{
for (i = 0; i < numPoints; ++i)
{
Q3Point3D_TransformAffine( inPoints3D, matrix4x4, outPoints3D );
inPoints3D++;
outPoints3D++;
}
}
else
{
// Transform the points - will be in-lined in release builds
for (i = 0; i < numPoints; ++i)
{
Q3Point3D_Transform(inPoints3D, matrix4x4, outPoints3D);
inPoints3D++;
outPoints3D++;
}
}
}
//-----------------------------------------------------------------------------
#pragma mark Q3Matrix4x4
TQ3Matrix4x4* Q3Matrix4x4_Transpose(
const TQ3Matrix4x4 *matrix4x4,
TQ3Matrix4x4 *result)
{
int i, j;
if (result != matrix4x4)
{
for (i = 0; i < 4; ++i)
for (j = 0; j < 4; ++j)
result->value[i][j] = matrix4x4->value[j][i];
}
else
{
__Q3Float_Swap(result->value[1][0], result->value[0][1]);
__Q3Float_Swap(result->value[2][0], result->value[0][2]);
__Q3Float_Swap(result->value[3][0], result->value[0][3]);
__Q3Float_Swap(result->value[2][1], result->value[1][2]);
__Q3Float_Swap(result->value[3][1], result->value[1][3]);
__Q3Float_Swap(result->value[2][3], result->value[3][2]);
}
return(result);
}
//=============================================================================
// e3matrix4x4_extract3x3 : Select the upper left 3x3 of a 4x4 matrix
//-----------------------------------------------------------------------------
static void e3matrix4x4_extract3x3( const TQ3Matrix4x4& in4x4, TQ3Matrix3x3& out3x3 )
{
for (int row = 0; row < 3; ++row)
{
for (int col = 0; col < 3; ++col)
{
out3x3.value[row][col] = in4x4.value[row][col];
}
}
}
//=============================================================================
// e3matrix3x3_invert : Transforms the given 3x3 matrix into its inverse.
//-----------------------------------------------------------------------------
// Note : This function uses Gauss-Jordon elimination with full pivoting
// to transform the given matrix to the identity matrix while
// transforming the identity matrix to the inverse. As the given
// matrix is reduced to 1's and 0's column-by-column, the inverse
// matrix is created in its place column-by-column.
//
// See Press, et al., "Numerical Recipes in C", 2nd ed., pp. 32 ff.
//-----------------------------------------------------------------------------
static void e3matrix3x3_invert(TQ3Matrix3x3* a)
{
#define A(x,y) a->value[x][y]
TQ3Int32 irow = 0, icol = 0;
TQ3Int32 i, j, k; // *** WARNING: 'k' must be a SIGNED integer ***
float big, element;
TQ3Int32 ipiv[3], indxr[3], indxc[3];
// Initialize ipiv: ipiv[j] is 0 (1) if row/column j has not (has) been pivoted
for (j = 0; j < 3; ++j)
ipiv[j] = 0;
// Loop over 3 pivots
for (k = 0; k < 3; ++k)
{
// Search unpivoted part of matrix for largest element to pivot on
big = -1.0f;
for (i = 0; i < 3; ++i)
{
if (ipiv[i])
continue;
for (j = 0; j < 3; ++j)
{
if (ipiv[j])
continue;
// Calculate absolute value of current element
element = A(i,j);
if (element < 0.0f)
element = -element;
// Compare current element to largest element so far
if (element > big)
{
big = element;
irow = i;
icol = j;
}
}
}
// If largest element is 0, the matrix is singular
// (If there are "nan" values in the matrix, "big" may still be -1.0.)
if (big <= 0.0f)
{
throw std::runtime_error("e3matrix3x3_invert: non-invertible matrix");
return;
}
// Mark pivot row and column
++ipiv[icol];
indxr[k] = irow;
indxc[k] = icol;
// If necessary, exchange rows to put pivot element on diagonal
if (irow != icol)
{
for (j = 0; j < 3; ++j)
__Q3Float_Swap(A(irow,j), A(icol,j));
}
// Divide pivot row by pivot element
//
// Note: If we were dividing by the same element many times, it would
// make sense to multiply by its inverse. Since we divide by a given
// elemen only 3 (4) times for a 3x3 (4x4) matrix, it doesn't make sense
// to pay for the extra floating-point operation.
element = A(icol,icol);
A(icol,icol) = 1.0f; // overwrite original matrix with inverse
for (j = 0; j < 3; ++j)
A(icol,j) /= element;
// Reduce other rows
for (i = 0; i < 3; ++i)
{
if (i == icol)
continue;
element = A(i,icol);
A(i,icol) = 0.0f; // overwrite original matrix with inverse
for (j = 0; j < 3; ++j)
A(i,j) -= A(icol,j)*element;
}
}
// Permute columns
for (k = 3; --k >= 0; ) // *** WARNING: 'k' must be a SIGNED integer ***
{
if (indxr[k] != indxc[k])
{
for (i = 0; i < 3; ++i)
__Q3Float_Swap(A(i,indxr[k]), A(i,indxc[k]));
}
}
#undef A
}
//=============================================================================
// e3matrix4x4_invert : Transforms the given 4x4 matrix into its inverse.
//-----------------------------------------------------------------------------
// Note : This function uses Gauss-Jordon elimination with full pivoting
// to transform the given matrix to the identity matrix while
// transforming the identity matrix to the inverse. As the given
// matrix is reduced to 1's and 0's column-by-column, the inverse
// matrix is created in its place column-by-column.
//
// See Press, et al., "Numerical Recipes in C", 2nd ed., pp. 32 ff.
//-----------------------------------------------------------------------------
static void e3matrix4x4_invert(TQ3Matrix4x4* a)
{
#define A(x,y) a->value[x][y]
TQ3Int32 irow = 0, icol = 0;
TQ3Int32 i, j, k; // *** WARNING: 'k' must be a SIGNED integer ***
float big, element;
TQ3Int32 ipiv[4], indxr[4], indxc[4];
// Initialize ipiv: ipiv[j] is 0 (1) if row/column j has not (has) been pivoted
for (j = 0; j < 4; ++j)
ipiv[j] = 0;
// Loop over 4 pivots
for (k = 0; k < 4; ++k)
{
// Search unpivoted part of matrix for largest element to pivot on
big = -1.0f;
for (i = 0; i < 4; ++i)
{
if (ipiv[i])
continue;
for (j = 0; j < 4; ++j)
{
if (ipiv[j])
continue;
// Calculate absolute value of current element
element = A(i,j);
if (element < 0.0f)
element = -element;
// Compare current element to largest element so far
if (element > big)
{
big = element;
irow = i;
icol = j;
}
}
}
// If largest element is 0, the matrix is singular
// (If there are "nan" values in the matrix, "big" may still be -1.0.)
if (big <= 0.0f)
{
throw std::runtime_error("e3matrix4x4_invert: non-invertible matrix");
return;
}
// Mark pivot row and column
++ipiv[icol];
indxr[k] = irow;
indxc[k] = icol;
// If necessary, exchange rows to put pivot element on diagonal
if (irow != icol)
{
for (j = 0; j < 4; ++j)
__Q3Float_Swap(A(irow,j), A(icol,j));
}
// Divide pivot row by pivot element
//
// Note: If we were dividing by the same element many times, it would
// make sense to multiply by its inverse. Since we divide by a given
// element only 3 (4) times for a 3x3 (4x4) matrix, it doesn't make sense
// to pay for the extra floating-point operation.
element = A(icol,icol);
A(icol,icol) = 1.0f; // overwrite original matrix with inverse
for (j = 0; j < 4; ++j)
A(icol,j) /= element;
// Reduce other rows
for (i = 0; i < 4; ++i)
{
if (i == icol)
continue;
element = A(i,icol);
A(i,icol) = 0.0f; // overwrite original matrix with inverse
for (j = 0; j < 4; ++j)
A(i,j) -= A(icol,j)*element;
}
}
// Permute columns
for (k = 4; --k >= 0; ) // *** WARNING: 'k' must be a SIGNED integer ***
{
if (indxr[k] != indxc[k])
{
for (i = 0; i < 4; ++i)
__Q3Float_Swap(A(i,indxr[k]), A(i,indxc[k]));
}
}
#undef A
}
TQ3Matrix4x4* Q3Matrix4x4_Invert(
const TQ3Matrix4x4 *matrix4x4,
TQ3Matrix4x4 *result)
{
if (result != matrix4x4)
*result = *matrix4x4;
// The 4x4 matrices used in 3D graphics often have a last column of
// (0, 0, 0, 1). In that case, we want the inverse to have exactly the same
// last column, and we can compute the inverse with fewer floating point
// multiplies and divides. The inverse of the matrix
// A 0
// v 1
// (where A is 3x3 and v is 1x3) is
// inv(A) 0
// -v * inv(A) 1 .
if ( (result->value[3][3] == 1.0f) && (result->value[0][3] == 0.0f) &&
(result->value[1][3] == 0.0f) && (result->value[2][3] == 0.0f) )
{
TQ3Matrix3x3 upperLeft;
e3matrix4x4_extract3x3( *result, upperLeft );
int i, j;
e3matrix3x3_invert( &upperLeft );
for (i = 0; i < 3; ++i)
{
for (j = 0; j < 3; ++j)
{
result->value[i][j] = upperLeft.value[i][j];
}
}
TQ3RationalPoint3D v = {
result->value[3][0], result->value[3][1], result->value[3][2]
};
Q3RationalPoint3D_Transform( &v, &upperLeft, &v );
result->value[3][0] = -v.x;
result->value[3][1] = -v.y;
result->value[3][2] = -v.w;
}
else
{
e3matrix4x4_invert(result);
}
return(result);
}

615
src/QD3D/QD3DMath.h Normal file
View File

@ -0,0 +1,615 @@
/*
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
OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#pragma once
#ifdef __cplusplus
extern "C" {
#endif
#include "QD3D.h"
#include <math.h>
#include <string.h>
#ifdef FLT_EPSILON
#define kQ3RealZero (FLT_EPSILON)
#else
#define kQ3RealZero ((TQ3Float32) 1.19209290e-07)
#endif
#ifdef FLT_MAX
#define kQ3MaxFloat (FLT_MAX)
#else
#define kQ3MaxFloat ((TQ3Float32) 3.40282347e+38)
#endif
#ifdef FLT_MIN
#define kQ3MinFloat (FLT_MIN)
#else
#define kQ3MinFloat ((TQ3Float32) 1.17549e-38)
#endif
#define kQ3Pi ((TQ3Float32) 3.1415926535898)
#define kQ32Pi ((TQ3Float32) (2.0 * 3.1415926535898))
#define kQ3PiOver2 ((TQ3Float32) (3.1415926535898 / 2.0))
#define kQ33PiOver2 ((TQ3Float32) (3.0 * 3.1415926535898 / 2.0))
#define Q3Math_DegreesToRadians(_x) ((TQ3Float32) ((_x) * kQ3Pi / 180.0f))
#define Q3Math_RadiansToDegrees(_x) ((TQ3Float32) ((_x) * 180.0f / kQ3Pi))
#define Q3Math_Min(_x,_y) ((_x) <= (_y) ? (_x) : (_y))
#define Q3Math_Max(_x,_y) ((_x) >= (_y) ? (_x) : (_y))
#define __Q3Memory_Clear(ptr, size) memset((ptr), 0, (size))
#define __Q3Float_Swap(_a, _b) \
do { \
float _temp; \
\
_temp = (_a); \
(_a) = (_b); \
(_b) = _temp; \
} while (0)
//-----------------------------------------------------------------------------
#pragma mark Q3Point2D
static inline float Q3Point2D_DistanceSquared(
const TQ3Point2D *p1,
const TQ3Point2D *p2)
{
return ((p1->x - p2->x) * (p1->x - p2->x))
+ ((p1->y - p2->y) * (p1->y - p2->y));
}
static inline float Q3Point2D_Distance(
const TQ3Point2D *p1,
const TQ3Point2D *p2)
{
return sqrtf(Q3Point2D_DistanceSquared(p1, p2));
}
//-----------------------------------------------------------------------------
#pragma mark Q3Point3D
static inline float Q3Point3D_DistanceSquared(
const TQ3Point3D *p1,
const TQ3Point3D *p2)
{
return ((p1->x - p2->x) * (p1->x - p2->x))
+ ((p1->y - p2->y) * (p1->y - p2->y))
+ ((p1->z - p2->z) * (p1->z - p2->z));
}
static inline float Q3Point3D_Distance(
const TQ3Point3D *p1,
const TQ3Point3D *p2)
{
return sqrtf(Q3Point3D_DistanceSquared(p1, p2));
}
static inline void Q3Point3D_CrossProductTri(
const TQ3Point3D* p1,
const TQ3Point3D* p2,
const TQ3Point3D* p3,
TQ3Point3D* result)
{
float v1_x = p2->x - p1->x;
float v1_y = p2->y - p1->y;
float v1_z = p2->z - p1->z;
float v2_x = p3->x - p2->x;
float v2_y = p3->y - p2->y;
float v2_z = p3->z - p2->z;
result->x = (v1_y * v2_z) - (v1_z * v2_y);
result->y = (v1_z * v2_x) - (v1_x * v2_z);
result->z = (v1_x * v2_y) - (v1_y * v2_x);
}
static inline TQ3Vector3D* Q3Vector3D_Transform(
const TQ3Vector3D *vector3D,
const TQ3Matrix4x4 *matrix4x4,
TQ3Vector3D *result)
{
// Save input to avoid problems when result is same as input
float x = vector3D->x;
float y = vector3D->y;
float z = vector3D->z;
#define M(x,y) matrix4x4->value[x][y]
result->x = x*M(0,0) + y*M(1,0) + z*M(2,0);
result->y = x*M(0,1) + y*M(1,1) + z*M(2,1);
result->z = x*M(0,2) + y*M(1,2) + z*M(2,2);
#undef M
return result;
}
static inline TQ3Point3D* Q3Point3D_Transform(
const TQ3Point3D *point3D,
const TQ3Matrix4x4 *matrix4x4,
TQ3Point3D *result)
{
// Save input to avoid problems when result is same as input
float x = point3D->x;
float y = point3D->y;
float z = point3D->z;
float neww;
#define M(x,y) matrix4x4->value[x][y]
result->x = x*M(0,0) + y*M(1,0) + z*M(2,0) + M(3,0);
result->y = x*M(0,1) + y*M(1,1) + z*M(2,1) + M(3,1);
result->z = x*M(0,2) + y*M(1,2) + z*M(2,2) + M(3,2);
neww = x*M(0,3) + y*M(1,3) + z*M(2,3) + M(3,3);
#undef M
if (neww == 0.0f)
{
// E3ErrorManager_PostError( kQ3ErrorInfiniteRationalPoint, kQ3False );
neww = 1.0f;
}
if (neww != 1.0f)
{
float invw = 1.0f / neww;
result->x *= invw;
result->y *= invw;
result->z *= invw;
}
return result;
}
static inline TQ3Point3D* Q3Point3D_TransformAffine(
const TQ3Point3D *point3D,
const TQ3Matrix4x4 *matrix4x4,
TQ3Point3D *result)
{
// Save input to avoid problems when result is same as input
float x = point3D->x;
float y = point3D->y;
float z = point3D->z;
#define M(x,y) matrix4x4->value[x][y]
result->x = x*M(0,0) + y*M(1,0) + z*M(2,0) + M(3,0);
result->y = x*M(0,1) + y*M(1,1) + z*M(2,1) + M(3,1);
result->z = x*M(0,2) + y*M(1,2) + z*M(2,2) + M(3,2);
#undef M
return(result);
}
void Q3Point3D_To3DTransformArray(
const TQ3Point3D *inPoints3D,
const TQ3Matrix4x4 *matrix4x4,
TQ3Point3D *outPoints3D,
TQ3Uns32 numPoints);
//-----------------------------------------------------------------------------
#pragma mark Q3RationalPoint3D
static inline TQ3RationalPoint3D* Q3RationalPoint3D_Transform(
const TQ3RationalPoint3D *rationalPoint3D,
const TQ3Matrix3x3 *matrix3x3,
TQ3RationalPoint3D *result)
{
// Save input to avoid problems when result is same as input
float x = rationalPoint3D->x;
float y = rationalPoint3D->y;
float w = rationalPoint3D->w;
#define M(x,y) matrix3x3->value[x][y]
result->x = x*M(0,0) + y*M(1,0) + w*M(2,0);
result->y = x*M(0,1) + y*M(1,1) + w*M(2,1);
result->w = x*M(0,2) + y*M(1,2) + w*M(2,2);
#undef M
return(result);
}
//-----------------------------------------------------------------------------
#pragma mark Q3Vector2D
static inline float Q3Vector2D_LengthSquared(
const TQ3Vector2D* v)
{
return (v->x * v->x) + (v->y * v->y);
}
static inline float Q3Vector2D_Length(
const TQ3Vector2D* v)
{
return sqrtf(Q3Vector2D_LengthSquared(v));
}
static inline void Q3Vector2D_Scale(
const TQ3Vector2D* v1,
float s,
TQ3Vector2D* result)
{
result->x = v1->x * s;
result->y = v1->y * s;
}
static inline void Q3Vector2D_Normalize(
const TQ3Vector2D* v1,
TQ3Vector2D* result)
{
// Normalization performance trick: usually, adding kQ3MinFloat to the length
// makes no difference, but it prevents division by zero without a branch.
float theLength = Q3Vector2D_Length(v1) + kQ3MinFloat;
Q3Vector2D_Scale(v1, 1.0f / theLength, result);
}
//-----------------------------------------------------------------------------
#pragma mark Q3Vector3D
static inline float Q3Vector3D_LengthSquared(
const TQ3Vector3D* v)
{
return (v->x * v->x) + (v->y * v->y) + (v->z * v->z);
}
static inline float Q3Vector3D_Length(
const TQ3Vector3D* v)
{
return sqrtf(Q3Vector3D_LengthSquared(v));
}
static inline float Q3Vector2D_Dot(
const TQ3Vector2D *v1,
const TQ3Vector2D *v2)
{
return (v1->x * v2->x) + (v1->y * v2->y);
}
static inline void Q3Vector3D_Cross(
const TQ3Vector3D* v1,
const TQ3Vector3D* v2,
TQ3Vector3D* result)
{
float rx = (v1->y * v2->z) - (v1->z * v2->y);
float ry = (v1->z * v2->x) - (v1->x * v2->z);
float rz = (v1->x * v2->y) - (v1->y * v2->x);
result->x = rx;
result->y = ry;
result->z = rz;
}
static inline float Q3Vector3D_Dot(
const TQ3Vector3D *v1,
const TQ3Vector3D *v2)
{
return (v1->x * v2->x)
+ (v1->y * v2->y)
+ (v1->z * v2->z);
}
static inline void Q3Vector3D_Scale(
const TQ3Vector3D *v1,
float scale,
TQ3Vector3D *result)
{
result->x = v1->x * scale;
result->y = v1->y * scale;
result->z = v1->z * scale;
}
static inline void Q3Vector3D_Normalize(
const TQ3Vector3D* v1,
TQ3Vector3D* result)
{
float theLength = Q3Vector3D_Length(v1) + kQ3MinFloat;
Q3Vector3D_Scale(v1, 1.0f / theLength, result);
}
//-----------------------------------------------------------------------------
#pragma mark Q3Matrix3x3
static inline TQ3Matrix3x3* Q3Matrix3x3_SetIdentity(
TQ3Matrix3x3 *matrix3x3)
{
__Q3Memory_Clear(matrix3x3, sizeof(TQ3Matrix3x3));
#define M(x,y) matrix3x3->value[x][y]
M(0,0) = 1.0f;
M(1,1) = 1.0f;
M(2,2) = 1.0f;
#undef M
return matrix3x3;
}
static inline TQ3Matrix3x3* Q3Matrix3x3_SetTranslate(
TQ3Matrix3x3* matrix3x3,
float xTrans,
float yTrans)
{
__Q3Memory_Clear(matrix3x3, sizeof(TQ3Matrix3x3));
#define M(x,y) matrix3x3->value[x][y]
M(0,0) = 1.0f;
M(1,1) = 1.0f;
M(2,0) = xTrans;
M(2,1) = yTrans;
M(2,2) = 1.0f;
#undef M
return matrix3x3;
}
//-----------------------------------------------------------------------------
#pragma mark Q3Matrix4x4
static inline TQ3Matrix4x4* Q3Matrix4x4_SetIdentity(
TQ3Matrix4x4 *matrix4x4)
{
__Q3Memory_Clear(matrix4x4, sizeof(TQ3Matrix4x4));
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = 1.0f;
M(1,1) = 1.0f;
M(2,2) = 1.0f;
M(3,3) = 1.0f;
#undef M
return matrix4x4;
}
static inline TQ3Matrix4x4* Q3Matrix4x4_SetScale(
TQ3Matrix4x4 *matrix4x4,
float xScale,
float yScale,
float zScale)
{
__Q3Memory_Clear(matrix4x4, sizeof(TQ3Matrix4x4));
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = xScale;
M(1,1) = yScale;
M(2,2) = zScale;
M(3,3) = 1.0f;
#undef M
return(matrix4x4);
}
static inline TQ3Matrix4x4* Q3Matrix4x4_SetTranslate(
TQ3Matrix4x4 *matrix4x4,
float xTrans,
float yTrans,
float zTrans)
{
__Q3Memory_Clear(matrix4x4, sizeof(TQ3Matrix4x4));
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = 1.0f;
M(1,1) = 1.0f;
M(2,2) = 1.0f;
M(3,0) = xTrans;
M(3,1) = yTrans;
M(3,2) = zTrans;
M(3,3) = 1.0f;
#undef M
return(matrix4x4);
}
static inline TQ3Matrix4x4* Q3Matrix4x4_Multiply(
const TQ3Matrix4x4 *m1,
const TQ3Matrix4x4 *m2,
TQ3Matrix4x4 *result)
{
// If result is alias of input, output to temporary
TQ3Matrix4x4 temp;
TQ3Matrix4x4* output = (result == m1 || result == m2 ? &temp : result);
#define A(x,y) m1->value[x][y]
#define B(x,y) m2->value[x][y]
#define M(x,y) output->value[x][y]
M(0,0) = A(0,0)*B(0,0) + A(0,1)*B(1,0) + A(0,2)*B(2,0) + A(0,3)*B(3,0);
M(0,1) = A(0,0)*B(0,1) + A(0,1)*B(1,1) + A(0,2)*B(2,1) + A(0,3)*B(3,1);
M(0,2) = A(0,0)*B(0,2) + A(0,1)*B(1,2) + A(0,2)*B(2,2) + A(0,3)*B(3,2);
M(0,3) = A(0,0)*B(0,3) + A(0,1)*B(1,3) + A(0,2)*B(2,3) + A(0,3)*B(3,3);
M(1,0) = A(1,0)*B(0,0) + A(1,1)*B(1,0) + A(1,2)*B(2,0) + A(1,3)*B(3,0);
M(1,1) = A(1,0)*B(0,1) + A(1,1)*B(1,1) + A(1,2)*B(2,1) + A(1,3)*B(3,1);
M(1,2) = A(1,0)*B(0,2) + A(1,1)*B(1,2) + A(1,2)*B(2,2) + A(1,3)*B(3,2);
M(1,3) = A(1,0)*B(0,3) + A(1,1)*B(1,3) + A(1,2)*B(2,3) + A(1,3)*B(3,3);
M(2,0) = A(2,0)*B(0,0) + A(2,1)*B(1,0) + A(2,2)*B(2,0) + A(2,3)*B(3,0);
M(2,1) = A(2,0)*B(0,1) + A(2,1)*B(1,1) + A(2,2)*B(2,1) + A(2,3)*B(3,1);
M(2,2) = A(2,0)*B(0,2) + A(2,1)*B(1,2) + A(2,2)*B(2,2) + A(2,3)*B(3,2);
M(2,3) = A(2,0)*B(0,3) + A(2,1)*B(1,3) + A(2,2)*B(2,3) + A(2,3)*B(3,3);
M(3,0) = A(3,0)*B(0,0) + A(3,1)*B(1,0) + A(3,2)*B(2,0) + A(3,3)*B(3,0);
M(3,1) = A(3,0)*B(0,1) + A(3,1)*B(1,1) + A(3,2)*B(2,1) + A(3,3)*B(3,1);
M(3,2) = A(3,0)*B(0,2) + A(3,1)*B(1,2) + A(3,2)*B(2,2) + A(3,3)*B(3,2);
M(3,3) = A(3,0)*B(0,3) + A(3,1)*B(1,3) + A(3,2)*B(2,3) + A(3,3)*B(3,3);
#undef A
#undef B
#undef M
if (output == &temp)
*result = temp;
return(result);
}
static inline TQ3Matrix4x4* Q3Matrix4x4_SetRotate_X(
TQ3Matrix4x4 *matrix4x4,
float angle)
{
float cosAngle = cosf(angle);
float sinAngle = sinf(angle);
__Q3Memory_Clear(matrix4x4, sizeof(TQ3Matrix4x4));
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = 1.0f;
M(1,1) = cosAngle;
M(1,2) = sinAngle;
M(2,1) = -sinAngle;
M(2,2) = cosAngle;
M(3,3) = 1.0f;
#undef M
return(matrix4x4);
}
static inline TQ3Matrix4x4* Q3Matrix4x4_SetRotate_Y(
TQ3Matrix4x4 *matrix4x4,
float angle)
{
float cosAngle = cosf(angle);
float sinAngle = sinf(angle);
__Q3Memory_Clear(matrix4x4, sizeof(TQ3Matrix4x4));
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = cosAngle;
M(0,2) = -sinAngle;
M(1,1) = 1.0f;
M(2,0) = sinAngle;
M(2,2) = cosAngle;
M(3,3) = 1.0f;
#undef M
return(matrix4x4);
}
static inline TQ3Matrix4x4* Q3Matrix4x4_SetRotate_Z(
TQ3Matrix4x4 *matrix4x4,
float angle)
{
float cosAngle = cosf(angle);
float sinAngle = sinf(angle);
__Q3Memory_Clear(matrix4x4, sizeof(TQ3Matrix4x4));
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = cosAngle;
M(0,1) = sinAngle;
M(1,0) = -sinAngle;
M(1,1) = cosAngle;
M(2,2) = 1.0f;
M(3,3) = 1.0f;
#undef M
return(matrix4x4);
}
static inline TQ3Matrix4x4* Q3Matrix4x4_SetRotate_XYZ(
TQ3Matrix4x4 *matrix4x4,
float xAngle,
float yAngle,
float zAngle)
{
float cosX = cosf(xAngle);
float sinX = sinf(xAngle);
float cosY = cosf(yAngle);
float sinY = sinf(yAngle);
float cosZ = cosf(zAngle);
float sinZ = sinf(zAngle);
float sinXsinY = sinX*sinY;
float cosXsinY = cosX*sinY;
#define M(x,y) matrix4x4->value[x][y]
M(0,0) = cosY*cosZ;
M(0,1) = cosY*sinZ;
M(0,2) = -sinY;
M(0,3) = 0.0f;
M(1,0) = sinXsinY*cosZ - cosX*sinZ;
M(1,1) = sinXsinY*sinZ + cosX*cosZ;
M(1,2) = sinX*cosY;
M(1,3) = 0.0f;
M(2,0) = cosXsinY*cosZ + sinX*sinZ;
M(2,1) = cosXsinY*sinZ - sinX*cosZ;
M(2,2) = cosX*cosY;
M(2,3) = 0.0f;
M(3,0) = 0.0f;
M(3,1) = 0.0f;
M(3,2) = 0.0f;
M(3,3) = 1.0f;
#undef M
return(matrix4x4);
}
TQ3Matrix4x4* Q3Matrix4x4_Transpose(
const TQ3Matrix4x4 *matrix4x4,
TQ3Matrix4x4 *result);
TQ3Matrix4x4* Q3Matrix4x4_Invert(
const TQ3Matrix4x4 *inMatrix,
TQ3Matrix4x4 *result);
#ifdef __cplusplus
}
#endif