/*
 * Converting no hs worlds to real binary worlds (with bsps).
 * No HS worlds are used in the generation process of worlds
 *
 * Copyright (c) 1998 Criterion Software Ltd.
 */

/****************************************************************************
 Includes
 */

#include <stdlib.h>
#include <math.h>
#include <string.h>

#include "rwcore.h"

#include "rpdbgerr.h"

#include "nhsstats.h"
#include "nhsutil.h"
#include "nhsworld.h"

static const char   rcsid[] __RWUNUSED__ =
    "@@(#)$Id: nhsworld.c,v 1.67 2001/08/28 10:32:29 mattt Exp $";

/****************************************************************************
 Local Types
 */

/****************************************************************************
 Local (Static) Prototypes
 */

/****************************************************************************
 Local Defines
 */

#if (defined(_WINDOWS))
#define RWCDECL __cdecl
#endif /* (defined(_WINDOWS)) */

#if (!defined(RWCDECL))
#define RWCDECL                /* No op */
#endif /* (!defined(RWCDECL)) */

/****************************************************************************
 Globals (across program)
 */

/****************************************************************************
 Local (static) Globals
 */

/****************************************************************************
 Functions
 */

/***************************************************************************
 ***************************************************************************
          Converting the world from build sectors to world sectors
 ***************************************************************************
 ***************************************************************************/

/****************************************************************************
 ImportBuildSectorCompress

 Compresses the build sector into the world sector
 Has to find the appropriate texture map tiling

 On entry   : World sector
            : Build sector
            : World
            : Material List (from noHsWorld)
 On exit    : World sector on success
 */

