//----------------------------------------------------------------------------
//
// rampmat.hpp
//
// Declares classes for ramp material handling.
//
// Copyright (C) Microsoft Corporation, 1997.
//
//----------------------------------------------------------------------------

#ifndef _RAMPMAT_HPP_
#define _RAMPMAT_HPP_

/* -*- c++ -*- */

#include "rampmap.h"
#include "palette.h"
#include "rgbmap.h"

struct ExtMaterial;
struct IntMaterial;
struct RampMaterial;
struct RLDDIRampLightingDriver;

struct AgeList {
    CIRCLE_QUEUE_MEMBER(AgeList) list;
    LIST_ROOT(alh,IntMaterial) agelist;
};

class RampCommon {
public:

    void* operator new(size_t size)
    {
    void* p;
        if (D3DMalloc(&p, size))
        return NULL;
    return p;
    }

    void operator delete(void *p)
    {
        D3DFree(p);
    }
};

/*
 * An ExtMaterial is the underlying driver object for an LPDIRECT3DMATERIAL.
 * When used, it creates IntMaterials which are distinguished by different
 * ambient light, fog, D3DMATERIAL value etc.
 *
 * The ExtMaterials are kept on a list in the driver and if not explicitly
 * freed, they are cleaned up when the driver is destroyed.
 *
 * The IntMaterials can outlive the ExtMaterial in the case that the
 * ExtMaterial is destroyed right after use.  We add these orphans to a list
 * which is emptied at Clear time since after Clear, no pixels are visible
 * which were rendered with the IntMaterial and it can be freed.
 */
struct ExtMaterial : public RampCommon {
    friend struct IntMaterial;
private:
    /*
     * Driver object which owns us.
     */
    RLDDIRampLightingDriver*    driver;

    /*
     * Current API level material definition.
     */
    D3DMATERIAL         mat;

    /*
     * List of internal materials derived from us.
     */
    LIST_ROOT(eml,IntMaterial)  intlist;

    /*
     * Generation count.  The generation is incremented each time
     * the material is changed.  This allows us to generate a new internal
     * material as needed.
     */
    int             generation;

    /*
     * This tracks the generation of any texture used by the material.  The
     * texture generation is bumped if its palette is changed or it is
     * loaded from another texture.
     */
    int             texture_generation;

    /*
     * This tracks the underlying texture object represented by the texture
     * handle (if any).  If texture handles are swapped, we need to know
     * so that we can make a new internal material for the new texture.
     */
    PD3DI_SPANTEX       texture;

    /*
     * Increment the generation either because the material was set or
     * because a texture changed.
     */
    void            Age();

public:
    /*
     * Chain for list of external materials created by this driver.
     */
    LIST_MEMBER(ExtMaterial)    list;

public:
    /*
     * Constructor/Destructor.
     */
    ExtMaterial(RLDDIRampLightingDriver* driver, D3DMATERIALHANDLE hMat);
    ~ExtMaterial();

    /*
     * Change the API level material.
     */
    void SetMaterial(D3DMATERIAL* mat);

    /*
     * Accessor to the current API level material.
     */
    D3DMATERIAL* GetMaterial() {
    return &mat;
    }

    /*
     * Find an internal material which corresponds to the current driver
     * object state.
     */
    IntMaterial* FindIntMaterial();

    /*
     * Find what range of values lighting should take.  The base is the
     * pixel value (in fixed point) of the dark end of the material.  The
     * shift value is user to convert a 0.16 fixed point shade into the
     * range needed for the material. e.g.
     *
     *      pixel = base + (VALTOFX8(shade) << shift);
     *
     */
    HRESULT FindLightingRange(unsigned long* base,
                  unsigned long* size,
                  BOOL* specular,
                  unsigned long** texture_colors);
};

/*
 * The IntMaterial is derived from an ExtMaterial taking into account the
 * driver state when the ExtMaterial was used.  Normally IntMaterials are
 * on a list in their owning ExtMaterial.  If the external material is
 * destroyed, any active internal materials which it owned are
 * transferred to an orphans list in the driver.  This is cleared out
 * next time Clear is called.
 *
 * The internal material has a list of underlying RampMaterials.  For
 * a non-textured material, there is exactly one and for a textured
 * material, there is one per color in the texture's palette.  The
 * ramp materials track color sharing between internal materials and
 * handle the details of allocating color resources.
 *
 * Internal materials are also chained onto one of a number of lists
 * based on their age.  The age of a material is the number of frames
 * since it was last used to render something.  When a material is
 * aged, it is rejuvenated by moving it to the age=0 list.  Each
 * frame, the lists are rotated by one notch and materials on the
 * oldest list are reclaimed.
 *
 * A material is either active or inactive.  Active materials have
 * color resources and are either on the age=0 list (active this
 * frame) or the age=1 list (active last frame).  When an inactive
 * material is used, it allocates color resources by attempting to
 * activate the underlying ramp materials.
 *
 * At the end of the frame, on Update, any active materials on the
 * age=1 list must be materials which were active last frame but were
 * not used this frame.  We remove their color resources by
 * deactivating the underlying ramp materials.
 */
