/*==LICENSE==*

CyanWorlds.com Engine - MMOG client, server and tools
Copyright (C) 2011  Cyan Worlds, Inc.

This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with this program.  If not, see <http://www.gnu.org/licenses/>.

You can contact Cyan Worlds, Inc. by email legal@cyan.com
 or by snail mail at:
      Cyan Worlds, Inc.
      14617 N Newport Hwy
      Mead, WA   99021

*==LICENSE==*/

#ifndef plDistributor_inc
#define plDistributor_inc

#include "plMath/plRandom.h"
#include "plDistTree.h"

class INode;
class Mesh;
class TriObject;

class BitmapTex;
class plLayerTex;
class plExportProgressBar;
class plMaxNode;
class plDistTree;

// To Use:
//
// First you must set the interface
// Second, you must set the node(s) to be replicated.
//      If multiple replicants, they will be randomly selected at each plant site.
// Set the spacing. Default is 10 or so, but shouldn't be counted on.
// Set the spacing range. Set it to less than half the spacing to prevent
//      replicants piling up on each other.
// Rather than increasing the spacing range (which causes pileup), you can increase
//      the randomness of placement with the overall probability factor. Reducing it
//      means fewer grid points will be used, so placement seems more random
//
// Options:
//      PolarRange - defines the cone about the surface normal to randomly fill for up direction
//      PolarBunch - defines tendency to bunch around the center of PolarRange cone,
//          with zero being uniform distribution, and one being all on cone axis.
//      AlignmentVector - defines a world space preferred up orientation.
//      AlignmentWeight - blend factor between random normal (from polar stuff) and
//          above AlignmentVector.
//      ScaleRange - defines range of non-uniform scale to apply to each replicant.
//      ProbabilityTexmap - A BitmapTex which maps the probability of an instance
//          taking root at any point on the surface. Various interpretations of the
//          map available (see ProbabilityChan). If the surface mesh doesn't have
//          the appropriate uvw mapping for the map, the map will be ignored.
//      ProbabilityChan - defines interpretation of texel value from ProbTexmap
//          into a probability. ColorChan enum types are pretty self-explanatory.
//          In all cases, a higher texel channel value means more likely of a
//          replicant taking root there.
//
// Finally, call Distrubute() with a node containing a surface to be populated.
//
// Modifying. Were actually creating the new node instances, but then we just
// want to re-pack them into clusters anyway. So all we really need is to
// know which template (fRepNodes) and what transform to use for it. We can
// use that to make our clusters, without bogging Max down with adding and
// deleting a gazillion INodes.

class plDistribInstance
{
public:
    INode*          fNode;
    Matrix3         fNodeTM;
    Matrix3         fObjectTM;

    INode*          fBone;
    BOOL            fRigid;

    Box3            fFade;

    Point3          fFlex;

    Mesh*           fMesh;
};

class plDistribInstTab : public Tab<plDistribInstance>
{
};

class plMeshCache
{
public:
    Mesh*       fMesh;
    Point3      fFlex;

    plMeshCache() {}
};

class plMeshCacheTab : public Tab<plMeshCache>
{
};

class plDistributor
{
public:
    enum ColorChan
    {
        kRed                        = 0x1,
        kGreen                      = 0x2,
        kBlue                       = 0x4,
        kAlpha                      = 0x8,
        kAverageRedGreen            = kRed | kGreen,
        kAverageRedGreenTimesAlpha  = kRed | kGreen | kAlpha,
        kAverage                    = kRed | kGreen | kBlue,
        kAverageTimesAlpha          = kAverage | kAlpha,
        kMax                        = 0x100,
        kMaxColor                   = kMax | kRed | kGreen | kBlue,
        kMaxColorTimesAlpha         = kMaxColor | kAlpha,
        kMaxRedGreen                = kMax | kRed | kGreen,
        kMaxRedGreenTimesAlpha      = kMaxRedGreen | kAlpha
    };
    enum 
    {
        kLockNone       = 0x0,
        kLockX          = 0x1,
        kLockY          = 0x2,
        kLockZ          = 0x4
    };
    enum
    {
        kWgtMapChan         = 66,
        kNormMapChan        = 67
    };
    enum IsoType
    {
        kIsoNone,
        kIsoLow,
        kIsoMedium,
        kIsoHigh,

        kIsoMax = kIsoHigh
    };
    enum ConformType
    {
        kConformNone,
        kConformAll,
        kConformHeight,
        kConformCheck,
        kConformBase
    };
protected:
    mutable INode*              fSurfNode;
    mutable Mesh*               fSurfMesh;
    mutable TriObject*          fSurfObjToDelete;

    mutable INodeTab            fRepNodes;

    mutable plDistTree*         fDistTree;
    mutable plDistTree          fMeshTree;

    Interface*                  fInterface;

    IsoType             fIsolation;
    ConformType         fConformity;
    BOOL                fFaceNormals;
    
    float               fSpacing;
    float               fRndPosRadius;

    Point3              fAlignVec;
    float               fAlignWgt;

    float               fOffsetMin;
    float               fOffsetMax;

