/*
* Copyright (c) 2003 Nokia Corporation and/or its subsidiary(-ies).
* All rights reserved.
* This component and the accompanying materials are made available
* under the terms of the License "Eclipse Public License v1.0"
* which accompanies this distribution, and is available
* at the URL "http://www.eclipse.org/legal/epl-v10.html".
*
* Initial Contributors:
* Nokia Corporation - initial contribution.
*
* Contributors:
*
* Description: Native implementation of the Loader class
*
*/
/*!
* \internal
* \file
* \brief Native implementation of the Loader class
*
*/
#include "m3g_object.h"
#include "m3g_array.h"
/*----------------------------------------------------------------------
* Internal data types
*--------------------------------------------------------------------*/
/*!
* \internal
* \brief Possible global states for the loader
*/
typedef enum {
/*! \internal \brief Loading not supported yet */
LOADSTATE_NOT_SUPPORTED = -2,
/*! \internal \brief Loading has terminated with an error */
LOADSTATE_ERROR = -1,
/*! \internal \brief Loading has not started yet */
LOADSTATE_INITIAL = 0,
/*! \internal \brief The identifier of the file is being read */
LOADSTATE_IDENTIFIER,
/*! \internal \brief The header of the section is being read */
LOADSTATE_SECTION,
/*! \internal \brief The header field of an object is being read */
LOADSTATE_OBJECT,
/*! \internal \brief Loading is finished */
LOADSTATE_DONE
} LoaderState;
/*!
* \internal
* \brief Possible local states for the loader
*/
typedef enum {
/*! \internal \brief Local state is entered */
LOADSTATE_ENTER,
/*! \internal \brief Local state is exited */
LOADSTATE_EXIT,
/*! \internal \brief Local state is section checksum */
LOADSTATE_CHECKSUM
} LoaderLocalState;
/*!
* \internal
* \brief Buffered byte stream class
*/
typedef struct
{
M3Gubyte *allocatedData;
M3Gubyte *data;
M3Gsizei capacity, bytesAvailable, totalBytes;
} BufferedStream;
/*!
* \internal
* \brief User data for a loaded object
*/
typedef struct
{
M3GObject object;
M3Gint numParams;
M3Gbyte **params;
M3Gsizei *paramLengths;
M3Gint *paramId;
} UserData;
/*!
* \internal
* \brief Loader instance data
*/
typedef struct M3GLoaderImpl
{
Object object;
BufferedStream stream;
M3Gsizei bytesRequired;
M3Gsizei sectionBytesRequired;
PointerArray refArray;
PointerArray userDataArray;
/*!
* \internal
* \brief The global state the loader is in
*
* This is a rather ordinary state machine thing; basically the
* type of object being loaded, or one of the possible error
* conditions. In here, it also amounts to a particular coroutine
* execution context.
*/
LoaderState state;
/*!
* \internal
* \brief The local state of the loader
*
* This is basically the line number within a particular coroutine
* function.
*/
M3Gint localState;
/*!
* \internal
* \brief Object being loaded
*/
M3Gint objectType;
/*!
* \internal
* \brief Loaded object
*/
M3GObject loadedObject;
/*!
* \internal
* \brief Pointer to the beginning of an object
*/
M3Gubyte *objectData;
/*!
* \internal
* \brief Pointer to the end of an object
*/
M3Gubyte *objectDataEnd;
/*!
* \internal
* \brief Pointer to the context data for the current coroutine
* context
*/
M3Gubyte *localData;
/*!
* \internal
* \brief Size of the current coroutine data
*
* This is grown dynamically as necessary, rather than trying to
* maintain a single size that fits all coroutines.
*/
size_t localDataSize;
/* File information */
M3Gbool hasReferences;
M3Gsizei fileSize;
M3Gsizei contentSize;
M3Gint triCount;
M3Gint triConstraint;
/* Section information */
M3Gbool compressed;
M3Gint sectionLength;
M3Gint sectionNum;
M3Gint inflatedLength;
M3Gubyte *sectionData;
M3Gubyte *allocatedSectionData;
/* Adler data */
M3Gint S12[2];
} Loader;
typedef struct {
const unsigned char *data;
int read;
int length;
} compressedData;
/* Type ID used for classifying objects derived from Node */
#define ANY_NODE_CLASS ((M3GClass)(-1))
#include <string.h>
#define m3gCmp(s1, s2, len) memcmp(s1, s2, len)
/*----------------------------------------------------------------------
* Private constants
*--------------------------------------------------------------------*/
#define M3G_MIN_OBJECT_SIZE (1 + 4)
#define M3G_MIN_SECTION_SIZE (1 + 4 + 4)
#define M3G_CHECKSUM_SIZE 4
#define M3G_ADLER_CONST 65521;
static const M3Gubyte M3G_FILE_IDENTIFIER[] = {
0xAB, 0x4A, 0x53, 0x52, 0x31, 0x38, 0x34, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
};
static const M3Gubyte PNG_FILE_IDENTIFIER[] = {
0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a
};
static void m3gCleanupLoader(M3GLoader loader);
/*----------------------------------------------------------------------
* Platform-specific "inflate" decompression code
*--------------------------------------------------------------------*/
/*!
* \internal
* \brief Decompresses a block of data into an output buffer
*
* \param srcLength number of bytes in the input (compressed) buffer
* \param src pointer to the input buffer
* \param dstLength number of bytes allocated in the output buffer
* \param dst pointer to the output buffer
* \return the number of bytes written to \c dst
*/
static M3Gsizei m3gInflateBlock(M3Gsizei srcLength, const M3Gubyte *src,
M3Gsizei dstLength, M3Gubyte *dst);
/* Include the platform-dependent implementation */
#if !defined(M3G_TARGET_GENERIC)
# include "m3g_loader_inflate.inl"
#endif
/*----------------------------------------------------------------------
* Private functions
*--------------------------------------------------------------------*/
/*!
* \internal
* \brief Destructor
*/
static void m3gDestroyLoader(Object *obj)
{
Loader* loader = (Loader *) obj;
M3G_VALIDATE_OBJECT(loader);
{
Interface *m3g = M3G_INTERFACE(loader);
M3Gint n, i;
m3gCleanupLoader(loader);
m3gDestroyArray(&loader->refArray, m3g);
n = m3gArraySize(&loader->userDataArray);
for (i = 0; i < n; ++i)
{
UserData *data = (UserData *)m3gGetArrayElement(&loader->userDataArray, i);
m3gFree(m3g, data->params);
m3gFree(m3g, data->paramLengths);
m3gFree(m3g, data->paramId);
m3gFree(m3g, data);
}
m3gDestroyArray(&loader->userDataArray, m3g);
m3gFree(m3g, loader->stream.allocatedData);
m3gFree(m3g, loader->allocatedSectionData);
}
m3gDestroyObject(obj);
}
/*!
* \internal
* \brief Stores new data in the stream buffer of this loader
*/
static M3Gbool m3gBufferData( M3GInterface m3g,
BufferedStream *stream,
M3Gsizei bytes,
const M3Gubyte *data)
{
M3Gsizei used;
/* Allocate initial buffer */
if (stream->allocatedData == NULL) {
stream->capacity = bytes + 512;
stream->allocatedData = m3gAllocZ(m3g, stream->capacity);
if (!stream->allocatedData) {
return M3G_FALSE;
}
stream->data = stream->allocatedData;
stream->bytesAvailable = 0;
stream->totalBytes = 0;
}
/* First skip used bytes */
used = stream->data - stream->allocatedData;
if (used > 0) {
m3gMove(stream->allocatedData, stream->data, stream->bytesAvailable);
stream->data = stream->allocatedData;
}
/* Check if new data fits in current buffer */
if ((stream->capacity - stream->bytesAvailable) < bytes) {
M3Gubyte *newData;
stream->capacity = stream->capacity + bytes + 512;
newData = m3gAllocZ(m3g, stream->capacity);
if (!newData) {
m3gFree(m3g, stream->allocatedData);
stream->allocatedData = NULL;
return M3G_FALSE;
}
m3gCopy(newData, stream->data, stream->bytesAvailable);
m3gFree(m3g, stream->allocatedData);
stream->allocatedData = newData;
stream->data = stream->allocatedData;
}
m3gCopy(stream->data + stream->bytesAvailable, data, bytes);
stream->bytesAvailable += bytes;
return M3G_TRUE;
}
/*!
* \internal
* \brief Resets buffered data
*/
static void m3gResetBufferedData(BufferedStream *stream)
{
stream->bytesAvailable = 0;
stream->data = stream->allocatedData;
stream->totalBytes = 0;
}
/*!
* \internal
* \brief Gets buffered data pointer
*/
static M3Gubyte *m3gGetBufferedDataPtr(BufferedStream *stream, M3Gint length)
{
if (stream->bytesAvailable >= length) {
return stream->data;
}
else {
return NULL;
}
}
/*!
* \internal
* \brief Advances buffered data pointer
*/
static void m3gAdvanceBufferedData(BufferedStream *stream, M3Gint length)
{
stream->data += length;
stream->bytesAvailable -= length;
stream->totalBytes += length;
}
/*!
* \internal
* \brief Verify a boolean
*/
static M3Gbool m3gVerifyBool(M3Gubyte *data)
{
return (*data == 0 || *data == 1);
}
/*!
* \internal
* \brief Loads ARGB color from data array
*/
static M3Guint m3gLoadARGB(M3Gubyte *data)
{
M3Guint v = data[3];
v <<= 8;
v |= data[0];
v <<= 8;
v |= data[1];
v <<= 8;
v |= data[2];
return v;
}
/*!
* \internal
* \brief Loads RGB color from data array
*/
static M3Guint m3gLoadRGB(M3Gubyte *data)
{
M3Guint v = data[0];
v <<= 8;
v |= data[1];
v <<= 8;
v |= data[2];
return v;
}
/*!
* \internal
* \brief Loads short from data array
*/
static M3Gshort m3gLoadShort(M3Gubyte *data)
{
M3Gshort v = data[1];
v <<= 8;
v |= data[0];
return v;
}
/*!
* \internal
* \brief Loads integer from data array
*/
static M3Gint m3gLoadInt(M3Gubyte *data)
{
M3Gint v = data[3];
v <<= 8;
v |= data[2];
v <<= 8;
v |= data[1];
v <<= 8;
v |= data[0];
return v;
}
/*!
* \internal
* \brief Loads integer from data array
*/
static M3Gbool m3gLoadFloat(M3Gubyte *data, M3Gfloat *res)
{
M3Guint v = data[3];
v <<= 8;
v |= data[2];
v <<= 8;
v |= data[1];
v <<= 8;
v |= data[0];
*res = (*(M3Gfloat*)&(v));
if ((v & 0x7f800000) == 0x7f800000 ||
v == 0x80000000 || // negative zero
((v & 0x007FFFFF ) != 0 && ( v & 0x7F800000 ) == 0))
return M3G_FALSE;
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads 4 * 4 matrix from data array
*/
static M3Gbool m3gLoadMatrix(Matrix *m, M3Gubyte *data)
{
M3Gint i;
M3Gfloat array[16];
for (i = 0; i < 16; i++) {
if (!m3gLoadFloat(data + 4 * i, &array[i]))
return M3G_FALSE;
}
m3gSetMatrixRows(m, array);
return M3G_TRUE;
}
/*!
* \internal
* \brief Inflates a section
*/
static M3Gubyte *m3gInflateSection(Loader *loader,
M3Gubyte *compressed,
M3Gint cLength, M3Gint iLength)
{
M3Gubyte *inflated = m3gAllocZ(M3G_INTERFACE(loader), iLength);
if (inflated && !m3gInflateBlock(cLength, compressed, iLength, inflated)) {
m3gFree(M3G_INTERFACE(loader), inflated);
return NULL;
}
return inflated;
}
/*!
* \internal
* \brief Loads file identifier
*/
static LoaderState m3gLoadIdentifier(Loader *loader)
{
M3Gubyte *data = m3gGetBufferedDataPtr(&loader->stream, loader->bytesRequired);
if (loader->localState == LOADSTATE_ENTER) {
if (m3gCmp(data, PNG_FILE_IDENTIFIER, sizeof(PNG_FILE_IDENTIFIER)) == 0) {
m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
return LOADSTATE_NOT_SUPPORTED;
}
else {
loader->localState = LOADSTATE_EXIT;
loader->bytesRequired = sizeof(M3G_FILE_IDENTIFIER);
return LOADSTATE_IDENTIFIER;
}
}
else {
if (m3gCmp(data, M3G_FILE_IDENTIFIER, sizeof(M3G_FILE_IDENTIFIER)) == 0) {
m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
loader->localState = LOADSTATE_ENTER;
loader->bytesRequired = M3G_MIN_SECTION_SIZE;
return LOADSTATE_SECTION;
}
loader->bytesRequired = 0;
return LOADSTATE_ERROR;
}
}
/*!
* \internal
* \brief Adler helper functions
*/
static void m3gInitAdler(M3Gint *S12)
{
S12[0] = 1; S12[1] = 0;
}
static void m3gUpdateAdler(M3Gint *S12, M3Gubyte *data, M3Gint length)
{
int i;
for (i = 0; i < length; i++) {
S12[0] = (S12[0] + data[i]) % M3G_ADLER_CONST;
S12[1] = (S12[1] + S12[0]) % M3G_ADLER_CONST;
}
}
static M3Gint m3gGetAdler(M3Gint *S12)
{
return (S12[1] << 16) | S12[0];
}
/*!
* \internal
* \brief Loads a section
*/
static LoaderState m3gLoadSection(Loader *loader)
{
M3Gubyte *data = m3gGetBufferedDataPtr(&loader->stream, loader->bytesRequired);
if (data == NULL) {
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return LOADSTATE_ERROR;
}
switch(loader->localState) {
case LOADSTATE_ENTER:
m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
m3gInitAdler(loader->S12);
m3gUpdateAdler(loader->S12, data, loader->bytesRequired);
if (*data > 1)
{
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return LOADSTATE_ERROR;
}
loader->compressed = data[0];
loader->sectionLength = m3gLoadInt(data + 1);
loader->inflatedLength = m3gLoadInt(data + 5);
loader->localState = LOADSTATE_EXIT;
loader->bytesRequired = loader->sectionLength - loader->bytesRequired;
if (!loader->compressed && loader->inflatedLength != (loader->bytesRequired - M3G_CHECKSUM_SIZE))
{
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return LOADSTATE_ERROR;
}
loader->sectionNum++;
/* Special case for empty sections */
if (loader->bytesRequired == M3G_CHECKSUM_SIZE) {
loader->localData = data + loader->sectionLength - M3G_CHECKSUM_SIZE;
loader->sectionData = loader->localData;
loader->compressed = M3G_FALSE;
loader->localState = LOADSTATE_CHECKSUM;
}
return LOADSTATE_SECTION;
case LOADSTATE_EXIT:
default:
m3gUpdateAdler(loader->S12, data, loader->bytesRequired - M3G_CHECKSUM_SIZE);
if (loader->compressed) {
if (loader->inflatedLength > 0) {
m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
loader->sectionData = m3gInflateSection(loader, data, loader->bytesRequired, loader->inflatedLength);
loader->allocatedSectionData = loader->sectionData;
if (!loader->sectionData) {
if (m3gErrorRaised(M3G_INTERFACE(loader)) == M3G_NO_ERROR)
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return LOADSTATE_ERROR;
}
}
else {
loader->sectionData = NULL;
}
}
else {
loader->sectionData = data;
}
loader->localData = loader->sectionData;
loader->sectionBytesRequired = M3G_MIN_OBJECT_SIZE;
loader->localState = LOADSTATE_ENTER;
return LOADSTATE_OBJECT;
case LOADSTATE_CHECKSUM:
if (loader->localData != loader->sectionData + loader->inflatedLength || /* Length */
m3gLoadInt(data + loader->bytesRequired - M3G_CHECKSUM_SIZE) != m3gGetAdler(loader->S12))
{
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
loader->allocatedSectionData = NULL;
return LOADSTATE_ERROR;
}
m3gAdvanceBufferedData(&loader->stream, loader->bytesRequired);
m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
loader->allocatedSectionData = NULL;
loader->localState = LOADSTATE_ENTER;
loader->bytesRequired = M3G_MIN_SECTION_SIZE;
return LOADSTATE_SECTION;
}
}
/*!
* \internal
* \brief Resets section data pointer to the beginning of an object
*/
static void m3gRewindObject(Loader *loader)
{
loader->localData = loader->objectData;
}
/*!
* \internal
* \brief Resets section data pointer to the end of an object
*/
static void m3gSkipObject(Loader *loader)
{
loader->localData = loader->objectDataEnd;
}
/*!
* \internal
* \brief Marks object to begin
*/
static void m3gBeginObject(Loader *loader)
{
loader->objectData = loader->localData;
}
/*!
* \internal
* \brief Marks object to end
*/
static void m3gEndObject(Loader *loader)
{
loader->objectDataEnd = loader->localData;
}
/*!
* \internal
* \brief Gets section data pointer
*/
static M3Gubyte *m3gGetSectionDataPtr(Loader *loader, M3Gint length)
{
if ((loader->localData + length) <= (loader->sectionData + loader->inflatedLength)) {
return loader->localData;
}
else {
return NULL;
}
}
/*!
* \internal
* \brief Advances section data pointer
*/
static void m3gAdvanceSectionData(Loader *loader, M3Gint length)
{
loader->localData += length;
}
/*!
* \internal
* \brief Check length of the available section data
*/
static M3Gbool m3gCheckSectionDataLength(Loader *loader, const M3Gubyte *data, M3Gsizei length)
{
if (data + length < data) return M3G_FALSE; /* Check for overflow */
return ((data + length) <= (loader->sectionData + loader->inflatedLength));
}
/*!
* \internal
* \brief References an object in the object array
*
* \note Uses lowest bit of the pointer to mark a reference
*/
static void m3gReferenceLoaded(PointerArray *array, M3Gint idx)
{
M3Guint ptr = (M3Guint)m3gGetArrayElement(array, idx);
ptr |= 1;
m3gSetArrayElement(array, idx, (void *)ptr);
}
/*!
* \internal
* \brief Gets an object in the object array and
* returns reference status
*/
static M3GObject m3gGetLoadedPtr(PointerArray *array, M3Gint idx, M3Gbool *referenced)
{
M3Guint ptr = (M3Guint)m3gGetArrayElement(array, idx);
if (referenced != NULL) {
*referenced = ptr & 1;
}
return (M3GObject)(ptr & (~1));
}
/*!
* \internal
* \brief Gets a loaded object and marks it referenced
*/
static M3GObject m3gGetLoaded(Loader *loader, M3Gint idx, M3GClass classID)
{
M3GObject obj;
M3GClass objClassID;
M3Gbool isCompatible;
if (idx == 0) return NULL;
idx -= 2;
if (idx < 0 || idx >= m3gArraySize(&loader->refArray)) {
/* Error, not loaded */
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return NULL;
}
obj = m3gGetLoadedPtr(&loader->refArray, idx, NULL);
objClassID = M3G_CLASS(obj);
/* Class type check; handle nodes as a special case */
if (classID == ANY_NODE_CLASS) {
switch (objClassID) {
case M3G_CLASS_CAMERA:
case M3G_CLASS_GROUP:
case M3G_CLASS_LIGHT:
case M3G_CLASS_MESH:
case M3G_CLASS_MORPHING_MESH:
case M3G_CLASS_SKINNED_MESH:
case M3G_CLASS_SPRITE:
case M3G_CLASS_WORLD:
isCompatible = M3G_TRUE;
break;
default:
isCompatible = M3G_FALSE;
}
}
else {
switch (classID) {
case M3G_ABSTRACT_CLASS:
M3G_ASSERT(M3G_FALSE);
isCompatible = M3G_FALSE;
break;
case M3G_CLASS_MESH:
isCompatible = (classID == M3G_CLASS_MESH)
|| (classID == M3G_CLASS_MORPHING_MESH)
|| (classID == M3G_CLASS_SKINNED_MESH);
break;
default:
isCompatible = (classID == objClassID);
}
}
if (!isCompatible) {
/* Error, class mismatch */
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return NULL;
}
/* Mark object as referenced */
m3gReferenceLoaded(&loader->refArray, idx);
return obj;
}
/*!
* \internal
* \brief Loads Object3D data
*/
static M3Gbool m3gLoadObject3DData(Loader *loader, M3GObject obj)
{
M3Guint animTracks, i, userParams, paramId, paramLength;
UserData *userData = NULL;
M3Gubyte *data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
m3gSetUserID(obj, m3gLoadInt(data));
data += 4;
animTracks = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (animTracks >= 0x1fffffff) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, animTracks * 4 + 4) == M3G_FALSE) return M3G_FALSE;
for (i = 0; i < animTracks; i++) {
M3GAnimationTrack at = (M3GAnimationTrack)m3gGetLoaded(loader, m3gLoadInt(data),M3G_CLASS_ANIMATION_TRACK);
if (at == NULL || m3gAddAnimationTrack(obj, at) == -1) {
return M3G_FALSE;
}
data += 4;
}
userParams = m3gLoadInt(data);
data += 4;
if (userParams != 0) {
/* Overflow? */
if (userParams >= 0x10000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, userParams * 8) == M3G_FALSE)
return M3G_FALSE; /* Check the minimum size to avoid useless allocation */
userData = (UserData *)m3gAllocZ(M3G_INTERFACE(loader), sizeof(UserData));
if (userData == NULL)
return M3G_FALSE;
userData->object = obj;
userData->numParams = userParams;
userData->params = (M3Gbyte **)m3gAllocZ(M3G_INTERFACE(loader), userParams*sizeof(M3Gbyte *));
userData->paramLengths = (M3Gsizei *)m3gAlloc(M3G_INTERFACE(loader), userParams*sizeof(M3Gsizei));
userData->paramId = (M3Gint *)m3gAlloc(M3G_INTERFACE(loader), userParams*sizeof(M3Gint));
if (userData->params == NULL ||
userData->paramLengths == NULL ||
userData->paramId == NULL ||
m3gArrayAppend(&loader->userDataArray, userData, M3G_INTERFACE(loader)) == -1) {
m3gFree(M3G_INTERFACE(loader), userData->params);
m3gFree(M3G_INTERFACE(loader), userData->paramLengths);
m3gFree(M3G_INTERFACE(loader), userData->paramId);
m3gFree(M3G_INTERFACE(loader), userData);
return M3G_FALSE;
}
for (i = 0; i < userParams; i++) {
if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) return M3G_FALSE;
paramId = m3gLoadInt(data);
data += 4;
paramLength = m3gLoadInt(data);
data += 4;
userData->paramId[i] = paramId;
userData->paramLengths[i] = paramLength;
if (m3gCheckSectionDataLength(loader, data, paramLength) == M3G_FALSE) return M3G_FALSE;
userData->params[i] = (M3Gbyte *)m3gAlloc(M3G_INTERFACE(loader), paramLength*sizeof(M3Gbyte));
if (userData->params[i] == NULL)
return M3G_FALSE;
m3gCopy(userData->params[i], data, paramLength);
data += paramLength;
}
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Skips Object3D data
*/
static M3Gbool m3gSkipObject3DData(Loader *loader)
{
M3Guint animTracks, i, userParams, paramLength;
M3Gubyte *data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
data += 4;
animTracks = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (animTracks >= 0x1fffffff) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, animTracks * 4 + 4) == M3G_FALSE) return M3G_FALSE;
for (i = 0; i < animTracks; i++) {
data += 4;
}
userParams = m3gLoadInt(data);
data += 4;
for (i = 0; i < userParams; i++) {
if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) return M3G_FALSE;
data += 4;
paramLength = m3gLoadInt(data);
if (m3gCheckSectionDataLength(loader, data, paramLength) == M3G_FALSE) return M3G_FALSE;
data += 4 + paramLength;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads transformable data
*/
static M3Gbool m3gLoadTransformableData(Loader *loader, M3GTransformable obj)
{
M3Gfloat f1, f2, f3, f4;
M3Gubyte *data;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 1);
if (data == NULL) return M3G_FALSE;
if (!m3gVerifyBool(data)) return M3G_FALSE;
/* Component transform ? */
if (*data++) {
if (m3gCheckSectionDataLength(loader, data, 40) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadFloat(data + 0, &f1) ||
!m3gLoadFloat(data + 4, &f2) ||
!m3gLoadFloat(data + 8, &f3))
return M3G_FALSE;
m3gSetTranslation(obj, f1, f2, f3);
if (!m3gLoadFloat(data + 12, &f1) ||
!m3gLoadFloat(data + 16, &f2) ||
!m3gLoadFloat(data + 20, &f3))
return M3G_FALSE;
m3gSetScale(obj, f1, f2, f3);
if (!m3gLoadFloat(data + 24, &f1) ||
!m3gLoadFloat(data + 28, &f2) ||
!m3gLoadFloat(data + 32, &f3) ||
!m3gLoadFloat(data + 36, &f4))
return M3G_FALSE;
m3gSetOrientation(obj, f1, f2, f3, f4);
data += 40;
}
if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
if (!m3gVerifyBool(data)) return M3G_FALSE;
/* Generic transform */
if (*data++) {
Matrix m;
if (m3gCheckSectionDataLength(loader, data, 64) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadMatrix(&m, data)) return M3G_FALSE;
m3gSetTransform(obj, &m);
data += 64;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Skips transformable data
*/
static M3Gbool m3gSkipTransformableData(Loader *loader)
{
M3Gubyte *data;
if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 1);
if (data == NULL) return M3G_FALSE;
/* Component transform ? */
if (*data++) {
if (m3gCheckSectionDataLength(loader, data, 40) == M3G_FALSE) return M3G_FALSE;
data += 40;
}
if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
/* Generic transform */
if (*data++) {
if (m3gCheckSectionDataLength(loader, data, 64) == M3G_FALSE) return M3G_FALSE;
data += 64;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads node data
*/
static M3Gbool m3gLoadNodeData(Loader *loader, M3GNode obj)
{
M3Gubyte *data;
if (!m3gLoadTransformableData(loader, (M3GTransformable)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gEnable(obj, 0, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gEnable(obj, 1, *data++);
{
unsigned a = *data++;
m3gSetAlphaFactor(obj, m3gDivif(a, 255));
}
m3gSetScope(obj, m3gLoadInt(data));
data += 4;
if (!m3gVerifyBool(data)) return M3G_FALSE;
if (*data++) {
M3Gubyte zt, yt;
M3Gint zr, yr;
if (m3gCheckSectionDataLength(loader, data, 10) == M3G_FALSE) return M3G_FALSE;
zt = *data++;
yt = *data++;
zr = m3gLoadInt(data);
yr = m3gLoadInt(data + 4);
m3gSetAlignment(obj, (M3GNode)m3gGetLoaded(loader, zr, ANY_NODE_CLASS),
zt,
(M3GNode)m3gGetLoaded(loader, yr, ANY_NODE_CLASS),
yt);
data += 8;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Skips node data
*/
static M3Gbool m3gSkipNodeData(Loader *loader)
{
M3Gubyte *data;
if (!m3gSkipTransformableData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
data += 7;
/* Alignment? */
if (*data++) {
if (m3gCheckSectionDataLength(loader, data, 10) == M3G_FALSE) return M3G_FALSE;
data += 10;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a camera
*/
static M3Gbool m3gLoadCamera(Loader *loader)
{
M3Gfloat f1, f2, f3, f4;
M3Gubyte *data;
M3GCamera obj = m3gCreateCamera(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 1);
if (data == NULL) return M3G_FALSE;
switch(*data++) {
case M3G_GENERIC:
{
Matrix m;
if (m3gCheckSectionDataLength(loader, data, 64) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadMatrix(&m, data)) return M3G_FALSE;
m3gSetProjectionMatrix(obj, &m);
data += 64;
}
break;
case M3G_PERSPECTIVE:
if (m3gCheckSectionDataLength(loader, data, 16) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadFloat(data + 0, &f1) ||
!m3gLoadFloat(data + 4, &f2) ||
!m3gLoadFloat(data + 8, &f3) ||
!m3gLoadFloat(data + 12, &f4))
return M3G_FALSE;
m3gSetPerspective(obj, f1, f2, f3, f4);
data += 16;
break;
case M3G_PARALLEL:
if (m3gCheckSectionDataLength(loader, data, 16) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadFloat(data + 0, &f1) ||
!m3gLoadFloat(data + 4, &f2) ||
!m3gLoadFloat(data + 8, &f3) ||
!m3gLoadFloat(data + 12, &f4))
return M3G_FALSE;
m3gSetParallel(obj, f1, f2, f3, f4);
data += 16;
break;
default:
/* Error */
break;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a background
*/
static M3Gbool m3gLoadBackground(Loader *loader)
{
M3Gubyte *data;
M3GBackground obj = m3gCreateBackground(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 28);
if (data == NULL) return M3G_FALSE;
m3gSetBgColor(obj, m3gLoadARGB(data));
data += 4;
m3gSetBgImage(obj, (M3GImage)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_IMAGE));
data += 4;
m3gSetBgMode(obj, data[0], data[1]);
data += 2;
m3gSetBgCrop(obj, m3gLoadInt(data),
m3gLoadInt(data + 4),
m3gLoadInt(data + 8),
m3gLoadInt(data + 12) );
data += 16;
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetBgEnable(obj, 0, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetBgEnable(obj, 1, *data++);
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a vertex array
*/
static M3Gbool m3gLoadVertexArray(Loader *loader)
{
M3Gint i, j;
M3Guint size;
M3Gushort vertices;
M3Gubyte *data, components, encoding;
M3Gdatatype componentSize;
M3GVertexArray obj;
m3gBeginObject(loader);
if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 5);
if (data == NULL) return M3G_FALSE;
if (*data != 1 && *data != 2) return M3G_FALSE;
componentSize = (*data++ == 1) ? M3G_BYTE : M3G_SHORT;
components = *data++;
encoding = *data++;
vertices = m3gLoadShort(data);
data += 2;
size = vertices * components * (componentSize == M3G_BYTE ? 1 : 2);
/* Overflow? */
if (size < vertices) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, size) == M3G_FALSE) return M3G_FALSE;
obj = m3gCreateVertexArray(M3G_INTERFACE(loader), vertices, components, componentSize);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (componentSize == M3G_BYTE) {
M3Gbyte previousValues[4];
m3gZero(previousValues, sizeof(previousValues));
for (i = 0; i < vertices; i++) {
for (j = 0; j < components; j++) {
if (encoding == 0) {
previousValues[j] = *data++;
}
else {
previousValues[j] = (M3Gbyte) (previousValues[j] + *data++);
}
}
m3gSetVertexArrayElements(obj, i, 1, sizeof(previousValues), componentSize, previousValues);
}
}
else {
M3Gshort previousValues[4];
m3gZero(previousValues, sizeof(previousValues));
for (i = 0; i < vertices; i++) {
for (j = 0; j < components; j++) {
if (encoding == 0) {
previousValues[j] = m3gLoadShort(data);
}
else {
previousValues[j] = (M3Gshort) (previousValues[j] + m3gLoadShort(data));
}
data += 2;
}
m3gSetVertexArrayElements(obj, i, 1, sizeof(previousValues), componentSize, previousValues);
}
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a vertex buffer
*/
static M3Gbool m3gLoadVertexBuffer(Loader *loader)
{
M3Gubyte *data;
M3GVertexArray va;
M3Gfloat bias[3], scale;
M3Guint i, taCount;
M3GVertexBuffer obj = m3gCreateVertexBuffer(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 36);
if (data == NULL) return M3G_FALSE;
m3gSetVertexDefaultColor(obj, m3gLoadARGB(data));
data += 4;
/* Positions */
va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
data += 4;
if (!m3gLoadFloat(data + 0, &bias[0])) return M3G_FALSE;
if (!m3gLoadFloat(data + 4, &bias[1])) return M3G_FALSE;
if (!m3gLoadFloat(data + 8, &bias[2])) return M3G_FALSE;
if (!m3gLoadFloat(data + 12, &scale)) return M3G_FALSE;
if (va != NULL) {
m3gSetVertexArray(obj, va, scale, bias, 3);
}
data += 16;
/* Normals */
va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
data += 4;
if (va != NULL) {
m3gSetNormalArray(obj, va);
}
/* Colors */
va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
data += 4;
if (va != NULL) {
m3gSetColorArray(obj, va);
}
/* Texture coordinates */
taCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (taCount >= 0x0ccccccc) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, taCount * 20) == M3G_FALSE) return M3G_FALSE;
for (i = 0; i < taCount; i++) {
va = (M3GVertexArray)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_ARRAY);
data += 4;
if (!m3gLoadFloat(data + 0, &bias[0])) return M3G_FALSE;
if (!m3gLoadFloat(data + 4, &bias[1])) return M3G_FALSE;
if (!m3gLoadFloat(data + 8, &bias[2])) return M3G_FALSE;
if (!m3gLoadFloat(data + 12, &scale)) return M3G_FALSE;
if (va != NULL) {
m3gSetTexCoordArray(obj, i, va, scale, bias, 3);
}
else {
return M3G_FALSE;
}
data += 16;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a triangle strip array
*/
static M3Gbool m3gLoadTsa(Loader *loader)
{
M3Gubyte *data;
M3GIndexBuffer obj = 0;
M3Gubyte encoding;
M3Guint startIndex = 0, indicesCount = 0, *indices = NULL;
M3Guint lengthCount;
M3Gint *lengths = NULL;
M3Guint i;
m3gBeginObject(loader);
if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 1);
if (data == NULL) return M3G_FALSE;
encoding = *data++;
switch(encoding) {
case 0:
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
startIndex = m3gLoadInt(data);
data += 4;
break;
case 1:
if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
startIndex = *data++;
break;
case 2:
if (m3gCheckSectionDataLength(loader, data, 2) == M3G_FALSE) return M3G_FALSE;
startIndex = (M3Gushort) m3gLoadShort(data);
data += 2;
break;
case 128:
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
indicesCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (indicesCount >= 0x20000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, indicesCount * 4) == M3G_FALSE) return M3G_FALSE;
indices = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * indicesCount);
if (!indices) return M3G_FALSE;
for (i = 0; i < indicesCount; i++ ) {
indices[i] = m3gLoadInt(data);
data += 4;
}
break;
case 129:
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
indicesCount = m3gLoadInt(data);
data += 4;
if (m3gCheckSectionDataLength(loader, data, indicesCount) == M3G_FALSE) return M3G_FALSE;
indices = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * indicesCount);
if (!indices) return M3G_FALSE;
for (i = 0; i < indicesCount; i++ ) {
indices[i] = *data++;
}
break;
case 130:
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
indicesCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (indicesCount >= 0x40000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, indicesCount * 2) == M3G_FALSE) return M3G_FALSE;
indices = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * indicesCount);
if (!indices) return M3G_FALSE;
for (i = 0; i < indicesCount; i++) {
indices[i] = (M3Gushort)m3gLoadShort(data);
data += 2;
}
break;
default:
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) goto cleanup;
lengthCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (lengthCount >= 0x20000000) {
goto cleanup;
}
if (m3gCheckSectionDataLength(loader, data, lengthCount * 4) == M3G_FALSE) goto cleanup;
lengths = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gint) * lengthCount);
if (!lengths) goto cleanup;
for (i = 0; i < lengthCount; i++) {
lengths[i] = m3gLoadInt(data);
data += 4;
}
if (encoding == 0 || encoding == 1 || encoding == 2) {
obj = m3gCreateImplicitStripBuffer( M3G_INTERFACE(loader),
lengthCount,
lengths,
startIndex);
}
else {
obj = m3gCreateStripBuffer( M3G_INTERFACE(loader),
M3G_TRIANGLE_STRIPS,
lengthCount,
lengths,
M3G_INT,
indicesCount,
indices);
}
cleanup:
m3gFree(M3G_INTERFACE(loader), indices);
m3gFree(M3G_INTERFACE(loader), lengths);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a compositing mode
*/
static M3Gbool m3gLoadCompositingMode(Loader *loader)
{
M3Gfloat f1, f2;
M3Gubyte *data;
M3GCompositingMode obj = m3gCreateCompositingMode(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 14);
if (data == NULL) return M3G_FALSE;
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gEnableDepthTest (obj, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gEnableDepthWrite (obj, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gEnableColorWrite (obj, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetAlphaWriteEnable (obj, *data++);
m3gSetBlending (obj, *data++);
{
unsigned a = *data++;
m3gSetAlphaThreshold(obj, m3gDivif(a, 255));
}
if (!m3gLoadFloat(data, &f1) || !m3gLoadFloat(data + 4, &f2)) return M3G_FALSE;
m3gSetDepthOffset (obj, f1, f2);
data += 8;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a polygon mode
*/
static M3Gbool m3gLoadPolygonMode(Loader *loader)
{
M3Gubyte *data;
M3GPolygonMode obj = m3gCreatePolygonMode(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 6);
if (data == NULL) return M3G_FALSE;
m3gSetCulling (obj, *data++);
m3gSetShading (obj, *data++);
m3gSetWinding (obj, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetTwoSidedLightingEnable (obj, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetLocalCameraLightingEnable (obj, *data++);
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetPerspectiveCorrectionEnable (obj, *data++);
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads an appearance
*/
static M3Gbool m3gLoadAppearance(Loader *loader)
{
M3Guint textures, i;
M3Gubyte *data;
M3GAppearance obj = m3gCreateAppearance(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 21);
if (data == NULL) return M3G_FALSE;
m3gSetLayer(obj, (M3Gbyte)*data++);
m3gSetCompositingMode(obj, (M3GCompositingMode)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_COMPOSITING_MODE));
data += 4;
m3gSetFog(obj, (M3GFog)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_FOG));
data += 4;
m3gSetPolygonMode(obj, (M3GPolygonMode)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_POLYGON_MODE));
data += 4;
m3gSetMaterial(obj, (M3GMaterial)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_MATERIAL));
data += 4;
textures = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (textures >= 0x20000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, textures * 4) == M3G_FALSE) return M3G_FALSE;
for (i = 0; i < textures; i++) {
M3GTexture tex = (M3GTexture)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_TEXTURE);
if (!tex) {
return M3G_FALSE;
}
m3gSetTexture(obj, i, tex);
data += 4;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a mesh
*/
static M3Gbool m3gLoadMesh(Loader *loader)
{
M3Guint subMeshCount, i;
M3GVertexBuffer vb;
M3GIndexBuffer *ib = NULL;
M3GAppearance *ap = NULL;
M3Gubyte *data;
M3GMesh obj = NULL;
m3gBeginObject(loader);
if (!m3gSkipNodeData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
vb = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
if (vb == NULL) return M3G_FALSE;
data += 4;
subMeshCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (subMeshCount >= 0x10000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, subMeshCount * 8) == M3G_FALSE) return M3G_FALSE;
ib = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ib) * subMeshCount);
ap = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ap) * subMeshCount);
if (!ib || !ap) goto cleanup;
for (i = 0; i < subMeshCount; i++) {
M3GIndexBuffer indexBuffer;
indexBuffer = (M3GIndexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_INDEX_BUFFER);
if (indexBuffer != NULL && loader->triConstraint != 0) {
loader->triCount += indexBuffer->indexCount;
if (loader->triCount > loader->triConstraint) goto cleanup;
}
ib[i] = indexBuffer;
data += 4;
ap[i] = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
data += 4;
}
obj = m3gCreateMesh( M3G_INTERFACE(loader),
vb,
ib,
ap,
subMeshCount);
cleanup:
m3gFree(M3G_INTERFACE(loader), ib);
m3gFree(M3G_INTERFACE(loader), ap);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads group data
*/
static M3Gbool m3gLoadGroupData(Loader *loader, M3GGroup obj)
{
M3Guint childCount, i;
M3Gubyte *data;
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 4);
if (data == NULL) return M3G_FALSE;
childCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (childCount >= 0x20000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, childCount * 4) == M3G_FALSE) return M3G_FALSE;
for (i = 0; i < childCount; i++) {
m3gAddChild(obj, (M3GNode)m3gGetLoaded(loader, m3gLoadInt(data), ANY_NODE_CLASS));
data += 4;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a group
*/
static M3Gbool m3gLoadGroup(Loader *loader)
{
M3GGroup obj = m3gCreateGroup(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadGroupData(loader, obj)) {
return M3G_FALSE;
}
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a world
*/
static M3Gbool m3gLoadWorld(Loader *loader)
{
M3GCamera cam;
M3Gubyte *data;
M3GWorld obj = m3gCreateWorld(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadGroupData(loader, (M3GGroup)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
cam = (M3GCamera)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_CAMERA);
data += 4;
if (cam != NULL) {
m3gSetActiveCamera(obj, cam);
}
m3gSetBackground(obj, (M3GBackground)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_BACKGROUND));
data += 4;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a light
*/
static M3Gbool m3gLoadLight(Loader *loader)
{
M3Gfloat f1, f2, f3;
M3Gubyte *data;
M3GLight obj = m3gCreateLight(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 28);
if (data == NULL) return M3G_FALSE;
if (!m3gLoadFloat(data, &f1) ||
!m3gLoadFloat(data + 4, &f2) ||
!m3gLoadFloat(data + 8, &f3)) return M3G_FALSE;
m3gSetAttenuation (obj, f1, f2, f3);
data += 12;
m3gSetLightColor (obj, m3gLoadRGB(data));
data += 3;
m3gSetLightMode (obj, *data++);
if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
m3gSetIntensity (obj, f1);
data += 4;
if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
m3gSetSpotAngle (obj, f1);
data += 4;
if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
m3gSetSpotExponent (obj, f1);
data += 4;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a keyframe sequence
*/
static M3Gbool m3gLoadKeyframeSequence(Loader *loader)
{
M3Guint i, j, interpolation, repeatMode, encoding, duration,
rangeFirst, rangeLast, components, keyFrames, size;
M3Gfloat *values;
M3Gubyte *data;
M3GKeyframeSequence obj;
m3gBeginObject(loader);
if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 23);
if (data == NULL) return M3G_FALSE;
interpolation = *data++;
repeatMode = *data++;
encoding = *data++;
duration = m3gLoadInt(data);
data += 4;
rangeFirst = m3gLoadInt(data);
data += 4;
rangeLast = m3gLoadInt(data);
data += 4;
components = m3gLoadInt(data);
data += 4;
keyFrames = m3gLoadInt(data);
data += 4;
if (encoding == 0) {
size = keyFrames * (4 + components * 4);
}
else {
size = components * 8 + keyFrames * (4 + components * (encoding == 1 ? 1 : 2));
}
/* Overflow? */
if (size < keyFrames) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, size) == M3G_FALSE) return M3G_FALSE;
obj = m3gCreateKeyframeSequence(M3G_INTERFACE(loader), keyFrames, components, interpolation);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gSetRepeatMode(obj, repeatMode);
m3gSetDuration(obj, duration);
m3gSetValidRange(obj, rangeFirst, rangeLast);
values = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gfloat) * components);
if (!values) return M3G_FALSE;
if (encoding == 0) {
for (i = 0; i < keyFrames; i++ ) {
M3Gint time = m3gLoadInt(data);
data += 4;
for (j = 0; j < components; j++ ) {
if (!m3gLoadFloat(data, &values[j])) {
m3gFree(M3G_INTERFACE(loader), values);
return M3G_FALSE;
}
data += 4;
}
m3gSetKeyframe(obj, i, time, components, values);
}
}
else {
M3Gfloat *vectorBiasScale = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gfloat) * components * 2);
if (!vectorBiasScale) {
m3gFree(M3G_INTERFACE(loader), values);
return M3G_FALSE;
}
for (i = 0; i < components; i++ ) {
if (!m3gLoadFloat(data, &vectorBiasScale[i])) {
m3gFree(M3G_INTERFACE(loader), vectorBiasScale);
m3gFree(M3G_INTERFACE(loader), values);
return M3G_FALSE;
}
data += 4;
}
for (i = 0; i < components; i++ ) {
if (!m3gLoadFloat(data, &vectorBiasScale[i + components])) {
m3gFree(M3G_INTERFACE(loader), vectorBiasScale);
m3gFree(M3G_INTERFACE(loader), values);
return M3G_FALSE;
}
data += 4;
}
for (i = 0; i < keyFrames; i++ ) {
M3Gint time;
time = m3gLoadInt(data);
data += 4;
if (encoding == 1) {
for (j = 0; j < components; j++ ) {
M3Gubyte v = *data++;
values[j] = vectorBiasScale[j] + ((vectorBiasScale[j + components] * v ) / 255.0f);
}
}
else {
for (j = 0; j < components; j++ ) {
M3Gushort v = m3gLoadShort(data);
data += 2;
values[j] = vectorBiasScale[j] + ((vectorBiasScale[j + components] * v) / 65535.0f);
}
}
m3gSetKeyframe(obj, i, time, components, values);
}
m3gFree(M3G_INTERFACE(loader), vectorBiasScale);
}
m3gFree(M3G_INTERFACE(loader), values);
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads an animation controller
*/
static M3Gbool m3gLoadAnimationController(Loader *loader)
{
M3Gfloat speed, weight, referenceSeqTime;
M3Gint referenceWorldTime;
M3Gubyte *data;
M3GAnimationController obj = m3gCreateAnimationController(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 24);
if (data == NULL) return M3G_FALSE;
if (!m3gLoadFloat(data, &speed)) return M3G_FALSE;
data += 4;
if (!m3gLoadFloat(data, &weight)) return M3G_FALSE;
data += 4;
m3gSetActiveInterval(obj, m3gLoadInt(data), m3gLoadInt(data + 4));
data += 8;
if (!m3gLoadFloat(data, &referenceSeqTime)) return M3G_FALSE;
data += 4;
referenceWorldTime = m3gLoadInt(data);
data += 4;
m3gSetPosition(obj, referenceSeqTime, referenceWorldTime);
m3gSetSpeed(obj, speed, referenceWorldTime);
m3gSetWeight(obj, weight);
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads an animation track
*/
static M3Gbool m3gLoadAnimationTrack(Loader *loader)
{
M3Gint property;
M3GKeyframeSequence ks;
M3GAnimationController ac;
M3Gubyte *data;
M3GAnimationTrack obj;
m3gBeginObject(loader);
if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 12);
if (data == NULL) return M3G_FALSE;
ks = (M3GKeyframeSequence)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_KEYFRAME_SEQUENCE);
data += 4;
ac = (M3GAnimationController)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_ANIMATION_CONTROLLER);
data += 4;
property = m3gLoadInt(data);
data += 4;
obj = m3gCreateAnimationTrack(M3G_INTERFACE(loader), ks, property);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gSetController(obj, ac);
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a material
*/
static M3Gbool m3gLoadMaterial(Loader *loader)
{
M3Gfloat f1;
M3Gubyte *data;
M3GMaterial obj = m3gCreateMaterial(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 18);
if (data == NULL) return M3G_FALSE;
m3gSetColor(obj, M3G_AMBIENT_BIT, m3gLoadRGB(data));
data += 3;
m3gSetColor(obj, M3G_DIFFUSE_BIT, m3gLoadARGB(data));
data += 4;
m3gSetColor(obj, M3G_EMISSIVE_BIT, m3gLoadRGB(data));
data += 3;
m3gSetColor(obj, M3G_SPECULAR_BIT, m3gLoadRGB(data));
data += 3;
if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
m3gSetShininess(obj, f1);
data += 4;
if (!m3gVerifyBool(data)) return M3G_FALSE;
m3gSetVertexColorTrackingEnable(obj, *data++);
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a fog
*/
static M3Gbool m3gLoadFog(Loader *loader)
{
M3Gfloat f1, f2;
M3Gubyte *data;
M3GFog obj = m3gCreateFog(M3G_INTERFACE(loader));
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
data = m3gGetSectionDataPtr(loader, 4);
if (data == NULL) return M3G_FALSE;
m3gSetFogColor(obj, m3gLoadRGB(data));
data += 3;
m3gSetFogMode(obj, *data);
if (*data++ == M3G_EXPONENTIAL_FOG) {
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadFloat(data, &f1)) return M3G_FALSE;
m3gSetFogDensity(obj, f1);
data += 4;
}
else {
if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) return M3G_FALSE;
if (!m3gLoadFloat(data, &f1) || !m3gLoadFloat(data + 4, &f2)) return M3G_FALSE;
m3gSetFogLinear(obj, f1, f2);
data += 8;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads an image
*/
static M3Gbool m3gLoadImage(Loader *loader)
{
M3GImageFormat format;
M3Guint width, height;
M3Gbyte isMutable;
M3Gubyte *data;
M3GImage obj;
m3gBeginObject(loader);
if (!m3gSkipObject3DData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 10);
if (data == NULL) return M3G_FALSE;
format = (M3GImageFormat)*data++;
if (!m3gVerifyBool(data)) return M3G_FALSE;
isMutable = *data++;
width = m3gLoadInt(data);
data += 4;
height = m3gLoadInt(data);
data += 4;
if (isMutable) {
obj = m3gCreateImage(M3G_INTERFACE(loader), format,
width, height,
M3G_RENDERING_TARGET);
}
else {
M3Gubyte *palette = NULL, *pixels = NULL;
M3Gint paletteLength, pixelsLength, bpp;
switch(format) {
case M3G_ALPHA: bpp = 1; break;
case M3G_LUMINANCE: bpp = 1; break;
case M3G_LUMINANCE_ALPHA: bpp = 2; break;
case M3G_RGB: bpp = 3; break;
case M3G_RGBA: bpp = 4; break;
default: return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
paletteLength = m3gLoadInt(data);
data += 4;
if (paletteLength > 0) {
if (m3gCheckSectionDataLength(loader, data, paletteLength) == M3G_FALSE) return M3G_FALSE;
palette = data;
data += paletteLength;
}
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) return M3G_FALSE;
pixelsLength = m3gLoadInt(data);
data += 4;
if (m3gCheckSectionDataLength(loader, data, pixelsLength) == M3G_FALSE) return M3G_FALSE;
pixels = data;
data += pixelsLength;
if (palette != NULL) {
obj = m3gCreateImage(M3G_INTERFACE(loader), format,
width, height,
M3G_PALETTED);
if (obj != NULL) {
M3Gint numEntries = paletteLength / bpp;
if (numEntries > 256) {
numEntries = 256;
}
m3gSetImage(obj, pixels);
m3gSetImagePalette(obj, numEntries, palette);
m3gCommitImage(obj);
}
}
else {
obj = m3gCreateImage(M3G_INTERFACE(loader), format,
width, height,
0);
if (obj != NULL) {
m3gSetImage(obj, pixels);
m3gCommitImage(obj);
}
}
}
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadObject3DData(loader, (M3GObject)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a texture
*/
static M3Gbool m3gLoadTexture(Loader *loader)
{
M3GImage image;
M3Gubyte *data;
M3GTexture obj;
m3gBeginObject(loader);
if (!m3gSkipTransformableData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 12);
if (data == NULL) return M3G_FALSE;
image = (M3GImage)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_IMAGE);
data += 4;
obj = m3gCreateTexture(M3G_INTERFACE(loader), image);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gSetBlendColor(obj, m3gLoadRGB(data));
data += 3;
m3gTextureSetBlending(obj, *data++);
m3gSetWrapping(obj, data[0], data[1]);
data += 2;
m3gSetFiltering(obj, data[0], data[1]);
data += 2;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadTransformableData(loader, (M3GTransformable)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a skinned mesh
*/
static M3Gbool m3gLoadSkinnedMesh(Loader *loader)
{
M3GVertexBuffer vb;
M3Guint i, subMeshCount, transformReferenceCount, firstVertex, vertexCount;
M3Gint weight;
M3GIndexBuffer *ib = NULL;
M3GAppearance *ap = NULL;
M3GGroup skeleton;
M3GNode bone;
M3Gubyte *data;
M3GSkinnedMesh obj = NULL;
m3gBeginObject(loader);
if (!m3gSkipNodeData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
vb = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
if (vb == NULL) return M3G_FALSE;
data += 4;
subMeshCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (subMeshCount >= 0x10000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, subMeshCount * 8) == M3G_FALSE) return M3G_FALSE;
ib = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ib) * subMeshCount);
ap = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ap) * subMeshCount);
if (!ib || !ap) goto cleanup;
for (i = 0; i < subMeshCount; i++) {
ib[i] = (M3GIndexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_INDEX_BUFFER);
data += 4;
ap[i] = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
data += 4;
}
if (m3gCheckSectionDataLength(loader, data, 8) == M3G_FALSE) goto cleanup;
skeleton = (M3GGroup)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_GROUP);
data += 4;
obj = m3gCreateSkinnedMesh( M3G_INTERFACE(loader),
vb,
ib,
ap,
subMeshCount,
skeleton);
cleanup:
m3gFree(M3G_INTERFACE(loader), ib);
m3gFree(M3G_INTERFACE(loader), ap);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
transformReferenceCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (transformReferenceCount >= 0x08000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, transformReferenceCount * 16) == M3G_FALSE) return M3G_FALSE;
for (i = 0; i < transformReferenceCount; i++) {
bone = (M3GNode)m3gGetLoaded(loader, m3gLoadInt(data), ANY_NODE_CLASS);
data += 4;
firstVertex = m3gLoadInt(data);
data += 4;
vertexCount = m3gLoadInt(data);
data += 4;
weight = m3gLoadInt(data);
data += 4;
m3gAddTransform(obj, bone, weight, firstVertex, vertexCount);
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a morphing mesh
*/
static M3Gbool m3gLoadMorphingMesh(Loader *loader)
{
M3GVertexBuffer vb, *targets = NULL;
M3Guint i, subMeshCount, targetCount;
M3Gfloat *weights = NULL;
M3GIndexBuffer *ib = NULL;
M3GAppearance *ap = NULL;
M3Gubyte *data;
M3GMorphingMesh obj = NULL;
m3gBeginObject(loader);
if (!m3gSkipNodeData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 8);
if (data == NULL) return M3G_FALSE;
vb = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
if (vb == NULL) return M3G_FALSE;
data += 4;
subMeshCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (subMeshCount >= 0x10000000) {
return M3G_FALSE;
}
if (m3gCheckSectionDataLength(loader, data, subMeshCount * 8) == M3G_FALSE) return M3G_FALSE;
ib = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ib) * subMeshCount);
ap = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*ap) * subMeshCount);
if (!ib || !ap) goto cleanup;
for (i = 0; i < subMeshCount; i++) {
ib[i] = (M3GIndexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_INDEX_BUFFER);
data += 4;
ap[i] = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
data += 4;
}
if (m3gCheckSectionDataLength(loader, data, 4) == M3G_FALSE) goto cleanup;
targetCount = m3gLoadInt(data);
data += 4;
/* Overflow? */
if (targetCount >= 0x10000000) {
goto cleanup;
}
if (m3gCheckSectionDataLength(loader, data, targetCount * 8) == M3G_FALSE) goto cleanup;
weights = m3gAllocZ(M3G_INTERFACE(loader), sizeof(M3Gfloat) * targetCount);
targets = m3gAllocZ(M3G_INTERFACE(loader), sizeof(*targets) * targetCount);
if (!weights || !targets) goto cleanup;
for (i = 0; i < targetCount; i++) {
targets[i] = (M3GVertexBuffer)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_VERTEX_BUFFER);
data += 4;
if (!m3gLoadFloat(data, &weights[i])) goto cleanup;
data += 4;
}
obj = m3gCreateMorphingMesh( M3G_INTERFACE(loader),
vb,
targets,
ib,
ap,
subMeshCount,
targetCount);
cleanup:
m3gFree(M3G_INTERFACE(loader), ib);
m3gFree(M3G_INTERFACE(loader), ap);
m3gFree(M3G_INTERFACE(loader), targets);
m3gFree(M3G_INTERFACE(loader), weights);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a sprite
*/
static M3Gbool m3gLoadSprite(Loader *loader)
{
M3GImage image;
M3GAppearance appearance;
M3Gubyte *data;
M3GSprite obj;
m3gBeginObject(loader);
if (!m3gSkipNodeData(loader)) return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 25);
if (data == NULL) return M3G_FALSE;
image = (M3GImage)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_IMAGE);
data += 4;
appearance = (M3GAppearance)m3gGetLoaded(loader, m3gLoadInt(data), M3G_CLASS_APPEARANCE);
data += 4;
if (!m3gVerifyBool(data)) return M3G_FALSE;
obj = m3gCreateSprite( M3G_INTERFACE(loader),
*data++,
image,
appearance);
loader->loadedObject = (M3GObject)obj;
if (!obj) return M3G_FALSE;
m3gSetCrop(obj, m3gLoadInt(data),
m3gLoadInt(data + 4),
m3gLoadInt(data + 8),
m3gLoadInt(data + 12));
data += 16;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
m3gEndObject(loader);
m3gRewindObject(loader);
if (!m3gLoadNodeData(loader, (M3GNode)obj)) {
return M3G_FALSE;
}
m3gSkipObject(loader);
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads a M3G file header
*/
static M3Gbool m3gLoadHeader(Loader *loader)
{
M3Gubyte *data;
data = m3gGetSectionDataPtr(loader, 12);
if (data == NULL) return M3G_FALSE;
/* Check version */
if (data[0] != 1 || data[1] != 0 || loader->sectionNum != 0) {
return M3G_FALSE;
}
data += 2;
if (!m3gVerifyBool(data)) return M3G_FALSE;
loader->hasReferences = *data++;
loader->fileSize = m3gLoadInt(data);
data += 4;
loader->contentSize = m3gLoadInt(data);
data += 4;
/* Skip authoring field */
while(*data++)
if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE) return M3G_FALSE;
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Skips external reference
*/
static M3Gbool m3gLoadExternalReference(Loader *loader)
{
M3Gubyte *data;
if (loader->sectionNum != 1 || !loader->hasReferences)
return M3G_FALSE;
data = m3gGetSectionDataPtr(loader, 1);
while(*data++) { /* Skip string */
if (m3gCheckSectionDataLength(loader, data, 1) == M3G_FALSE)
return M3G_FALSE;
}
m3gAdvanceSectionData(loader, data - m3gGetSectionDataPtr(loader, 0));
return M3G_TRUE;
}
/*!
* \internal
* \brief Loads an object
*/
static LoaderState m3gLoadObject(Loader *loader)
{
M3Gubyte *data = m3gGetSectionDataPtr(loader, loader->sectionBytesRequired);
if (data == NULL) {
loader->localState = LOADSTATE_CHECKSUM;
return LOADSTATE_SECTION;
}
m3gAdvanceSectionData(loader, loader->sectionBytesRequired);
if (loader->localState == LOADSTATE_ENTER) {
M3Gbool status = M3G_TRUE;
loader->objectType = *data++;
loader->loadedObject = NULL;
loader->localState = LOADSTATE_EXIT;
loader->sectionBytesRequired = m3gLoadInt(data);
data += 4;
if (loader->sectionNum == 0 && loader->objectType != 0) {
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return LOADSTATE_ERROR;
}
switch(loader->objectType) {
case 0: /* Header Object */
status = m3gLoadHeader(loader);
break;
case 1: /* AnimationController */
status = m3gLoadAnimationController(loader);
break;
case 2: /* AnimationTrack */
status = m3gLoadAnimationTrack(loader);
break;
case 3: /* Appearance */
status = m3gLoadAppearance(loader);
break;
case 4: /* Background */
status = m3gLoadBackground(loader);
break;
case 5: /* Camera */
status = m3gLoadCamera(loader);
break;
case 6: /* CompositingMode */
status = m3gLoadCompositingMode(loader);
break;
case 7: /* Fog */
status = m3gLoadFog(loader);
break;
case 8: /* PolygonMode */
status = m3gLoadPolygonMode(loader);
break;
case 9: /* Group */
status = m3gLoadGroup(loader);
break;
case 10: /* Image2D */
status = m3gLoadImage(loader);
break;
case 11: /* TriangleStripArray */
status = m3gLoadTsa(loader);
break;
case 12: /* Light */
status = m3gLoadLight(loader);
break;
case 13: /* Material */
status = m3gLoadMaterial(loader);
break;
case 14: /* Mesh */
status = m3gLoadMesh(loader);
break;
case 15: /* MorphingMesh */
status = m3gLoadMorphingMesh(loader);
break;
case 16: /* SkinnedMesh */
status = m3gLoadSkinnedMesh(loader);
break;
case 17: /* Texture2D */
status = m3gLoadTexture(loader);
break;
case 18: /* Sprite */
status = m3gLoadSprite(loader);
break;
case 19: /* KeyframeSequence */
status = m3gLoadKeyframeSequence(loader);
break;
case 20: /* VertexArray */
status = m3gLoadVertexArray(loader);
break;
case 21: /* VertexBuffer */
status = m3gLoadVertexBuffer(loader);
break;
case 22: /* World */
status = m3gLoadWorld(loader);
break;
case 255: /* External Reference */
status = m3gLoadExternalReference(loader);
break;
default: /* 23 ... 254 Reserved for use in future versions of the file format */
status = M3G_FALSE;
break;
}
/* Check if object loading caused an error */
if (m3gErrorRaised(M3G_INTERFACE(loader))) {
m3gDeleteObject(loader->loadedObject);
return LOADSTATE_ERROR;
}
if (!status ||
m3gGetSectionDataPtr(loader, 0) != data + loader->sectionBytesRequired) {
m3gDeleteObject(loader->loadedObject);
m3gRaiseError(M3G_INTERFACE(loader), M3G_IO_ERROR);
return LOADSTATE_ERROR;
}
loader->sectionBytesRequired = 0;
/* Add object to loaded objects array */
if (loader->loadedObject != NULL) {
if (m3gArrayAppend(&loader->refArray, loader->loadedObject, M3G_INTERFACE(loader)) == -1) {
/* OOM */
m3gDeleteObject(loader->loadedObject);
return LOADSTATE_ERROR;
}
m3gAddRef(loader->loadedObject);
}
}
else {
loader->sectionBytesRequired = M3G_MIN_OBJECT_SIZE;
loader->localState = LOADSTATE_ENTER;
}
return LOADSTATE_OBJECT;
}
/*!
* \internal
* \brief Handles branching to different subroutines upon re-entry
*
* When sufficient data is available in internal buffers, this
* function gets called and directs execution into the coroutine
* matching the current global state.
*/
static LoaderState m3gLoaderMain(Loader *loader)
{
M3G_VALIDATE_OBJECT(loader);
M3G_ASSERT(loader->bytesRequired > 0);
M3G_ASSERT(loader->bytesRequired <= loader->stream.bytesAvailable);
switch (loader->state) {
case LOADSTATE_INITIAL:
loader->stream.totalBytes = 0;
loader->localState = LOADSTATE_ENTER;
loader->fileSize = 0x00ffffff;
loader->bytesRequired = sizeof(PNG_FILE_IDENTIFIER);
return LOADSTATE_IDENTIFIER;
case LOADSTATE_IDENTIFIER:
return m3gLoadIdentifier(loader);
case LOADSTATE_SECTION:
return m3gLoadSection(loader);
case LOADSTATE_OBJECT:
return m3gLoadObject(loader);
default:
loader->bytesRequired = 0;
return LOADSTATE_ERROR;
}
}
/*!
* \brief Deletes all unreferenced objects
*/
static void m3gCleanupLoader(M3GLoader loader)
{
M3Gint i, j, n;
PointerArray *refs;
M3Gbool referenced;
M3GObject obj;
refs = &loader->refArray;
n = m3gArraySize(refs);
/* All unreferenced objects will be deleted, as their ref count becomes 0 */
for (i = 0; i < n; ++i) {
obj = m3gGetLoadedPtr(refs, i, &referenced);
m3gDeleteRef(obj);
}
m3gClearArray(&loader->refArray);
n = m3gArraySize(&loader->userDataArray);
for (i = 0; i < n; ++i)
{
UserData *data = (UserData *)m3gGetArrayElement(&loader->userDataArray, i);
for (j = 0; j < data->numParams; ++j)
m3gFree(M3G_INTERFACE(loader), data->params[j]);
m3gFree(M3G_INTERFACE(loader), data->params);
m3gFree(M3G_INTERFACE(loader), data->paramLengths);
m3gFree(M3G_INTERFACE(loader), data->paramId);
m3gFree(M3G_INTERFACE(loader), data);
}
m3gClearArray(&loader->userDataArray);
m3gFree(M3G_INTERFACE(loader), loader->allocatedSectionData);
loader->allocatedSectionData = NULL;
}
/*!
* \internal
* \brief Resets the loader
*/
static void m3gResetLoader(Loader *loader)
{
/* Reset loader state */
loader->state = LOADSTATE_INITIAL;
loader->bytesRequired = sizeof(PNG_FILE_IDENTIFIER);
m3gCleanupLoader(loader);
m3gResetBufferedData(&loader->stream);
}
/*----------------------------------------------------------------------
* Virtual function table
*--------------------------------------------------------------------*/
static const ObjectVFTable m3gvf_Loader = {
NULL, /* ApplyAnimation */
NULL, /* IsCompatible */
NULL, /* UpdateProperty */
NULL, /* DoGetReferences */
NULL, /* FindID */
NULL, /* Duplicate */
m3gDestroyLoader
};
/*----------------------------------------------------------------------
* Public interface
*--------------------------------------------------------------------*/
/*!
* \brief Creates a new loader instance
*/
M3G_API M3GLoader m3gCreateLoader(M3GInterface m3g)
{
Loader *loader;
M3G_VALIDATE_INTERFACE(m3g);
loader = m3gAllocZ(m3g, sizeof(*loader));
if (loader != NULL) {
m3gInitObject(&loader->object, m3g, M3G_CLASS_LOADER);
m3gInitArray(&loader->refArray);
m3gInitArray(&loader->userDataArray);
loader->bytesRequired = sizeof(PNG_FILE_IDENTIFIER);
loader->state = LOADSTATE_INITIAL;
loader->sectionNum = -1;
}
return loader;
}
/*!
* \brief Import a set of objects into the reference table of a loader
*
* This is intended for passing in external references, decoded in
* Java
*/
M3G_API void m3gImportObjects(M3GLoader loader, M3Gint n, M3GObject *refs)
{
int i;
M3G_VALIDATE_OBJECT(loader);
if (loader->state == LOADSTATE_DONE)
m3gResetLoader(loader);
for (i = 0; i < n; ++i) {
/* For loop is interrupted in case of OOM */
if (m3gArrayAppend(&loader->refArray,
refs[i],
M3G_INTERFACE(loader)) == -1) {
break;
}
m3gAddRef(refs[i]);
}
}
/*!
* \brief Return the complete reference table for this loader instance
*
* The reference table will contain the handle of each object that has
* been loaded so far.
*
* \param loader loader instance
* \param buffer destination buffer, or NULL to just get
* the number of unreferenced objects
* \return number of unreferenced objects
*/
M3G_API M3Gint m3gGetLoadedObjects(M3GLoader loader, M3GObject *buffer)
{
PointerArray *refs;
int i, n, unref = 0;
M3Gbool referenced;
M3GObject obj, *dst = buffer;
M3G_VALIDATE_OBJECT(loader);
/* If error in decoding, reset and return 0 objects */
if (loader->state < LOADSTATE_INITIAL) {
return 0;
}
refs = &loader->refArray;
n = m3gArraySize(refs);
/* Scan unreferenced objects */
for (i = 0; i < n; ++i) {
obj = m3gGetLoadedPtr(refs, i, &referenced);
if (!referenced) {
unref++;
if (dst != NULL) {
*dst++ = obj;
}
}
}
return unref;
}
/*!
* \brief Submits data to the loader for processing
*
* Upon data input, the loader will either read more objects from the
* stream or buffer the data for processing later on. The return value
* will indicate how many bytes of data are required before the next
* data element can be loaded, but that data can still be submitted in
* smaller blocks.
*
* \param loader the loader instance
* \param bytes the number of bytes in the data
* \param data pointer to the data
*
* \return the number of bytes required to load the next data element,
* or zero to indicate that loading has finished
*/
#ifdef M3G_ENABLE_PROFILING
static M3Gsizei m3gDecodeDataInternal(M3GLoader loader,
M3Gsizei bytes,
const M3Gubyte *data);
M3G_API M3Gsizei m3gDecodeData(M3GLoader loader,
M3Gsizei bytes,
const M3Gubyte *data)
{
M3Gsizei bytesReq;
M3G_BEGIN_PROFILE(M3G_INTERFACE(loader), M3G_PROFILE_LOADER_DECODE);
bytesReq = m3gDecodeDataInternal(loader, bytes, data);
M3G_END_PROFILE(M3G_INTERFACE(loader), M3G_PROFILE_LOADER_DECODE);
return bytesReq;
}
static M3Gsizei m3gDecodeDataInternal(M3GLoader loader,
M3Gsizei bytes,
const M3Gubyte *data)
#else
M3G_API M3Gsizei m3gDecodeData(M3GLoader loader,
M3Gsizei bytes,
const M3Gubyte *data)
#endif
{
m3gErrorHandler *errorHandler;
Interface *m3g = M3G_INTERFACE(loader);
M3G_VALIDATE_OBJECT(loader);
M3G_VALIDATE_INTERFACE(m3g);
/* Check for errors */
if (bytes <= 0 || data == NULL) {
m3gRaiseError(m3g, M3G_INVALID_VALUE);
return 0;
}
if (loader->state == LOADSTATE_DONE)
m3gResetLoader(loader);
/* Submit data, then load until we run out of data again or are
* finished */
if (!m3gBufferData(m3g, &loader->stream, bytes, data)) {
return 0;
}
/* Disable error handler */
errorHandler = m3gSetErrorHandler(m3g, NULL);
/* Continue loading if sufficient data has arrived */
while (loader->bytesRequired > 0
&& loader->bytesRequired <= loader->stream.bytesAvailable) {
loader->state = m3gLoaderMain(loader);
}
/* Restore error handler */
m3gSetErrorHandler(m3g, errorHandler);
/* Check if error was raised */
if (m3gErrorRaised(m3g) != M3G_NO_ERROR) {
/* Need to free all loaded objects */
m3gResetLoader(loader);
/* Raise again with original error handler in place */
if (m3gErrorRaised(m3g) == M3G_OUT_OF_MEMORY)
m3gRaiseError(m3g, M3G_OUT_OF_MEMORY);
else
m3gRaiseError(m3g, M3G_IO_ERROR);
return 0;
}
/* Return the number of bytes we need for loading to proceed
* further; clamp to zero in case we're exiting due to an error,
* or just have been fed excess data */
{
M3Gsizei bytesReq =
loader->bytesRequired - loader->stream.bytesAvailable;
/* Check if whole file is done */
if (loader->stream.totalBytes >= loader->fileSize) {
loader->state = LOADSTATE_DONE;
bytesReq = 0;
}
return (bytesReq >= 0) ? bytesReq : 0;
}
}
/*!
* \brief Return all loaded objects with user parameters
*
* \param loader the loader instance
* \param objects an array for objects with user parameters,
* or null to return the number of objects
*
* \return Number of objects with user parameters
*/
M3G_API M3Gint m3gGetObjectsWithUserParameters(M3GLoader loader, M3GObject *objects) {
const Loader *ldr = (const Loader *) loader;
M3G_VALIDATE_OBJECT(ldr);
{
M3Gint i, n;
n = m3gArraySize(&ldr->userDataArray);
if (objects != NULL)
for (i = 0; i < n; ++i)
{
const UserData *data = (const UserData *)m3gGetArrayElement(&ldr->userDataArray, i);
objects[i] = data->object;
}
return n;
}
}
/*!
* \brief Return the number of user parameters loaded for an object
*/
M3G_API M3Gint m3gGetNumUserParameters(M3GLoader loader, M3Gint object)
{
const Loader *ldr = (const Loader *) loader;
M3G_VALIDATE_OBJECT(ldr);
{
const UserData *data = (const UserData *)m3gGetArrayElement(&ldr->userDataArray, object);
return (data != NULL) ? data->numParams : 0;
}
}
/*!
* \brief Set constraints for loading.
* \param triConstraint maximum triangle count
*/
M3G_API void m3gSetConstraints(M3GLoader loader, M3Gint triConstraint)
{
M3G_VALIDATE_OBJECT(loader);
loader->triConstraint = triConstraint;
}
/*!
* \brief Return the given user parameter for an object
*
* \param loader the loader instance
* \param object the object to query
* \param index index of the string to query
* \param buffer buffer to copy the data into,
* or NULL to just query the length
*
* \return id of the parameter, or the length of the string if data was NULL
*/
M3G_API M3Gsizei m3gGetUserParameter(M3GLoader loader,
M3Gint object,
M3Gint index,
M3Gbyte *buffer)
{
const Loader *ldr = (const Loader *) loader;
M3G_VALIDATE_OBJECT(ldr);
{
const UserData *data = (const UserData *)m3gGetArrayElement(&ldr->userDataArray, object);
if (data != NULL && m3gInRange(index, 0, data->numParams - 1)) {
const char *src = (const char *)data->params[index];
M3Gsizei len = data->paramLengths[index];
if (buffer != NULL) {
m3gCopy(buffer, src, len);
return data->paramId[index];
}
else
return len;
}
else
return 0;
}
}
#undef ANY_NODE_CLASS