static RpWorldSector *
ImportBuildSectorCompress(RpWorldSector * worldSector,
                          RtWorldImportBuildSector * buildSector,
                          RpWorld * world, RpMaterialList * noHsMatList,
                          RtWorldImportUserdataCallBacks *
                          UserDataCallBacks)
{
    RwInt32             nI, nJ;

    RWFUNCTION(RWSTRING("ImportBuildSectorCompress"));
    RWASSERT(worldSector);
    RWASSERT(buildSector);

    /* Compress the sector */
    if (buildSector->numPolygons)
    {
        RtWorldImportBuildVertex *boundaries;
        RwUInt8             matUsedFlags[256];
        RpPolygon          *destPolygon;
        RwInt32             vertMapSize = (sizeof(RwInt32) *
                                           buildSector->numVertices);
        RwInt32            *vertMap = (RwInt32 *) RwMalloc(vertMapSize);

        if (vertMap)
        {
            RwInt32             currentIndex = 0;

            /* clear the map */
            for (nJ = 0; nJ < buildSector->numVertices; nJ++)
            {
                vertMap[nJ] = -1;
            }

            /* first build a vertex map */
            for (nJ = 0;
                 nJ < rpMaterialListGetNumMaterials(noHsMatList); nJ++)
            {
                boundaries = buildSector->boundaries;
                for (nI = 0; nI < buildSector->numPolygons;
                     nI++, boundaries += 4)
                {
                    if (boundaries[3].pinfo.matIndex == nJ)
                    {
                        RtWorldImportBuildVertexMode *mode;
                        RwInt32             index;

                        /* the indices */
                        mode = &boundaries[0].mode;
                        index = mode->vpVert - buildSector->vertices;

                        if (-1 == vertMap[index])
                        {
                            vertMap[index] = currentIndex++;
                        }

                        mode = &boundaries[1].mode;
                        index = mode->vpVert - buildSector->vertices;

                        if (-1 == vertMap[index])
                        {
                            vertMap[index] = currentIndex++;
                        }

                        mode = &boundaries[2].mode;
                        index = mode->vpVert - buildSector->vertices;
                        if (-1 == vertMap[index])
                        {
                            vertMap[index] = currentIndex++;
                        }
                    }
                }
            }
        }
        else
        {
            RWERROR((E_RW_NOMEM, vertMapSize));
            RWRETURN((RpWorldSector *) NULL);
        }

        /* Copy over the coords */
        {
            RwV3d              *vertices = worldSector->vertices;

            for (nI = 0; nI < buildSector->numVertices; nI++)
            {
                RwV3d               pos;

                pos = buildSector->vertices[nI].OC;

                vertices[vertMap[nI]] = pos;
            }
        }

        /* Copy over the normal info if necessary */
        if (rwObjectTestFlags(world, rpWORLDNORMALS))
        {
            RpVertexNormal     *normals = worldSector->normals;
            RwV3d               anormal;
            RwReal              length2;
            RwReal              factor;

            for (nI = 0; nI < buildSector->numVertices; nI++)
            {
                anormal = buildSector->vertices[nI].normal;

                length2 = RwV3dDotProduct(&anormal, &anormal);

                factor = ((0 < length2) ?
                          ((RwReal) 1) / ((RwReal) sqrt(length2)) :
                          ((RwReal) 0));

                RwV3dScale(&anormal, &anormal, factor);

                RPVERTEXNORMALFROMRWV3D(normals[vertMap[nI]], anormal);
            }
        }

        /* Copy over the prelighting info if necessary */
        if (rwObjectTestFlags(world, rpWORLDPRELIT))
        {
            RwRGBA             *preLitLum = worldSector->preLitLum;

            for (nI = 0; nI < buildSector->numVertices; nI++)
            {
                preLitLum[vertMap[nI]] =
                    buildSector->vertices[nI].preLitCol;
            }
        }

        /* Copy over vertex texture coords if necessary */
        for (nI=0; nI<world->numTexCoordSets; nI++)
        {
            RwTexCoords  *texCoords = worldSector->texCoords[nI];

            for (nJ=0; nJ<buildSector->numVertices; nJ++)
            {
                texCoords[vertMap[nJ]].u = 
                    buildSector->vertices[nJ].texCoords[nI].u;
                texCoords[vertMap[nJ]].v =
                    buildSector->vertices[nJ].texCoords[nI].v;
            }
        }

        /* Call vertex userdata callback on each vertex */
        if (UserDataCallBacks->sectorSetVertexUserdata)
        {
            for (nI = 0; nI < buildSector->numVertices; nI++)
            {
                UserDataCallBacks->
                    sectorSetVertexUserdata(&buildSector->vertices[nI].
                                            pUserdata, worldSector,
                                            vertMap[nI]);
            }
        }

        /* Establish a base window address */
        if (rpMaterialListGetNumMaterials(&world->matList) > 256)
        {
            worldSector->matListWindowBase = (RwUInt16)
                rpMaterialListGetNumMaterials(&world->matList) - 256;
        }
        else
        {
            worldSector->matListWindowBase = 0;
        }

        /* ensure that all used materials are within 256 of each other */
        memset(matUsedFlags, 0U, sizeof(matUsedFlags));
        boundaries = buildSector->boundaries;
        for (nI = 0; nI < buildSector->numBoundaries;
             nI++, boundaries++)
        {
            RtWorldImportBuildVertexMode const *mode =
                &boundaries->mode;

            if (!mode->vpVert)
            {
                RpMaterial         *mat =
                    rpMaterialListGetMaterial(noHsMatList,
                                              boundaries->pinfo.
                                              matIndex);

                /* can we see this within Material window? */
                for (nJ = worldSector->matListWindowBase;
                     nJ <
                     rpMaterialListGetNumMaterials(&world->matList);
                     nJ++)
                {
                    if (rpMaterialListGetMaterial(&world->matList, nJ)
                        == mat)
                    {
                        /* found */
                        matUsedFlags[nJ -
                                     worldSector->matListWindowBase] =
                            1;
                        break;
                    }
                }

                /* we didn't find material within window */
                if (nJ ==
                    rpMaterialListGetNumMaterials(&world->matList))
                {
                    RwInt32             matIndex;

                    /* slide window base along until material can be added */
                    while ((worldSector->matListWindowBase + 256) <=
                           rpMaterialListGetNumMaterials(&world->
                                                         matList))
                    {
                        RwUInt8             usedMatSlidOut =
                            matUsedFlags[0];

                        worldSector->matListWindowBase++;

                        memmove(&matUsedFlags[0], &matUsedFlags[1],
                                &matUsedFlags[256] - &matUsedFlags[1]);
                        matUsedFlags[255] = 0;

                        /* by sliding the window base along
                         * we've blocked reference to a material
                         * we were using, mandating a duplicate 
                         pointer to it */
                        if (usedMatSlidOut)
                        {
                            RpMaterial         *mat2Dupe;
                            RwInt32             newMatIndex;

                            mat2Dupe =
                                rpMaterialListGetMaterial(&world->
                                                          matList,
                                                          worldSector->
                                                          matListWindowBase
                                                          - 1);
                            newMatIndex =
                                rpMaterialListAppendMaterial(&world->
                                                             matList,
                                                             mat2Dupe);
                            matUsedFlags[newMatIndex -
                                         worldSector->
                                         matListWindowBase] = 1;
                        }
                    }

                    matIndex =
                        rpMaterialListAppendMaterial(&world->matList,
                                                     mat);
                    matUsedFlags[matIndex -
                                 worldSector->matListWindowBase] = 1;
                }
            }
        }

        /* compress the triangles */
        destPolygon = worldSector->polygons;

        boundaries = buildSector->boundaries;
        for (nI = 0; nI < buildSector->numPolygons;
             nI++, boundaries += 4)
        {
            RpMaterial         *mat;

            RtWorldImportSectorSetPolygonUserdataCallBack
                sectorSetPolygonUserdata;
            RtWorldImportBuildVertexMode *mode;
            RwInt32             index;

            /* the indices */
            mode = &boundaries[0].mode;
            index = mode->vpVert - buildSector->vertices;
            destPolygon->vertIndex[0] = (RwUInt16) vertMap[index];

            mode = &boundaries[1].mode;
            index = mode->vpVert - buildSector->vertices;
            destPolygon->vertIndex[1] = (RwUInt16) vertMap[index];

            mode = &boundaries[2].mode;
            index = mode->vpVert - buildSector->vertices;
            destPolygon->vertIndex[2] = (RwUInt16) vertMap[index];

            /* the material  - we need to find where in window */
            mat = rpMaterialListGetMaterial(noHsMatList,
                                            boundaries[3].pinfo.
                                            matIndex);

            for (nJ = worldSector->matListWindowBase;
                 nJ < rpMaterialListGetNumMaterials(&world->matList);
                 nJ++)
            {
                if (rpMaterialListGetMaterial(&world->matList, nJ) ==
                    mat)
                {
                    destPolygon->matIndex =
                        (RwUInt16) (nJ -
                                    worldSector->matListWindowBase);
                    break;
                }
            }

            RWASSERT(UserDataCallBacks);

            sectorSetPolygonUserdata =
                UserDataCallBacks->sectorSetPolygonUserdata;

            /* Call polygon userdata callback on each vertex */
            if (sectorSetPolygonUserdata)
            {
                void              **pUserdata =
                    (void **) &boundaries[3].pinfo.pUserdata;

                sectorSetPolygonUserdata(pUserdata, worldSector, nI);

            }

            /* Onto the next triangle */
            destPolygon++;
        }

        RwFree(vertMap);
        if ((RwUInt32) (destPolygon - worldSector->polygons) !=
            (RwUInt32) worldSector->numPolygons)
        {
            RWERROR((E_RW_SECTORINVNOPOLYGONS));
            RWRETURN((RpWorldSector *) NULL);
        }
    }
    else
    {
        worldSector->numVertices = 0;
        worldSector->numPolygons = 0;
    }

    RWRETURN(worldSector);
}

