/* bzflag
 * Copyright (c) 1993-2025 Tim Riker
 *
 * This package is free software;  you can redistribute it and/or
 * modify it under the terms of the license found in the file
 * named COPYING that should have accompanied this file.
 *
 * THIS PACKAGE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#include "common.h"

// implementatino header
#include "MeshObstacle.h"

// system headers
#include <math.h>
#include <stdlib.h>

// common headers
#include "global.h"
#include "Pack.h"
#include "MeshFace.h"
#include "CollisionManager.h"
#include "Intersect.h"
#include "MeshDrawInfo.h"
#include "MeshTransform.h"
#include "StateDatabase.h"

// local headers
#include "Triangulate.h"


const char* MeshObstacle::typeName = "MeshObstacle";


MeshObstacle::MeshObstacle()
{
    faceCount = faceSize = 0;
    faces = NULL;
    checkCount = 0;
    checkTypes = NULL;
    checkPoints = NULL;
    vertexCount = normalCount = 0;
    vertices = normals = NULL;
    texcoordCount = 0;
    texcoords = NULL;
    noclusters = false;
    smoothBounce = false;
    driveThrough = false;
    ricochet = false;
    shootThrough = false;
    inverted = false;
    drawInfo = NULL;
    return;
}


static void cfvec3ListToArray(const std::vector<cfvec3>& list,
                              int& count, afvec3* &array)
{
    count = list.size();
    array = new afvec3[count];
    for (int i = 0; i < count; i++)
        memcpy (array[i], list[i].data, sizeof(afvec3));
    return;
}

static void arrayToCafvec3List(const afvec3* array, int count,
                               std::vector<cfvec3>& list)
{
    list.clear();
    for (int i = 0; i < count; i++)
        list.push_back(array[i]);
    return;
}


MeshObstacle::MeshObstacle(const MeshTransform& transform,
                           const std::vector<char>& checkTypesL,
                           const std::vector<cfvec3>& checkList,
                           const std::vector<cfvec3>& verticeList,
                           const std::vector<cfvec3>& normalList,
                           const std::vector<cfvec2>& texcoordList,
                           int _faceCount, bool _noclusters,
                           bool bounce, bool drive, bool shoot, bool rico)
{
    unsigned int i;

    // get the transform tool
    MeshTransform::Tool xformtool(transform);
    inverted = xformtool.isInverted();

    // copy the info
    checkTypes = new char[checkTypesL.size()];
    for (i = 0; i < checkTypesL.size(); i++)
        checkTypes[i] = checkTypesL[i];
    cfvec3ListToArray (checkList, checkCount, checkPoints);
    cfvec3ListToArray (verticeList, vertexCount, vertices);
    cfvec3ListToArray (normalList, normalCount, normals);

    // modify according to the transform
    int j;
    for (j = 0; j < checkCount; j++)
        xformtool.modifyVertex(checkPoints[j]);
    for (j = 0; j < vertexCount; j++)
        xformtool.modifyVertex(vertices[j]);
    for (j = 0; j < normalCount; j++)
        xformtool.modifyNormal(normals[j]);

    texcoordCount = texcoordList.size();
    texcoords = new afvec2[texcoordCount];
    for (i = 0; i < (unsigned int)texcoordCount; i++)
        memcpy (texcoords[i], texcoordList[i].data, sizeof(afvec2));

    faceSize = _faceCount;
    faceCount = 0;
    faces = new MeshFace*[faceSize];

    noclusters = _noclusters;
    smoothBounce = bounce;
    driveThrough = drive;
    shootThrough = shoot;
    ricochet = rico;

    drawInfo = NULL;

    return;
}


bool MeshObstacle::addFace(const std::vector<int>& _vertices,
                           const std::vector<int>& _normals,
                           const std::vector<int>& _texcoords,
                           const BzMaterial* _material, int phydrv,
                           bool _noclusters,
                           bool bounce, bool drive, bool shoot, bool rico,
                           bool triangulate)
{
    // protect the face list from overrun
    if (faceCount >= faceSize)
        return false;

    // make sure the list lengths are the same
    unsigned int i;
    unsigned int count = _vertices.size();
    if ((count < 3) ||
            ((_normals.size() > 0) && (_normals.size() != count)) ||
            ((_texcoords.size() > 0) && (_texcoords.size() != count)))
        return false;

    // validate the indices
    for (i = 0; i < _vertices.size(); i++)
    {
        if (_vertices[i] >= vertexCount)
            return false;
    }
    for (i = 0; i < _normals.size(); i++)
    {
        if (_normals[i] >= normalCount)
            return false;
    }
    for (i = 0; i < _texcoords.size(); i++)
    {
        if (_texcoords[i] >= texcoordCount)
            return false;
    }

    // use the indices to makes lists of pointers
    float **v, **n, **t;
    makeFacePointers(_vertices, _normals, _texcoords, v, n, t);

    // override the flags if they are set for the whole mesh
    _noclusters = _noclusters || noclusters;
    bounce = bounce || smoothBounce;
    drive = drive || driveThrough;
    shoot = shoot || shootThrough;
    rico  = rico  || ricochet;

    // override the triangulation setting depending on count
    triangulate = triangulate && (count > 3);

    // make the face
    MeshFace* face;
    if (triangulate)
    {
        // avoid warnings that may not apply
        int tmpDebugLevel = debugLevel;
        debugLevel = 0;
        face = new MeshFace(this, count, v, n, t, _material, phydrv,
                            _noclusters, bounce, drive, shoot, rico);
        debugLevel = tmpDebugLevel;
    }
    else
    {
        face = new MeshFace(this, count, v, n, t, _material, phydrv,
                            _noclusters, bounce, drive, shoot, rico);
    }

    // check its validity
    if (face->isValid())
    {
        faces[faceCount] = face;
        faceCount++;
    }
    else if (triangulate)
    {
        // triangulate
        std::vector<TriIndices> triIndices;
        triangulateFace(count, v, triIndices);
        delete face; // delete the old face
        const unsigned int triSize = triIndices.size();
        if (triSize == 0)
            return false;
        else
        {
            // prepare array for extra faces
            const int extra = (int)(triIndices.size() - 1);
            MeshFace** tmp = new MeshFace*[faceSize + extra];
            memcpy(tmp, faces, faceCount * sizeof(MeshFace*));
            delete[] faces;
            faces = tmp;
            faceSize += extra;
            // add the triangles
            for (i = 0; i < triSize; i++)
            {
                std::vector<int> triV, triN, triT;
                for (int j = 0; j < 3; j++)
                {
                    const int index = triIndices[i].indices[j];
                    triV.push_back(_vertices[index]);
                    if (_normals.size() > 0)
                        triN.push_back(_normals[index]);
                    if (_texcoords.size() > 0)
                        triT.push_back(_texcoords[index]);
                }
                makeFacePointers(triV, triN, triT, v, n, t);
                face = new MeshFace(this, 3, v, n, t, _material, phydrv,
                                    _noclusters, bounce, drive, shoot, rico);
                if (face->isValid())
                {
                    faces[faceCount] = face;
                    faceCount++;
                }
                else
                    delete face;
            }
        }
    }
    else
    {
        // just nuke it
        delete face;
        return false;
    }

    return true;
}


void MeshObstacle::makeFacePointers(const std::vector<int>& _vertices,
                                    const std::vector<int>& _normals,
                                    const std::vector<int>& _texcoords,
                                    float**& v, float**& n, float**& t)
{
    const int count = _vertices.size();

    // use the indices to makes lists of pointers
    v = new float*[count];
    n = NULL;
    t = NULL;

    if (_normals.size() > 0)
        n = new float*[count];
    if (_texcoords.size() > 0)
        t = new float*[count];

    for (int i = 0; i < count; i++)
    {
        // invert the vertices if required
        int index = (inverted ? ((count - 1) - i) : i);
        v[index] = (float*)vertices[_vertices[i]];
        if (n != NULL)
            n[index] = (float*)normals[_normals[i]];
        if (t != NULL)
            t[index] = (float*)texcoords[_texcoords[i]];
    }
    return;
}


MeshObstacle::~MeshObstacle()
{
    delete[] checkTypes;
    delete[] checkPoints;
    delete[] vertices;
    delete[] normals;
    delete[] texcoords;
    for (int i = 0; i < faceCount; i++)
        delete faces[i];
    delete[] faces;
    delete drawInfo;
    return;
}


Obstacle* MeshObstacle::copyWithTransform(const MeshTransform& xform) const
{
    MeshObstacle* copy;
    std::vector<char> ctlist;
    std::vector<cfvec3> clist;
    std::vector<cfvec3> vlist;
    std::vector<cfvec3> nlist;
    std::vector<cfvec2> tlist;

    // empty arrays for copies of pure visual meshes
    if ((drawInfo != NULL) &&
            ((faceCount <= 0) || (driveThrough && shootThrough)))
    {
        // load blanks for pure visual meshes
        copy = new MeshObstacle(xform, ctlist, clist,
                                vlist, nlist, tlist, 0, noclusters,
                                smoothBounce, driveThrough, shootThrough, ricochet);
    }
    else
    {
        int i;
        for (i = 0; i < checkCount; i++)
            ctlist.push_back(checkTypes[i]);
        arrayToCafvec3List(checkPoints, checkCount, clist);
        arrayToCafvec3List(vertices, vertexCount, vlist);
        arrayToCafvec3List(normals, normalCount, nlist);
        for (i = 0; i < texcoordCount; i++)
            tlist.push_back(texcoords[i]);

        copy = new MeshObstacle(xform, ctlist, clist,
                                vlist, nlist, tlist, faceCount, noclusters,
                                smoothBounce, driveThrough, shootThrough, ricochet);

        for (i = 0; i < faceCount; i++)
            copyFace(i, copy);
    }

    copy->finalize();

    return copy;
}


void MeshObstacle::copyFace(int f, MeshObstacle* mesh) const
{
    MeshFace* face = faces[f];

    std::vector<int> vlist;
    std::vector<int> nlist;
    std::vector<int> tlist;
    const int vcount = face->getVertexCount();
    for (int i = 0; i < vcount; i++)
    {
        int index;
        index = ((const afvec3*) face->getVertex(i)) - vertices;
        vlist.push_back(index);

        if (face->useNormals())
        {
            index = ((const afvec3*) face->getNormal(i)) - normals;
            nlist.push_back(index);
        }
        if (face->useTexcoords())
        {
            index = ((const afvec2*) face->getTexcoord(i)) - texcoords;
            tlist.push_back(index);
        }
    }

    mesh->addFace(vlist, nlist, tlist, face->getMaterial(),
                  face->getPhysicsDriver(), face->noClusters(),
                  face->isSmoothBounce(),
                  face->isDriveThrough(), face->isShootThrough(),
                  face->canRicochet(), false);
    return;
}


void MeshObstacle::finalize()
{
    int f;

    // cross-link the face edges - FIXME
    for (f = 0; f < faceCount; f++)
        faces[f]->edges = NULL;

    // set the extents
    for (f = 0; f < faceCount; f++)
    {
        const Extents& exts = faces[f]->getExtents();
        extents.expandToBox(exts);
    }

    // setup fake obstacle parameters
    pos[0] = (extents.maxs[0] + extents.mins[0]) / 2.0f;
    pos[1] = (extents.maxs[1] + extents.mins[1]) / 2.0f;
    pos[2] = extents.mins[2];
    size[0] = (extents.maxs[0] - extents.mins[0]) / 2.0f;
    size[1] = (extents.maxs[1] - extents.mins[1]) / 2.0f;
    size[2] = (extents.maxs[2] - extents.mins[2]);
    angle = 0.0f;
    ZFlip = false;

    return;
}


const char* MeshObstacle::getType() const
{
    return typeName;
}


const char* MeshObstacle::getClassName() // const
{
    return typeName;
}


void MeshObstacle::setDrawInfo(MeshDrawInfo* di)
{
    if (drawInfo != NULL)
    {
        printf("MeshObstacle::setMeshDrawInfo() already exists\n");
        exit(1);
    }
    else
        drawInfo = di;
    return;
}


bool MeshObstacle::isValid() const
{
    // check the planes
    /* FIXME - kill the whole thing for one bad face?
      for (int f = 0; f < faceCount; f++) {
        if (!faces[f]->isValid()) {
          return false;
        }
      }
    */

    // now check the vertices
    for (int v = 0; v < vertexCount; v++)
    {
        for (int a = 0; a < 3; a++)
        {
            if (fabsf(vertices[v][a]) > maxExtent)
                return false;
        }
    }

    return true;
}