    Point3              fAngProbVec;
    float               fAngProbLo;
    float               fAngProbHi;
    float               fAngProbTrans;

    float               fAltProbLo;
    float               fAltProbHi;
    float               fAltProbTrans;

    float               fPolarRange;
    float               fTanPolarRange;
    float               fAzimuthRange;

    float               fOverallProb;

    float               fPolarBunch;

    ULONG               fScaleLock;             
    Point3              fScaleLo;
    Point3              fScaleHi;

    BitmapTex*          fProbBitmapTex;
    plLayerTex*         fProbLayerTex;
    ColorChan           fProbColorChan;

    float               fProbRemapFromLo;
    float               fProbRemapFromHi;
    float               fProbRemapToLo;
    float               fProbRemapToHi;

    float               fMaxConform; // in feet

    Box3                fFade;
    INode*              fBone;
    BOOL                fRigid;

    // Temps used during processing.
    mutable Matrix3     fSurfToWorld;
    mutable Matrix3     fWorldToSurf;
    mutable Matrix3     fSurfToWorldVec;
    mutable Matrix3     fWorldToSurfVec;
    mutable Point3      fSurfAlignVec;
    mutable Point3      fSurfAngProbVec;
    mutable plRandom    fRand;
    mutable float       fCosAngProbHi;
    mutable float       fCosAngProbHiTrans;
    mutable float       fCosAngProbLo;
    mutable float       fCosAngProbLoTrans;

    void                ISetAngProbCosines() const;
    BOOL                ISetSurfaceNode(INode* node) const;
    BOOL                IGetMesh(INode* node, TriObject*& objToDelete, Mesh*& retMesh) const;