/****************************************************************************
 _rwImportWorldSectorCreate

 Compresses a Build sector into an world sector

 On entry       : RtWorldImportBuildSector
 On exit        : RpWorldSector
 */

static RpWorldSector *
ImportWorldSectorCreate(RtWorldImportBuildSector * buildSector,
                        RpWorld * world, RpMaterialList * noHsMatList,
                        RtWorldImportUserdataCallBacks *
                        UserDataCallBacks)
{
    RpWorldSector      *worldSector;
    RwInt32             numTriangles;
    RtWorldImportBuildSector *triangulatedBuildSector;

    RWFUNCTION(RWSTRING("ImportWorldSectorCreate"));
    RWASSERT(buildSector);

    numTriangles = _rtImportBuildSectorFindNumTriangles(buildSector);

    worldSector = (RpWorldSector *) RwMalloc(sectorTKList.sizeOfStruct);
    if (!worldSector)
    {
        RWERROR((E_RW_NOMEM, sectorTKList.sizeOfStruct));
        RWRETURN((RpWorldSector *) NULL);
    }

    if (!_rtImportWorldSectorInitialize
        (worldSector, world, &buildSector->boundingBox, numTriangles,
         buildSector->numVertices))
    {
        RwFree(worldSector);
        RWRETURN((RpWorldSector *) NULL);
    }

    /* Initialize memory allocated to toolkits */
    rwPluginRegistryInitObject(&sectorTKList, worldSector);

    if (numTriangles)
    {
        /* Set up the triangle array */
        triangulatedBuildSector =
            _rtImportBuildSectorTriangulize(buildSector, numTriangles,
                                            UserDataCallBacks);

        /* Copy over the information */
        ImportBuildSectorCompress(worldSector, triangulatedBuildSector,
                                  world, noHsMatList,
                                  UserDataCallBacks);

        /* Done with the triangulized one */
        _rtImportBuildSectorDestroy(triangulatedBuildSector,
                                    UserDataCallBacks);
    }

    /* Sanity check */
    _rtImportWorldSectorCheck(worldSector);

    /* Done */
    RWRETURN(worldSector);
}

