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

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

#include <rwcore.h>

#include "basector.h"
#include "baworld.h"
#include "bamateri.h"
#include "bamatlst.h"
#include "babinwor.h"

#if (!defined(DOXYGEN))
static const char   rcsid[] __RWUNUSED__ =
"@@(#)$Id: babinwor.c,v 1.161 2001/09/25 11:34:17 iestynb Exp $";
#endif /* (!defined(DOXYGEN)) */

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

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

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

#define RWSTREAMTYPE(_type)                                              \
  ( ( rwNASTREAM == (_type) ) ? "rwNASTREAM" :                           \
    ( ( rwSTREAMFILE == (_type) ) ? "rwSTREAMFILE" :                     \
      ( ( rwSTREAMFILENAME == (_type) ) ? "rwSTREAMFILENAME" :           \
        ( ( rwSTREAMMEMORY == (_type) ) ? "rwSTREAMMEMORY" : "Unknown" ) \
        ) ) )

/*
 * World format (as stored in the binary stream):-
 *  0xg000nngg, nn = num tex coords, gg = flags
 * See also baworld.h: rpWORLDTEXCOORDSETS(_num)
 *
 * To determine number of texture coordinate sets, the 'num tex coords'
 * field takes precedence, but if zero, we use the backward compatibility
 * flags.
 */
#define WorldFormatGetNumTexCoordSetsMacro(_fmt)            \
    (((_fmt) & 0xff0000) ? (((_fmt) & 0xff0000)>>16) :      \
        (((_fmt) & rpGEOMETRYTEXTURED2) ? 2 :               \
            (((_fmt) & rpGEOMETRYTEXTURED) ? 1 : 0)))

#define WorldFormatGetFlagsMacro(_fmt) ((_fmt) & 0xff

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

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

static RwModuleInfo binWorldModule;


/* !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!

                     Rights support

 !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! */

static RwUInt32 lastSeenWorldRightsPluginId;

RwStream*
_rpReadWorldRights(RwStream *s,
                   RwInt32 len __RWUNUSED__,
                   void *obj __RWUNUSED__,
                   RwInt32 off __RWUNUSED__,
                   RwInt32 size __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("_rpReadWorldRights"));

    if (!RwStreamReadInt32(s, (RwInt32*)&(lastSeenWorldRightsPluginId),
                           sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }
    RWRETURN(s);
}