    BOOL                INeedMeshTree() const;
    void                IMakeMeshTree() const;
    void                IFindFaceSet(const Box3& box, Tab<Int32>& faces) const;
    BOOL                IProjectVertex(const Point3& pt, const Point3& dir, float maxDist, Tab<Int32>&faces, Point3& projPt) const;
    BOOL                IConform(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const;
    BOOL                IConformHeight(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const;
    BOOL                IConformAll(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const;
    BOOL                IConformCheck(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const;
    BOOL                IConformBase(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const;

    Matrix3             IOTM(int iRepNode) const;
    Matrix3             IInvOTM(int iRepNode) const;

    Matrix3             IGenerateTransform(int iRepNode, int iFace, const Point3& pt, const Point3& bary) const;
    hsBool              IProbablyDoIt(int iFace, Point3& del, const Point3& bary) const;
    hsBool              IFailsAltProb(int iFace, const Point3& bary) const;
    hsBool              IFailsAngProb(int iFace, const Point3& bary) const;
    hsBool              IFailsProbBitmap(int iFace, const Point3& bary) const;
    Box3                ISetupGrid(const Point3& p0, const Point3& p1, const Point3& p2) const;
    Point3&             IPerturbPoint(Point3& pt) const;
    int                 ISelectRepNode() const;
    Point3              IPerpAxis(const Point3& p) const;
    Point3              IGetSurfaceNormal(int iFace, const Point3& bary) const;

    BOOL                ISpaceClear(int iRepNode, const Matrix3& l2w, Box3& clearBox, plMeshCacheTab& cache) const;
    void                IReserveSpace(const Box3& clearBox) const;

    void                IReplicate(Matrix3& l2w, int iRep, plDistribInstTab& reps, plMeshCache& mCache) const;
    void                IDistributeOverFace(int iFace, plDistribInstTab& reps, plMeshCacheTab& cache) const;
    BOOL                IDistributeOverMesh(plDistribInstTab& reps, plMeshCacheTab& cache, plExportProgressBar& bar) const;

    void                IClear();
    BOOL                IValidateSettings(INode* surfNode, plMeshCacheTab& cache) const;

    BOOL                ISetupNormals(plMaxNode* node, Mesh* mesh, BOOL radiateNorm) const;
    BOOL                IDuplicate2Sided(plMaxNode* node, Mesh* mesh) const;
    BOOL                ISetupSkinWeights(plMaxNode* node, Mesh* mesh, const Point3& flex) const;
    BOOL                IReadyRepNodes(plMeshCacheTab& cache) const;
public:

    plDistributor();
    virtual ~plDistributor();

    void                SetTheInterface(Interface* i) { fInterface = i; }
    Interface*          GetTheInterface() const { return fInterface; }

    BOOL                Distribute(INode* surfNode, plDistribInstTab& replicants, plMeshCacheTab& cache, plExportProgressBar& bar) const;

    void                Reset();
    UInt32              GetRandSeed() const;
    void                SetRandSeed(int seed);

    void                ClearReplicateNodes();
    void                AddReplicateNode(INode* node);
    int                 GetNumReplicateNodes() const { return fRepNodes.Count(); }
    INode*              GetReplicateNode(int i) const { return fRepNodes[i]; }

    INode*              GetSurfaceNode() const { return fSurfNode; }

    void                SetSpacing(float f) { fSpacing = f; }
    float               GetSpacing() const { return fSpacing; }

    void                SetSpacingRange(float f) { fRndPosRadius = f; }
    float               GetSpacingRange() const { return fRndPosRadius; }

    void                SetAlignmentVec(const Point3& v) { fAlignVec = v; }
    Point3              GetAlignmentVec() const { return fAlignVec; }

    void                SetAlignmentWeight(float w) { fAlignWgt = w / 100.f; }
    float               GetAlignmentWeight() const { return fAlignWgt * 100.f; }

    void                SetPolarRange(float deg);
    float               GetPolarRange() const { return hsScalarRadToDeg(fPolarRange); }

    void                SetAzimuthRange(float deg) { fAzimuthRange = hsScalarDegToRad(deg); }
    float               GetAzimuthRange() const { return hsScalarRadToDeg(fAzimuthRange); }

    void                SetOverallProb(float percent) { fOverallProb = percent/100.f; }
    float               GetOverallProb() const { return fOverallProb * 100.f; }

    void                SetAngleProbVec(const Point3& v) { fAngProbVec = v; }
    Point3              GetAngleProbVec() const { return fAngProbVec; }

    void                SetAngleProbHi(float deg) { fAngProbHi = deg; }
    float               GetAngleProbHi() const { return fAngProbHi; }

    void                SetAngleProbLo(float deg) { fAngProbLo = deg; }
    float               GetAngleProbLo() const { return fAngProbLo; }

    void                SetAngleProbTransition(float deg) { fAngProbTrans = deg; }
    float               GetAngleProbTransition() const { return fAngProbTrans; }

    void                SetMinAltitude(float feet) { fAltProbLo = feet; }
    float               GetMinAltitude() const { return fAltProbLo; }

    void                SetMaxAltitude(float feet) { fAltProbHi = feet; }
    float               GetMaxAltitude() const { return fAltProbHi; }

    void                SetAltitudeTransition(float feet) { fAltProbTrans = feet; }
    float               GetAltitudeTransition() const { return fAltProbTrans; }

    void                SetPolarBunch(float b) { fPolarBunch = b/100.f; }
    float               GetPolarBunch() const { return fPolarBunch * 100.f; }

    void                SetScaleRange(const Point3& lo, const Point3& hi) { fScaleLo = lo; fScaleHi = hi; }
    Point3              GetScaleRangeMin() const { return fScaleLo; }
    Point3              GetScaleRangeMax() const { return fScaleHi; }

    void                SetProbabilityBitmapTex(BitmapTex* t);
    BitmapTex*          GetProbabilityBitmapTex() const { return fProbBitmapTex; }

    void                SetProbabilityLayerTex(plLayerTex* t);
    plLayerTex*         GetProbabilityLayerTex() const { return fProbLayerTex; }

    void                SetProbabilityChan(ColorChan c) { fProbColorChan = c; }
    ColorChan           GetProbabilityChan() const { return fProbColorChan; }

    void                SetProbabilityRemapFromLo(float f) { fProbRemapFromLo = f / 255.f; }
    float               GetProbabilityRemapFromLo() const { return fProbRemapFromLo * 255.f; }

    void                SetProbabilityRemapFromHi(float f) { fProbRemapFromHi = f / 255.f; }
    float               GetProbabilityRemapFromHi() const { return fProbRemapFromHi * 255.f; }

    void                SetProbabilityRemapToLo(float f) { fProbRemapToLo = f / 255.f; }
    float               GetProbabilityRemapToLo() const { return fProbRemapToLo * 255.f; }

    void                SetProbabilityRemapToHi(float f) { fProbRemapToHi = f / 255.f; }
    float               GetProbabilityRemapToHi() const { return fProbRemapToHi * 255.f; }

    // We don't really know what fades are, they're just something we're handed that
    // we stamp on every distribInstance we generate. See plDistribComponent.h.
    void                SetFade(const Box3& fade) { fFade = fade; }
    Box3                GetFade() const { return fFade; }

    void                SetBone(INode* b) { fBone = b; }
    INode*              GetBone() const { return fBone; }

    void                SetRigid(BOOL b) { fRigid = b; }
    BOOL                GetRigid() const { return fRigid; }

    void                SetScaleLock(ULONG f) { fScaleLock = f; }
    ULONG               GetScaleLock() const { return fScaleLock; }

    void                SetDistTree(plDistTree* dt) { fDistTree = dt; }
    plDistTree*         GetDistTree() const { return fDistTree; }

    void                SetIsolation(IsoType t) { fIsolation = t; }
    IsoType             GetIsolation() const { return fIsolation; }

    void                SetConformity(ConformType t) { fConformity = t; }
    ConformType         GetConformity() const { return fConformity; }

    void                SetMaxConform(float feet) { fMaxConform = feet; }
    float               GetMaxConform() const { return fMaxConform; }

    void                SetMinOffset(float feet) { fOffsetMin = feet; }
    float               GetMinOffset() const { return fOffsetMin; }

    void                SetMaxOffset(float feet) { fOffsetMax = feet; }
    float               GetMaxOffset() const { return fOffsetMax; }

    void                SetFaceNormals(BOOL on=true) { fFaceNormals = on; }
    BOOL                GetFaceNormals() const { return fFaceNormals; }
};

#endif // plDistributor_inc