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

#include "HeadSpin.h"
#include "hsGeometry3.h"
#include "hsWindows.h"

#include "MaxMain/MaxAPI.h"

#include "MaxExport/plExportProgressBar.h"
#include "MaxPlasmaMtls/Layers/plLayerTexBitmapPB.h"
#include "MaxPlasmaMtls/Layers/plLayerTex.h"

#include "hsMaterialConverter.h"

#include "plMath/plTriUtils.h"

#include "plDistributor.h"
#include "MaxMain/plMaxNode.h"

#include "plDistTree.h"

static const float kMaxHeight = 10.f;

// Inputs:
//  seed mesh       - geometric entity to replicate over the surface
//  surf mesh       - surface to cover with seed data
//  spacing         - nominal distance between replicants (before randomizing).
//  rnd pos radius  - replicants spread uniformly about nominal spacing by +- radius
//  alignment vector- preferred up direction for replicants
//  alignment weight- weight between alignment vector and interpolated face normal
//  normal range    - amount to randomize the weighted normal 
//  normal bunch    - amount randomized normals bunch around input normal
//  rotation range  - amount to randomize the azimuthal rotation about the randomized normal
//  prob bitmap     - describes probability that a seed planted here on the surface will sprout
//  prob uvws       - mapping from verts to prob bitmap, one per src span vertex
//  overall rnd     - overall probability that a candidate seed point (passing all other criteria) will be used
//


static inline const hsPoint3& hsP3(const Point3& p) { return reinterpret_cast<const hsPoint3&>(p); }
static inline const hsVector3& hsV3(const Point3& p) { return reinterpret_cast<const hsVector3&>(p); }
static inline hsPoint3& hsP3(Point3& p) { return reinterpret_cast<hsPoint3&>(p); }
static inline hsVector3& hsV3(Point3& p) { return reinterpret_cast<hsVector3&>(p); }
static inline Matrix3 Transpose(const Matrix3& m)
{
    return Matrix3(m.GetColumn3(0), m.GetColumn3(1), m.GetColumn3(2), Point3(0,0,0));
}

plDistributor::plDistributor()
{
    IClear();
    fRand.SetSeed((int)(uintptr_t)this);
}

plDistributor::~plDistributor()
{
    Reset();
}

void plDistributor::Reset()
{
    // Let stuff go...
    if( fSurfObjToDelete )
        fSurfObjToDelete->DeleteThis();

    // Then clear
    IClear();
}

void plDistributor::IClear()
{
    fInterface = nullptr;

    fDistTree = nullptr;

    fMeshTree.Reset();

    fSurfNode = nullptr;
    fSurfMesh = nullptr;
    fSurfObjToDelete = nullptr;

    fRepNodes.ZeroCount();

    fIsolation = kIsoLow;
    fConformity = kConformNone;
    fFaceNormals = false;

    fMaxConform = 1.f;

    fSpacing = 5.f;
    fRndPosRadius = 2.5f;

    fAlignVec.Set(0,0,1.f);
    fAlignWgt = 0;

    fOffsetMin = fOffsetMax = 0;

    fPolarRange = 0;
    fTanPolarRange = 0;
    fAzimuthRange = hsConstants::pi<float>;

    fOverallProb = 1.f;

    fPolarBunch = 0;

    fScaleLock = kLockNone;
    fScaleLo.Set(1.f,1.f,1.f);
    fScaleHi.Set(1.f,1.f,1.f);

    fProbBitmapTex = nullptr;
    fProbLayerTex = nullptr;
    fProbColorChan = kRed;

    fProbRemapFromLo = 0;
    fProbRemapFromHi = 1.f;
    fProbRemapToLo = 0;
    fProbRemapToHi = 1.f;

    fAngProbVec.Set(0.f, 0.f, 1.f);
    fAngProbHi = fAngProbLo = 0;
    fAngProbTrans = 0;

    fAltProbHi = fAltProbLo = 0;
    fAltProbTrans = 0;

    fFade.pmin = fFade.pmax = Point3(0,0,0);

    fBone = nullptr;
}

BOOL plDistributor::IGetMesh(INode* node, TriObject*& objToDelete, Mesh*& retMesh) const
{
    if( objToDelete )
        objToDelete->DeleteThis();

    retMesh = nullptr;
    objToDelete = nullptr;

    // Get da object
    Object *obj = node->EvalWorldState(TimeValue(0)).obj;
    if( !obj )
        return false;

    if( !obj->CanConvertToType( triObjectClassID ) )
        return false;

    // Convert to triMesh object
    TriObject   *meshObj = (TriObject*)obj->ConvertToType(TimeValue(0), triObjectClassID);
    if( !meshObj )
        return false;

    if( meshObj != obj )
        objToDelete = meshObj;

    // Get the mesh
    Mesh* mesh = &(meshObj->mesh);
    if( !mesh->getNumFaces() )
    {
        if( objToDelete )
            objToDelete->DeleteThis();
        objToDelete = nullptr;
        return false;
    }

    retMesh = mesh;
    retMesh->checkNormals(true);
    
    return true;
}

void plDistributor::ISetAngProbCosines() const
{
    if( fAngProbHi == fAngProbLo )
        return;

    float maxAng, minAng;
    if( fAngProbHi > fAngProbLo )
    {
        maxAng = hsDegreesToRadians(fAngProbHi);
        minAng = hsDegreesToRadians(fAngProbLo);
    }
    else
    {
        maxAng = hsDegreesToRadians(fAngProbLo);
        minAng = hsDegreesToRadians(fAngProbHi);
    }

    float transAng = hsDegreesToRadians(fAngProbTrans);
    if( transAng > (maxAng - minAng) * 0.5f )
        transAng = (maxAng - minAng) * 0.5f;

    float transAngMax = maxAng < hsConstants::pi<float> ? transAng : 0.f;
    float transAngMin = minAng > 0.f ? transAng : 0.f;

    fCosAngProbHi = cos(minAng);
    fCosAngProbLo = cos(maxAng);
    fCosAngProbHiTrans = cos(minAng + transAngMin);
    fCosAngProbLoTrans = cos(maxAng - transAngMax);
}