RwStream*
_rpWriteWorldRights(RwStream *s,
                    RwInt32 len __RWUNUSED__,
                    const void *obj,
                    RwInt32 off __RWUNUSED__,
                    RwInt32 size __RWUNUSED__)
{
    const RpWorld *wrl;

    RWFUNCTION(RWSTRING("_rpWriteWorldRights"));
    RWASSERT(obj);

    wrl = (const RpWorld*)obj;
    RWASSERT(wrl->pipeline);
    RWASSERT(wrl->pipeline->pluginId);

    if (!RwStreamWriteInt32(s, (RwInt32*)&(wrl->pipeline->pluginId),
                            sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    RWRETURN(s);
}

RwInt32
_rpSizeWorldRights(const void *obj, 
                   RwInt32 off __RWUNUSED__,
                   RwInt32 size __RWUNUSED__)
{
    const RpWorld *wrl;

    RWFUNCTION(RWSTRING("_rpSizeWorldRights"));
    RWASSERT(obj);

    wrl = (const RpWorld*)obj;
    if ((wrl->pipeline) && (wrl->pipeline->pluginId))
    {
        RWRETURN(sizeof(RwUInt32));
    }
    else
    {
        RWRETURN(0);
    }
}

static RwUInt32 lastSeenSectRightsPluginId;

RwStream*
_rpReadSectRights(RwStream *s,
                  RwInt32 len __RWUNUSED__,
                  void *obj __RWUNUSED__,
                  RwInt32 off __RWUNUSED__,
                  RwInt32 size __RWUNUSED__)
{
    RWFUNCTION(RWSTRING("_rpReadSectRights"));

    if (!RwStreamReadInt32(s, (RwInt32*)&(lastSeenSectRightsPluginId),
                           sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }
    RWRETURN(s);
}

RwStream*
_rpWriteSectRights(RwStream *s,
                   RwInt32 len __RWUNUSED__,
                   const void *obj,
                   RwInt32 off __RWUNUSED__,
                   RwInt32 size __RWUNUSED__)
{
    const RpWorldSector *sect;

    RWFUNCTION(RWSTRING("_rpWriteSectRights"));
    RWASSERT(obj);

    sect = (const RpWorldSector*)obj;
    RWASSERT(sect->pipeline);
    RWASSERT(sect->pipeline->pluginId);

    if (!RwStreamWriteInt32(s, (RwInt32*)&(sect->pipeline->pluginId),
                            sizeof(RwInt32)))
    {
        RWRETURN((RwStream *) NULL);
    }

    RWRETURN(s);
}

RwInt32
_rpSizeSectRights(const void *obj, 
                  RwInt32 off __RWUNUSED__,
                  RwInt32 size __RWUNUSED__)
{
    const RpWorldSector *sect;

    RWFUNCTION(RWSTRING("_rpSizeSectRights"));
    RWASSERT(obj);

    sect = (const RpWorldSector*)obj;
    if ((sect->pipeline) && (sect->pipeline->pluginId))
    {
        RWRETURN(sizeof(RwUInt32));
    }
    else
    {
        RWRETURN(0);
    }
}

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

/****************************************************************************
 WorldSectorStreamGetSizeActual

 On entry   : world sector
 On exit    : size of world sector actual (without extension chunks)
 */

static RwUInt32
WorldSectorStreamGetSizeActual(const RpWorldSector * worldSector,
                               const RpWorld * world,
                               RwUInt32 flags)
{
    RwUInt32            size;

    RWFUNCTION(RWSTRING("WorldSectorStreamGetSizeActual"));
    RWASSERT(worldSector);

    /* Structure header */
    size = sizeof(_rpWorldSector);

    /* Vertices */
    size += sizeof(RwV3d) * worldSector->numVertices;

    /* Normals */
    if (flags & rpWORLDNORMALS)
    {
        size += sizeof(RpVertexNormal) * worldSector->numVertices;
    }

    /* PreLitLums */
    if (flags & rpWORLDPRELIT)
    {
        size += sizeof(RwRGBA) * worldSector->numVertices;
    }

    /* Texture coordinates  - written as floats */
    size += sizeof(RwTexCoords) * worldSector->numVertices
            * world->numTexCoordSets;

    /* Polygons */
    size += sizeof(RpPolygon) * worldSector->numPolygons;

    /* BSP collision stuff - can only exist for worlds originating
     * from pre v304 data
     */
    if (worldSector->colSectorRoot)
    {
        size += sizeof(RpCollSector) * (1 << rwMAXCOLLISIONCUTS);
    }

    RWRETURN(size);
}

/****************************************************************************
 WorldSectorStreamGetSize

 On entry   :
 On exit    : Size of Binary worldSector
 */

static RwUInt32
WorldSectorStreamGetSize(const RpWorldSector * worldSector, const
                         RpWorld * world, RwUInt32 flags)
{
    RwUInt32            size;

    RWFUNCTION(RWSTRING("WorldSectorStreamGetSize"));
    RWASSERT(worldSector);

    size = WorldSectorStreamGetSizeActual(worldSector, world,
                                          flags) + rwCHUNKHEADERSIZE;

    /* Plugin data size */
    size +=
        rwPluginRegistryGetSize(&sectorTKList,
                                worldSector) + rwCHUNKHEADERSIZE;

    RWRETURN(size);
}

/****************************************************************************
 WorldSectorStreamWrite

 On entry   : Stream to write to
 On exit    :
 */

static const RpWorldSector *
WorldSectorStreamWrite(const RpWorldSector * worldSector, RwStream
                       * stream, const RpWorld * world, RwUInt32 flags)
{
    _rpWorldSector      as;

    RWFUNCTION(RWSTRING("WorldSectorStreamWrite"));
    RWASSERT(worldSector);
    RWASSERT(stream);

    if (!RwStreamWriteChunkHeader(stream, rwID_ATOMICSECT,
                                  WorldSectorStreamGetSize
                                  (worldSector, world, flags)))
    {
        RWRETURN((const RpWorldSector *)NULL);
    }

    /* wrap structured data */
    if (!RwStreamWriteChunkHeader(stream, rwID_STRUCT,
                                  WorldSectorStreamGetSizeActual
                                  (worldSector, world, flags)))
    {
        RWRETURN((const RpWorldSector *)NULL);
    }

    /* Fill it - don't forget fancy way of assigning floats */
    as.matListWindowBase = worldSector->matListWindowBase;
    as.numPolygons = worldSector->numPolygons;
    as.numVertices = worldSector->numVertices;
    RwV3dAssign(&as.inf, &worldSector->boundingBox.inf);
    RwV3dAssign(&as.sup, &worldSector->boundingBox.sup);
    as.unused = FALSE;
    as.collSectorPresent =
        ((worldSector->colSectorRoot != NULL) ? TRUE : FALSE);

    /* Convert it */
    RwMemRealToFloat32(&as.inf, sizeof(as.inf));
    RwMemRealToFloat32(&as.sup, sizeof(as.sup));
    RwMemLittleEndian(&as, sizeof(as));

    /* Write it */
    if (!RwStreamWrite(stream, &as, sizeof(as)))
    {
        RWRETURN((const RpWorldSector *)NULL);
    }

    if (worldSector->numVertices)
    {
        RwInt32             vertexSize =

            sizeof(RwV3d) * worldSector->numVertices;

        /* Write off vertices - we always have these */
        /* Don't forget they are reals - they need converting */
        if (!RwStreamWriteReal(stream, (RwReal *)
                               worldSector->vertices, vertexSize))
        {
            RWRETURN((const RpWorldSector *)NULL);
        }

        /* Write off normals - conditional on whether the world has these */
        if (flags & rpWORLDNORMALS)
        {
            RwInt32             normalSize =

                sizeof(RpVertexNormal) * worldSector->numVertices;

            /* These are made of chars - don't need converting */
            if (!RwStreamWrite(stream, worldSector->normals, normalSize))
            {
                RWRETURN((const RpWorldSector *)NULL);
            }
        }

        /* Write off prelighting info -
         * conditional on whether the world has these */
        if (flags & rpWORLDPRELIT)
        {
            RwInt32             preLitLumSize =

                sizeof(RwRGBA) * worldSector->numVertices;

            /* Effectively RwUint8 [] */
            if (!RwStreamWrite(stream, (void *)
                               worldSector->preLitLum, preLitLumSize))
            {
                RWRETURN((const RpWorldSector *)NULL);
            }
        }

        /* Write off vertex texture coordinates
         * - conditional on whether the world has these */
        if (world->numTexCoordSets > 0)
        {
            RwInt32     i;
            RwInt32             vertexTexCoordSize;

            vertexTexCoordSize =
                sizeof(RwTexCoords) * worldSector->numVertices;

            for (i=0; i<world->numTexCoordSets; i++)
            {
                RWASSERT(worldSector->texCoords[i]);

                if (!RwStreamWriteReal(stream, 
                        (RwReal *) worldSector->texCoords[i], 
                        vertexTexCoordSize))
                {
                    RWRETURN((const RpWorldSector *)NULL);
                }
            }
        }

    }

    /* Write off polygons */
    if (worldSector->numPolygons)
    {
        RwInt32 polygonSize = sizeof(RpPolygon) * worldSector->numPolygons;

        /* Write off polygons - we always have these */
        /* RpPolygon's consist of a RwUInt16 & an array of 3 RwUInt16's */
        if (!RwStreamWriteInt16(stream,
                                (RwInt16 *)worldSector->polygons,
                                polygonSize))
        {
            RWRETURN((const RpWorldSector *)NULL);
        }
    }

    /* Write off collision sectors - these can only exist for worlds
     * originating from pre v304 data.
     * These are just made up of chars at the bottom layer
     * - no conversion necessary */
    if (worldSector->colSectorRoot)
    {
        RwInt32             colSectorSize;
        RpCollSector        coll[1 << rwMAXCOLLISIONCUTS];

        colSectorSize =
            ((_rpCollSectorFindSize(worldSector->colSectorRoot) + 3) & (-4));

        /* Copy into a full size buffer, then write out of there because the
         * memory allocated in the world may not be big enough for a full
         * collision sector
         */
        memcpy(coll, worldSector->colSectorRoot, colSectorSize);

        /* Write it out - no conversion necessary, it's made of chars */
        if (!RwStreamWrite
            (stream, coll, sizeof(RpCollSector) * (1 << rwMAXCOLLISIONCUTS)))
        {
            RWRETURN((const RpWorldSector *)NULL);
        }
    }

    /* write out the plugin info */
    if (!rwPluginRegistryWriteDataChunks(&sectorTKList, stream, worldSector))
    {
        /* Failed to read extension data */
        RWRETURN((const RpWorldSector *)NULL);
    }

    RWRETURN(worldSector);
}

/****************************************************************************
 BinaryWorldMalloc

 This selects whether to draw memory form a malloc pool,
 or to RwMalloc some memory

 On entry   : Address binary world memory pointer, size of memory to allocate
 On exit    : Pointer to memory allocated
 */

static void        *
BinaryWorldMalloc(RwUInt8 ** binaryWorldMallocAddr, RwInt32 size)
{
    void               *pMemory;

    RWFUNCTION(RWSTRING("BinaryWorldMalloc"));

    /* We should allocate from the pool of pre-allocated memory */
    pMemory = (void *) (*binaryWorldMallocAddr);
    *binaryWorldMallocAddr += size;

    RWRETURN(pMemory);
}


/****************************************************************************
 Polygon4byteTo8ByteConvert

 This function takes the world binary stream polygon array from a pre 3.04
 stream and expands it to the new RpPolygon format of 16-bit indices.

 On entry   : Pointer to RpPolygon array and the number of polygons
 On exit    : void
 */

static void
Polygon4byteTo8ByteConvert(RpPolygon * polygons, RwInt32 numPolygons)
{
    RwUInt8            *indices, *tempInds;
    RwInt32             i;

    RWFUNCTION(RWSTRING("Polygon4byteTo8ByteConvert"));
    RWASSERT(polygons);

    indices = (RwUInt8 *) RwMalloc(numPolygons * 4);
    tempInds = indices;

    memcpy(indices, polygons, numPolygons * 4);

    for (i = 0; i < numPolygons; i++)
    {
        polygons[i].matIndex = *tempInds++;
        polygons[i].vertIndex[0] = *tempInds++;
        polygons[i].vertIndex[1] = *tempInds++;
        polygons[i].vertIndex[2] = *tempInds++;
    }

    RwFree(indices);

    RWRETURNVOID();
}


#ifdef RWDEBUG

static const RpWorldSector *
WorldSectorIsCorrectlySorted(const RpWorldSector * sector, RwBool * result)
{
    /* For PowerPipe, we need to check that the data is in a valid arrangement.
     * A valid arrangement is such that for each mesh (group of triangles
     * referencing the same material) in the object, the vertices that its
     * triangles reference are in a contiguous block, disjoint with all such
     * blocks for other meshes in the object.
     * This test calculates the min/max indices for each mesh and then sums up
     * (1 + maxIndex - minIndex) for all meshes. This should be equal to
     * the total number of vertices if the data is arranged correctly. */
    RWFUNCTION(RWSTRING("WorldSectorIsCorrectlySorted"));
    RWASSERT(sector != NULL);

    if (sector != NULL)
    {
        struct MatIndexBounds
        {
                RwUInt16            minIndex;
                RwUInt16            maxIndex;
        };
        struct MatIndexBounds *matIndexBounds;
        RwInt32             sumOfMeshVertIndexRanges;
        RwUInt16            vertIndex;
        RwInt16             matIndex;
        RwInt32             i, j;
        RwUInt32            bytes;
        RwInt32             numMaterials;
        RpWorld            *world;

        world = RpWorldSectorGetWorld(sector);
        RWASSERT(world);

        numMaterials = RpWorldGetNumMaterials(world);
        if (numMaterials <= 1)
        {
            *result = TRUE;
            RWRETURN(sector);
        }

        bytes = numMaterials * sizeof(struct MatIndexBounds);

        matIndexBounds = (struct MatIndexBounds *) RwMalloc(bytes);

        if (NULL == matIndexBounds)
        {
            RWERROR((E_RW_NOMEM, (bytes)));
            RWRETURN((const RpWorldSector *)NULL);
        }

        for (i = 0; i < numMaterials; i++)
        {
            matIndexBounds[i].minIndex = 65535;
            matIndexBounds[i].maxIndex = 0;
        }

        for (i = 0; i < sector->numPolygons; i++)
        {
            matIndex = (sector->polygons)[i].matIndex;
            RWASSERT(matIndex < numMaterials);
            for (j = 0; j < 3; j++)
            {
                vertIndex = (sector->polygons)[i].vertIndex[j];
                if (vertIndex > matIndexBounds[matIndex].maxIndex)
                {
                    matIndexBounds[matIndex].maxIndex = vertIndex;
                }
                if (vertIndex < matIndexBounds[matIndex].minIndex)
                {
                    matIndexBounds[matIndex].minIndex = vertIndex;
                }
            }
        }

        /* Is this geometry arranged ok? */
        sumOfMeshVertIndexRanges = 0;
        for (i = 0; i < numMaterials; i++)
        {
            sumOfMeshVertIndexRanges += 1 + matIndexBounds[i].maxIndex -
                matIndexBounds[i].minIndex;
        }

        RwFree(matIndexBounds);

        if (sumOfMeshVertIndexRanges > sector->numVertices)
        {
            /* Some meshes' ranges must overlap */
            *result = FALSE;
            RWRETURN(sector);
        }
        else
        {
            /* This geometry is correctly sorted for rendering by PowerPipe */
            *result = TRUE;
            RWRETURN(sector);
        }
    }
    else
    {
        RWRETURN((const RpWorldSector *)NULL);
    }
}

#endif /* RWDEBUG */


/****************************************************************************
 WorldSectorStreamRead

 On entry   : Stream to read from
 On exit    : worldSector created
 */
static RpWorldSector *
WorldSectorStreamRead(RwStream * stream,
                      RwUInt8 ** binaryWorldMallocAddr,
                      RpWorld * world,
                      RwUInt32  flags)
{
    RpWorldSector      *worldSector;
    _rpWorldSector      as;
    RwUInt32            version;
    RwInt32             i;

    RWFUNCTION(RWSTRING("WorldSectorStreamRead"));
    RWASSERT(stream);

    if (!RwStreamFindChunk(stream, (RwUInt32)rwID_STRUCT, 
                           (RwUInt32 *)NULL, &version))
    {
        RWRETURN((RpWorldSector *)NULL);
    }

    if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RpWorldSector *)NULL);
    }

    /* Read it */
    if (RwStreamRead(stream, &as, sizeof(as)) != sizeof(as))
    {
        RWRETURN((RpWorldSector *)NULL);
    }

    /* Convert it */
    RwMemNative(&as, sizeof(as));
    RwMemFloat32ToReal(&as.inf, sizeof(as.inf));
    RwMemFloat32ToReal(&as.sup, sizeof(as.sup));

    /* Grab some memory */
    worldSector = (RpWorldSector *)
        BinaryWorldMalloc(binaryWorldMallocAddr,
                          sectorTKList.sizeOfStruct);
    if (!worldSector)
    {
        RWERROR((E_RW_NOMEM, (sectorTKList.sizeOfStruct)));
        RWRETURN((RpWorldSector *)NULL);
    }

    /* Copy over values */
    worldSector->type = rwSECTORATOMIC;
    worldSector->boundingBox.inf = as.inf;
    worldSector->boundingBox.sup = as.sup;
    worldSector->matListWindowBase = (RwInt16) as.matListWindowBase;
    worldSector->numPolygons = (RwInt16) as.numPolygons;
    worldSector->numVertices = (RwInt16) as.numVertices;
    worldSector->polygons = (RpPolygon *)NULL;
    worldSector->vertices = (RwV3d *)NULL;
    worldSector->preLitLum = (RwRGBA *)NULL;
    worldSector->normals = (RpVertexNormal *)NULL;
    memset(worldSector->texCoords, 0, sizeof(RwTexCoords *) * rwMAXTEXTURECOORDS);
    worldSector->colSectorRoot = (RpCollSector *)NULL;
    worldSector->pipeline = (RxPipeline *)NULL;

    /* Not instanced !!! */
    worldSector->repEntry = (RwResEntry *)NULL;
    worldSector->mesh = (RpMeshHeader *)NULL;

    /* Set so it contains nothing */
    rwLinkListInitialize(&worldSector->collAtomicsInWorldSector);
    rwLinkListInitialize(&worldSector->noCollAtomicsInWorldSector);
    rwLinkListInitialize(&worldSector->lightsInWorldSector);

    if (as.numVertices)
    {
        RwInt32             vertexSize = sizeof(RwV3d) * as.numVertices;

        /* Memory allocation draws from the pre-allocated pool, or RwMallocs
         * a separate buffer as necessary
         */
        worldSector->vertices = (RwV3d *)
            BinaryWorldMalloc(binaryWorldMallocAddr, vertexSize);
        if (!worldSector->vertices)
        {
            /* Deallocation is performed in one block when we return */
            RWERROR((E_RW_NOMEM, vertexSize));
            RWRETURN((RpWorldSector *)NULL);
        }

        /* Read off vertices */
        /* Don't forget they are reals, so need converting */
        if (!RwStreamReadReal(stream, (RwReal *)
                              worldSector->vertices, vertexSize))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpWorldSector *)NULL);
        }

        if (flags & rpWORLDNORMALS)
        {
            RwInt32             normalSize =

                sizeof(RpVertexNormal) * as.numVertices;

            worldSector->normals = (RpVertexNormal *)
                BinaryWorldMalloc(binaryWorldMallocAddr, normalSize);
            if (!worldSector->normals)
            {
                /* Deallocation is performed in one block when we return */
                RWERROR((E_RW_NOMEM, normalSize));
                RWRETURN((RpWorldSector *)NULL);
            }

            /* These are made of chars - don't need converting */
            if (RwStreamRead(stream, worldSector->normals, normalSize)
                != (RwUInt32) normalSize)
            {
                /* Deallocation is performed in one block when we return */
                RWRETURN((RpWorldSector *)NULL);
            }
        }

        /* Read off Prelighting info
         * - conditional on whether the world has these */
        if (flags & rpWORLDPRELIT)
        {
            RwInt32             preLitLumSize =

                sizeof(RwRGBA) * as.numVertices;

            worldSector->preLitLum = (RwRGBA *)
                BinaryWorldMalloc(binaryWorldMallocAddr, preLitLumSize);
            if (!worldSector->preLitLum)
            {
                /* Deallocation is performed in one block when we return */
                RWERROR((E_RW_NOMEM, preLitLumSize));
                RWRETURN((RpWorldSector *)NULL);
            }

            if (!RwStreamRead
                (stream, (void *) worldSector->preLitLum, preLitLumSize))
            {
                /* Deallocation is performed in one block when we return */
                RWRETURN((RpWorldSector *)NULL);
            }
        }

        /* Read off vertex texture cooords
         * - conditional on whether the world has these */
        if (world->numTexCoordSets > 0)
        {
            RwInt32     texCoordSize;

            texCoordSize = sizeof(RwTexCoords) * worldSector->numVertices;

            for (i = 0; i < world->numTexCoordSets; i++)
            {
                worldSector->texCoords[i] = (RwTexCoords *)
                    BinaryWorldMalloc(binaryWorldMallocAddr, texCoordSize);
                if (!worldSector->texCoords[i])
                {
                    /* Deallocation is performed in one block when we return */
                    RWERROR((E_RW_NOMEM, texCoordSize));
                    RWRETURN((RpWorldSector *)NULL);
                }

                if (!RwStreamReadReal(stream, 
                        (RwReal *) worldSector->texCoords[i], texCoordSize))
                {
                    /* Deallocation is performed in one block when we return */
                    RWRETURN((RpWorldSector *)NULL);
                }
            }
        }
    }

    if (as.numPolygons)
    {
        RwInt32             polygonSize = sizeof(RpPolygon) * as.numPolygons;
        RwInt32             sizeToRead;

        /* Allocate all the memory first */
        worldSector->polygons = (RpPolygon *)
            BinaryWorldMalloc(binaryWorldMallocAddr, polygonSize);
        if (!worldSector->polygons)
        {
            /* Deallocation is performed in one block when we return */
            RWERROR((E_RW_NOMEM, polygonSize));
            RWRETURN((RpWorldSector *)NULL);
        }

        sizeToRead = polygonSize;

        /*
         * If this is an old stream format, halve the amount of bytes to read
         * because polygons are half the size.
         */
        if (version < 0x30400)
        {
            sizeToRead /= 2;
        }

        /* Read off polygons */
        if (RwStreamRead(stream, worldSector->polygons, sizeToRead)
            != (RwUInt32) sizeToRead)
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpWorldSector *)NULL);
        }

        /*
         * If this is an old stream format, convert the polygons to have
         * 16 bit indices rather than 8 bit ones.
         */
        if (version < 0x30400)
        {
            Polygon4byteTo8ByteConvert(worldSector->polygons,
                                       worldSector->numPolygons);
        }
        else
        {
            /* From this library version upwards, polygons are shorts */
            RwMemNative16(worldSector->polygons, polygonSize);
        }

