/*==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 plCullTree_inc
#define plCullTree_inc

#include "hsBounds.h"
#include "hsGeometry3.h"
#include "hsBitVector.h"
#include "plCuller.h"
#include "plScene/plCullPoly.h"

#ifdef HS_DEBUGGING
#define DEBUG_POINTERS
#endif // HS_DEBUGGING

class plCullTree;
class plCullNode;

// for vis
struct hsPoint3;
struct hsVector3;
struct hsColorRGBA;

class plCullTree : public plCuller
{
protected:

    // Visualization stuff, to be nuked from production version.
    mutable hsBool                          fCapturePolys;
    mutable hsTArray<hsPoint3>              fVisVerts;
    mutable hsTArray<hsVector3>             fVisNorms;
    mutable hsTArray<hsColorRGBA>           fVisColors;
    mutable hsTArray<UInt16>                fVisTris;
    mutable hsScalar                        fVisYon;

    mutable hsTArray<plCullPoly>        fScratchPolys;
    mutable hsLargeArray<Int16>     fScratchClear;
    mutable hsLargeArray<Int16>     fScratchSplit;
    mutable hsLargeArray<Int16>     fScratchCulled;
    mutable hsBitVector             fScratchBitVec;
    mutable hsBitVector             fScratchTotVec;

    void        IVisPolyShape(const plCullPoly& poly, hsBool dark) const;
    void        IVisPolyEdge(const hsPoint3& p0, const hsPoint3& p1, hsBool dark) const;
    void        IVisPoly(const plCullPoly& poly, hsBool dark) const;


    hsPoint3                        fViewPos;

    Int16                           fRoot;
    mutable hsTArray<plCullNode>    fNodeList; // Scratch list we make the tree from.
    plCullNode*                     IGetRoot() const { return IGetNode(fRoot); }
    plCullNode*                     IGetNode(Int16 i) const { return i >= 0 ? &fNodeList[i] : nil; }

    void                ITestNode(const plSpaceTree* space, Int16 who, hsTArray<Int16>& outList) const; // Appends to outlist
    void                ITestList(const plSpaceTree* space, const hsTArray<Int16>& inList, hsTArray<Int16>& outList) const;

    Int16               IAddPolyRecur(const plCullPoly& poly, Int16 iNode);
    Int16               IMakeHoleSubTree(const plCullPoly& poly) const;
    Int16               IMakePolySubTree(const plCullPoly& poly) const;
    Int16               IMakePolyNode(const plCullPoly& poly, int i0, int i1) const;

    // Some scratch areas for the nodes use when building the tree etc.
    hsTArray<plCullPoly>&           ScratchPolys() const { return fScratchPolys; }
    hsLargeArray<Int16>&            ScratchClear() const { return fScratchClear; }
    hsLargeArray<Int16>&            ScratchSplit() const { return fScratchSplit; }
    hsLargeArray<Int16>&            ScratchCulled() const { return fScratchCulled; }
    hsBitVector&                    ScratchBitVec() const { return fScratchBitVec; }
    hsBitVector&                    ScratchTotVec() const { return fScratchTotVec; }

    void                            ISetupScratch(UInt16 nNodes);

    friend class plCullNode;

public:
    plCullTree();
    ~plCullTree();

    void                    Reset(); // Called before starting to add polys for this frame.
    void                    InitFrustum(const hsMatrix44& world2NDC);
    void                    SetViewPos(const hsPoint3& pos);
    void                    AddPoly(const plCullPoly& poly);

    UInt32                  GetNumNodes() const { return fNodeList.GetCount(); }

    virtual void            Harvest(const plSpaceTree* space, hsTArray<Int16>& outList) const;
    virtual hsBool          BoundsVisible(const hsBounds3Ext& bnd) const;
    virtual hsBool          SphereVisible(const hsPoint3& center, hsScalar rad) const;

    // Visualization stuff. Only to be called by the pipeline (or some other vis manager).
    void                    SetVisualizationYon(hsScalar y) const { fVisYon = y; }
    void                    BeginCapturePolys() const { fCapturePolys = true; }
    void                    EndCapturePolys() const { fCapturePolys = false; }
    hsTArray<hsPoint3>&     GetCaptureVerts() const { return fVisVerts; }
    hsTArray<hsVector3>&    GetCaptureNorms() const { return fVisNorms; }
    hsTArray<hsColorRGBA>&  GetCaptureColors() const { return fVisColors; }
    hsTArray<UInt16>&       GetCaptureTris() const { return fVisTris; }
    void                    ReleaseCapture() const;
};

class plCullNode
{
public:
enum plCullStatus
{
    kClear,
    kCulled,
    kSplit,
    kPureSplit
};
protected:
    hsVector3           fNorm;
    hsScalar            fDist;

    hsBool              fIsFace;

    Int16               fInnerChild;
    Int16               fOuterChild;

    const plCullTree*           fTree;

    plCullNode*                 IGetNode(Int16 i) const;

#ifdef DEBUG_POINTERS
    mutable plCullNode*         fInnerPtr;
    mutable plCullNode*         fOuterPtr;

    void                        ISetPointersRecur() const;
#else // DEBUG_POINTERS
    void                        ISetPointersRecur() const {}
#endif // DEBUG_POINTERS

    // Bounds only version
    plCullNode::plCullStatus    ITestBoundsRecur(const hsBounds3Ext& bnd) const;
    plCullNode::plCullStatus    ITestSphereRecur(const hsPoint3& center, hsScalar rad) const;

    // Using the nodes
    plCullNode::plCullStatus    ITestNode(const plSpaceTree* space, Int16 who, hsLargeArray<Int16>& clear, hsLargeArray<Int16>& split, hsLargeArray<Int16>& culled) const;
    void                        ITestNode(const plSpaceTree* space, Int16 who, hsBitVector& totList, hsBitVector& outList) const;
    void                        IHarvest(const plSpaceTree* space, hsTArray<Int16>& outList) const;

    // Constructing the tree
    hsScalar                    IInterpVert(const hsPoint3& p0, const hsPoint3& p1, hsPoint3& out) const;
    plCullNode::plCullStatus    ISplitPoly(const plCullPoly& poly, plCullPoly*& innerPoly, plCullPoly*& outerPoly) const;
    void                        IMarkClipped(const plCullPoly& poly, const hsBitVector& onVerts) const;
    void                        ITakeHalfPoly(const plCullPoly& scrPoly, 
                                   const hsTArray<int>& vtxIdx, 
                                   const hsBitVector& onVerts, 
                                   plCullPoly& outPoly) const;
    void                        IBreakPoly(const plCullPoly& poly, const hsTArray<hsScalar>& depths,
                                    hsBitVector& inVerts,
                                    hsBitVector& outVerts,
                                    hsBitVector& onVerts,
                                    plCullPoly& srcPoly) const;

    hsTArray<plCullPoly>&           ScratchPolys() const { return fTree->ScratchPolys(); }
    hsLargeArray<Int16>&            ScratchClear() const { return fTree->ScratchClear(); }
    hsLargeArray<Int16>&            ScratchSplit() const { return fTree->ScratchSplit(); }
    hsLargeArray<Int16>&            ScratchCulled() const { return fTree->ScratchCulled(); }
    hsBitVector&                    ScratchBitVec() const { return fTree->ScratchBitVec(); }
    hsBitVector&                    ScratchTotVec() const { return fTree->ScratchTotVec(); }

    friend class plCullTree;
public:

    void    Init(const plCullTree* t, const hsVector3& n, hsScalar d) { fIsFace = false; fTree = t; fInnerChild = fOuterChild = -1; SetPlane(n, d); }
    void    Init(const plCullTree* t, const plCullPoly& poly) { Init(t, poly.fNorm, poly.fDist); }

    void    SetPlane(const hsVector3& n, hsScalar d) { fNorm = n; fDist = d; }
    
    const hsVector3& GetNormal() const { return fNorm; }
    const hsScalar GetDist() const { return fDist; }

    plCullStatus    TestBounds(const hsBounds3Ext& bnd) const;
    plCullStatus    TestSphere(const hsPoint3& center, hsScalar rad) const;
};

inline plCullNode* plCullNode::IGetNode(Int16 i) const
{ 
    return fTree->IGetNode(i); 
}

#endif // plCullTree_inc