/*
 * Mipmap K
 */

#include "rpplugin.h"
#include "rwcore.h"
#include "rpdbgerr.h"
#include "rpworld.h"
#include "rtmipk.h"

#if (!defined(SKY)) && (!defined(NULLSKY_DRVMODEL_H))
#include "rpmipkl.h"
#endif

#if (!defined(DOXYGEN))
static const char rcsid[] __RWUNUSED__ =
    "@@@@(#)$Id: rtmipk.c,v 1.22 2001/08/30 14:18:52 Ads Exp $";
#endif /* (!defined(DOXYGEN)) */

typedef struct kInfoStruct kInfoStruct;
struct kInfoStruct
{
    RpWorld            *world;
    RpClump            *clump;
    RwTexture          *texture;
    RwReal              kSum;
    RwInt32             numCounts;
    RwReal              polyToPixelArea;
};

#define TEXELAREA(a, b, c)                                              \
    ( ((RwReal)0.5) * ( ( (a).u * (b).v ) - ( (b).u * (a).v ) +         \
                        ( (b).u * (c).v ) - ( (c).u * (b).v ) +         \
                        ( (c).u * (a).v ) - ( (a).u * (c).v ) ) )

static RpWorldSector *
MipKSumKValuesForSector(RpWorldSector * sector, void *pData)
{
    int                 i;
    kInfoStruct        *kInfo = (kInfoStruct *) pData;

    RWFUNCTION(RWSTRING("MipKSumKValuesForSector"));

    for (i = 0; i < sector->numPolygons; i++)
    {
        RpPolygon          *poly = &sector->polygons[i];
        RpMaterial         *polyMat =
            RpWorldGetMaterial(kInfo->world, poly->matIndex);

        if (RpMaterialGetTexture(polyMat) == kInfo->texture)
        {
            /* add this one in */
            RwReal              pixelArea, texelArea;
            RwReal              polyArea, pixelToTexel;
            RwV3d               vTmp1, vTmp2, area;
            RwRaster           *raster =
                RwTextureGetRaster(kInfo->texture);

            texelArea =
                TEXELAREA(sector->texCoords[0][poly->vertIndex[0]],
                          sector->texCoords[0][poly->vertIndex[1]],
                          sector->texCoords[0][poly->vertIndex[2]]);
            texelArea = (RwReal) fabs(texelArea);
            texelArea *=
                RwRasterGetWidth(raster) * RwRasterGetHeight(raster);

            /* calculate a 3d poly area */
            RwV3dSub(&vTmp1, &sector->vertices[poly->vertIndex[1]],
                     &sector->vertices[poly->vertIndex[0]]);
            RwV3dSub(&vTmp2, &sector->vertices[poly->vertIndex[2]],
                     &sector->vertices[poly->vertIndex[0]]);
            RwV3dCrossProduct(&area, &vTmp1, &vTmp2);

            polyArea = 0.5f * RwV3dLength(&area);

            if (polyArea > 0.0f && texelArea > 1.0f)
            {
                /* calculate a screenspace (pixel) area */
                pixelArea = polyArea * kInfo->polyToPixelArea;

                /* calculate the pixel to texel ratio */
                pixelToTexel = pixelArea / texelArea;

                /* pixel to texel ratio scales linearly, i.e twice
                 * as far away has 1/4 pixel to texel ratio */
                kInfo->kSum += (RwReal) rwSqrt(pixelToTexel);

                kInfo->numCounts++;
            }
        }
    }

    RWRETURN(sector);
}