bool MeshObstacle::containsPoint(const float point[3]) const
{
    // this should use the CollisionManager's rayTest function
//  const ObsList* olist = COLLISIONMGR.rayTest (&ray, t);
    return containsPointNoOctree(point);
}


bool MeshObstacle::containsPointNoOctree(const float point[3]) const
{
    if (checkCount <= 0)
        return false;

    int c, f;
    float dir[3];
    bool hasOutsides = false;

    for (c = 0; c < checkCount; c++)
    {
        if (checkTypes[c] == CheckInside)
        {
            vec3sub (dir, checkPoints[c], point);
            Ray ray(point, dir);
            bool hitFace = false;
            for (f = 0; f < faceCount; f++)
            {
                const MeshFace* face = faces[f];
                const float hittime = face->intersect(ray);
                if ((hittime > 0.0f) && (hittime <= 1.0f))
                {
                    hitFace = true;
                    break;
                }
            }
            if (!hitFace)
                return true;
        }
        else if (checkTypes[c] == CheckOutside)
        {
            hasOutsides = true;
            vec3sub (dir, point, checkPoints[c]);
            Ray ray(checkPoints[c], dir);
            bool hitFace = false;
            for (f = 0; f < faceCount; f++)
            {
                const MeshFace* face = faces[f];
                const float hittime = face->intersect(ray);
                if ((hittime > 0.0f) && (hittime <= 1.0f))
                {
                    hitFace = true;
                    break;
                }
            }
            if (!hitFace)
                return false;
        }
        else
        {
            printf ("checkType (%i) is not supported yet\n", checkTypes[c]);
            exit (1);
        }
    }

    return hasOutsides;
}