#ifdef RWDEBUG
        /*
         * For PowerPipe we have to do more work. We need to get vertices
         * sorted by material and duplicated at material boundaries.
         * Inform the app if this has not been done.
         */
        {
            RwBool              result;

            if (WorldSectorIsCorrectlySorted(worldSector, &result) == NULL)
            {
                /* Out of memory most probably */
                RWRETURN((RpWorldSector *)NULL);
            }

            if (result == FALSE)
            {
                RWMESSAGE((RWSTRING
                           ("Warning: World sector is in an invalid format for "
                            "RxPipeline rendering. There may be visible artifacts "
                            "and/or decreased performance.")));
            }
        }
#endif /* RWDEBUG */
    }

    /* BSP collision stuff - these are still supported but only exist
     * for worlds originating from pre v304 data */
    if (as.collSectorPresent)
    {
        RwInt32             colSectorSize;
        RpCollSector        coll[1 << rwMAXCOLLISIONCUTS];
        RwUInt32            colSectorMaxSize;

        /* Read into a full size buffer,
         * then move to a smaller buffer
         * when we know how big it needs to be */
        /* No conversion necessary - these are made of chars */
        colSectorMaxSize = sizeof(RpCollSector) * (1 << rwMAXCOLLISIONCUTS);
        if (RwStreamRead(stream, coll, colSectorMaxSize) != colSectorMaxSize)
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpWorldSector *)NULL);
        }

        colSectorSize = ((_rpCollSectorFindSize(coll) + 3) & (-4));

        worldSector->colSectorRoot = (RpCollSector *)
            BinaryWorldMalloc(binaryWorldMallocAddr, colSectorSize);
        if (!worldSector->colSectorRoot)
        {
            /* Deallocation is performed in one block when we return */
            RWERROR((E_RW_NOMEM, colSectorSize));
            RWRETURN((RpWorldSector *)NULL);
        }
        memcpy(worldSector->colSectorRoot, coll, colSectorSize);
    }
    else
    {
        worldSector->colSectorRoot = (RpCollSector *)NULL;
    }

    /* Call the constructor as late as we can */
    rwPluginRegistryInitObject(&sectorTKList, worldSector);

    lastSeenSectRightsPluginId = 0;

    /* Read in the plugin info */
    if (!rwPluginRegistryReadDataChunks(&sectorTKList, stream, worldSector))
    {
        /* Failed to read extension data */
        RWRETURN((RpWorldSector *)NULL);
    }

    if (lastSeenSectRightsPluginId)
    {
        /* Silently ignore errors if any */
        _rwPluginRegistryInvokeRights(&sectorTKList, lastSeenSectRightsPluginId,
                                      worldSector);
    }

    RWRETURN(worldSector);
}