static RwTexture   *
MipKCalculateWorldKValue(RwTexture * texture, void *pData)
{
    kInfoStruct        *kInfo = (kInfoStruct *) pData;

    RWFUNCTION(RWSTRING("MipKCalculateWorldKValue"));
    RWASSERT(kInfo);

    kInfo->texture = texture;
    kInfo->kSum = 0.0f;
    kInfo->numCounts = 0;

    RpWorldForAllWorldSectors(kInfo->world,
                              MipKSumKValuesForSector, kInfo);

    if (kInfo->numCounts > 0)
    {
        /* find k = log2(1/averageDist) */
        const RwReal        averageDist = ((kInfo->kSum) /
                                           ((RwReal) kInfo->numCounts));
        const RwReal        k = -((RwReal) ((log(averageDist)) /
                                            log(2.0)));

#if (defined(SKY)) || (defined(NULLSKY_DRVMODEL_H))
        RpSkyTextureSetMipmapK(texture, k);
#else
        RpMipmapKLTextureSetK(texture, k);
#endif /* (defined(SKY)) || (defined(NULLSKY_DRVMODEL_H)) */
    }

    RWRETURN(texture);
}

/**
 * \ingroup rtmipk
 * \ref RtMipKWorldCalculateKValues computes the Sky specific K values
 * for textures used in a world, based on the current video mode
 * and the view window of the specified camera.
 * \param world  pointer to the world
 * \param camera  pointer to the camera
 */
void
RtMipKWorldCalculateKValues(RpWorld * world, RwCamera * camera)
{
    kInfoStruct         kInfo;
    RwInt32             vidMode;
    RwVideoMode         modeInfo;
    RwReal              viewWidth;
    RwReal              viewHeight;

    RWAPIFUNCTION(RWSTRING("RtMipKWorldCalculateKValues"));

    RWASSERT(world);
    RWASSERT(camera);

    if (RpWorldGetFlags(world) & (rpWORLDTEXTURED|rpWORLDTEXTURED2))
    {
        vidMode = RwEngineGetCurrentVideoMode();

        RwEngineGetVideoModeInfo(&modeInfo, vidMode);
        viewWidth = RwCameraGetViewWindow(camera)->x * 2.0f;
        viewHeight = RwCameraGetViewWindow(camera)->y * 2.0f;

        kInfo.polyToPixelArea = ((modeInfo.width * modeInfo.height) /
                                 (viewWidth * viewHeight));
        kInfo.clump = NULL;
        kInfo.world = world;
        RwTexDictionaryForAllTextures(RwTexDictionaryGetCurrent(),
                                      MipKCalculateWorldKValue, &kInfo);
    }

    RWRETURNVOID();
}

static RpAtomic    *
MipKSumKValuesForAtomic(RpAtomic * atomic, void *pData)
{
    int                 i;
    kInfoStruct        *kInfo = (kInfoStruct *) pData;
    RpGeometry         *geom;
    RpMorphTarget      *morphTarget;
    RwMatrix           *LTM;

    RWFUNCTION(RWSTRING("MipKSumKValuesForAtomic"));

    LTM = RwFrameGetLTM(RpAtomicGetFrame(atomic));
    geom = RpAtomicGetGeometry(atomic);

    if (geom)
    {
        if (RpGeometryGetFlags(geom) & (rpGEOMETRYTEXTURED|rpGEOMETRYTEXTURED2))
        {
            RwV3d              *transformedVerts = (RwV3d *)
                RwMalloc(sizeof(RwV3d) * geom->numVertices);

            if (transformedVerts)
            {
                morphTarget = RpGeometryGetMorphTarget(geom, 0);
                RwV3dTransformPoints(transformedVerts,
                                     morphTarget->verts,
                                     geom->numVertices, LTM);
                for (i = 0; i < geom->numTriangles; i++)
                {
                    RpTriangle         *poly = &geom->triangles[i];
                    RpMaterial         *polyMat =
                        RpGeometryGetMaterial(geom, poly->matIndex);

                    if (RpMaterialGetTexture(polyMat) == kInfo->texture)
                    {
                        /* add this one in */
                        RwReal              pixelArea, texelArea;
                        RwReal              polyArea, pixelToTexel;
                        RwV3d               vTmp1, vTmp2, area;
                        RwRaster           *raster =
                            RwTextureGetRaster(kInfo->texture);

                        texelArea =
                            TEXELAREA(geom->
                                      texCoords[0][poly->
                                                      vertIndex[0]],
                                      geom->texCoords[0][poly->
                                                            vertIndex
                                                            [1]],
                                      geom->texCoords[0][poly->
                                                            vertIndex
                                                            [2]]);
                        texelArea = (RwReal) fabs(texelArea);
                        texelArea *=
                            RwRasterGetWidth(raster) *
                            RwRasterGetHeight(raster);

                        /* calculate a 3d poly area */
                        RwV3dSub(&vTmp1,
                                 &transformedVerts[poly->vertIndex[1]],
                                 &transformedVerts[poly->vertIndex[0]]);
                        RwV3dSub(&vTmp2,
                                 &transformedVerts[poly->vertIndex[2]],
                                 &transformedVerts[poly->vertIndex[0]]);
                        RwV3dCrossProduct(&area, &vTmp1, &vTmp2);

                        polyArea = 0.5f * RwV3dLength(&area);

                        if (polyArea > 0.0f && texelArea > 1.0f)
                        {
                            /* calculate a screenspace (pixel) area */
                            pixelArea =
                                polyArea * kInfo->polyToPixelArea;

                            /* calculate the pixel to texel ratio */
                            pixelToTexel = pixelArea / texelArea;

                            /* pixel to texel ratio scales linearly, i.e twice
                             * as far away has 1/4 pixel to texel ratio */
                            kInfo->kSum +=
                                (RwReal) rwSqrt(pixelToTexel);

                            kInfo->numCounts++;
                        }
                    }
                }

                RwFree(transformedVerts);
            }
        }
    }

    RWRETURN(atomic);
}