float MeshObstacle::intersect(const Ray& UNUSED(ray)) const
{
    return -1.0f; // rays only intersect with mesh faces
}


void MeshObstacle::get3DNormal(const float* UNUSED(p), float* UNUSED(n)) const
{
    return; // this should never be called if intersect() is always < 0.0f
}


void MeshObstacle::getNormal(const float* p, float* n) const
{
    const afvec3 center = { pos[0], pos[1], pos[2] + (0.5f * size[2]) };
    afvec3 out;
    vec3sub (out, p, center);
    if (out[2] < 0.0f)
        out[2] = 0.0f;
    float len = vec3dot(out, out);
    if (len > 0.0f)
    {
        len = 1 / sqrtf(len);
        n[0] = out[0] * len;
        n[1] = out[1] * len;
        n[2] = out[2] * len;
    }
    else
    {
        n[0] = 0.0f;
        n[1] = 0.0f;
        n[2] = 1.0f;
    }

    return;
}


bool MeshObstacle::getHitNormal(const float* UNUSED(oldPos), float UNUSED(oldAngle),
                                const float* p, float UNUSED(_angle),
                                float, float, float UNUSED(height),
                                float* n) const
{
    if (n != NULL)
        getNormal(p, n);
    return true;
}


bool MeshObstacle::inCylinder(const float* p,
                              float UNUSED(radius), float height) const
{
    const float mid[3] = { p[0], p[1], p[2] + (0.5f * height) };
    return containsPoint(mid);
}