struct IntMaterial : public RampCommon {
private:
    /*
     * Driver object which owns us.
     */
    RLDDIRampLightingDriver*    driver;

    /*
     * External material which created us.
     */
    ExtMaterial*        extmat;

public:
    /*
     * Too annoying to have these private.
     */

    /*
     * Chain of internal materials created by our external material owner
     * which owns us.  The chain is also used to place the material on the
     * driver's orphans list if appropriate.
     */
    LIST_MEMBER(IntMaterial)    list;

    /*
     * Current ageing list which we are on.
     */
    AgeList*            age;
    LIST_MEMBER(IntMaterial)    agelist;
private:

    /*
     * D3DMATERIAL which the external material had when it created us.
     */
    D3DMATERIAL         mat;

    /*
     * TRUE if we are active.  We are active if we currently have color
     * allocation resources assigned to us.
     */
    int             active;

    /*
     * Base values of our color allocation resources.  Contents are valid
     * only when active.
     */
    unsigned long*      colors;

    /*
     * Features which distinguish us from other internal materials owned by
     * our external material.
     */
    float                  ambient;        // ambient shade
    unsigned long       viewport_id;    // viewport which owns it
    int             fog_enable; // material incorporates fog
    unsigned long       fog_color;  // the fogging color
    int             generation;

    /*
     * RampMaterials used by this internal material.
     */
    int             ramp_count;
    RampMaterial**      ramps;

public:

    /*
     * Constructor/Destructor.
     */
    IntMaterial(ExtMaterial* extmat);
    ~IntMaterial();

    /*
     * Returns TRUE if we match the current settings of our external material
     * and driver state.
     */
    int Valid();

    /*
     * See ExtMaterial
     */
    HRESULT FindLightingRange(unsigned long* base,
                  unsigned long* size,
                  BOOL* specular,
                  unsigned long** texture_colors);

    /*
     * Activate the material and allocate color resources for it.
     */
    HRESULT Activate();

    /*
     * Deactivate the material, freeing color resources.  Always works.
     */
    void Deactivate();

    /*
     * Returns TRUE if the material is currently active.
     */
    int IsActive() {
    return active;
    }

    /*
     * Add the material to the driver's orphaned material list.  Called when
     * the external material is destroyed.
     */
    void Orphan();
};

/*
 * RampMaterials are used by internal materials to represent ranges of
 * colors.  They perform low level color allocation by allocating
 * color ranges (RLDDIRamps) from a rampmap.
 *
 * A textured internal material can use many ramp materials.
 * Several internal materials can use the same ramp material if the
 * colors match.  This can happen easily if many textures use the same
 * palette.  The ramp material maintains a usage of how many internal
 * materials are using it and is freed when the last one stops.
 *
 * Ramp materials, like internal materials are either active or
 * inactive.  Active materials have color resources and inactive
 * materials do not.  A ramp material is made active when any of its
 * internal material users are active and inactive when none of then
 * are active.  To track this a count of how many active users is
 * maintained.
 *
 * When a material is made active, it attempts to allocate a color
 * range to use.  If that is successful, it sets the colors in the
 * range to an appropriate ramp of colors.  If is no more space in the
 * colormap for a new range, it finds the closes active ramp material
 * and shares its ramp.
 *
 * To track active materials and sharing materials, the driver has a
 * list of active materials and each material has a list of sharers.
 * The sharers list is only valid for materials which are both active
 * and which own their ramp.
 */
struct RampMaterial : public RampCommon {
private:
    /*
     * Driver object which owns us.
     */
    RLDDIRampLightingDriver* driver;

    /*
     * List of materials which have the same hash value.
     */
    LIST_MEMBER(RampMaterial) hash;

    /*
     * Materials sharing this ramp.  Only valid for active materials
     * with owner == TRUE.
     */
    LIST_ROOT(name6,RampMaterial) sharers;

    /*
     * If the material is active, then it is on the drivers rmactive list
     * if it owns the ramp, otherwise it is on the sharers list of the
     * material which does own the ramp.  Only valid for active materials.
     */
    LIST_MEMBER(RampMaterial) list;

    /*
     * If were sharing a ramp and inherit it from the owner, we need to
     * defer setting the colors until the next frame to avoid possible
     * palette flashing.  The driver has a list of materials for which
     * color setting is deferred, chainged through here.
     *
     * We make sure that deferredlist.le_next is NULL unless we are
     * actually on the deferred list so that we can avoid list problems
     * if we are destroyed or passed on to another inheritor before the
     * list is processed.
     */
    LIST_MEMBER(RampMaterial) deferredlist;

    /*
     * A count of the number of internal materials which use us.
     */
    int         usage;

    /*
     * A count of active internal materials which use us.
     */
    int         active;

    /*
     * If we are active (active > 0), this is the underlying color
     * range for the material.
     */
    RLDDIRamp*          ramp;

    /*
     * TRUE if we created or inherited the ramp.  FALSE if I am merely
     * sharing some other material.
     */
    int                 owner;