/****************************************************************************
 PlaneSectorStreamGetSize

 On entry   :
 On exit    : Size of Binary PlaneSector
 */

static RwUInt32
PlaneSectorStreamGetSize(const RpPlaneSector * planeSector, const
                         RpWorld * world, RwUInt32 flags)
{
    RwUInt32            size;

    RWFUNCTION(RWSTRING("PlaneSectorStreamGetSize"));
    RWASSERT(planeSector);

    size = sizeof(_rpPlaneSector) + rwCHUNKHEADERSIZE;

    /* Left side first */
    if (planeSector->leftSubTree->type < 0)
    {
        /* Atomic sector */
        size += WorldSectorStreamGetSize((RpWorldSector *)
                                         planeSector->leftSubTree,
                                         world, flags) + rwCHUNKHEADERSIZE;
    }
    else
    {
        /* Plane sector - recurse */
        size += PlaneSectorStreamGetSize((RpPlaneSector *)
                                         planeSector->leftSubTree,
                                         world, flags) + rwCHUNKHEADERSIZE;
    }

    /* Right side next */
    if (planeSector->rightSubTree->type < 0)
    {
        /* Atomic sector */
        size += WorldSectorStreamGetSize((RpWorldSector *)
                                         planeSector->rightSubTree,
                                         world, flags) + rwCHUNKHEADERSIZE;
    }
    else
    {
        /* Plane sector - recurse */
        size += PlaneSectorStreamGetSize((RpPlaneSector *)
                                         planeSector->rightSubTree,
                                         world, flags) + rwCHUNKHEADERSIZE;
    }
    RWRETURN(size);
}

/****************************************************************************
 PlaneSectorStreamWrite

 On entry   : Stream to write to
 On exit    :
 */