BOOL plDistributor::ISetSurfaceNode(INode* surfNode) const
{
    if( !IGetMesh(surfNode, fSurfObjToDelete, fSurfMesh) )
    {
        return false;
    }
    fSurfNode = surfNode;

    fSurfToWorld = surfNode->GetObjectTM(TimeValue(0));
    fWorldToSurf = Inverse(fSurfToWorld);

    fSurfToWorldVec = Transpose(fWorldToSurf);
    fWorldToSurfVec = Transpose(fSurfToWorld);

    fSurfAngProbVec = FNormalize(fWorldToSurfVec * fAngProbVec);
    // This doesn't have anything to do with the surface node, but it
    // does have to do with the SurfAngProbVec, and this is as good a
    // place as any to do it.
    ISetAngProbCosines();

    fSurfAlignVec = FNormalize(fWorldToSurfVec * fAlignVec);

    if( INeedMeshTree() )
        IMakeMeshTree();

    return true;
}

BOOL plDistributor::INeedMeshTree() const
{
    switch( fConformity )
    {
    case kConformAll:
    case kConformHeight:
    case kConformCheck:
    case kConformBase:
        return true;
    }
    return false;
}

void plDistributor::IMakeMeshTree() const
{
    fMeshTree.Reset();

    const Box3 nonFade = fMeshTree.NonFade();
    int i;
    for( i = 0; i < fSurfMesh->getNumFaces(); i++ )
    {
        Point3 p0 = fSurfMesh->getVert(fSurfMesh->faces[i].getVert(0)) * fSurfToWorld;
        Point3 p1 = fSurfMesh->getVert(fSurfMesh->faces[i].getVert(1)) * fSurfToWorld;
        Point3 p2 = fSurfMesh->getVert(fSurfMesh->faces[i].getVert(2)) * fSurfToWorld;

        Box3 box(p0, p0);
        box += p1;
        box += p2;

        fMeshTree.AddBoxIData(box, nonFade, i);

    }
}

void plDistributor::IFindFaceSet(const Box3& box, Tab<int32_t>& faces) const
{
    Tab<int32_t> distNodes;
    fMeshTree.HarvestBox(box, distNodes);
    int i;
    for( i = 0; i < distNodes.Count(); i++ )
    {
        int32_t iFace = int32_t(fMeshTree.GetBox(distNodes[i]).fIData);
        faces.Append(1, &iFace);
    }
}

BOOL plDistributor::IValidateSettings(INode* surfNode, plMeshCacheTab& cache) const
{
    if( !fInterface )
        return false;

    if( !ISetSurfaceNode(surfNode) )
        return false;

    if( !IReadyRepNodes(cache) )
        return false;

    return true;
}

BOOL plDistributor::Distribute(INode* surfNode, plDistribInstTab& reps, plMeshCacheTab& cache, plExportProgressBar& bar) const
{
    // Validate current settings.
    if( !IValidateSettings(surfNode, cache) )
        return false;


    return IDistributeOverMesh(reps, cache, bar);
}

BOOL plDistributor::IDistributeOverMesh(plDistribInstTab& reps, plMeshCacheTab& cache, plExportProgressBar& bar) const
{
    int iUpdate = (fSurfMesh->getNumFaces() >> 4) + 1;
    int i;
    for( i = 0; i < fSurfMesh->getNumFaces(); i++ )
    {
        IDistributeOverFace(i, reps, cache);

        if( ((i / iUpdate) * iUpdate) == i )
        {
            if (bar.Update(nullptr))
                return false;
        }
    }
    return true;
}

Point3& plDistributor::IPerturbPoint(Point3& pt) const
{
    pt.x += fRand.RandMinusOneToOne() * fRndPosRadius;
    pt.y += fRand.RandMinusOneToOne() * fRndPosRadius;
    pt.z += fRand.RandMinusOneToOne() * fRndPosRadius;
    return pt;
}

Box3 plDistributor::ISetupGrid(const Point3& p0, const Point3& p1, const Point3& p2) const
{
    // Add half spacing to max's to protect against precision errors.
    Box3 box(p0, p0);
    box += p1;
    box += p2;

    Point3 mins, maxs;
    int i;
    for( i = 0; i < 3; i++ )
    {
        float t = box.Min()[i];
        t /= fSpacing;
        t = floor(t);
        t *= fSpacing;
        mins[i] = t;

        t = box.Max()[i];
        t /= fSpacing;
        t = ceil(t);
        t *= fSpacing;
        maxs[i] = t + fSpacing*0.5f;
    }
    box = Box3(mins, maxs);

    return box;
}