bool MeshObstacle::inBox(const float* p, float UNUSED(_angle),
                         float UNUSED(dx), float UNUSED(dy), float height) const
{
    const float mid[3] = { p[0], p[1], p[2] + (0.5f * height) };
    return containsPoint(mid);
}


bool MeshObstacle::inMovingBox(const float*, float,
                               const float* p, float UNUSED(_angle),
                               float UNUSED(dx), float UNUSED(dy), float height) const
{
    const float mid[3] = { p[0], p[1], p[2] + (0.5f * height) };
    return containsPoint(mid);
}


bool MeshObstacle::isCrossing(const float* UNUSED(p), float UNUSED(_angle),
                              float UNUSED(dx), float UNUSED(dy), float UNUSED(height),
                              float* UNUSED(plane)) const
{
    return false; // the MeshFaces should handle this case
}


int MeshObstacle::packSize() const
{
    int fullSize = 5 * sizeof(int32_t);
    fullSize += sizeof(char) * checkCount;
    fullSize += sizeof(afvec3) * checkCount;
    fullSize += sizeof(afvec3) * vertexCount;
    fullSize += sizeof(afvec3) * normalCount;
    fullSize += sizeof(afvec2) * texcoordCount;
    if ((drawInfo != NULL) && !drawInfo->isCopy())
    {
        const int drawInfoPackSize = drawInfo->packSize();
        // align the packing
        const int align = sizeof(afvec2);
        fullSize += align * ((drawInfoPackSize + (align - 1)) / align);
        // drawInfo fake txcd count
        fullSize += sizeof(afvec2);

        if (debugLevel >= 4)
        {
            logDebugMessage(0,"DrawInfo packSize = %i, align = %i, full = %i\n",
                            drawInfoPackSize,
                            align * ((drawInfoPackSize + (align - 1)) / align),
                            align * ((drawInfoPackSize + (align - 1)) / align) + (int)sizeof(afvec2));
        }
    }
    for (int f = 0; f < faceCount; f++)
        fullSize += faces[f]->packSize();
    fullSize += sizeof(unsigned char); // state byte
    return fullSize;
}