static RwTexture   *
MipKCalculateClumpKValue(RwTexture * texture, void *pData)
{
    kInfoStruct        *kInfo = (kInfoStruct *) pData;

    RWFUNCTION(RWSTRING("MipKCalculateClumpKValue"));
    RWASSERT(kInfo);

    kInfo->texture = texture;
    kInfo->kSum = 0.0f;
    kInfo->numCounts = 0;

    RpClumpForAllAtomics(kInfo->clump, MipKSumKValuesForAtomic, kInfo);

    if (kInfo->numCounts > 0)
    {
        /* find k = log2(1/averageDist) */
        const RwReal        averageDist = ((kInfo->kSum) /
                                           ((RwReal) kInfo->numCounts));
        const RwReal        k = -((RwReal) ((log(averageDist)) /
                                            log(2.0)));

#if (defined(SKY)) || (defined(NULLSKY_DRVMODEL_H))
        RpSkyTextureSetMipmapK(texture, k);
#else
        RpMipmapKLTextureSetK(texture, k);
#endif /* (defined(SKY)) || (defined(NULLSKY_DRVMODEL_H)) */
    }

    RWRETURN(texture);
}

/**
 * \ingroup rtmipk
 * \ref RtMipKClumpCalculateKValues computes the Sky specific K values
 * for textures used in a clumps atomics, based on the current video mode
 * and the view window of the specified camera.
 * \param clump  pointer to the clump
 * \param camera  pointer to the camera
 */
void
RtMipKClumpCalculateKValues(RpClump * clump, RwCamera * camera)
{
    kInfoStruct         kInfo;
    RwInt32             vidMode;
    RwVideoMode         modeInfo;
    RwReal              viewWidth;
    RwReal              viewHeight;

    RWAPIFUNCTION(RWSTRING("RtMipKClumpCalculateKValues"));

    RWASSERT(clump);
    RWASSERT(camera);

    vidMode = RwEngineGetCurrentVideoMode();

    RwEngineGetVideoModeInfo(&modeInfo, vidMode);
    viewWidth = RwCameraGetViewWindow(camera)->x * 2.0f;
    viewHeight = RwCameraGetViewWindow(camera)->y * 2.0f;

    kInfo.polyToPixelArea = ((modeInfo.width * modeInfo.height) /
                             (viewWidth * viewHeight));
    kInfo.clump = clump;
    kInfo.world = NULL;
    RwTexDictionaryForAllTextures(RwTexDictionaryGetCurrent(),
                                  MipKCalculateClumpKValue, &kInfo);

    RWRETURNVOID();
}