bool plDistributor::IFailsProbBitmap(int iFace, const Point3& bary) const
{
    // If we don't have a probability map, or we don't have
    // valid coordinates into it, just return false. That is,
    // with no valid probability map, everything goes.
    int uvwChan = 1;
    Matrix3 uvtrans(true);
    Bitmap* bm = nullptr;
    UINT filtType = BMM_FILTER_PYRAMID;
    if( fProbBitmapTex )
    {
        uvwChan = fProbBitmapTex->GetMapChannel();
        fProbBitmapTex->GetUVTransform(uvtrans);

        bm = fProbBitmapTex->GetBitmap(TimeValue(0));

        if( bm && !bm->HasFilter() )
        {
            switch( fProbBitmapTex->GetFilterType() )
            {
            default:
            case FILTER_PYR:
                filtType = BMM_FILTER_PYRAMID;
                break;
            case FILTER_SAT:
                filtType = BMM_FILTER_SUM;
                break;
            case FILTER_NADA:
                filtType = BMM_FILTER_NONE;
                break;
            }
        }
    }
    else if( fProbLayerTex )
    {
        uvwChan = fProbLayerTex->GetMapChannel();
        fProbLayerTex->GetUVTransform(uvtrans);

        bm = fProbLayerTex->GetBitmap(TimeValue(0));

    }

    if( !bm )
        return false;

    if( !bm->HasFilter() )
        bm->SetFilter(filtType);

    bm->PrepareGChannels(&bm->Storage()->bi);

    if( !fSurfMesh->mapSupport(uvwChan) )
        return false;

    if( !fSurfMesh->mapFaces(uvwChan) || !fSurfMesh->mapVerts(uvwChan) )
        return false;

    // Lookup the appropriate texel value
    Point3 uvw;
    uvw = fSurfMesh->mapVerts(uvwChan)[fSurfMesh->mapFaces(uvwChan)[iFace].getTVert(0)] * bary[0];
    uvw += fSurfMesh->mapVerts(uvwChan)[fSurfMesh->mapFaces(uvwChan)[iFace].getTVert(1)] * bary[1];
    uvw += fSurfMesh->mapVerts(uvwChan)[fSurfMesh->mapFaces(uvwChan)[iFace].getTVert(2)] * bary[2];

    uvw = uvw * uvtrans;

    float fu = uvw.x - int(uvw.x);
    if( fu < 0 )
        fu += 1.f;
    float fv = 1.0f - (uvw.y - int(uvw.y));
    if( fv < 0 )
        fv += 1.f;
    float du = 1.f / bm->Width();
    float dv = 1.f / bm->Height();

    BMM_Color_fl evCol;
    bm->GetFiltered(fu, fv, du, dv, &evCol);
    
    float frac;
    switch( fProbColorChan )
    {
    case kRed:
        frac = evCol.r;
        break;
    case kGreen:
        frac = evCol.g;
        break;
    case kBlue:
        frac = evCol.b;
        break;
    case kAlpha:
        frac = evCol.a;
        break;
    case kAverageRedGreen:
        frac = (evCol.r + evCol.g) / 2.f;
        break;
    case kAverageRedGreenTimesAlpha:
        frac = (evCol.r + evCol.g) / 2.f * evCol.a;
        break;
    case kAverage:
        frac = (evCol.r + evCol.g + evCol.b ) / 3.f;
        break;
    case kAverageTimesAlpha:
        frac = (evCol.r + evCol.g + evCol.b ) / 3.f * evCol.a;
        break;
    case kMax:
    case kMaxColor:
        frac = std::max({ evCol.r, evCol.g, evCol.b });
        break;
    case kMaxColorTimesAlpha:
        frac = std::max({ evCol.r, evCol.g, evCol.b }) * evCol.a;
        break;
    case kMaxRedGreen:
        frac = std::max(evCol.r, evCol.g);
        break;
    case kMaxRedGreenTimesAlpha:
        frac = std::max(evCol.r, evCol.g) * evCol.a;
        break;
    }

    if( fProbRemapFromHi != fProbRemapFromLo )
        frac = fProbRemapToLo + (frac - fProbRemapFromLo) / (fProbRemapFromHi - fProbRemapFromLo) * (fProbRemapToHi - fProbRemapToLo);
    else
        frac = frac > fProbRemapFromHi ? fProbRemapToHi : fProbRemapToLo;

    return frac < fRand.RandZeroToOne();
}

Point3 plDistributor::IGetSurfaceNormal(int iFace, const Point3& bary) const
{
    fSurfMesh->checkNormals(true);

    if( !fFaceNormals )
    {
        Face& face = fSurfMesh->faces[iFace];
        Point3 norm = FNormalize(fSurfMesh->getNormal(face.getVert(0))) * bary[0];
        norm += FNormalize(fSurfMesh->getNormal(face.getVert(1))) * bary[1];
        norm += FNormalize(fSurfMesh->getNormal(face.getVert(2))) * bary[2];

        return norm;
    }

    Point3 faceNorm = fSurfMesh->getFaceNormal(iFace);
    return FNormalize(faceNorm);
}

bool plDistributor::IFailsAngProb(int iFace, const Point3& bary) const
{
    if( fAngProbLo == fAngProbHi )
        return false;

    Point3 norm = IGetSurfaceNormal(iFace, bary);

    float dot = DotProd(norm, fSurfAngProbVec);

    if( dot > fCosAngProbHi )
        return true;
    if( dot < fCosAngProbLo )
        return true;

    if( dot > fCosAngProbHiTrans )
    {
        float prob = fCosAngProbHi - dot;
        prob /= fCosAngProbHi - fCosAngProbHiTrans;
        return fRand.RandZeroToOne() >= prob;
    }

    if( dot < fCosAngProbLoTrans )
    {
        float prob = dot - fCosAngProbLo;
        prob /= fCosAngProbLoTrans - fCosAngProbLo;
        return fRand.RandZeroToOne() >= prob;
    }

    return false;

}

bool plDistributor::IFailsAltProb(int iFace, const Point3& bary) const
{
    if( fAltProbLo == fAltProbHi )
        return false;

    Face& face = fSurfMesh->faces[iFace];
    Point3 pos = fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(0)) * bary[0];
    pos += fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(1)) * bary[1];
    pos += fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(2)) * bary[2];

    pos = pos * fSurfToWorld;

    if( pos.z > fAltProbHi )
        return true;
    if( pos.z < fAltProbLo )
        return true;
    if( pos.z < fAltProbLo + fAltProbTrans )
    {
        float prob = (pos.z - fAltProbLo) / fAltProbTrans;
        return fRand.RandZeroToOne() >= prob;
    }
    if( pos.z > fAltProbHi - fAltProbTrans )
    {
        float prob = (fAltProbHi - pos.z) / fAltProbTrans;
        return fRand.RandZeroToOne() >= prob;
    }

    return false;

}