    /*
     * Distinguishing features.
     */
    float          ambient;        /* ambient shade */
    D3DMATERIAL         mat;            /* material we represent */
    int         fog_enable; /* material incorporates fog */
    unsigned long   fog_color;  /* the fogging color */

private:

    /*
     * Constructor/Destructor.
     * These are private.  Callers should use Find/Release.
     */
    RampMaterial(RLDDIRampLightingDriver* driver,
                 D3DMATERIAL* mat, float ambient);
    ~RampMaterial();

public:
    /*
     * Find a material which matches the api material and driver state.
     * If no such material exists, make one.
     */
    static RampMaterial* Find(RLDDIRampLightingDriver* driver,
                  D3DMATERIAL* mat);

private:
    /*
     * Next functions are implementation detail of Find.
     */

    /*
     * Compare two api materials to see if they are identical.
     */
    static int MaterialSame(D3DMATERIAL* mat1, D3DMATERIAL* mat2);

    /*
     * Return a measure of how close two rgb colors are in rgb space.
     */
    static int RGBDist(D3DCOLORVALUE* rgb1, D3DCOLORVALUE* rgb2);

    /*
     * Return a measure of how close two api materials are.
     */
    static int CompareMaterials(D3DMATERIAL* mat1, D3DMATERIAL* mat2);

public:
    /*
     * Called by a user when it is no longer needed.
     */
    void Release();

    /*
     * Called by a user when the material is about to be used.
     */
    HRESULT Activate();

    /*
     * Called by a user when the material has not been used this frame.
     */
    void Deactivate();

    /*
     * Return the base color of an active material.
     */
    unsigned long Base();

private:
    /*
     * Implementation details of Activate and Deactivate.
     */

    /*
     * Allocate a ramp of colors for us to use.
     * May only be called for an inactive material.
     */
    HRESULT AllocateColors();

    /*
     * Free our color allocation.  May only be called for an active material.
     */
    void FreeColors();

    /*
     * Set the color values of our color allocation.
     */
private:
    void SetColorsStd();
    void SetColorsFog();
public:
    void SetColors();
};

///*
// * Workspace used when evaluating complex lighting models.
// */
//struct Workspace {
//    float  diffuse;
//    float  specular;
//};
//
//struct SpecularTable {
//    LIST_MEMBER(SpecularTable) list;
//    float          power;          /* shininess power */
//    float          table[260];     /* space for overflows */
//};
//
//#define WORKSPACE_SIZE  1024

#define HASH_SIZE       257
/*
 * If the app is rendering at 30fps, this means that a material can survive
 * for about 5 seconds.  This seems like enough to cope with stuff moving
 * out of shot and back in again soon after.
 */
#define AGE_MAX         (30*5)      /* age at which unused materials die */

typedef struct _RLDDISoftLightingDriver {
    D3DMATERIAL     material;
    float          ambient;
    D3DFOGMODE      fog_mode;
    float          fog_start;
    float          fog_end;
    float          fog_density;
    D3DMATERIALHANDLE   hMat;
    D3DCOLORMODEL   color_model;
    DWORD           ambient_save;
} RLDDISoftLightingDriver;

struct RLDDIRampLightingDriver {
    RLDDISoftLightingDriver         driver; /* common fields */

    /*
     * Colormap
     */
    RLDDIPalette*   palette;
    RLDDIRGBMap*    rgbmap;
    unsigned long*  pixelmap; /* map color indices to pixels */
    PALETTEENTRY    ddpalette[256];
    int         paletteChanged;

    RLDDIRampmap*       rampmap;
    unsigned long   viewport_id;    /* current viewport */
    int         fog_enable;
    unsigned long   fog_color;

    /*
     * External materials created by this driver.
     */
    LIST_ROOT(deml,ExtMaterial) materials;

    /*
     * Active internal materials orphaned by their owning external material.
     */
    LIST_ROOT(diml,IntMaterial) orphans;

    AgeList     agelists[AGE_MAX];
    CIRCLE_QUEUE_ROOT(aqh,AgeList) agequeue;    /* age sorted queue of lists */
    AgeList*        active;     /* current active list */

    /*
     * This is set to TRUE after Clear has advanced the ages one notch and
     * cleared on update.  It is used to stop multiple calls to Clear from
     * confusing the aging system
     */
    int         already_aged;

    LIST_ROOT(name7,RampMaterial) rmactive; /* materials with ramps */
    LIST_ROOT(name8,RampMaterial) rmdeferred;
    LIST_ROOT(name9,RampMaterial) hash[HASH_SIZE]; /* materials hash table */

    /*
     * Current material.
     */
    ExtMaterial*    current_material;
};

typedef struct _RLDDILookupMaterialData {
    D3DMATERIALHANDLE   hMat;            /* material to look up */
    unsigned long       base;           /* base pixel value */
    unsigned long       size;           /* size of index range */
    unsigned long       texture_index;  /* texture table for textures */
} RLDDILookupMaterialData;


#endif // _RAMPMAT_HPP_