/*==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 .
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 "hsWindows.h"
#include
#include "Max.h"
#include "stdmat.h"
#include "bmmlib.h"
#include "iparamb2.h"
#include "meshdlib.h"
#include "../MaxExport/plExportProgressBar.h"
#include "../MaxPlasmaMtls/Layers/plLayerTexBitmapPB.h"
#include "../MaxPlasmaMtls/Layers/plLayerTex.h"
#include "hsMaterialConverter.h"
#include "hsTypes.h"
#include "hsGeometry3.h"
#include "hsUtils.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 hsPoint3& hsP3(const Point3& p) { return *(hsPoint3*)&p; }
static inline hsVector3& hsV3(const Point3& p) { return *(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(UInt32(this));
}
plDistributor::~plDistributor()
{
Reset();
}
void plDistributor::Reset()
{
// Let stuff go...
if( fSurfObjToDelete )
fSurfObjToDelete->DeleteThis();
// Then clear
IClear();
}
void plDistributor::IClear()
{
fInterface = nil;
fDistTree = nil;
fMeshTree.Reset();
fSurfNode = nil;
fSurfMesh = nil;
fSurfObjToDelete = nil;
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 = hsScalarPI;
fOverallProb = 1.f;
fPolarBunch = 0;
fScaleLock = kLockNone;
fScaleLo.Set(1.f,1.f,1.f);
fScaleHi.Set(1.f,1.f,1.f);
fProbBitmapTex = nil;
fProbLayerTex = nil;
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 = nil;
}
BOOL plDistributor::IGetMesh(INode* node, TriObject*& objToDelete, Mesh*& retMesh) const
{
if( objToDelete )
objToDelete->DeleteThis();
retMesh = nil;
objToDelete = nil;
// 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 = nil;
return false;
}
retMesh = mesh;
retMesh->checkNormals(true);
return true;
}
void plDistributor::ISetAngProbCosines() const
{
if( fAngProbHi == fAngProbLo )
return;
float maxAng, minAng;
if( fAngProbHi > fAngProbLo )
{
maxAng = hsScalarDegToRad(fAngProbHi);
minAng = hsScalarDegToRad(fAngProbLo);
}
else
{
maxAng = hsScalarDegToRad(fAngProbLo);
minAng = hsScalarDegToRad(fAngProbHi);
}
float transAng = hsScalarDegToRad(fAngProbTrans);
if( transAng > (maxAng - minAng) * 0.5f )
transAng = (maxAng - minAng) * 0.5f;
float transAngMax = maxAng < hsScalarPI ? transAng : 0;
float transAngMin = minAng > 0 ? transAng : 0;
fCosAngProbHi = hsCosine(minAng);
fCosAngProbLo = hsCosine(maxAng);
fCosAngProbHiTrans = hsCosine(minAng + transAngMin);
fCosAngProbLoTrans = hsCosine(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& faces) const
{
Tab distNodes;
fMeshTree.HarvestBox(box, distNodes);
int i;
for( i = 0; i < distNodes.Count(); i++ )
{
Int32 iFace = Int32(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(nil) )
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++ )
{
hsScalar t = box.Min()[i];
t /= fSpacing;
t = hsFloor(t);
t *= fSpacing;
mins[i] = t;
t = box.Max()[i];
t /= fSpacing;
t = hsCeil(t);
t *= fSpacing;
maxs[i] = t + fSpacing*0.5f;
}
box = Box3(mins, maxs);
return box;
}
hsBool 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 = nil;
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 = hsMaximum(evCol.r, hsMaximum(evCol.g, evCol.b));
break;
case kMaxColorTimesAlpha:
frac = hsMaximum(evCol.r, hsMaximum(evCol.g, evCol.b)) * evCol.a;
break;
case kMaxRedGreen:
frac = hsMaximum(evCol.r, evCol.g);
break;
case kMaxRedGreenTimesAlpha:
frac = hsMaximum(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);
}
hsBool 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;
}
hsBool 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.
hsBool 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 hsScalar 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 = hsSquareRoot(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 < hsScalarPI * 0.5f )
{
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
{
const char* 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
{
const char* 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 = nil;
TriObject* obj = nil;
if( IGetMesh(fRepNodes[i], obj, mesh) )
{
plMaxNode* repNode = (plMaxNode*)fRepNodes[i];
int iCache = cache.Count();
cache.SetCount(iCache + 1);
cache[iCache].fMesh = TRACKED_NEW Mesh(*mesh);
cache[iCache].fFlex = repNode->GetFlexibility();
if( obj )
obj->DeleteThis();
BOOL hasXImp = nil != 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&faces, Point3& projPt) const
{
BOOL retVal = false;
plTriUtils triUtil;
int i;
for( i = 0; i < faces.Count(); i++ )
{
int iFace = faces[i];
const hsPoint3& p0 = hsP3(fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(0)) * fSurfToWorld);
const hsPoint3& p1 = hsP3(fSurfMesh->getVert(fSurfMesh->faces[iFace].getVert(1)) * fSurfToWorld);
const 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 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 faces;
IFindFaceSet(bnd, faces);
// l2w, iRepNode, cache, &iCache, maxScaledDist, dir
iCache = cache.Count();
cache.SetCount(iCache + 1);
cache[iCache] = cache[iRepNode];
cache[iCache].fMesh = TRACKED_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 faces;
IFindFaceSet(bnd, faces);
// l2w, iRepNode, cache, &iCache, maxScaledDist, dir
iCache = cache.Count();
cache.SetCount(iCache + 1);
cache[iCache] = cache[iRepNode];
cache[iCache].fMesh = TRACKED_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 faces;
IFindFaceSet(bnd, faces);
// l2w, iRepNode, cache, &iCache, maxScaledDist, dir
iCache = cache.Count();
cache.SetCount(iCache + 1);
cache[iCache] = cache[iRepNode];
cache[iCache].fMesh = TRACKED_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);
hsScalar delta = fSpacing;
hsScalar 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 plDistributor::GetRandSeed() const
{
return fRand.GetSeed();
}
void plDistributor::SetRandSeed(int seed)
{
fRand.SetSeed(seed);
}
void plDistributor::SetPolarRange(float deg)
{
fPolarRange = hsScalarDegToRad(deg);
fTanPolarRange = tan(fPolarRange);
}
void plDistributor::SetProbabilityBitmapTex(BitmapTex* t)
{
fProbLayerTex = nil;
fProbBitmapTex = t;
}
void plDistributor::SetProbabilityLayerTex(plLayerTex* t)
{
fProbBitmapTex = nil;
fProbLayerTex = t;
}