#include "fmod_geometryi.h"

#ifdef FMOD_SUPPORT_GEOMETRY

#include "fmod_3d.h"
#include "fmod_memory.h"
#include "fmod_geometry_mgr.h"
#include "fmod_systemi.h"
#include "fmod_localcriticalsection.h"

#include <string.h>

namespace FMOD
{

#if defined(_DEBUG) && defined(FMOD_GEOMETRY_DEBUGGING)
#define ASSERT(x) do { if (!(x)) __asm { int 3 } } while (false)
#else
#define ASSERT(x)    
#endif

// if the sound transsmision drop this low then
// don't bother doing any more ray testing
const float MIN_TRANSMISSION = 0.05f;

static inline float MAX(float  x, float  y) { return x > y ? x : y; }
static inline float MIN(float  x, float  y) { return x < y ? x : y; }


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
static inline void cross(const FMOD_VECTOR& a, const FMOD_VECTOR& b, FMOD_VECTOR& result)
{
    result.x = a.y * b.z - a.z * b.y;
    result.y = a.z * b.x - a.x * b.z;
    result.z = a.x * b.y - a.y * b.x;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
static void matrixMult(const float matrix[3][4], const FMOD_VECTOR *src, FMOD_VECTOR *dst)
{
    dst->x = matrix[0][0] * src->x + matrix[0][1] * src->y + matrix[0][2] * src->z;
    dst->y = matrix[1][0] * src->x + matrix[1][1] * src->y + matrix[1][2] * src->z;
    dst->z = matrix[2][0] * src->x + matrix[2][1] * src->y + matrix[2][2] * src->z;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
void GeometryI::calculateMatrix()
{
    mMatrix[1][0] = mUp.x * mScale.y;
    mMatrix[1][1] = mUp.y * mScale.y;
    mMatrix[1][2] = mUp.z * mScale.y;
    mMatrix[2][0] = mForward.x * mScale.z;
    mMatrix[2][1] = mForward.y * mScale.z;
    mMatrix[2][2] = mForward.z * mScale.z;
    mMatrix[0][0] = (mUp.y * mForward.z - mUp.z * mForward.y) * mScale.x;
    mMatrix[0][1] = (mUp.z * mForward.x - mUp.x * mForward.z) * mScale.x;
    mMatrix[0][2] = (mUp.x * mForward.y - mUp.y * mForward.x) * mScale.x;

    mInvMatrix[0][1] = mUp.x / mScale.y;
    mInvMatrix[1][1] = mUp.y / mScale.y;
    mInvMatrix[2][1] = mUp.z / mScale.y;
    mInvMatrix[0][2] = mForward.x / mScale.z;
    mInvMatrix[1][2] = mForward.y / mScale.z;
    mInvMatrix[2][2] = mForward.z / mScale.z;
    mInvMatrix[0][0] = (mUp.y * mForward.z - mUp.z * mForward.y) / mScale.x;
    mInvMatrix[1][0] = (mUp.z * mForward.x - mUp.x * mForward.z) / mScale.x;
    mInvMatrix[2][0] = (mUp.x * mForward.y - mUp.y * mForward.x) / mScale.x;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
void GeometryI::updateSpatialData()
{
    // find world aabb
    FMOD_VECTOR center;
    center.x = (mAABB.xMax + mAABB.xMin) * 0.5f;
    center.y = (mAABB.yMax + mAABB.yMin) * 0.5f;
    center.z = (mAABB.zMax + mAABB.zMin) * 0.5f;
    FMOD_VECTOR newCenter;
    matrixMult(mMatrix, &center, &newCenter);
    newCenter.x += mPosition.x;
    newCenter.y += mPosition.y;
    newCenter.z += mPosition.z;


    float xExtent = (mAABB.xMax - mAABB.xMin) * 0.5f;
    float yExtent = (mAABB.yMax - mAABB.yMin) * 0.5f;
    float zExtent = (mAABB.zMax - mAABB.zMin) * 0.5f;

    float xNewExtent = (float)fabs(mMatrix[0][0]) * xExtent + (float)fabs(mMatrix[1][0]) * yExtent + (float)fabs(mMatrix[2][0]) * zExtent;
    float yNewExtent = (float)fabs(mMatrix[0][1]) * xExtent + (float)fabs(mMatrix[1][1]) * yExtent + (float)fabs(mMatrix[2][1]) * zExtent;
    float zNewExtent = (float)fabs(mMatrix[0][2]) * xExtent + (float)fabs(mMatrix[1][2]) * yExtent + (float)fabs(mMatrix[2][2]) * zExtent;


    ASSERT(mSpatialData);
    mSpatialData->octreeNode.aabb.xMax = newCenter.x + xNewExtent;
    mSpatialData->octreeNode.aabb.xMin = newCenter.x - xNewExtent;
    mSpatialData->octreeNode.aabb.yMax = newCenter.y + yNewExtent;
    mSpatialData->octreeNode.aabb.yMin = newCenter.y - yNewExtent;
    mSpatialData->octreeNode.aabb.zMax = newCenter.z + zNewExtent;
    mSpatialData->octreeNode.aabb.zMin = newCenter.z - zNewExtent;

    if (mActive)
    {
        mGeometryMgr->mainOctree()->updateItem(&mSpatialData->octreeNode);
    }
    else
    {
        mGeometryMgr->mainOctree()->deleteItem(&mSpatialData->octreeNode);
    }
}
    

/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
void GeometryI::setToBeUpdated()
{
    mGeometryMgr->mMoved = true;

    if (!mToBeUpdated)
    {        
        mToBeUpdated = true;
        mNextUpdateItem = mGeometryMgr->mFirstUpdateItem;
        mGeometryMgr->mFirstUpdateItem = this;
    }
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
bool GeometryI::octreeLineTestCallback(OctreeNode* item, void* data)
{
    LineTestData        *lineTestData = (LineTestData*)data;

    ASSERT(lineTestData);
    ASSERT(lineTestData->geometryI);

    const FMOD_VECTOR&   a = lineTestData->start;
    const FMOD_VECTOR&   b = lineTestData->end;

    FMOD_POLYGON& polygon = *(FMOD_POLYGON*)item;

    float dotA = FMOD_Vector_DotProduct(&polygon.normal, &a) - polygon.distance;
    float dotB = FMOD_Vector_DotProduct(&polygon.normal, &b) - polygon.distance;

    if (dotA >= 0.0f && dotB >= 0.0f || dotA <= 0.0f && dotB <= 0.0f)
    {
        return true; // no collision
    }

    if (dotA > 0.0f && (polygon.flags & FMOD_POLYGON_FLAG_DOUBLE_SIDED) == 0)
    {
        return true; // no collision
    }

    // find point on plane
    float time = dotA / (dotA - dotB);

    FMOD_VECTOR collisionPosition;

    collisionPosition.x = a.x + (b.x - a.x) * time;
    collisionPosition.y = a.y + (b.y - a.y) * time;
    collisionPosition.z = a.z + (b.z - a.z) * time;

    FMOD_VECTOR* vertices = &polygon.vertices;

    // find if point lies inside all edges of polygon
    int side;
    for (side = 0; side < (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK); side++)
    {
        FMOD_VECTOR diff;
        FMOD_VECTOR edgeVector;
        int next = side + 1;

        if (next >= (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK))
        {
            next = 0;
        }

        diff.x = vertices[next].x - vertices[side].x;
        diff.y = vertices[next].y - vertices[side].y;
        diff.z = vertices[next].z - vertices[side].z;

        cross(diff, polygon.normal, edgeVector);

        float dot = 
            edgeVector.x * (collisionPosition.x - vertices[side].x) +
            edgeVector.y * (collisionPosition.y - vertices[side].y) +
            edgeVector.z * (collisionPosition.z - vertices[side].z);

        if (dot > 0.0f)
        {
            break; // point is outside polygon so no interestection occured
        }
    }
    if (side == (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK))
    {
        if (lineTestData->geometryI->mGeometryMgr->mSystem->mFlags & FMOD_INIT_GEOMETRY_USECLOSEST)
        {
            float newDirectTransmission = (1.0f - polygon.directOcclusion);
            float newReverbTransmission = (1.0f - polygon.reverbOcclusion);

            if (newDirectTransmission < lineTestData->directTransmission || (newDirectTransmission == lineTestData->directTransmission && newReverbTransmission < lineTestData->reverbTransmission))
            {
                lineTestData->directTransmission = newDirectTransmission;
                lineTestData->reverbTransmission = newReverbTransmission;
            }
        }
        else
        {
            lineTestData->directTransmission *= (1.0f - polygon.directOcclusion);
            lineTestData->reverbTransmission *= (1.0f - polygon.reverbOcclusion);
        }

#if defined(FMOD_GEOMETRY_DEBUGGING)
        // for function testLineTestForEachPolygon to see if this
        // polygon has actually been intersected with
        polygon.flags &= ~0x80000000;
#else
        if (lineTestData->directTransmission < MIN_TRANSMISSION &&
            lineTestData->reverbTransmission < MIN_TRANSMISSION)
        {
            // sounds level has dropped too low so stop doing line testing
            return false;
        }    
#endif
    } 

    return true;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
GeometryI::GeometryI(GeometryMgr* geometryMgr) : LinkedListNode(), mOctree(geometryMgr->getWorldSize())
{
    mGeometryMgr = geometryMgr;
    mMaxNumVertices = 0;
    mNumVertices = 0;
    mMaxNumPolygons = 0;
    mNumPolygons = 0;
    mPolygonOffsets = 0;
    mPolygonDataPos = 0;
    mPolygonData = 0;

    mForward.x = 0.0f;
    mForward.y = 0.0f;
    mForward.z = 1.0f;
    mUp.x = 0.0f;
    mUp.y = 1.0f;
    mUp.z = 0.0f;
    mPosition.x = 0.0f;
    mPosition.y = 0.0f;
    mPosition.z = 0.0f;
    mScale.x = 1.0f;
    mScale.y = 1.0f;
    mScale.z = 1.0f;
    calculateMatrix();

    mPolygonUpdateList = 0;
    mNextUpdateItem = 0;
    mToBeUpdated = false;
    mActive = true;    
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::release()
{      
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
   // mGeometryMgr->flushAll();

    // find out if we are in the "to be updated" list and remove us.
    // Other wise we will try to update a deleted object
    GeometryI* geometryI = mGeometryMgr->mFirstUpdateItem;
    GeometryI* prevGeometryI = 0;
    while (geometryI)
    {
        if (geometryI == this)
        {
            if (prevGeometryI)
            {
                prevGeometryI->mNextUpdateItem = geometryI->mNextUpdateItem;
            }
            else
            {
                mGeometryMgr->mFirstUpdateItem = geometryI->mNextUpdateItem;
            }
            break;
        }
        prevGeometryI = geometryI;    
        geometryI = geometryI->mNextUpdateItem;
    }


    if (mSpatialData)
    {
        mGeometryMgr->mainOctree()->deleteItem(&mSpatialData->octreeNode);
        mGeometryMgr->mainOctree()->removeInternalNode(&mSpatialData->octreeInternalNode);
        mGeometryMgr->releaseMainOctree();
        FMOD_Memory_Free(mSpatialData);
        mSpatialData = 0;
    }
    if (mPolygonData)
    {
        FMOD_Memory_Free(mPolygonData);
        mPolygonData = 0;
    }
    if (mPolygonOffsets)
    {
        FMOD_Memory_Free(mPolygonOffsets);
        mPolygonOffsets = 0;
    }

    mGeometryMgr->mMoved = true;    // Make it reset the voices back to unoccluded.
    mGeometryMgr->mSystem->update();

    if (mGeometryMgr->mSystem->mGeometryList == this)
    {
        mGeometryMgr->mSystem->mGeometryList = (GeometryI*)getNext();
        if (mGeometryMgr->mSystem->mGeometryList == this)
        {
            mGeometryMgr->mSystem->mGeometryList = 0;
        }
    }
    removeNode();

    FMOD_Memory_Free(this);

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::alloc(int maxNumPolygons, int maxNumVertices)
{    
    FMOD_RESULT result;
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif

    if (mPolygonData || mPolygonOffsets )
    {
        return FMOD_ERR_INTERNAL;
    }

    mMaxNumVertices = maxNumVertices;
    mNumVertices = 0;
    mMaxNumPolygons = maxNumPolygons;
    mNumPolygons = 0;
    mPolygonOffsets = (int*)FMOD_Memory_Alloc(mMaxNumVertices * sizeof (int));
    if (!mPolygonOffsets)
    {
        return FMOD_ERR_MEMORY;
    }
    mPolygonDataPos = 0;
    int polygonDataSize = (sizeof(FMOD_POLYGON) - sizeof (FMOD_VECTOR)) * maxNumPolygons;
    polygonDataSize += sizeof (FMOD_VECTOR) * maxNumVertices;

    mPolygonData = (unsigned char*)FMOD_Memory_Alloc(polygonDataSize);
    if (!mPolygonData)
    {
        return FMOD_ERR_MEMORY;
    }

    result = mGeometryMgr->aquireMainOctree();
    if (result != FMOD_OK)
    {
        return result;
    }

    mSpatialData = (SpatialData *)FMOD_Memory_Alloc(sizeof (SpatialData));
    if (!mSpatialData)
    {
        return FMOD_ERR_MEMORY;
    }

    FMOD_memset(mSpatialData, 0, sizeof (SpatialData));
    mSpatialData->geometry = this;
    
    mGeometryMgr->mainOctree()->addInternalNode(&mSpatialData->octreeInternalNode);

    return FMOD_OK;
}
    

/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::addPolygon(float directOcclusion, float reverbOcclusion, bool doubleSided, int numVertices, const FMOD_VECTOR *vertices, int *polygonIndex)
{
    ASSERT(vertices);
    ASSERT(numVertices >= 3);
    ASSERT(mNumPolygons < mMaxNumPolygons);
    ASSERT(mNumVertices + numVertices <= mMaxNumVertices);
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
    
    if (vertices == 0)
    {
         return FMOD_ERR_INVALID_PARAM;
    }
    if (numVertices < 3)
    {
         return FMOD_ERR_INVALID_PARAM;
    }
    if (mNumPolygons >= mMaxNumPolygons)
    {
         return FMOD_ERR_INVALID_PARAM;
    }
    if (mNumVertices + numVertices > mMaxNumVertices)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if (polygonIndex)
    {
        *polygonIndex = mNumPolygons;
    }

    mNumVertices += numVertices;
    mPolygonOffsets[mNumPolygons] = mPolygonDataPos;
    FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonDataPos];
    mPolygonDataPos += sizeof(FMOD_POLYGON) + (numVertices - 1) * sizeof (FMOD_VECTOR);
    mNumPolygons++;

    FMOD_memset(&polygon.node, 0, sizeof (OctreeNode));
    FMOD_memset(&polygon.nodeInternal, 0, sizeof (OctreeNode));
    polygon.directOcclusion = directOcclusion;
    polygon.reverbOcclusion = reverbOcclusion;
    polygon.flags = numVertices;
    if (doubleSided)
        polygon.flags |= FMOD_POLYGON_FLAG_DOUBLE_SIDED;

    int vertex;
    for (vertex = 0; vertex < (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK); vertex++)
        (&polygon.vertices)[vertex] = vertices[vertex];    

    // add to update list
    polygon.node.nextItem = mPolygonUpdateList;
    mPolygonUpdateList = &polygon.node;

    setToBeUpdated();
    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getNumPolygons(int *numPolygons)
{
    if (!numPolygons)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    *numPolygons = mNumPolygons;

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getMaxPolygons(int *maxNumPolygons, int *maxVertices)
{
    if (maxNumPolygons)
    {
        *maxNumPolygons = mMaxNumPolygons;
    }
    if (maxVertices)
    {
        *maxVertices = mMaxNumVertices;
    }

    return FMOD_OK;
}
    

/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getPolygonNumVertices    (int polygonIndex, int *numVertices)
{
    ASSERT(polygonIndex >= 0);
    ASSERT(polygonIndex < mNumPolygons);
    ASSERT(mPolygonOffsets[polygonIndex] >= 0);
    ASSERT(mPolygonOffsets[polygonIndex] < mPolygonDataPos);
    
    if (polygonIndex < 0 || polygonIndex >= mNumPolygons)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[polygonIndex]];

    if (numVertices)
    {
        *numVertices = (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK);
    }

    return FMOD_OK;    
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setPolygonVertex(int polygonIndex, int vertexIndex, const FMOD_VECTOR *vertex)
{    
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
    ASSERT(polygonIndex >= 0);
    ASSERT(polygonIndex < mNumPolygons);
    ASSERT(mPolygonOffsets[polygonIndex] >= 0);
    ASSERT(mPolygonOffsets[polygonIndex] < mPolygonDataPos);
    
    if (polygonIndex < 0 || polygonIndex >= mNumPolygons)
    {
         return FMOD_ERR_INVALID_PARAM;
    }
    
    FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[polygonIndex]];
    ASSERT(vertexIndex >= 0);
    ASSERT(vertexIndex < (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK));
    ASSERT(vertex);

    if (vertexIndex < 0 || vertexIndex >= (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK))
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if (vertex == 0)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if ((&polygon.vertices)[vertexIndex].x == vertex->x &&
        (&polygon.vertices)[vertexIndex].y == vertex->y &&
        (&polygon.vertices)[vertexIndex].z == vertex->z)
    {
        return FMOD_OK;
    }

    (&polygon.vertices)[vertexIndex] = *vertex;

    // if we are in the octreee then remove and add to the update list
    if (polygon.node.flags & OCTREE_FLAG_INSERTED)
    {
        mOctree.deleteItem(&polygon.node);
        polygon.node.nextItem = mPolygonUpdateList;
        mPolygonUpdateList = &polygon.node;
    }

    setToBeUpdated();
    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getPolygonVertex(int polygonIndex, int vertexIndex, FMOD_VECTOR *vertex)
{
    ASSERT(polygonIndex >= 0);
    ASSERT(polygonIndex < mNumPolygons);
    ASSERT(mPolygonOffsets[polygonIndex] >= 0);
    ASSERT(mPolygonOffsets[polygonIndex] < mPolygonDataPos);

    if (polygonIndex < 0 || polygonIndex >= mNumPolygons || !vertex)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[polygonIndex]];
    ASSERT(vertexIndex >= 0);
    ASSERT(vertexIndex < (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK));

    if (vertexIndex < 0 || vertexIndex >= (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK))
    {
         return FMOD_ERR_INVALID_PARAM;
    }
   
    *vertex = (&polygon.vertices)[vertexIndex];

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setPolygonAttributes(int polygonIndex, float directOcclusion, float reverbOcclusion, bool doubleSided)
{
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
    ASSERT(polygonIndex >= 0);
    ASSERT(polygonIndex < mNumPolygons);
    ASSERT(mPolygonOffsets[polygonIndex] >= 0);
    ASSERT(mPolygonOffsets[polygonIndex] < mPolygonDataPos);
    if (polygonIndex < 0 || polygonIndex >= mNumPolygons)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[polygonIndex]];
    polygon.directOcclusion = directOcclusion;
    polygon.reverbOcclusion = reverbOcclusion;
    if (doubleSided)
    {
        polygon.flags |= FMOD_POLYGON_FLAG_DOUBLE_SIDED;
    }
    else
    {
        polygon.flags &= ~FMOD_POLYGON_FLAG_DOUBLE_SIDED;
    }

    setToBeUpdated();
    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getPolygonAttributes(int polygonIndex, float *directOcclusion, float *reverbOcclusion, bool *doubleSided)
{    
    ASSERT(polygonIndex >= 0);
    ASSERT(polygonIndex < mNumPolygons);
    ASSERT(mPolygonOffsets[polygonIndex] >= 0);
    ASSERT(mPolygonOffsets[polygonIndex] < mPolygonDataPos);

    if (polygonIndex < 0 || polygonIndex >= mNumPolygons)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    {
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
        LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
        FMOD_POLYGON &polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[polygonIndex]];
    
        if (directOcclusion)
        {
            *directOcclusion = polygon.directOcclusion;
        }
        if (reverbOcclusion)
        {
            *reverbOcclusion = polygon.reverbOcclusion;
        }
        if (doubleSided)
        {
            *doubleSided = (polygon.flags & FMOD_POLYGON_FLAG_DOUBLE_SIDED) != 0;
        }
    }

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::flush()
{
    /*
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
    */
    int vertex;

    // iterate through polygons in the update list, calucate normals, aabb and add to octree.
    OctreeNode* node = mPolygonUpdateList;
    mPolygonUpdateList = 0;
    while (node)
    {
        OctreeNode* next = node->nextItem;
        node->nextItem = 0;

        FMOD_POLYGON& polygon = *(FMOD_POLYGON*)node;
        FMOD_VECTOR* vertices =& polygon.vertices;

        // calculate polygon normal
        float xN = 0.0f;
        float yN = 0.0f;
        float zN = 0.0f;
        // todo: return an error if a polygon has less then 3 vertices.
        for (vertex = 0; vertex < (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK) - 2; vertex++)
        {
            float xA = vertices[vertex + 1].x -vertices[0].x;
            float yA = vertices[vertex + 1].y -vertices[0].y;
            float zA = vertices[vertex + 1].z -vertices[0].z;
            float xB = vertices[vertex + 2].x -vertices[0].x;
            float yB = vertices[vertex + 2].y -vertices[0].y;
            float zB = vertices[vertex + 2].z -vertices[0].z;
            // cross product
            xN += yA * zB - zA * yB;
            yN += zA * xB - xA * zB;
            zN += xA * yB - yA * xB;
        }    
        float fMagnidued = (float)sqrt(xN * xN + yN * yN + zN * zN);
        if (fMagnidued > 0.0f) // a tollerance here might be called for
        {
            polygon.flags &= ~FMOD_POLYGON_FLAG_INVALID;
            xN /= fMagnidued;
            yN /= fMagnidued;
            zN /= fMagnidued;
        }
        else
        {
            polygon.flags |= FMOD_POLYGON_FLAG_INVALID;
        }
        polygon.normal.x = xN;
        polygon.normal.y = yN;
        polygon.normal.z = zN;
        polygon.distance = FMOD_Vector_DotProduct(&vertices[0], &polygon.normal);
        
        // add to octree    
        mOctree.addInternalNode(&polygon.nodeInternal);
        FMOD_AABB& aabb = polygon.node.aabb;
        aabb.xMax = aabb.xMin = vertices[0].x;
        aabb.yMax = aabb.yMin = vertices[0].y;
        aabb.zMax = aabb.zMin = vertices[0].z;        
        for (vertex = 1; vertex < (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK); vertex++)
        {
            aabb.xMax = MAX(aabb.xMax, vertices[vertex].x);
            aabb.xMin = MIN(aabb.xMin, vertices[vertex].x);
            aabb.yMax = MAX(aabb.yMax, vertices[vertex].y);
            aabb.yMin = MIN(aabb.yMin, vertices[vertex].y);
            aabb.zMax = MAX(aabb.zMax, vertices[vertex].z);
            aabb.zMin = MIN(aabb.zMin, vertices[vertex].z);
        }
        // we need to grow the bounding box a little to cope with the
        // case of an axis alligned polygon that might be right on the edge of a
        // bounding box.
        // Becaouse the line is clamped to each box in the tree before testing against
        // the polygon, we need to make the boxes a little bit bigger then the
        // polygons to make sure the line still passes through them.
        float fTollerance = aabb.xMax - aabb.xMin;
        fTollerance = MAX(fTollerance, aabb.yMax - aabb.yMin);
        fTollerance = MAX(fTollerance, aabb.zMax - aabb.zMin);
        fTollerance *= 0.01f;
        aabb.xMin -= fTollerance;
        aabb.xMax += fTollerance;
        aabb.yMin -= fTollerance;
        aabb.yMax += fTollerance;
        aabb.zMin -= fTollerance;
        aabb.zMax += fTollerance;
        
        if ((polygon.flags & FMOD_POLYGON_FLAG_INVALID) == 0)
        {
            mOctree.insertItem(&polygon.node);
        }
        node = next;
    }

    // update spacial data
    //mOctree.calculateAverageNodeElements(); // just for testing
    mOctree.getAABB(&mAABB);
    updateSpatialData();
    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setActive(bool active)
{
    setToBeUpdated();

    mActive = active;

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getActive(bool *active)
{
    if (!active)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    *active = mActive;

    return FMOD_OK;
}
    

/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setRotation(const FMOD_VECTOR* forward, const FMOD_VECTOR* up)
{
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif

    if (!forward)
    {
         return FMOD_ERR_INVALID_PARAM;
    }
    if (!up)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if (mForward.x == forward->x && 
        mForward.y == forward->y && 
        mForward.z == forward->z && 
        mUp.x == up->x && 
        mUp.y == up->y && 
        mUp.z == up->z)
    {
        return FMOD_OK;
    }

    mForward = *forward;
    mUp = *up;
    calculateMatrix();
    setToBeUpdated();
    return FMOD_OK;
}

/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getRotation(FMOD_VECTOR* forward, FMOD_VECTOR* up)
{
    if (forward)
    {
        *forward = mForward;
    }

    if (up)
    {
        *up = mUp;
    }

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setPosition(const FMOD_VECTOR* position)
{
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif

    if (!position)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if (mPosition.x == position->x && mPosition.y == position->y && mPosition.z == position->z)
    {
        return FMOD_OK;
    }
    
    mPosition = *position;

    setToBeUpdated();

    return FMOD_OK;
}


/*
[
    [DESCRIPTION]

    [PARAMETERS]

    [RETURN_VALUE]

    [REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

    [SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getPosition(FMOD_VECTOR* position)
{
    if (!position)
    {
        return FMOD_ERR_INVALID_PARAM;
    }
    
    *position = mPosition;

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setScale(const FMOD_VECTOR* scale)
{
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif

    if (!scale)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if (scale->x == 0.0f || scale->y == 0.0f || scale->z == 0.0f)
    {
         return FMOD_ERR_INVALID_PARAM;
    }

    if (mScale.x == scale->x && 
        mScale.y == scale->y && 
        mScale.z == scale->z)
    {
        return FMOD_OK;
    }
    mScale = *scale;
    calculateMatrix();
    setToBeUpdated();
    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getScale(FMOD_VECTOR *scale)
{    
    if (!scale)
    {
        return FMOD_ERR_INVALID_PARAM;
    }
    
    *scale = mScale;

    return FMOD_OK;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::save(void *data, int *dataSize)
{    
    FMOD_RESULT result = FMOD_OK;

    if (!dataSize)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    if (data)
    {
        int tmpDataSize = *dataSize;
        result = serialiser(data, &tmpDataSize, true, false, saveData);
    }
    else
    {
        result = serialiser(data, dataSize, false, false, countData);
    }

    return result;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::load(const void *data, int dataSize)
{   
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif

    if (!data)
    {
        return FMOD_ERR_INVALID_PARAM;
    }
    
    return serialiser((void*)data, &dataSize, false, true, loadData);
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::saveData(void* fileData, int dataSize, int* fileDataIndex, void* liveData, int liveDataSize)
{
    if (*fileDataIndex + liveDataSize > dataSize)
        return FMOD_ERR_INVALID_PARAM;
    
#ifdef PLATFORM_ENDIAN_BIG
    if (liveDataSize == 4)
    {
        *(unsigned int*)&((unsigned char*)fileData)[*fileDataIndex] = *(unsigned int*)liveData;
    }
    else
    {
        FMOD_memcpy(&((unsigned char*)fileData)[*fileDataIndex], liveData, liveDataSize);
    }
#else
    FMOD_memcpy(&((unsigned char*)fileData)[*fileDataIndex], liveData, liveDataSize);
#endif

    *fileDataIndex += liveDataSize;

    return FMOD_OK;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::loadData(void* fileData, int dataSize, int* fileDataIndex, void* liveData, int liveDataSize)
{
    if (*fileDataIndex + liveDataSize > dataSize)
        return FMOD_ERR_INVALID_PARAM;

#ifdef PLATFORM_ENDIAN_BIG
    if (liveDataSize == 4)
    {
        *(unsigned int*)liveData = *(unsigned int*)&((unsigned char*)fileData)[*fileDataIndex];
    }
    else
    {
        FMOD_memcpy(liveData, &((unsigned char*)fileData)[*fileDataIndex], liveDataSize);
    }
#else
    FMOD_memcpy(liveData, &((unsigned char*)fileData)[*fileDataIndex], liveDataSize);
#endif

    *fileDataIndex += liveDataSize;
    return FMOD_OK;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::countData(void* fileData, int dataSize, int* fileDataIndex, void* liveData, int liveDataSize)
{
    *fileDataIndex += liveDataSize;
    return FMOD_OK;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::serialiser(
    void *data, 
    int *dataSize, 
    bool bWrite, 
    bool bRead, 
    FMOD_RESULT (*serialiseData)(void* fileData, int dataSize, int* fileDataIndex, void* liveData, int liveDataSize))
{ 
    bool bCount = !bWrite && !bRead;
   
    #define _CHECK_RESULT(x)                      \
    {                                             \
        FMOD_RESULT result = (x);                 \
        if (result != FMOD_OK)                    \
        {                                         \
            if (vertexArray)                      \
               FMOD_Memory_Free(vertexArray);     \
            return result;                        \
        }                                         \
    }

    FMOD_VECTOR* vertexArray = 0;
    int index = 0;

    unsigned int header;
    const unsigned int realHeader = 'F' + ('M' << 8) + ('O' << 16) + ('D' << 24);
    header = realHeader;
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &header, sizeof (int)))
    if (header != realHeader)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    int dataSizeCheck = *dataSize;
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &dataSizeCheck, sizeof (int)))
    if (bRead)
    {
        if (*dataSize != dataSizeCheck)
        {
            return FMOD_ERR_INVALID_PARAM;
        }
    }

    int numPolygons;
    if (bWrite || bCount)
    {
        _CHECK_RESULT(getNumPolygons(&numPolygons))
    }
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &numPolygons, sizeof (int)))

    int maxPolygons;
    int maxVertices;
    if (bWrite || bCount)
    {
        _CHECK_RESULT(getMaxPolygons(&maxPolygons, &maxVertices))
    }
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &maxPolygons, sizeof (int)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &maxVertices, sizeof (int)))

    int maxNumVerticesInOnPolygon = 64; // not very likely that this will be broken
    vertexArray = (FMOD_VECTOR*)FMOD_Memory_Alloc(maxNumVerticesInOnPolygon * sizeof (FMOD_VECTOR));
    if (!vertexArray)
        return FMOD_ERR_MEMORY;

    if (bRead)
    {
        _CHECK_RESULT(alloc(maxPolygons, maxVertices))
    }
    for (int polygonIndex = 0; polygonIndex < numPolygons; polygonIndex++)
    {
        int numVertices;
        if (bWrite || bCount)
        {
            _CHECK_RESULT(getPolygonNumVertices(polygonIndex, &numVertices))
        }
        _CHECK_RESULT(serialiseData(data, *dataSize, &index, &numVertices, sizeof (int)))

        if (maxNumVerticesInOnPolygon < numVertices)
        {
            // in the unlikly event we have more then 64 vertices in one polygon we need to 
            // reallocate the array
            FMOD_Memory_Free(vertexArray);
            vertexArray = (FMOD_VECTOR*)FMOD_Memory_Alloc(maxNumVerticesInOnPolygon * sizeof (FMOD_VECTOR));
            if (!vertexArray)
                return FMOD_ERR_MEMORY;
        }

        for (int vertexIndex = 0; vertexIndex < numVertices; vertexIndex++)
        {
           // FMOD_VECTOR vertex = FMOD_Memory_Alloc(mMaxNumVertices * sizeof (int));
            if (bWrite)
            {
                _CHECK_RESULT(getPolygonVertex(polygonIndex, vertexIndex, &vertexArray[vertexIndex]))
            }
            _CHECK_RESULT(serialiseData(data, *dataSize, &index, &vertexArray[vertexIndex].x, sizeof (float)))
            _CHECK_RESULT(serialiseData(data, *dataSize, &index, &vertexArray[vertexIndex].y, sizeof (float)))
            _CHECK_RESULT(serialiseData(data, *dataSize, &index, &vertexArray[vertexIndex].z, sizeof (float)))
        }

        float directOcclusion;
        float reverbOcclusion;
        bool bDoubleSided = false;;
        if (bWrite || bCount)
        {
            _CHECK_RESULT(getPolygonAttributes(polygonIndex, &directOcclusion, &reverbOcclusion, &bDoubleSided))
        }
        int doubleSided = bDoubleSided ? 1 : 0;
        _CHECK_RESULT(serialiseData(data, *dataSize, &index, &directOcclusion, sizeof (int)))
        _CHECK_RESULT(serialiseData(data, *dataSize, &index, &reverbOcclusion, sizeof (int)))
        _CHECK_RESULT(serialiseData(data, *dataSize, &index, &doubleSided, sizeof (int)))
        bDoubleSided = doubleSided != 0;

        if (bRead)
        {
            _CHECK_RESULT(addPolygon(directOcclusion, reverbOcclusion, bDoubleSided, numVertices, vertexArray, 0))
        }
    }

    FMOD_Memory_Free(vertexArray);
    vertexArray = 0;

    FMOD_VECTOR forward;
    FMOD_VECTOR up;
    if (bWrite || bCount)
    {
        _CHECK_RESULT(getRotation(&forward, &up))
    }
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &forward.x, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &forward.y, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &forward.z, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &up.x, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &up.y, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &up.z, sizeof (float)))
    if (bRead)
    {
        _CHECK_RESULT(setRotation(&forward, &up))
    }

    FMOD_VECTOR position;
    if (bWrite || bCount)
    {
        _CHECK_RESULT(getPosition(&position))
    }
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &position.x, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &position.y, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &position.z, sizeof (float)))
    if (bRead)
    {
        _CHECK_RESULT(setPosition(&position))
    }

    FMOD_VECTOR scale;
    if (bWrite || bCount)
    {
        _CHECK_RESULT(getScale(&scale))
    }
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &scale.x, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &scale.y, sizeof (float)))
    _CHECK_RESULT(serialiseData(data, *dataSize, &index, &scale.z, sizeof (float)))
    if (bRead)
    {
        _CHECK_RESULT(setScale(&scale))
    }
        
#undef _CHECK_RESULT

    if (bRead || bWrite)
    {
        if (*dataSize != index)
        {
            return FMOD_ERR_INVALID_PARAM;
        }
    }
    else
    {
        *dataSize = index;
    }


    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setUserData(void *userdata)
{
    mUserData = userdata;
    
    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getUserData(void **userdata)
{
    if (!userdata)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    *userdata = mUserData;
    
    return FMOD_OK;
}

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
bool GeometryI::lineTest(LineTestData* lineTestData)
{
    FMOD_VECTOR startBck = lineTestData->start;
    FMOD_VECTOR endBck = lineTestData->end;

    FMOD_VECTOR startTmp = lineTestData->start;
    startTmp.x -= mPosition.x;
    startTmp.y -= mPosition.y;
    startTmp.z -= mPosition.z;
    FMOD_VECTOR endTmp = lineTestData->end;
    endTmp.x -= mPosition.x;
    endTmp.y -= mPosition.y;
    endTmp.z -= mPosition.z;

    matrixMult(mInvMatrix, &startTmp, &lineTestData->start);
    matrixMult(mInvMatrix, &endTmp, &lineTestData->end);

    bool bResult = mOctree.testLine(octreeLineTestCallback, lineTestData, lineTestData->start, lineTestData->end);

    lineTestData->start = startBck;
    lineTestData->end = endBck;

    lineTestData->geometryI = 0;
    return bResult;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::setWorldSize(float worldSize)
{
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
    mOctree.setMaxSize(worldSize);
    int poly;
    for (poly = 0; poly < mNumPolygons; poly++)
    {
        FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[poly]];
        mOctree.deleteItem(&polygon.node);
    }
    for (poly = 0; poly < mNumPolygons; poly++)
    {
        FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[poly]];
        polygon.node.nextItem = mPolygonUpdateList;
        mPolygonUpdateList = &polygon.node;
    }
    setToBeUpdated();

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
void GeometryI::removeFromTree()
{
#ifdef FMOD_SUPPORT_GEOMETRY_THREADED
    LocalCriticalSection crit(mGeometryMgr->mGeometryCrit, true);
#endif
    mGeometryMgr->mainOctree()->deleteItem(&mSpatialData->octreeNode);
}


#if defined(FMOD_GEOMETRY_DEBUGGING)
/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
void GeometryI::testLineTestForEachPolygon()
{
    for (int poly = 0; poly < mNumPolygons; poly++)
    {
        FMOD_POLYGON& polygon = *(FMOD_POLYGON*)&mPolygonData[mPolygonOffsets[poly]];
        if (polygon.flags & FMOD_POLYGON_FLAG_INVALID)
            continue;
        FMOD_VECTOR center = { 0.0f, 0.0f, 0.0f };
        int numVertices = (polygon.flags & FMOD_POLYGON_NUM_VERTICES_MASK);
        ASSERT(numVertices >= 3);
        for (int i = 0; i < numVertices; i++)
        {
            center.x += (&polygon.vertices)[i].x;
            center.y += (&polygon.vertices)[i].y;
            center.z += (&polygon.vertices)[i].z;
        }
        center.x /= numVertices;
        center.y /= numVertices;
        center.z /= numVertices;
        FMOD_VECTOR start;
        start.x = center.x - polygon.normal.x * 100.0f;
        start.y = center.y - polygon.normal.y * 100.0f;
        start.z = center.z - polygon.normal.z * 100.0f;
        FMOD_VECTOR end;
        end.x = center.x + polygon.normal.x * 100.0f;
        end.y = center.y + polygon.normal.y * 100.0f;
        end.z = center.z + polygon.normal.z * 100.0f;
        polygon.flags |= 0x80000000;
        float a, b;
        mGeometryMgr->lineTestAll(&start, &end, &a, &b);
        ASSERT((polygon.flags & 0x80000000) == 0);
        polygon.flags &= ~0x80000000;
    }
}
#endif

/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]
    FMOD_OK

	[REMARKS]

    [PLATFORMS]
    Win32, Win64, Linux, Macintosh, XBox, PlayStation 2, GameCube

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::validate(Geometry *geometry, GeometryI **geometryi)
{
    if (!geometryi)
    {
        return FMOD_ERR_INVALID_PARAM;
    }

    if (!geometry)
    {
        return FMOD_ERR_INVALID_HANDLE;
    }

    *geometryi = (GeometryI *)geometry;

    return FMOD_OK;
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

	[SEE_ALSO]
]
*/
FMOD_RESULT GeometryI::getMemoryInfo(unsigned int memorybits, unsigned int event_memorybits, unsigned int *memoryused, FMOD_MEMORY_USAGE_DETAILS *memoryused_details)
{
#ifdef FMOD_SUPPORT_MEMORYTRACKER
    GETMEMORYINFO_IMPL
#else
    return FMOD_ERR_UNIMPLEMENTED;
#endif
}


/*
[
	[DESCRIPTION]

	[PARAMETERS]
 
	[RETURN_VALUE]

	[REMARKS]

    [PLATFORMS]

	[SEE_ALSO]
]
*/

#ifdef FMOD_SUPPORT_MEMORYTRACKER

FMOD_RESULT GeometryI::getMemoryUsedImpl(MemoryTracker *tracker)
{
    //AJS incomplete
    tracker->add(false, FMOD_MEMBITS_GEOMETRY, sizeof(*this));

    return FMOD_OK;
}

#endif


}

#endif // FMOD_SUPPORT_GEOMETRY