void *MeshObstacle::pack(void *buf) const
{
    int i;

    buf = nboPackInt(buf, checkCount);
    for (i = 0; i < checkCount; i++)
    {
        buf = nboPackUByte(buf, checkTypes[i]);
        buf = nboPackVector(buf, checkPoints[i]);
    }

    buf = nboPackInt(buf, vertexCount);
    for (i = 0; i < vertexCount; i++)
        buf = nboPackVector(buf, vertices[i]);

    buf = nboPackInt(buf, normalCount);
    for (i = 0; i < normalCount; i++)
        buf = nboPackVector(buf, normals[i]);

    void* txcdStart = buf;
    buf = nboPackInt(buf, texcoordCount);
    for (i = 0; i < texcoordCount; i++)
    {
        buf = nboPackFloat(buf, texcoords[i][0]);
        buf = nboPackFloat(buf, texcoords[i][1]);
    }

    // pack hidden drawInfo data as extra texture coordinates
    const bool drawInfoOwner = ((drawInfo != NULL) && !drawInfo->isCopy());
    if (drawInfoOwner)
    {
        void* drawInfoStart = buf;
        buf = drawInfo->pack(buf);
        // align the packing
        const int align = sizeof(afvec2);
        const int length = (char*)buf - (char*)drawInfoStart;
        const int missing = (align - (length % align)) % align;
        for (i = 0; i < missing; i++)
            buf = nboPackUByte(buf, 0);
        // bump up the texture coordinate count
        const int fullLength = ((char*)buf - (char*)drawInfoStart);
        const int extraTxcds = fullLength / sizeof(afvec2);
        const int fakeTxcdCount = texcoordCount + extraTxcds + 1;
        nboPackInt(txcdStart, fakeTxcdCount); // NOTE: 'buf' is not being set
        // add the drawInfo length at the end
        buf = nboPackInt(buf, fullLength + sizeof(afvec2));
        buf = nboPackInt(buf, 0); // for alignment to afvec2

        logDebugMessage(4,"DrawInfo packing: length = %i, missing = %i\n", length, missing);
        logDebugMessage(4,"  texcoordCount = %i, fakeTxcdCount = %i, rewindLen = %i\n",
                        texcoordCount, fakeTxcdCount, fullLength + (int)sizeof(afvec2));
    }

    buf = nboPackInt(buf, faceCount);
    for (i = 0; i < faceCount; i++)
        buf = faces[i]->pack(buf);

    // pack the state byte
    unsigned char stateByte = 0;
    stateByte |= isDriveThrough() ? (1 << 0) : 0;
    stateByte |= isShootThrough() ? (1 << 1) : 0;
    stateByte |= smoothBounce     ? (1 << 2) : 0;
    stateByte |= noclusters       ? (1 << 3) : 0;
    stateByte |= drawInfoOwner    ? (1 << 4) : 0;
    stateByte |= canRicochet()    ? (1 << 5) : 0;
    buf = nboPackUByte(buf, stateByte);

    return buf;
}