// If |projGridPt - GridPt| < gridCubeRadius
// and probBitmap->GetPixel(src->UVW(bary)) < RandomZeroToOne()
// Also a generic random factor.
bool plDistributor::IProbablyDoIt(int iFace, Point3& del, const Point3& bary) const
{
    if( fRand.RandZeroToOne() >= fOverallProb )
    {
        return false;
    }

    if( (kIsoNone == fIsolation) || (kIsoLow == fIsolation) )
    {
        if( LengthSquared(del) >= fSpacing*fSpacing )
        {
            return false;
        }

        Point3 faceNorm = fSurfMesh->FaceNormal(iFace, FALSE);
        if( DotProd(del, faceNorm) < 0 )
        {
            return false;
        }
    }

    if( IFailsAngProb(iFace, bary) )
    {
        return false;
    }

    if( IFailsAltProb(iFace, bary) )
    {
        return false;
    }

    if( IFailsProbBitmap(iFace, bary) )
    {
        return false;
    }

    return true;
}

Point3 plDistributor::IPerpAxis(const Point3& p) const
{
    const float kMinLengthSquared = 1.e-1f;

    int minAx = p.MinComponent();
    Point3 ax(0,0,0);
    ax[minAx] = 1.f;

    Point3 perp = p ^ ax;
    if( perp.LengthSquared() < kMinLengthSquared )
    {
        // hmm, think we might be screwed, but this shouldn't happen.
    }

    return perp = perp.FNormalize();
}

// Generate local to world from face info (pos, normal, etc)
Matrix3 plDistributor::IGenerateTransform(int iRepNode, int iFace, const Point3& pt, const Point3& bary) const
{
    const float kMinVecLengthSq = 1.e-6f;
    Matrix3 l2w(true);

    // First, set the scale
    Point3 scale;
    switch( fScaleLock )
    {
    case kLockX | kLockY:
        scale.x = fRand.RandRangeF(fScaleLo.x, fScaleHi.x);
        scale.y = scale.x;
        scale.z = fRand.RandRangeF(fScaleLo.z, fScaleHi.z);
        break;
    case kLockX | kLockY | kLockZ:
        scale.x = fRand.RandRangeF(fScaleLo.x, fScaleHi.x);
        scale.y = scale.z = scale.x;
        break;
    default:
        scale.x = fRand.RandRangeF(fScaleLo.x, fScaleHi.x);
        scale.y = fRand.RandRangeF(fScaleLo.y, fScaleHi.y);
        scale.z = fRand.RandRangeF(fScaleLo.z, fScaleHi.z);
        break;
    }

    l2w.Scale(scale);

    // Next up, get the rotation.
    // First we'll randomly rotate about local Z
    float azimRot = fRand.RandMinusOneToOne() * fAzimuthRange;
    Matrix3 azimMat;
    azimMat.SetRotateZ(azimRot);

    l2w = l2w * azimMat;

    // Now align with the surface.
    // Get the interpolated surface normal.
    Point3 surfNorm = IGetSurfaceNormal(iFace, bary);

    Matrix3 repNodeTM = fRepNodes[iRepNode]->GetNodeTM(TimeValue(0));

    Point3 alignVec = repNodeTM.GetRow(2);
    alignVec = alignVec * fWorldToSurfVec;
    alignVec = FNormalize(alignVec);

    Point3 norm = surfNorm + (alignVec - surfNorm) * fAlignWgt;
    // The norm can come out of this zero length, if the surace normal
    // is directly opposite the "natural" up direction and the weight
    // is 50% (for example). In that case, this is just a bad place
    // to drop this replicant.
    if( norm.LengthSquared() < kMinVecLengthSq )
    {
        l2w.IdentityMatrix();
        return l2w;
    }
    norm = norm.Normalize();

    // Randomize through the cone around that.
    Point3 rndNorm = norm;
    Point3 rndDir = IPerpAxis(norm);
    Point3 rndOut = rndDir ^ norm;
    rndDir *= fRand.RandMinusOneToOne();
    float len = sqrt(1.f - rndDir.LengthSquared());
    rndOut *= len;
    if( fRand.RandMinusOneToOne() < 0 )
        rndOut *= -1.f;
    Point3 rndPol = rndDir + rndOut;

    float polScale = fRand.RandZeroToOne() * fTanPolarRange;

    // Combine using the bunching factor
    polScale = polScale * (1.f - fPolarBunch) + polScale * polScale * fPolarBunch;

    rndPol *= polScale;
    rndNorm += rndPol;
    norm = rndNorm.Normalize();

    // Have "up" alignment, now just generate random dir vector perpindicular to up
    Point3 dir = repNodeTM.GetRow(1);
    dir = dir * fWorldToSurfVec;
    Point3 out = dir ^ norm;
    if( out.LengthSquared() < kMinVecLengthSq )
    {
        if (fAzimuthRange < hsConstants::half_pi<float>)
        {
            l2w.IdentityMatrix();
            return l2w;
        }
        else
        {
            dir = IPerpAxis(norm);
            out = dir ^ norm;
        }
    }
    out = FNormalize(out);
    dir = norm ^ out;

    // If our "up" direction points into the surface, return an "up" direction
    // tangent to the surface. Also, make the "dir" direction point out from
    // the surface. So if the alignVec/fAlignWgt turns the replicant around
    // to penetrate the surface, it just lies down instead.
    //
    // There's an early out here, for the case where the surface normal is
    // exactly opposed to the destination normal. This usually means the
    // surface normal is directly opposite the alignVec. In that
    // case, we just want to bag it.
    if( DotProd(norm, surfNorm) < 0 )
    {
        dir = surfNorm;
        dir = dir.Normalize();
        out = dir ^ norm;
        if( out.LengthSquared() < kMinVecLengthSq )
        {
            l2w.IdentityMatrix();
            return l2w;
        }
        out = out.Normalize();
        norm = out ^ dir;
    }

    Matrix3 align;
    align.Set(out, dir, norm, Point3(0,0,0));

    l2w = l2w * align;


    // Lastly, set the position.
    Point3 pos = pt;
    const float offset = fRand.RandRangeF(fOffsetMin, fOffsetMax);
    pos += norm * offset;
    l2w.Translate(pos);

    l2w = l2w * fSurfNode->GetObjectTM(TimeValue(0));

    return l2w;
}

