mirror of
https://github.com/jorio/Pomme.git
synced 2024-11-27 05:49:18 +00:00
Import QD3D/3DMF code from Nanosaur's new renderer
This commit is contained in:
parent
ad669a3fd4
commit
00b4a7e72b
@ -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()
|
||||
|
39
LICENSE.md
39
LICENSE.md
@ -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
157
src/QD3D/3DMFInternal.h
Normal 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
503
src/QD3D/3DMFParser.cpp
Normal 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
342
src/QD3D/QD3D.cpp
Normal 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
345
src/QD3D/QD3D.h
Normal 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
384
src/QD3D/QD3DMath.cpp
Normal 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
615
src/QD3D/QD3DMath.h
Normal 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
|
Loading…
Reference in New Issue
Block a user