const void *MeshObstacle::unpack(const void *buf)
{
    int i;
    int32_t inTmp;

    buf = nboUnpackInt(buf, inTmp);
    checkCount = int(inTmp);
    checkTypes = new char[checkCount];
    checkPoints = new afvec3[checkCount];
    for (i = 0; i < checkCount; i++)
    {
        unsigned char tmp;
        buf = nboUnpackUByte(buf, tmp);
        checkTypes[i] = tmp;
        buf = nboUnpackVector(buf, checkPoints[i]);
    }

    buf = nboUnpackInt(buf, inTmp);
    vertexCount = int(inTmp);
    vertices = new afvec3[vertexCount];
    for (i = 0; i < vertexCount; i++)
        buf = nboUnpackVector(buf, vertices[i]);

    buf = nboUnpackInt(buf, inTmp);
    normalCount = int(inTmp);
    normals = new afvec3[normalCount];
    for (i = 0; i < normalCount; i++)
        buf = nboUnpackVector(buf, normals[i]);

    buf = nboUnpackInt(buf, inTmp);
    texcoordCount = int(inTmp);
    texcoords = new afvec2[texcoordCount];
    // note where the texture coordinates begin; skip past them
    const void* texCoordPtr = buf;
    buf = (const char*)buf + sizeof(float) * 2 * texcoordCount;
    const char* texcoordEnd = (const char*)buf;   // for locating hidden drawInfo data

    buf = nboUnpackInt(buf, inTmp);
    faceSize = int(inTmp);
    faces = new MeshFace*[faceSize];
    faceCount = 0;
    for (i = 0; i < faceSize; i++)
    {
        faces[faceCount] = new MeshFace(this);
        buf = faces[faceCount]->unpack(buf);
        if (!faces[faceCount]->isValid())
            delete faces[faceCount];
        else
            faceCount++;
    }

    // unpack the state byte
    bool drawInfoOwner;
    unsigned char stateByte;
    buf = nboUnpackUByte(buf, stateByte);
    driveThrough  = (stateByte & (1 << 0)) != 0;
    shootThrough  = (stateByte & (1 << 1)) != 0;
    smoothBounce  = (stateByte & (1 << 2)) != 0;
    noclusters    = (stateByte & (1 << 3)) != 0;
    drawInfoOwner = (stateByte & (1 << 4)) != 0;
    ricochet      = (stateByte & (1 << 5)) != 0;

    if (drawInfoOwner && (vertexCount >= 1))
    {
        // remove the extraneous vertex
        vertexCount--;
    }

    // unpack hidden drawInfo data as from extra texture coordinates
    if (drawInfoOwner &&  (texcoordCount >= 2))
    {
        nboUseErrorChecking(false);
        {
            const void* drawInfoSize = texcoordEnd - sizeof(afvec2);
            int32_t rewindLen;
            nboUnpackInt(drawInfoSize, rewindLen);

            const bool useDrawInfo = BZDB.isTrue("useDrawInfo");

            if (rewindLen <= (int)(texcoordCount * sizeof(afvec2)))
            {
                // unpack the drawInfo
                if (useDrawInfo)
                {
                    const void* drawInfoData = texcoordEnd - rewindLen;
                    drawInfo = new MeshDrawInfo();
                    drawInfo->unpack(drawInfoData);
                    name = drawInfo->getName(); // get the proxied name
                }

                // free the bogus texcoords
                // FIXME - don't allocate storage we won't use
                const int fakeTxcds = rewindLen / sizeof(afvec2);
                texcoordCount = texcoordCount - fakeTxcds;
                afvec2* tmpTxcds = new afvec2[texcoordCount];
                delete[] texcoords;
                int ptrDiff = (char*)tmpTxcds - (char*)texcoords;
                texcoords = tmpTxcds;
                // unpack actual texcoords
                for (i = 0; i < texcoordCount; i++)
                {
                    texCoordPtr = nboUnpackFloat(texCoordPtr, texcoords[i][0]);
                    texCoordPtr = nboUnpackFloat(texCoordPtr, texcoords[i][1]);
                }

                // setup the drawInfo arrays
                if (useDrawInfo)
                {
                    drawInfo->clientSetup(this);
                    if (!drawInfo->isValid())
                    {
                        delete drawInfo;
                        drawInfo = NULL;
                    }
                }

                // remap the texcoord pointers in the faces
                for (i = 0; i < faceCount; i++)
                {
                    MeshFace* face = faces[i];
                    if (face->useTexcoords())
                    {
                        for (int v = 0; v < face->getVertexCount(); v++)
                        {
                            face->texcoords[v] =
                                (float*)((char*)face->texcoords[v] + ptrDiff);
                        }
                    }
                }

                logDebugMessage(4,"DrawInfo unpacking: fakeTxcds = %i, realTxcds = %i\n",
                                fakeTxcds, texcoordCount);
            }
        }
        nboUseErrorChecking(true);
    }
    else
    {
        // no "hidden" drawInfo data; just unpack the texcoords
        for (i = 0; i < texcoordCount; i++)
        {
            texCoordPtr = nboUnpackFloat(texCoordPtr, texcoords[i][0]);
            texCoordPtr = nboUnpackFloat(texCoordPtr, texcoords[i][1]);
        }
    }

    finalize();

    return buf;
}