int plDistributor::ISelectRepNode() const
{
    if( fRepNodes.Count() < 2 )
        return 0;

    int i = fRand.RandRangeI(0, fRepNodes.Count()-1);

    return i;
}

BOOL plDistributor::ISetupNormals(plMaxNode* node, Mesh* mesh, BOOL radiateNorm) const
{
    auto dbgNodeName = node->GetName();

    UVVert *normMap = mesh->mapVerts(kNormMapChan); 
    int numNormVerts = mesh->getNumMapVerts(kNormMapChan);
    if( !mesh->mapSupport(kNormMapChan) || !mesh->mapVerts(kNormMapChan) || !mesh->mapFaces(kNormMapChan) )
    {
        mesh->setMapSupport(kNormMapChan);

        mesh->setNumMapVerts(kNormMapChan, mesh->getNumVerts());
        mesh->setNumMapFaces(kNormMapChan, mesh->getNumFaces());
    }

    int i;
    if( radiateNorm )
    {
        Matrix3 otm = node->GetOTM();
        Matrix3 invOtm = Inverse(otm);
        invOtm.SetTrans(Point3(0,0,0));
        invOtm.ValidateFlags();

        for( i = 0; i < mesh->getNumVerts(); i++ )
        {
            Point3 pos = mesh->getVert(i) * otm;
            pos = pos * invOtm;

            mesh->setMapVert(kNormMapChan, i, pos);
        }
    }
    else
    {
        mesh->checkNormals(true);

        for( i = 0; i < mesh->getNumVerts(); i++ )
        {
            Point3 norm = mesh->getNormal(i);

            mesh->setMapVert(kNormMapChan, i, norm);
        }
    }

    TVFace* mapFaces = mesh->mapFaces(kNormMapChan);
    Face* faces = mesh->faces;
    for( i = 0; i < mesh->getNumFaces(); i++ )
    {
        mapFaces[i].setTVerts(faces[i].getVert(0), faces[i].getVert(1), faces[i].getVert(2));
    }

    return true;
}


BOOL plDistributor::ISetupSkinWeights(plMaxNode* node, Mesh* mesh, const Point3& flex) const
{
    auto dbgNodeName = node->GetName();

    Matrix3 otm = node->GetOTM();

    Box3 bnd = mesh->getBoundingBox() * otm;

    float meshHeight = bnd.Max().z;
    float maxHeight = kMaxHeight;
    if( meshHeight > maxHeight )
        maxHeight = meshHeight;
    float maxNorm = meshHeight / maxHeight;

    float flexibility = flex[0];

    UVVert *wgtMap = mesh->mapVerts(kWgtMapChan);   
    int numWgtVerts = mesh->getNumMapVerts(kWgtMapChan);
    if( !mesh->mapSupport(kWgtMapChan) || !mesh->mapVerts(kWgtMapChan) || !mesh->mapFaces(kWgtMapChan) )
    {
        mesh->setMapSupport(kWgtMapChan);

        mesh->setNumMapVerts(kWgtMapChan, mesh->getNumVerts());
        mesh->setNumMapFaces(kWgtMapChan, mesh->getNumFaces());
    }

    int i;
    for( i = 0; i < mesh->getNumVerts(); i++ )
    {
        Point3 pos = mesh->getVert(i) * otm;
        float wgt = pos.z / meshHeight;
        wgt *= wgt > 0 ? wgt : 0;
        wgt *= maxNorm;
        wgt *= flexibility;

        pos.x = wgt;
        pos.y = wgt;
        pos.z = wgt;

        mesh->setMapVert(kWgtMapChan, i, pos);
    }

    TVFace* mapFaces = mesh->mapFaces(kWgtMapChan);
    Face* faces = mesh->faces;
    for( i = 0; i < mesh->getNumFaces(); i++ )
    {
        mapFaces[i].setTVerts(faces[i].getVert(0), faces[i].getVert(1), faces[i].getVert(2));
    }

    return true;
}

BOOL plDistributor::IDuplicate2Sided(plMaxNode* node, Mesh* mesh) const
{
    Mtl* mtl = node->GetMtl();

    BitArray faces(mesh->getNumFaces());

    int num2Sided = 0;

    int origNumFaces = mesh->getNumFaces();

    int i;
    for( i = 0; i < mesh->getNumFaces(); i++ )
    {
        if( hsMaterialConverter::IsTwoSided(mtl, mesh->faces[i].getMatID()) )
        {
            num2Sided++;
            faces.Set(i);
        }
    }

    if( !num2Sided )
        return false;

    MeshDelta meshDelta(*mesh);
    meshDelta.CloneFaces(*mesh, faces);
    meshDelta.Apply(*mesh);

    BitArray verts(mesh->getNumVerts());
    verts.SetAll();
    const float kWeldThresh = 0.1f;
    meshDelta.WeldByThreshold(*mesh, verts, kWeldThresh);
    meshDelta.Apply(*mesh);

    hsAssert(origNumFaces + num2Sided == mesh->getNumFaces(), "Whoa, lost or gained, unexpected");

    for( i = origNumFaces; i < mesh->getNumFaces(); i++ )
    {
        meshDelta.FlipNormal(*mesh, i);
    }
    meshDelta.Apply(*mesh);

    return true;
}

BOOL plDistributor::IReadyRepNodes(plMeshCacheTab& cache) const
{
    int i;
    for( i = 0; i < fRepNodes.Count(); i++ )
    {
        Mesh* mesh = nullptr;
        TriObject* obj = nullptr;
        if( IGetMesh(fRepNodes[i], obj, mesh) )
        {
            plMaxNode* repNode = (plMaxNode*)fRepNodes[i];

            int iCache = cache.Count();
            cache.SetCount(iCache + 1);

            cache[iCache].fMesh = new Mesh(*mesh);
            cache[iCache].fFlex = repNode->GetFlexibility();
            
            if( obj )
                obj->DeleteThis();

            BOOL hasXImp = nullptr != repNode->GetXImposterComp();

            ISetupNormals(repNode, cache[iCache].fMesh, hasXImp);

            ISetupSkinWeights(repNode, cache[iCache].fMesh, cache[iCache].fFlex);
        }
        else
        {
            fRepNodes.Delete(i, 1);
            i--;
        }
    }

    return fRepNodes.Count() > 0;
}