static const RpPlaneSector *
PlaneSectorStreamWrite(const RpPlaneSector * planeSector, RwStream
                       * stream, const RpWorld * world, RwUInt32 flags)
{
    _rpPlaneSector      ps;

    RWFUNCTION(RWSTRING("PlaneSectorStreamWrite"));
    RWASSERT(planeSector);
    RWASSERT(stream);

    if (!RwStreamWriteChunkHeader(stream, rwID_PLANESECT,
                                  PlaneSectorStreamGetSize
                                  (planeSector, world, flags)))
    {
        RWRETURN((const RpPlaneSector *)NULL);
    }

    /* wrap structured data */
    if (!RwStreamWriteChunkHeader
        (stream, rwID_STRUCT, sizeof(_rpPlaneSector)))
    {
        RWRETURN((const RpPlaneSector *)NULL);
    }

    /* Fill it in */
    ps.type = planeSector->type;
    ps.value = planeSector->value;
    ps.leftIsWorldSector = ((planeSector->leftSubTree)->type < 0);
    ps.rightIsWorldSector = ((planeSector->rightSubTree)->type < 0);
    ps.leftValue = planeSector->leftValue;
    ps.rightValue = planeSector->rightValue;

    /* Convert it */
    RwMemRealToFloat32(&ps.value, sizeof(ps.value));
    RwMemRealToFloat32(&ps.leftValue, sizeof(ps.leftValue));
    RwMemRealToFloat32(&ps.rightValue, sizeof(ps.rightValue));
    RwMemLittleEndian(&ps, sizeof(ps));

    /* And write it */
    if (!RwStreamWrite(stream, &ps, sizeof(ps)))
    {
        RWRETURN((const RpPlaneSector *)NULL);
    }

    /* Left first */
    if (planeSector->leftSubTree->type < 0)
    {
        /* Atomic */
        if (!WorldSectorStreamWrite((RpWorldSector *)
                                    planeSector->leftSubTree,
                                    stream, world, flags))
        {
            RWRETURN((const RpPlaneSector *)NULL);
        }
    }
    else
    {
        /* Plane */
        if (!PlaneSectorStreamWrite((RpPlaneSector *)
                                    planeSector->leftSubTree,
                                    stream, world, flags))
        {
            RWRETURN((const RpPlaneSector *)NULL);
        }
    }

    /* Right second */
    if (planeSector->rightSubTree->type < 0)
    {
        /* Atomic */

        if (!WorldSectorStreamWrite((RpWorldSector *)
                                    planeSector->rightSubTree,
                                    stream, world, flags))
        {
            RWRETURN((const RpPlaneSector *)NULL);
        }
    }
    else
    {
        /* Plane */
        if (!PlaneSectorStreamWrite((RpPlaneSector *)
                                    planeSector->rightSubTree,
                                    stream, world, flags))
        {
            RWRETURN((const RpPlaneSector *)NULL);
        }
    }

    RWRETURN(planeSector);
}

/****************************************************************************
 PlaneSectorStreamRead

 On entry   : Stream to read from
 On exit    : PlaneSector created
 */
static RpPlaneSector *
PlaneSectorStreamRead(RwStream * stream, RwUInt8 **
                      binaryWorldMallocAddr, RpWorld * world,
                      RwUInt32 flags)
{
    RpPlaneSector      *planeSector;
    _rpPlaneSector      ps;
    RwUInt32            size, version;

    RWFUNCTION(RWSTRING("PlaneSectorStreamRead"));
    RWASSERT(stream);

    if (!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
    {
        RWRETURN((RpPlaneSector *)NULL);
    }

    if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RpPlaneSector *)NULL);
    }

    /* The size read from the stream is the size stored,
     * no question about it */

    /* Read it */
    RWASSERT(size <= sizeof(ps));
    memset(&ps, 0, sizeof(ps));
    if (RwStreamRead(stream, &ps, size) != size)
    {
        RWRETURN((RpPlaneSector *)NULL);
    }

    /* Convert it */
    RwMemNative(&ps, sizeof(ps));
    RwMemFloat32ToReal(&ps.value, sizeof(ps.value));
    RwMemFloat32ToReal(&ps.leftValue, sizeof(ps.leftValue));
    RwMemFloat32ToReal(&ps.rightValue, sizeof(ps.rightValue));

    planeSector = (RpPlaneSector *)
        BinaryWorldMalloc(binaryWorldMallocAddr, sizeof(RpPlaneSector));
    if (!planeSector)
    {
        RWERROR((E_RW_NOMEM, (sizeof(RpPlaneSector))));
        RWRETURN((RpPlaneSector *)NULL);
    }

    /* Use contents */
    planeSector->type = ps.type;
    planeSector->value = ps.value;
    if (flags & rpWORLDSECTORSOVERLAP)
    {
        planeSector->leftValue = ps.leftValue;
        planeSector->rightValue = ps.rightValue;
    }
    else
    {
        planeSector->leftValue = ps.value;
        planeSector->rightValue = ps.value;
    }

    /* Left */
    if (ps.leftIsWorldSector)
    {
        if (!RwStreamFindChunk(stream, (RwUInt32)rwID_ATOMICSECT, 
                               (RwUInt32 *)NULL, &version))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }

        if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
        {
            RWERROR((E_RW_BADVERSION));
            RWRETURN((RpPlaneSector *)NULL);
        }

        if (!(planeSector->leftSubTree =
              (RpSector *) WorldSectorStreamRead(stream,
                                        binaryWorldMallocAddr, world, flags)))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }
    }
    else
    {
        if (!RwStreamFindChunk(stream, (RwUInt32)rwID_PLANESECT,
                               (RwUInt32 *)NULL, &version))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }

        if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
        {
            RWERROR((E_RW_BADVERSION));
            RWRETURN((RpPlaneSector *)NULL);
        }

        if (!(planeSector->leftSubTree =
              (RpSector *) PlaneSectorStreamRead(stream,
                                         binaryWorldMallocAddr, world, flags)))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }
    }

    /* Right */
    if (ps.rightIsWorldSector)
    {
        if (!RwStreamFindChunk(stream, (RwUInt32)rwID_ATOMICSECT, 
                               (RwUInt32 *)NULL, &version))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }

        if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
        {
            RWERROR((E_RW_BADVERSION));
            RWRETURN((RpPlaneSector *)NULL);
        }

        if (!(planeSector->rightSubTree =
              (RpSector *) WorldSectorStreamRead(stream,
                                        binaryWorldMallocAddr, world, flags)))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }
    }
    else
    {
        if (!RwStreamFindChunk(stream, (RwUInt32)rwID_PLANESECT,
                               (RwUInt32 *)NULL, &version))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }

        if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
        {
            RWERROR((E_RW_BADVERSION));
            RWRETURN((RpPlaneSector *)NULL);
        }

        if (!(planeSector->rightSubTree =
              (RpSector *) PlaneSectorStreamRead(stream,
                                         binaryWorldMallocAddr, world, flags)))
        {
            /* Deallocation is performed in one block when we return */
            RWRETURN((RpPlaneSector *)NULL);
        }
    }
    RWRETURN(planeSector);
}


/****************************************************************************
 WorldFindSize

 Finds the block size to hold the whole world

 On entry   : World, pointer to numbers to receive various stats
 On exit    : Size of the world in bytes
 */