static void outputFloat(std::ostream& out, float value)
{
    char buffer[32];
    snprintf(buffer, 30, " %.8f", value);
    out << buffer;
    return;
}


void MeshObstacle::print(std::ostream& out, const std::string& indent) const
{
    out << indent << "mesh" << std::endl;

    out << indent << "# faces = " << faceCount << std::endl;
    out << indent << "# checks = " << checkCount << std::endl;
    out << indent << "# vertices = " << vertexCount << std::endl;
    out << indent << "# normals = " << normalCount << std::endl;
    out << indent << "# texcoords = " << texcoordCount << std::endl;
    out << indent << "# mins = " << extents.mins[0] << " "
        << extents.mins[1] << " "
        << extents.mins[2] << std::endl;
    out << indent << "# maxs = " << extents.maxs[0] << " "
        << extents.maxs[1] << " "
        << extents.maxs[2] << std::endl;

    if (name.size() > 0)
        out << indent << "  name " << name << std::endl;

    if (noclusters)
        out << indent << "  noclusters" << std::endl;
    if (smoothBounce)
        out << indent << "  smoothBounce" << std::endl;
    if (driveThrough && shootThrough)
        out << indent << "  passable" << std::endl;
    else
    {
        if (driveThrough)
            out << indent << "  driveThrough" << std::endl;
        if (shootThrough)
            out << indent << "  shootThrough" << std::endl;
    }
    if (ricochet)
        out << indent << "  ricochet" << std::endl;

    int i, j;
    for (i = 0; i < checkCount; i++)
    {
        if (checkTypes[i] == CheckInside)
            out << indent << "  inside";
        else
            out << indent << "  outside";
        for (j = 0; j < 3; j++)
            outputFloat(out, checkPoints[i][j]);
        out << std::endl;
    }
    for (i = 0; i < vertexCount; i++)
    {
        out << indent << "  vertex";
        for (j = 0; j < 3; j++)
            outputFloat(out, vertices[i][j]);
        out << std::endl;
    }
    for (i = 0; i < normalCount; i++)
    {
        out << indent << "  normal";
        for (j = 0; j < 3; j++)
            outputFloat(out, normals[i][j]);
        out << std::endl;
    }
    for (i = 0; i < texcoordCount; i++)
    {
        out << indent << "  texcoord";
        for (j = 0; j < 2; j++)
            outputFloat(out, texcoords[i][j]);
        out << std::endl;
    }

    for (int f = 0; f < faceCount; f++)
        faces[f]->print(out, indent);

    // MeshDrawInfo
    if ((drawInfo != NULL) && !drawInfo->isCopy())
    {
        std::string indent2 = indent + "  ";
        drawInfo->print(out, indent2);
    }

    out << indent << "end" << std::endl;

    return;
}