void plDistributor::ClearReplicateNodes() 
{ 
    fRepNodes.ZeroCount(); 
}

void plDistributor::AddReplicateNode(INode* node) 
{ 
    fRepNodes.Append(1, &node); 
}

BOOL plDistributor::IProjectVertex(const Point3& pt, const Point3& dir, float maxDist, Tab<int32_t>&faces, Point3& projPt) const
{
    BOOL retVal = false;
    plTriUtils triUtil;
    int i;
    for( i = 0; i < faces.Count(); i++ )
    {
        int iFace = faces[i];
        hsPoint3 p0 = hsP3(fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(0)) * fSurfToWorld);
        hsPoint3 p1 = hsP3(fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(1)) * fSurfToWorld);
        hsPoint3 p2 = hsP3(fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(2)) * fSurfToWorld);

        Point3 plnPt = pt;
        if( triUtil.ProjectOntoPlaneAlongVector(p0, p1, p2, hsV3(dir), hsP3(plnPt)) )
        {
            Point3 bary = plnPt;
            plTriUtils::Bary baryVal = triUtil.ComputeBarycentric(p0, p1, p2, hsP3(plnPt), hsP3(bary));
            if( (plTriUtils::kOutsideTri != baryVal) && (plTriUtils::kDegenerateTri != baryVal) )
            {
                float dist = DotProd((pt - plnPt), dir);
                if( (dist <= maxDist) && (dist >= -maxDist) )
                {
                    projPt = plnPt;
                    maxDist = dist >= 0 ? dist : -dist;
                    retVal = true;
                }
            }
        }

    }
    return retVal;
}