static void
WorldFindSize(const RpWorld * world, RwInt32 * numPolygons,
              RwInt32 * numVertices,
              RwInt32 * numPlaneSectors,
              RwInt32 * numWorldSectors, RwInt32 * sizeOfCollSectors)
{
    RwInt32             nStack = 0;
    RpSector           *spaStack[rpWORLDMAXBSPDEPTH];
    RpSector           *spSect = world->rootSector;

    RWFUNCTION(RWSTRING("WorldFindSize"));
    RWASSERT(world);
    RWASSERTISTYPE(world, rpWORLD);

    *numPolygons = 0;
    *numVertices = 0;
    *numPlaneSectors = 0;
    *numWorldSectors = 0;
    *sizeOfCollSectors = 0;

    do
    {
        if (spSect->type < 0)
        {
            /* Atomic sector */
            RpWorldSector      *worldSector = (RpWorldSector *) spSect;

            /* One more world sector */
            (*numWorldSectors)++;

            /* Add on the vertices and polygons */
            (*numPolygons) += worldSector->numPolygons;
            (*numVertices) += worldSector->numVertices;

            /* Add the size of the collision sectors dword aligned */
            (*sizeOfCollSectors) +=
                ((_rpCollSectorFindSize(worldSector->colSectorRoot)
                  + 3) & (-4));

            /* Try next */
            spSect = spaStack[nStack--];
        }
        else
        {
            RpPlaneSector      *pspPlane = (RpPlaneSector *) spSect;

            /* One more plane sector */
            (*numPlaneSectors)++;

            /* Go left, stack right */
            spSect = pspPlane->leftSubTree;
            spaStack[++nStack] = pspPlane->rightSubTree;
        }
    }
    while (nStack >= 0);

    RWRETURNVOID();
}

/**
 * \ingroup rpworldsub
 * \ref RpWorldStreamGetSize is used to  determine the size in bytes
 * of the binary representation of the given world. Only static objects in
 * the world are included. This is used in
 * the binary chunk header to indicate the size of the chunk. The size does
 * not include the size of the chunk header.
 *
 * The world plugin must be attached before using this function.
 *
 * \param world  Pointer to the world.
 *
 * \return Returns a RwUInt32 value equal to the chunk size of
 * the specified world in bytes if successful or zero if there is an error.
 *
 * \see RpWorldStreamRead
 * \see RpWorldStreamWrite
 * \see RpWorldPluginAttach
 *
 */
RwUInt32
RpWorldStreamGetSize(const RpWorld * world)
{
    RwUInt32            size;
    RwUInt32            flags;
    RWAPIFUNCTION(RWSTRING("RpWorldStreamGetSize"));

    RWASSERT(binWorldModule.numInstances);
    RWASSERT(world);
    RWASSERTISTYPE(world, rpWORLD);

    flags = RpWorldGetFlags(world);
    size = sizeof(_rpWorld) + rwCHUNKHEADERSIZE;

    /* Size of material list */
    size += _rpMaterialListStreamGetSize(&world->matList) + rwCHUNKHEADERSIZE;

    if (world->rootSector->type < 0)
    {
        /* Atomic sector */

        size += WorldSectorStreamGetSize((RpWorldSector *)
                                         world->rootSector,
                                         world, flags) + rwCHUNKHEADERSIZE;
    }
    else
    {
        /* Plane sector */

        size += PlaneSectorStreamGetSize((RpPlaneSector *)
                                         world->rootSector,
                                         world, flags) + rwCHUNKHEADERSIZE;
    }

    /* Plugin data size */
    size +=
        (rwPluginRegistryGetSize(&worldTKList, world) + rwCHUNKHEADERSIZE);

    RWRETURN(size);
}

/**
 * \ingroup rpworldsub
 * \ref RpWorldStreamWrite is used to write the specified world to the
 * given binary stream. Only those parts of the world contained in static
 * objects are written to the stream. Note that the stream must be opened
 * prior to this function call.
 *
 * The world plugin must be attached before using this function.
 *
 * \param world  Pointer to the world.
 * \param stream  Pointer to the stream.
 *
 * \return Returns pointer to the specified world if successful or NULL if
 * there is an error.
 *
 * \see RpWorldStreamRead
 * \see RpWorldStreamGetSize
 * \see RwStreamOpen
 * \see RwStreamClose
 * \see RpWorldPluginAttach
 *
 */
const RpWorld      *
RpWorldStreamWrite(const RpWorld * world, RwStream * stream)
{
    _rpWorld            w;
    RwInt32             numPolygons, numVertices;
    RwInt32             numPlaneSectors, numWorldSectors, colSectorSize;
    RwUInt32            flags;
    const RwSurfaceProperties *source;

    RWAPIFUNCTION(RWSTRING("RpWorldStreamWrite"));

    RWASSERT(binWorldModule.numInstances);
    RWASSERT(world);
    RWASSERTISTYPE(world, rpWORLD);
    RWASSERT(stream);

    flags = RpWorldGetFlags(world);

    if (!RwStreamWriteChunkHeader(stream, rwID_WORLD,
                                  RpWorldStreamGetSize(world)))
    {
        RWRETURN((const RpWorld *)NULL);
    }

    /* wrap structured data */
    if (!RwStreamWriteChunkHeader(stream, rwID_STRUCT, sizeof(_rpWorld)))
    {
        RWRETURN((const RpWorld *)NULL);
    }

    /* Find out how big the world is */
    WorldFindSize(world, &numPolygons, &numVertices,
                  &numPlaneSectors, &numWorldSectors, &colSectorSize);

    /* Verify texture flags */
    RWASSERT((world->numTexCoordSets == 1) == ((flags & rpWORLDTEXTURED) > 0));
    RWASSERT((world->numTexCoordSets > 1) == ((flags & rpWORLDTEXTURED2) > 0));

    /* Fill it */
    w.format = flags 
        | rpWORLDTEXCOORDSETS(world->numTexCoordSets) 
        | rpWORLDSECTORSOVERLAP;
    w.rootIsWorldSector = (world->rootSector->type < 0);
    RwV3dScale(&w.invWorldOrigin, &world->worldOrigin, (RwReal) (-1.0f));

    source = RpWorldGetSurfaceProperties(world);
    RwSurfacePropertiesAssign(&w.surfaceProps, source);

    w.numVertices = numVertices;
    w.numPolygons = numPolygons;
    w.numPlaneSectors = numPlaneSectors;
    w.numWorldSectors = numWorldSectors;
    w.colSectorSize = colSectorSize;

    /* Convert it */
    RwMemRealToFloat32(&w.invWorldOrigin, sizeof(w.invWorldOrigin));
    RwMemRealToFloat32(&w.surfaceProps, sizeof(w.surfaceProps));
    RwMemLittleEndian(&w, sizeof(w));

    /* Write it */
    if (!RwStreamWrite(stream, &w, sizeof(w)))
    {
        RWRETURN((const RpWorld *)NULL);
    }

    /* Write out all the materials */
    if (!_rpMaterialListStreamWrite(&world->matList, stream))
    {
        RWRETURN((const RpWorld *)NULL);
    }

    /* Check what sector type to write */
    if (world->rootSector->type < 0)
    {
        /* Atomic sector */
        if (!WorldSectorStreamWrite
            ((RpWorldSector *) world->rootSector, stream, world, flags))
        {
            RWRETURN((const RpWorld *)NULL);
        }
    }
    else
    {
        /* Plane sector */
        if (!PlaneSectorStreamWrite
            ((RpPlaneSector *) world->rootSector, stream, world, flags))
        {
            RWRETURN((const RpWorld *)NULL);
        }
    }

    /* write out the plugin info */
    if (!rwPluginRegistryWriteDataChunks(&worldTKList, stream, world))
    {
        /* Failed to read extension data */
        RWRETURN((const RpWorld *)NULL);
    }

    RWRETURN(world);
}