void MeshObstacle::printOBJ(std::ostream& out, const std::string& UNUSED(indent)) const
{
    // save as OBJ
    int i;

    out << "# OBJ - start" << std::endl;
    if (name.size() > 0)
        out << "o " << name << "_" << getObjCounter() << std::endl;
    else
        out << "o unnamed_" << getObjCounter() << std::endl;

    out << "# faces = " << faceCount << std::endl;
    out << "# vertices = " << vertexCount << std::endl;
    out << "# normals = " << normalCount << std::endl;
    out << "# texcoords = " << texcoordCount << std::endl;

    const float* tmp;
    tmp = extents.mins;
    out << "# mins = " << tmp[0] << " " << tmp[1] << " " << tmp[2] << std::endl;
    tmp = extents.maxs;
    out << "# maxs = " << tmp[0] << " " << tmp[1] << " " << tmp[2] << std::endl;


    for (i = 0; i < vertexCount; i++)
    {
        out << "v";
        outputFloat(out, vertices[i][0]);
        outputFloat(out, vertices[i][1]);
        outputFloat(out, vertices[i][2]);
        out << std::endl;
    }
    for (i = 0; i < normalCount; i++)
    {
        out << "vn";
        outputFloat(out, normals[i][0]);
        outputFloat(out, normals[i][1]);
        outputFloat(out, normals[i][2]);
        out << std::endl;
    }
    for (i = 0; i < texcoordCount; i++)
    {
        out << "vt";
        outputFloat(out, texcoords[i][0]);
        outputFloat(out, texcoords[i][1]);
        out << std::endl;
    }
    const BzMaterial* bzmat = NULL;
    for (int f = 0; f < faceCount; f++)
    {
        const MeshFace* face = faces[f];
        const BzMaterial* nextMat = face->getMaterial();
        if (bzmat != nextMat)
        {
            bzmat = nextMat;
            out << "usemtl ";
            MATERIALMGR.printReference(out, bzmat);
            out << std::endl;
        }
        const int vCount = face->getVertexCount();
        const bool useNormals = face->useNormals();
        const bool useTexcoords = face->useTexcoords();
        out << "f";
        for (i = 0; i < vCount; i++)
        {
            int vIndex = (const afvec3*)face->getVertex(i) - vertices;
            vIndex = vIndex - vertexCount;
            out << " " << vIndex;
            if (useTexcoords)
            {
                int tIndex = (const afvec2*)face->getTexcoord(i) - texcoords;
                tIndex = tIndex - texcoordCount;
                out << "/" << tIndex;
            }
            if (useNormals)
            {
                if (!useTexcoords)
                    out << "/";
                int nIndex = (const afvec3*)face->getNormal(i) - normals;
                nIndex = nIndex - normalCount;
                out << "/" << nIndex;
            }
        }
        out << std::endl;
    }

    out << "# OBJ - end" << std::endl << std::endl;

    incObjCounter();

    return;
}


// Local Variables: ***
// mode: C++ ***
// tab-width: 4 ***
// c-basic-offset: 4 ***
// indent-tabs-mode: nil ***
// End: ***
// ex: shiftwidth=4 tabstop=4