/****************************************************************************
 _rtImportWorldSectorCompressTree

 On entry       : Sector tree
 On exit        : Pointer to root node
 */

RpSector           *
_rtImportWorldSectorCompressTree(RpSector * rootSector, RpWorld * world,
                                 RpMaterialList * noHsMatList,
                                 RtWorldImportUserdataCallBacks *
                                 UserDataCallBacks)
{
    RWFUNCTION(RWSTRING("_rtImportWorldSectorCompressTree"));
    RWASSERT(rootSector);

    /* Handle the cases */
    switch (rootSector->type)
    {
        case rwSECTORATOMIC:
            {
                /* This sector is already a world sector -> do nothing */
                break;
            }
        case rwSECTORBUILD:
            {
                RpSector           *spNew;
                RtWorldImportBuildSector *buildSector =
                    (RtWorldImportBuildSector *) rootSector;

                /* This is a build sector -> make it a world sector */
                spNew =
                    (RpSector *) ImportWorldSectorCreate(buildSector,
                                                         world,
                                                         noHsMatList,
                                                         UserDataCallBacks);

                /* Record that more polygons have been compressed */
                _rtWorldImportNumPolysInCompressedLeaves +=
                    buildSector->numPolygons;
                _rtImportWorldSendProgressMessage
                    (rtWORLDIMPORTPROGRESSBSPCOMPRESSUPDATE,
                     ((RwReal) _rtWorldImportNumPolysInCompressedLeaves
                      / (RwReal) _rtWorldImportTotalPolysInWorld) *
                     100);

                /* Destroy the old */
                _rtImportBuildSectorDestroy(buildSector,
                                            UserDataCallBacks);

                /* Return the new sector */
                RWRETURN(spNew);
            }
        default:
            {
                RpPlaneSector      *planeSector =
                    (RpPlaneSector *) rootSector;

                /* There is some junk after the structure,
                 * but we can live with that ;> */
                planeSector->leftSubTree =
                    _rtImportWorldSectorCompressTree(planeSector->
                                                     leftSubTree, world,
                                                     noHsMatList,
                                                     UserDataCallBacks);
                planeSector->rightSubTree =
                    _rtImportWorldSectorCompressTree(planeSector->
                                                     rightSubTree,
                                                     world, noHsMatList,
                                                     UserDataCallBacks);

                /* Return the sector */
                break;
            }
    }

    RWRETURN(rootSector);
}
