/*==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/>.

Additional permissions under GNU GPL version 3 section 7

If you modify this Program, or any covered work, by linking or
combining it with any of RAD Game Tools Bink SDK, Autodesk 3ds Max SDK,
NVIDIA PhysX SDK, Microsoft DirectX SDK, OpenSSL library, Independent
JPEG Group JPEG library, Microsoft Windows Media SDK, or Apple QuickTime SDK
(or a modified version of those libraries),
containing parts covered by the terms of the Bink SDK EULA, 3ds Max EULA,
PhysX SDK EULA, DirectX SDK EULA, OpenSSL and SSLeay licenses, IJG
JPEG Library README, Windows Media SDK EULA, or QuickTime SDK EULA, the
licensors of this Program grant you additional
permission to convey the resulting work. Corresponding Source for a
non-source form of such a combination shall include the source code for
the parts of OpenSSL and IJG JPEG Library used as well as that of the covered
work.

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 "pnEncryption/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_t>& faces) const;
    BOOL                IProjectVertex(const Point3& pt, const Point3& dir, float maxDist, Tab<int32_t>&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_t              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 hsRadiansToDegrees(fPolarRange); }

    void                SetAzimuthRange(float deg) { fAzimuthRange = hsDegreesToRadians(deg); }
    float               GetAzimuthRange() const { return hsRadiansToDegrees(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