/**
 * \ingroup rpworldsub
 * \ref RpWorldStreamRead is used to read a world from the specified
 * binary stream. Note that prior to this function call a binary world chunk
 * must be found in the stream using the \ref RwStreamFindChunk API function.
 * Note also that the world created by this function only contains static
 * objects.
 *
 * The world plugin must be attached before using this function.
 *
 * \param stream  Pointer to the stream.
 *
 * \return Returns pointer to the world if successful or NULL if
 * there is an error.
 *
 * \see RpWorldStreamWrite
 * \see RpWorldStreamGetSize
 * \see RwStreamOpen
 * \see RwStreamClose
 * \see RwStreamFindChunk
 * \see RpWorldPluginAttach
 *
 * The sequence to locate and read a world from a binary stream is
 * as follows: 
 * \verbatim
   RwStream *stream;
   RpWorld *newWorld;
  
   stream = RwStreamOpen(rwSTREAMFILENAME, rwSTREAMREAD, "mybinary.xxx");
   if( stream )
   {
       if( RwStreamFindChunk(stream, rwID_WORLD, NULL, NULL) )
       {
           newWorld = RpWorldStreamRead(stream);
       }
  
       RwStreamClose(stream, NULL);
   }
   \endverbatim
 */
RpWorld            *
RpWorldStreamRead(RwStream * stream)
{
    RpWorld            *world;
    _rpWorld            w;
    RwInt32             i;
    RwUInt8            *binaryWorldMallocAddr;
    RwInt32             worldSize;
    RwUInt32            size, version;
    RwInt32             flags;
    RwUInt32            numTexCoordSets;

    RWAPIFUNCTION(RWSTRING("RpWorldStreamRead"));

    RWASSERT(binWorldModule.numInstances);
    RWASSERT(stream);

    if (!RwStreamFindChunk(stream, rwID_STRUCT, &size, &version))
    {
        RWRETURN((RpWorld *)NULL);
    }

    if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RpWorld *)NULL);
    }

    /* Read it */
    RWASSERT(size <= sizeof(w));
    memset(&w, 0, sizeof(w));
    if (RwStreamRead(stream, &w, size) != size)
    {
        RWRETURN((RpWorld *)NULL);
    }

    /* Convert it */
    RwMemNative(&w, sizeof(w));
    RwMemFloat32ToReal(&w.invWorldOrigin, sizeof(w.invWorldOrigin));
    RwMemFloat32ToReal(&w.surfaceProps, sizeof(w.surfaceProps));

    /* Figure out how big it is */
    worldSize = worldTKList.sizeOfStruct;
    worldSize += w.numPlaneSectors * sizeof(RpPlaneSector);
    worldSize += w.numWorldSectors * sectorTKList.sizeOfStruct;
    worldSize += w.numVertices * sizeof(RwV3d);

    /* The format contains the flags, so just copy */
    flags = w.format;

    if (flags & (RwInt32) rpWORLDNORMALS)
    {
        worldSize += w.numVertices * sizeof(RpVertexNormal);
    }
    if (flags & (RwInt32) rpWORLDPRELIT)
    {
        worldSize += w.numVertices * sizeof(RwRGBA);
    }

    numTexCoordSets = WorldFormatGetNumTexCoordSetsMacro(w.format);
    RWASSERT(numTexCoordSets <= rwMAXTEXTURECOORDS);
    if (numTexCoordSets > 0)
    {
        /* Allow space for texture coordinates and pointer arrays in sectors */
        worldSize += numTexCoordSets * w.numVertices * sizeof(RwTexCoords);
    }

    worldSize += w.numPolygons * sizeof(RpPolygon);
    worldSize += w.colSectorSize;

    /* Can just allocate for the whole world's memory */
    world = (RpWorld *) RwMalloc(worldSize);
    if (!world)
    {
        RWERROR((E_RW_NOMEM, (worldSize)));
        RWRETURN(FALSE);
    }

    /* Clear it to zero. This would set pointers to NULL */
    memset(world, 0, worldSize);

    binaryWorldMallocAddr = (RwUInt8 *) world + worldTKList.sizeOfStruct;
    rwObjectInitialize(world, rpWORLD, 0);
    rwObjectSetPrivateFlags(world, rpWORLDSINGLEMALLOC);

    /* Add to the list */
    _rpWorldRegisterWorld(world, worldSize);

    /* Set the worlds flags */
    RpWorldSetFlags(world, flags);

    /* Set the default rendering order */
    RpWorldSetRenderOrder(world, rpWORLDRENDERBACK2FRONT);

    /* Set the worlds surface lighting properties */
    RpWorldSetSurfaceProperties(world, &w.surfaceProps);

    /* Set the world offset */
    RwV3dScale(&world->worldOrigin, &w.invWorldOrigin, (RwReal) (-1.0f));

    /* Set number of texture coordinates used in this world */
    world->numTexCoordSets = numTexCoordSets;

    /* No atomic sectors yet */
    world->rootSector = (RpSector *)NULL;

    /* use the default world sector object pipeline */
    world->pipeline = (RxPipeline *)NULL;

    /* Read all of the materials */
    if (!RwStreamFindChunk(stream, (RwUInt32)rwID_MATLIST, 
                           (RwUInt32 *)NULL, &version))
    {
        _rpWorldUnregisterWorld(world);
        RwFree(world);
        RWRETURN((RpWorld *)NULL);
    }

    if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
    {
        RWERROR((E_RW_BADVERSION));
        RWRETURN((RpWorld *)NULL);
    }

    if (!_rpMaterialListStreamRead(stream, &world->matList))
    {
        _rpWorldUnregisterWorld(world);
        RwFree(world);
        RWRETURN((RpWorld *)NULL);
    }

    /* Create all of the sectors */
    if (w.rootIsWorldSector)
    {
        /* We have an atomic sector */
        if (!RwStreamFindChunk(stream, (RwUInt32)rwID_ATOMICSECT, 
                               (RwUInt32 *)NULL, &version))
        {
            _rpWorldUnregisterWorld(world);
            RwFree(world);
            RWRETURN((RpWorld *)NULL);
        }

        if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
        {
            RWERROR((E_RW_BADVERSION));
            _rpWorldUnregisterWorld(world);
            RwFree(world);
            RWRETURN((RpWorld *)NULL);
        }

        if (!(world->rootSector = (RpSector *)
              WorldSectorStreamRead(stream,
                                    &binaryWorldMallocAddr, world, flags)))
        {
            _rpWorldUnregisterWorld(world);
            RwFree(world);
            RWRETURN((RpWorld *)NULL);
        }
    }
    else
    {
        /* We have a plane sector */
        if (!RwStreamFindChunk(stream, (RwUInt32)rwID_PLANESECT, 
                               (RwUInt32 *)NULL, &version))
        {
            _rpWorldUnregisterWorld(world);
            RwFree(world);
            RWRETURN((RpWorld *)NULL);
        }

        if ((version < rwLIBRARYBASEVERSION) || (version > rwLIBRARYCURRENTVERSION))
        {
            RWERROR((E_RW_BADVERSION));
            _rpWorldUnregisterWorld(world);
            RwFree(world);
            RWRETURN((RpWorld *)NULL);
        }

        if (!(world->rootSector = (RpSector *)
              PlaneSectorStreamRead(stream,
                                    &binaryWorldMallocAddr, world, flags)))
        {
            _rpWorldUnregisterWorld(world);
            RwFree(world);
            RWRETURN((RpWorld *)NULL);
        }
    }

    /* Check that our single malloc area hasn't been overrun */
    RWASSERT(((RwUInt32)worldSize) == ((RwUInt32)binaryWorldMallocAddr - (RwUInt32)world));

    /* There are no atomics in the world */
    rwLinkListInitialize(&world->clumpList);

    world->numClumpsInWorld = 0;
    world->currentClumpLink = rwLinkListGetTerminator(&world->clumpList);

    /* Initialize (non directional) lights linked list */
    rwLinkListInitialize(&world->lightList);

    /* Directional lights in world */
    rwLinkListInitialize(&world->directionalLightList);

    /* Set up the bbox */
    _rpWorldFindBBox(world, &world->boundingBox);

    /* Reset all its callback functions */
    RpWorldSetSectorRenderCallBack(world, 
                                   (RpWorldSectorCallBackRender)NULL);

    /* Go through the mat list - make textured correct */
    for (i = 0; i < world->matList.numMaterials; i++)
    {
        RwTexture          *tex;
        RpMaterial         *mat = (world->matList).materials[i];

        tex = RpMaterialGetTexture(mat);

        if (tex)
        {
            RpMaterialSetTexture(mat, tex);
        }
    }

    /* Call the constructor on the world as late as
     * we can (so world sectors are in place)
     */
    rwPluginRegistryInitObject(&worldTKList, world);

    lastSeenWorldRightsPluginId = 0;

    /* Read in the plugin info */
    if (!rwPluginRegistryReadDataChunks(&worldTKList, stream, world))
    {
        /* Failed to read extension data */
        _rpWorldUnregisterWorld(world);
        RwFree(world);
        RWRETURN((RpWorld *)NULL);
    }

    if (lastSeenWorldRightsPluginId)
    {
        /* Silently ignore errors if any */
        _rwPluginRegistryInvokeRights(&worldTKList, lastSeenWorldRightsPluginId,
                                      world);
    }

    /* Release the world so platform specific info can be set up */
    if (!_rpWorldUnlock(world))
    {
        /* Failed to unlock, fail the world read */
        RpWorldDestroy(world);
        RWRETURN((RpWorld *)NULL);
    }

    RWRETURN(world);
}