BOOL plDistributor::IConformCheck(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const
{
    Matrix3 OTM = IOTM(iRepNode);
    Mesh* mesh = cache[iRepNode].fMesh;

    Point3 dir = l2w.VectorTransform(Point3(0.f, 0.f, 1.f));
    dir = FNormalize(dir);

    const float kOneOverSqrt2 = 0.707107f;
    Point3 scalePt(kOneOverSqrt2, kOneOverSqrt2, 0.f);
    scalePt = l2w.VectorTransform(scalePt);
    float maxScaledDist = fMaxConform * scalePt.Length();

    Box3 bnd = mesh->getBoundingBox() * OTM;
    bnd = Box3(Point3(bnd.Min().x, bnd.Min().y, -bnd.Max().z), bnd.Max());
    bnd = bnd * l2w;
    Tab<int32_t> faces;
    IFindFaceSet(bnd, faces);

    int i;
    for( i = 0; i < mesh->getNumVerts(); i++ )
    {
        Point3 pt = mesh->getVert(i) * OTM;
        pt.z = 0;

        pt = pt * l2w;

        Point3 projPt;
        if( !IProjectVertex(pt, dir, maxScaledDist, faces, projPt) )
            return false;
    }
    return true;
}

BOOL plDistributor::IConformAll(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const
{
    Matrix3 OTM = IOTM(iRepNode);
    Mesh* mesh = cache[iRepNode].fMesh;

    Point3 dir = l2w.VectorTransform(Point3(0.f, 0.f, 1.f));
    dir = FNormalize(dir);

    const float kOneOverSqrt2 = 0.707107f;
    Point3 scalePt(kOneOverSqrt2, kOneOverSqrt2, 0.f);
    scalePt = l2w.VectorTransform(scalePt);
    float maxScaledDist = fMaxConform * scalePt.Length();

    Box3 bnd = mesh->getBoundingBox() * OTM;
    bnd = Box3(Point3(bnd.Min().x, bnd.Min().y, -bnd.Max().z), bnd.Max());
    bnd = bnd * l2w;
    Tab<int32_t> faces;
    IFindFaceSet(bnd, faces);

    // l2w, iRepNode, cache, &iCache, maxScaledDist, dir
    iCache = cache.Count();
    cache.SetCount(iCache + 1);
    cache[iCache] = cache[iRepNode];
    cache[iCache].fMesh = new Mesh(*mesh);

    mesh = cache[iCache].fMesh;

    Matrix3 v2w = OTM * l2w;
    Matrix3 w2v = Inverse(v2w);

    BOOL retVal = true;
    int i;
    for( i = 0; i < mesh->getNumVerts(); i++ )
    {
        Point3 pt = mesh->getVert(i) * OTM;
        pt.z = 0;

        pt = pt * l2w;

        Point3 projPt;
        if( !IProjectVertex(pt, dir, maxScaledDist, faces, projPt) )
        {
            retVal = false;
            break;
        }

        Point3 del = w2v.VectorTransform(projPt - pt);
        mesh->getVert(i) += del;
    }
    if( !retVal )
    {
//      delete cache[iCache].fMesh;
        delete mesh;
        cache.SetCount(iCache);
        iCache = iRepNode;
    }
    return retVal;
}

BOOL plDistributor::IConformHeight(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const
{
    Matrix3 OTM = IOTM(iRepNode);
    Mesh* mesh = cache[iRepNode].fMesh;

    Point3 dir = l2w.VectorTransform(Point3(0.f, 0.f, 1.f));
    dir = FNormalize(dir);

    const float kOneOverSqrt2 = 0.707107f;
    Point3 scalePt(kOneOverSqrt2, kOneOverSqrt2, 0.f);
    scalePt = l2w.VectorTransform(scalePt);
    float maxScaledDist = fMaxConform * scalePt.Length();

    Box3 bnd = mesh->getBoundingBox() * OTM;
    bnd = Box3(Point3(bnd.Min().x, bnd.Min().y, -bnd.Max().z), bnd.Max());
    bnd = bnd * l2w;
    Tab<int32_t> faces;
    IFindFaceSet(bnd, faces);

    // l2w, iRepNode, cache, &iCache, maxScaledDist, dir
    iCache = cache.Count();
    cache.SetCount(iCache + 1);
    cache[iCache] = cache[iRepNode];
    cache[iCache].fMesh = new Mesh(*mesh);

    mesh = cache[iCache].fMesh;

    Matrix3 v2w = OTM * l2w;
    Matrix3 w2v = Inverse(v2w);


    float maxZ = (mesh->getBoundingBox() * OTM).Max().z;

    BOOL retVal = true;
    int i;
    for( i = 0; i < mesh->getNumVerts(); i++ )
    {
        Point3 pt = mesh->getVert(i) * OTM;

        float conScale = 1.f - pt.z / maxZ;
        if( conScale > 1.f )
            conScale = 1.f;
        else if( conScale < 0 )
            conScale = 0;

        pt.z = 0;

        pt = pt * l2w;

        Point3 projPt;
        if( !IProjectVertex(pt, dir, maxScaledDist, faces, projPt) )
        {
            retVal = false;
            break;
        }

        Point3 del = w2v.VectorTransform(projPt - pt);
        del *= conScale;
        mesh->getVert(i) += del;
    }
    if( !retVal )
    {
        delete cache[iCache].fMesh;
        cache.SetCount(iCache);
        iCache = iRepNode;
    }
    return retVal;
}

BOOL plDistributor::IConformBase(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const
{
    Matrix3 OTM = IOTM(iRepNode);
    Mesh* mesh = cache[iRepNode].fMesh;

    Point3 dir = l2w.VectorTransform(Point3(0.f, 0.f, 1.f));
    dir = FNormalize(dir);

    const float kOneOverSqrt2 = 0.707107f;
    Point3 scalePt(kOneOverSqrt2, kOneOverSqrt2, 0.f);
    scalePt = l2w.VectorTransform(scalePt);
    float maxScaledDist = fMaxConform * scalePt.Length();

    Box3 bnd = mesh->getBoundingBox() * OTM;
    bnd = Box3(Point3(bnd.Min().x, bnd.Min().y, -bnd.Max().z), bnd.Max());
    bnd = bnd * l2w;
    Tab<int32_t> faces;
    IFindFaceSet(bnd, faces);

    // l2w, iRepNode, cache, &iCache, maxScaledDist, dir
    iCache = cache.Count();
    cache.SetCount(iCache + 1);
    cache[iCache] = cache[iRepNode];
    cache[iCache].fMesh = new Mesh(*mesh);

    mesh = cache[iCache].fMesh;

    Matrix3 v2w = OTM * l2w;
    Matrix3 w2v = Inverse(v2w);


    float maxZ = (mesh->getBoundingBox() * OTM).Max().z;

    BOOL retVal = true;
    int i;
    for( i = 0; i < mesh->getNumVerts(); i++ )
    {
        Point3 pt = mesh->getVert(i) * OTM;

        const float kMaxConformZ = 0.5f;
        if( pt.z < kMaxConformZ )
        {
            pt.z = 0;

            pt = pt * l2w;

            Point3 projPt;
            if( !IProjectVertex(pt, dir, maxScaledDist, faces, projPt) )
            {
                retVal = false;
                break;
            }

            Point3 del = w2v.VectorTransform(projPt - pt);
            mesh->getVert(i) += del;
        }
    }
    if( !retVal )
    {
        delete cache[iCache].fMesh;
        cache.SetCount(iCache);
        iCache = iRepNode;
    }
    return retVal;
}

BOOL plDistributor::IConform(Matrix3& l2w, int iRepNode, plMeshCacheTab& cache, int& iCache) const
{
    iCache = iRepNode;
    switch( fConformity )
    {
    case kConformAll:
        return IConformAll(l2w, iRepNode, cache, iCache);
    case kConformHeight:
        return IConformHeight(l2w, iRepNode, cache, iCache);
    case kConformCheck:
        return IConformCheck(l2w, iRepNode, cache, iCache);
    case kConformBase:
        return IConformBase(l2w, iRepNode, cache, iCache);
    }
    return true;
}


// Clone the replicant and set its transform appropriately. Should set the plMaxNode property
// that the node's spans are collapseable? No, we'll make a separate component for that.
void plDistributor::IReplicate(Matrix3& l2w, int iRepNode, plDistribInstTab& reps, plMeshCache& mCache) const
{
    INode* repNode = fRepNodes[iRepNode];
    plDistribInstance inst;
    inst.fNode = repNode;
    inst.fNodeTM = l2w;
    inst.fObjectTM = repNode->GetObjectTM(TimeValue(0)) * Inverse(repNode->GetNodeTM(TimeValue(0))) * l2w;

    inst.fMesh = mCache.fMesh;

    inst.fFlex = mCache.fFlex;

    inst.fFade = fFade;

    inst.fBone = fBone;

    inst.fRigid = fRigid;

    reps.Append(1, &inst);
}

void plDistributor::IDistributeOverFace(int iFace, plDistribInstTab& reps, plMeshCacheTab& cache) const
{
    Point3 p0 = fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(0));
    Point3 p1 = fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(1));
    Point3 p2 = fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(2));

    Box3 grid = ISetupGrid(p0, p1, p2);

    float delta = fSpacing;

    float x, y, z;
    for( x = grid.Min().x; x < grid.Max().x; x += delta )
    {
        for( y = grid.Min().y; y < grid.Max().y; y += delta )
        {
            for( z = grid.Min().z; z < grid.Max().z; z += delta )
            {
                Point3 pt(x, y, z);

                pt = IPerturbPoint(pt);

                Point3 plnPt = pt;
                Point3 bary;

                // Get Barycentric coord of projection of grid pt onto face
                plTriUtils triUtil;
                plTriUtils::Bary baryVal = triUtil.ComputeBarycentricProjection(hsP3(p0), hsP3(p1), hsP3(p2), hsP3(plnPt), hsP3(bary));
                if( !(baryVal & (plTriUtils::kOutsideTri | plTriUtils::kDegenerateTri)) )
                {
                    int iRepNode = ISelectRepNode();

                    Matrix3 l2w = IGenerateTransform(iRepNode, iFace, plnPt, bary);

                    // l2w as ident means this position turned out to be not so good afterall.
                    if( l2w.IsIdentity() )
                    {
                        continue;
                    }

                    Box3 clearBox;
                    if( !ISpaceClear(iRepNode, l2w, clearBox, cache) )
                    {
                        continue;
                    }

                    int iCacheNode = iRepNode;
                    if( !IConform(l2w, iRepNode, cache, iCacheNode) )
                    {
                        continue;
                    }

                    // If |projGridPt - GridPt| < gridCubeRadius
                    // and probBitmap->GetPixel(src->UVW(bary)) < RandomZeroToOne()
                    // Also a generic random factor.
                    if( IProbablyDoIt(iFace, pt - plnPt, bary) )
                    {
                        IReplicate(l2w, iRepNode, reps, cache[iCacheNode]);

                        IReserveSpace(clearBox);
                    }
                }
            }
        }
    }
}

Matrix3 plDistributor::IInvOTM(int iRepNode) const
{
    // objectTM = otm * nodeTM
    // invOTM * objectTM = nodeTM
    // invOTM = nodeTM * invObjectTM
    INode* repNode = fRepNodes[iRepNode];
    Matrix3 objectTM = repNode->GetObjectTM(TimeValue(0));
    Matrix3 nodeTM = repNode->GetNodeTM(TimeValue(0));
    Matrix3 invOTM = nodeTM * Inverse(objectTM);
    return invOTM;
}

Matrix3 plDistributor::IOTM(int iRepNode) const
{
    // objectTM = otm * nodeTM
    // objectTM * Inverse(nodeTM) = otm
    INode* repNode = fRepNodes[iRepNode];
    Matrix3 objectTM = repNode->GetObjectTM(TimeValue(0));
    Matrix3 nodeTM = repNode->GetNodeTM(TimeValue(0));
    Matrix3 OTM = objectTM * Inverse(nodeTM);
    return OTM;
}

BOOL plDistributor::ISpaceClear(int iRepNode, const Matrix3& l2w, Box3& clearBox, plMeshCacheTab& cache) const
{
    if( !fDistTree )
        return true;

    // If we have high isolation,
    //      clearBox = Box3(Point3(-fSpacing*0.5f, -fSpacing*0.5f, 0), Point3(fSpacing*0.5f, fSpacing*0.5f, fSpacing));
    // Else if we have medium isolation
    //      clearBox = cache[iRepNode]->getBoundingBox(); // The mesh's bounds
    // Else if we have low isolation or None isolation
    //      clearBox = Box3(Point3(-kSmallSpace, -kSmallSpace, 0), Point3(kSmallSpace, kSmallSpace, kSmallSpace)); // kSmallSpace ~= 0.5f or one or something

    // We want to set up the box (for high, low and none) in Post OTM space. So instead of multiplying
    // by l2w, we want to multiply box = box * invOTM * l2w (because l2w already has OTM folded in). When using
    // the mesh bounds (Medium), l2w is the right transform.
    // objectTM = otm * nodeTM
    // invOTM * objectTM = nodeTM
    // invOTM = nodeTM * invObjectTM
    const float kSmallSpace = 0.5f;
    switch( fIsolation )
    {
    case kIsoHigh:
        {
            INode* repNode = fRepNodes[iRepNode];
            Matrix3 objectTM = repNode->GetObjectTM(TimeValue(0));
            Matrix3 nodeTM = repNode->GetNodeTM(TimeValue(0));
            Matrix3 invOTM = nodeTM * Inverse(objectTM);
            clearBox = Box3(Point3(-fSpacing*0.5f, -fSpacing*0.5f, 0.f), Point3(fSpacing*0.5f, fSpacing*0.5f, fSpacing));
            clearBox = clearBox * invOTM;
        }
        break;
    case kIsoMedium:
        clearBox = cache[iRepNode].fMesh->getBoundingBox(); // The mesh's bounds
        break;
    case kIsoLow:
    case kIsoNone:
    default:
        {
            INode* repNode = fRepNodes[iRepNode];
            Matrix3 objectTM = repNode->GetObjectTM(TimeValue(0));
            Matrix3 nodeTM = repNode->GetNodeTM(TimeValue(0));
            Matrix3 invOTM = nodeTM * Inverse(objectTM);
            clearBox = Box3(Point3(-kSmallSpace, -kSmallSpace, 0.f), Point3(kSmallSpace, kSmallSpace, kSmallSpace)); 
            clearBox = clearBox * invOTM;
        }
        break;
    }

    clearBox = clearBox * l2w;

    return fDistTree->BoxClear(clearBox, fFade);
}

void plDistributor::IReserveSpace(const Box3& clearBox) const
{
    // if isolation isn't None, add the box.
    if( fDistTree && (fIsolation != kIsoNone) )
        fDistTree->AddBox(clearBox, fFade);
}

uint32_t plDistributor::GetRandSeed() const
{
    return fRand.GetSeed();
}

void plDistributor::SetRandSeed(int seed)
{
    fRand.SetSeed(seed);
}

void plDistributor::SetPolarRange(float deg) 
{ 
    fPolarRange = hsDegreesToRadians(deg); 
    fTanPolarRange = tan(fPolarRange); 
}

void plDistributor::SetProbabilityBitmapTex(BitmapTex* t)
{
    fProbLayerTex = nullptr;
    fProbBitmapTex = t;
}

void plDistributor::SetProbabilityLayerTex(plLayerTex* t)
{
    fProbBitmapTex = nullptr;
    fProbLayerTex = t;
}