RpWorldSectorChunkInfo *
_rpWorldSectorChunkInfoRead(RwStream * stream,
                            RpWorldSectorChunkInfo *
                            worldSectorChunkInfo, 
                            RwInt32 * bytesRead)
{
    RwUInt32            size;
    RwUInt32            readSize;
    RWAPIFUNCTION(RWSTRING("_rpWorldSectorChunkInfoRead"));

    RWASSERT(stream);
    RWASSERT(worldSectorChunkInfo);

    if (!RwStreamFindChunk(stream, (RwUInt32)rwID_STRUCT, 
                           &size, (RwUInt32 *)NULL))
    {
        RWRETURN((RpWorldSectorChunkInfo *)NULL);
    }

    RWASSERT(size >= sizeof(RpWorldSectorChunkInfo));
    readSize = sizeof(RpWorldSectorChunkInfo);
    memset(worldSectorChunkInfo, 0, readSize);
    if (RwStreamRead(stream, worldSectorChunkInfo, readSize) != readSize)
    {
        RWRETURN((RpWorldSectorChunkInfo *)NULL);
    }

    *bytesRead = size + (sizeof(RwInt32) * 3);
    /* move on to known place */
    RwStreamSkip(stream, size - readSize);

    /* Convert it */
    RwMemNative(worldSectorChunkInfo, sizeof(RpWorldSectorChunkInfo));
    RwMemFloat32ToReal(&worldSectorChunkInfo->inf,
                       sizeof(worldSectorChunkInfo->inf));
    RwMemFloat32ToReal(&worldSectorChunkInfo->sup,
                       sizeof(worldSectorChunkInfo->sup));

    RWRETURN(worldSectorChunkInfo);
}

RpPlaneSectorChunkInfo *
_rpPlaneSectorChunkInfoRead(RwStream * stream,
                            RpPlaneSectorChunkInfo * planeSectorChunkInfo, 
                            RwInt32 * bytesRead)
{
    RwUInt32            size;
    RwUInt32            readSize;
    RWAPIFUNCTION(RWSTRING("_rpPlaneSectorChunkInfoRead"));

    RWASSERT(stream);
    RWASSERT(planeSectorChunkInfo);

    if (!RwStreamFindChunk(stream, (RwUInt32)rwID_STRUCT, 
                           &size, (RwUInt32 *)NULL))
    {
        RWRETURN((RpPlaneSectorChunkInfo *)NULL);
    }

    /* The size read from the stream is the size stored,
     * no question about it */

    /* Read it */
    RWASSERT(size <= sizeof(RpPlaneSectorChunkInfo));
    readSize = sizeof(RpPlaneSectorChunkInfo);
    memset(planeSectorChunkInfo, 0, readSize);
    if (RwStreamRead(stream, planeSectorChunkInfo, readSize) != readSize)
    {
        RWRETURN((RpPlaneSectorChunkInfo *)NULL);
    }

    *bytesRead = size + (sizeof(RwInt32) * 3);
    /* move on to known place */
    RwStreamSkip(stream, size - readSize);

    /* Convert it */
    RwMemNative(planeSectorChunkInfo, sizeof(RpPlaneSectorChunkInfo));
    RwMemFloat32ToReal(&planeSectorChunkInfo->value,
                       sizeof(planeSectorChunkInfo->value));
    RwMemFloat32ToReal(&planeSectorChunkInfo->leftValue,
                       sizeof(planeSectorChunkInfo->leftValue));
    RwMemFloat32ToReal(&planeSectorChunkInfo->rightValue,
                       sizeof(planeSectorChunkInfo->rightValue));

    RWRETURN(planeSectorChunkInfo);
}

RpWorldChunkInfo   *
_rpWorldChunkInfoRead(RwStream * stream,
                      RpWorldChunkInfo * worldChunkInfo, 
                      RwInt32 * bytesRead)
{
    RwUInt32            size;
    RwUInt32            readSize;
    RWAPIFUNCTION(RWSTRING("_rpWorldChunkInfoRead"));

    RWASSERT(stream);
    RWASSERT(worldChunkInfo);

    if (!RwStreamFindChunk(stream, (RwUInt32)rwID_STRUCT, 
                           &size, (RwUInt32 *)NULL))
    {
        RWRETURN((RpWorldChunkInfo *)NULL);
    }

    /* Read it */
    RWASSERT(size <= sizeof(RpWorldChunkInfo));
    readSize = sizeof(RpWorldChunkInfo);
    memset(worldChunkInfo, 0, readSize);
    if (RwStreamRead(stream, worldChunkInfo, readSize) != readSize)
    {
        RWRETURN((RpWorldChunkInfo *)NULL);
    }

    *bytesRead = size + (sizeof(RwInt32) * 3);
    /* move on to known place */
    RwStreamSkip(stream, size - readSize);

    /* Convert it */
    RwMemNative(worldChunkInfo, sizeof(RpWorldChunkInfo));
    RwMemFloat32ToReal(&worldChunkInfo->invWorldOrigin,
                       sizeof(worldChunkInfo->invWorldOrigin));
    RwMemFloat32ToReal(&worldChunkInfo->surfaceProps,
                       sizeof(worldChunkInfo->surfaceProps));

    RWRETURN(worldChunkInfo);
}


/****************************************************************************
 _rpBinaryWorldClose

 On entry   : instance, offset, size
 On exit    : instance on success
 */

void               *
_rpBinaryWorldClose(void *instance,
                    RwInt32 __RWUNUSED__ offset, 
                    RwInt32 __RWUNUSED__ size)
{
    RWFUNCTION(RWSTRING("_rpBinaryWorldClose"));

    /* One less instance */
    binWorldModule.numInstances--;

    /* All done */
    RWRETURN(instance);
}

/****************************************************************************
 _rpBinaryWorldOpen

 On entry   : instance, offset, size
 On exit    : instance on success
 */

void               *
_rpBinaryWorldOpen(void *instance,
                   RwInt32 __RWUNUSED__ offset, RwInt32 __RWUNUSED__ size )
{
    RWFUNCTION(RWSTRING("_rpBinaryWorldOpen"));

    /* One more instance */
    binWorldModule.numInstances++;

    /* All done */
    RWRETURN(instance);
}

