You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

4116 lines
136 KiB

/*==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 "plgDispatch.h"
#include "hsFastMath.h"
#include "pnKeyedObject/plKey.h"
#include "plRenderLevel.h"
#include "hsSTLStream.h"
#include "hsStringTokenizer.h"
#include "hsTemplates.h"
#include "plMaxNode.h"
#include "plMaxNodeData.h"
#include "MaxComponent/plComponent.h"
#include <guplib.h>
#include <iparamm2.h>
#include <iskin.h>
#include <mnmath.h>
#include <utilapi.h>
#pragma hdrstop
#include "GlobalUtility.h"
#include "plPluginResManager.h"
#include "MaxConvert/plConvert.h"
#include "MaxConvert/hsConverterUtils.h"
#include "MaxConvert/hsControlConverter.h"
#include "MaxConvert/plMeshConverter.h"
#include "MaxConvert/hsMaterialConverter.h"
#include "MaxConvert/plLayerConverter.h"
#include "MaxConvert/UserPropMgr.h"
#include "MaxExport/plErrorMsg.h"
#include "MaxConvert/hsVertexShader.h"
#include "MaxConvert/plLightMapGen.h"
#include "plMaxMeshExtractor.h"
#include "MaxPlasmaMtls/Layers/plLayerTex.h"
#include "pnSceneObject/plSceneObject.h"
#include "plScene/plSceneNode.h"
#include "plPhysX/plPXPhysical.h"
#include "plDrawable/plInstanceDrawInterface.h"
#include "plDrawable/plSharedMesh.h"
#include "pnSceneObject/plSimulationInterface.h"
#include "pnSceneObject/plAudioInterface.h"
#include "pnSceneObject/plCoordinateInterface.h"
#include "pfAnimation/plFilterCoordInterface.h"
#include "plParticleSystem/plBoundInterface.h"
#include "plPhysical/plPickingDetector.h"
#include "plModifier/plLogicModifier.h"
#include "plModifier/plResponderModifier.h"
#include "plModifier/plInterfaceInfoModifier.h"
#include "pfAnimation/plLightModifier.h"
#include "plAvatar/plAGModifier.h"
#include "plAvatar/plAGAnim.h"
#include "plAvatar/plPointChannel.h"
#include "plAvatar/plScalarChannel.h"
#include "plAvatar/plAGMasterMod.h"
#include "plMessage/plReplaceGeometryMsg.h"
#include "plGImage/plMipmap.h"
#include "plModifier/plSpawnModifier.h"
#include "plInterp/plController.h"
#include "plInterp/hsInterp.h"
#include "pnMessage/plTimeMsg.h"
#include "pfAnimation/plViewFaceModifier.h" // mf horse temp hack testing to be thrown away
#include "plScene/plOccluder.h"
#include "plDrawable/plDrawableSpans.h"
#include "plDrawable/plGeometrySpan.h"
#include "plPipeline/plFogEnvironment.h"
#include "plGLight/plLightInfo.h"
#include "plGLight/plLightKonstants.h"
#include "plSurface/plLayerInterface.h"
#include "plSurface/plLayer.h"
#include "plSurface/hsGMaterial.h"
#include "pnMessage/plObjRefMsg.h"
#include "pnMessage/plNodeRefMsg.h"
#include "pnMessage/plIntRefMsg.h"
#include "MaxExport/plExportProgressBar.h"
#include "MaxPlasmaMtls/Materials/plDecalMtl.h"
#include "MaxComponent/plAnimComponent.h"
#include "MaxComponent/plComponentTools.h"
#include "MaxComponent/plComponentExt.h"
#include "MaxComponent/plFlexibilityComponent.h"
#include "MaxComponent/plLightMapComponent.h"
#include "MaxComponent/plXImposter.h"
#include "MaxComponent/plMiscComponents.h"
#include "plParticleSystem/plParticleSystem.h"
#include "plParticleSystem/plParticleEmitter.h"
#include "plParticleSystem/plParticleEffect.h"
#include "plParticleSystem/plParticleGenerator.h"
#include "plParticleSystem/plConvexVolume.h"
#include "MaxPlasmaLights/plRealTimeLightBase.h"
#include "MaxPlasmaLights/plRTProjDirLight.h"
extern UserPropMgr gUserPropMgr;
bool ThreePlaneIntersect(const hsVector3& norm0, const hsPoint3& point0,
const hsVector3& norm1, const hsPoint3& point1,
const hsVector3& norm2, const hsPoint3& point2, hsPoint3& loc);
// Begin external component toolbox ///////////////////////////////////////////////////////////////
static plKey ExternAddModifier(plMaxNodeBase *node, plModifier *mod)
{
return nil;//((plMaxNode*)node)->AddModifier(mod);
}
static plKey ExternGetNewKey(const plString &name, plModifier *mod, plLocation loc)
{
return nil;//hsgResMgr::ResMgr()->NewKey(name, mod, loc);
}
// In plResponderComponent (for no apparent reason).
int GetMatAnimModKey(Mtl* mtl, plMaxNodeBase* node, const plString &segName, hsTArray<plKey>& keys);
// In plAudioComponents
int GetSoundNameAndIdx(plComponentBase *comp, plMaxNodeBase *node, const char*& name);
static plString GetAnimCompAnimName(plComponentBase *comp)
{
if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
return ((plAnimComponentBase*)comp)->GetAnimName();
return plString::Null;
}
static plKey GetAnimCompModKey(plComponentBase *comp, plMaxNodeBase *node)
{
if (comp->ClassID() == ANIM_COMP_CID || comp->ClassID() == ANIM_GROUP_COMP_CID)
return ((plAnimComponentBase*)comp)->GetModKey((plMaxNode*)node);
return nil;
}
plComponentTools gComponentTools(ExternAddModifier,
ExternGetNewKey,
nil,
GetAnimCompModKey,
GetAnimCompAnimName,
GetMatAnimModKey,
GetSoundNameAndIdx);
// End external component toolbox //////////////////////////////////////////////////////////////////
void plMaxBoneMap::AddBone(plMaxNodeBase *bone)
{
char *dbgNodeName = bone->GetName();
if (fBones.find(bone) == fBones.end())
fBones[bone] = fNumBones++;
}
void plMaxBoneMap::FillBoneArray(plMaxNodeBase **boneArray)
{
BoneMap::const_iterator boneIt = fBones.begin();
for (; boneIt != fBones.end(); boneIt++)
boneArray[(*boneIt).second] = (*boneIt).first;
}
uint8_t plMaxBoneMap::GetIndex(plMaxNodeBase *bone)
{
hsAssert(fBones.find(bone) != fBones.end(), "Bone missing in remap!");
return fBones[bone];
}
uint32_t plMaxBoneMap::GetBaseMatrixIndex(plDrawable *draw)
{
if (fBaseMatrices.find(draw) == fBaseMatrices.end())
return (uint32_t)-1;
return fBaseMatrices[draw];
}
void plMaxBoneMap::SetBaseMatrixIndex(plDrawable *draw, uint32_t idx)
{
fBaseMatrices[draw] = idx;
}
// Don't call this after you've started assigning indices to spans, or
// you'll be hosed (duh).
void plMaxBoneMap::SortBones()
{
plMaxNodeBase **tempBones = new plMaxNodeBase*[fNumBones];
FillBoneArray(tempBones);
// Look ma! An n^2 bubble sort!
// (It's a 1-time thing for an array of less than 100 items. Speed is not essential here)
int i,j;
for (i = 0; i < fNumBones; i++)
{
bool swap = false;
for (j = i + 1; j < fNumBones; j++)
{
if (strcmp(tempBones[i]->GetName(), tempBones[j]->GetName()) > 0)
{
plMaxNodeBase *temp = tempBones[i];
tempBones[i] = tempBones[j];
tempBones[j] = temp;
swap = true;
}
}
if (!swap)
break;
}
for (i = 0; i < fNumBones; i++)
fBones[tempBones[i]] = i;
delete [] tempBones;
}
///////////////////////////////////////////////////////////////////////////////////////////////////
plKey plMaxNode::AddModifier(plModifier *pMod, const plString& name)
{
plKey modKey = pMod->GetKey();
if (!modKey)
modKey = hsgResMgr::ResMgr()->NewKey(name, pMod, GetLocation());
hsgResMgr::ResMgr()->AddViaNotify(modKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
return modKey;
}
bool plMaxNode::DoRecur(PMaxNodeFunc pDoFunction,plErrorMsg *pErrMsg, plConvertSettings *settings,plExportProgressBar*bar)
{
#ifdef HS_DEBUGGING
const char *tmpName = GetName();
#endif
// If there is a progess bar, update it with the current node
// If the user cancels during the update, set bogus so we'll exit
if (bar && bar->Update(GetName()))
{
pErrMsg->Set(true);
return false;
}
// If we can't convert (and we aren't the root node) stop recursing.
if (!IsRootNode() && !CanConvert())
return false;
(this->*pDoFunction)(pErrMsg, settings);
for (int i = 0; (!pErrMsg || !pErrMsg->IsBogus()) && i < NumberOfChildren(); i++)
{
plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
pChild->DoRecur(pDoFunction, pErrMsg, settings, bar);
}
return true;
}
// This is the same as DoRecur except that it ignores the canconvert field. We
// need this for things like clearing the old data, where we need to ignore the old value.
bool plMaxNode::DoAllRecur(PMaxNodeFunc pDoFunction,plErrorMsg *pErrMsg, plConvertSettings *settings,plExportProgressBar*bar)
{
#ifdef HS_DEBUGGING
const char *tmpName = GetName();
#endif
// If there is a progess bar, update it with the current node
// If the user cancels during the update, set bogus so we'll exit
if (bar && bar->Update(GetName()))
{
pErrMsg->Set(true);
return false;
}
(this->*pDoFunction)(pErrMsg, settings);
for (int i = 0; (!pErrMsg || !pErrMsg->IsBogus()) && i < NumberOfChildren(); i++)
{
plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
pChild->DoAllRecur(pDoFunction, pErrMsg, settings, bar);
}
return true;
}
bool plMaxNode::ConvertValidate(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
TimeValue t = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState( t ).obj;
const char* dbgName = GetName();
// Always want to recalculate if this object can convert at this point.
// In general there won't be any cached flag anyway, but in the SceneViewer
// there can be if we're reconverting.
bool canConvert = CanConvert(true);
plMaxNodeData thisNodeData; // Extra data stored for each node
if (IsTarget())
{
plMaxNode* targetNode = ((plMaxNode*)GetLookatNode());
if (targetNode)
{
Object* targObj = targetNode->EvalWorldState( 0 ).obj;
if (targObj && targObj->SuperClassID() == CAMERA_CLASS_ID)
canConvert = true;
else
canConvert = false;
}
else
canConvert = false;
}
if (canConvert && obj->SuperClassID() == LIGHT_CLASS_ID)
{
thisNodeData.SetDrawable(false);
thisNodeData.SetRunTimeLight(true);
thisNodeData.SetForceLocal(true);
}
if (UserPropExists("Occluder"))
{
// thisNodeData.SetDrawable(false);
}
if( UserPropExists("PSRunTimeLight") )
thisNodeData.SetRunTimeLight(true);
if (GetParticleRelated())
thisNodeData.SetForceLocal(true);
// if (UserPropExists("cloth"))
// {
// thisNodeData.SetForceLocal(true);
// }
// If we want a physicals only world, set everything to not drawable by default.
if (settings->fPhysicalsOnly)
thisNodeData.SetDrawable(false);
// Remember info in MaxNodeData block for later
thisNodeData.SetCanConvert(canConvert);
SetMaxNodeData(&thisNodeData);
#define MF_DISABLE_INSTANCING
#ifndef MF_DISABLE_INSTANCING
// Send this node off to the instance list, to see if we're instanced
if( CanMakeMesh( obj, pErrMsg, settings ) )
{
hsTArray<plMaxNode *> nodes;
uint32_t numInstances = IBuildInstanceList( GetObjectRef(), t, nodes );
if( numInstances > 1 )
{
/// INSTANCED. Make sure to force local on us
SetForceLocal( true );
SetInstanced( true );
}
}
#endif // MF_DISABLE_INSTANCING
// If this is for the SceneViewer, turn off the dirty flags so we won't try
// reconverting this node again.
if (settings->fSceneViewer)
SetDirty(kAllDirty, false);
return canConvert;
}
bool plMaxNode::ClearMaxNodeData(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
// The right place to delete the boneMap is really in ~plMaxNodeData, but that class
// is only allowed to know about stuff in nucleusLib.
if (GetBoneMap() && GetBoneMap()->fOwner == this)
delete GetBoneMap();
if (GetSwappableGeom())
{
// Ref and unref it, so it goes away if no one kept it around (i.e. we just
// looked at the mesh during export for reference, but don't want to keep it.)
GetSwappableGeom()->GetKey()->RefObject();
GetSwappableGeom()->GetKey()->UnRefObject();
}
SetMaxNodeData( nil );
return true;
}
#include "plGetLocationDlg.h"
//
// Helper for setting synchedObject options, until we have a GUI
//
#include "plResMgr/plKeyFinder.h"
#include "plMaxCFGFile.h"
#include "plAgeDescription/plAgeDescription.h"
#include "plResMgr/plPageInfo.h"
#include "pnNetCommon/plSDLTypes.h"
void plMaxNode::CheckSynchOptions(plSynchedObject* so)
{
if (so)
{
//////////////////////////////////////////////////////////////////////////
// TEMP - remove
//
TSTR sdata,sdataList[128];
int i,num;
//
// check for LocalOnly or DontPersist props
//
if (gUserPropMgr.UserPropExists(this, "LocalOnly"))
so->SetLocalOnly(true); // disable net synching and persistence
else
if (gUserPropMgr.UserPropExists(this, "DontPersistAny")) // disable all types of persistence
so->SetSynchFlagsBit(plSynchedObject::kExcludeAllPersistentState);
else
{
if (gUserPropMgr.GetUserPropStringList(this, "DontPersist", num, sdataList))
{
for(i=0;i<num;i++)
so->AddToSDLExcludeList((const char *)sdataList[i]); // disable a type of persistence
}
}
//
// Check for Volatile prop
//
if (gUserPropMgr.UserPropExists(this, "VolatileAll")) // make all sdl types on this object Volatile
so->SetSynchFlagsBit(plSynchedObject::kAllStateIsVolatile);
else
{
if (gUserPropMgr.GetUserPropStringList(this, "Volatile", num, sdataList))
{
for(i=0;i<num;i++)
so->AddToSDLVolatileList((const char *)sdataList[i]); // make volatile a type of persistence
}
}
bool tempOldOverride = (gUserPropMgr.UserPropExists(this, "OverrideHighLevelSDL") != 0);
//
// TEMP - remove
//////////////////////////////////////////////////////////////////////////
// If this object isn't in a global room, turn off sync flags
if ((!tempOldOverride && !GetOverrideHighLevelSDL()) && !GetLocation().IsReserved())
{
bool isDynSim = GetPhysicalProps()->GetGroup() == plSimDefs::kGroupDynamic;
bool hasPFC = false;
int count = NumAttachedComponents();
for (uint32_t x = 0; x < count; x++)
{
plComponentBase *comp = GetAttachedComponent(x);
if (comp->ClassID() == Class_ID(0x670d3629, 0x559e4f11))
{
hasPFC = true;
break;
}
}
if (!isDynSim && !hasPFC)
{
so->SetSynchFlagsBit(plSynchedObject::kExcludeAllPersistentState);
}
else
{
so->AddToSDLExcludeList(kSDLAGMaster);
so->AddToSDLExcludeList(kSDLResponder);
so->AddToSDLExcludeList(kSDLLayer);
so->AddToSDLExcludeList(kSDLSound);
so->AddToSDLExcludeList(kSDLXRegion);
}
}
}
}
bool plMaxNode::MakeSceneObject(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
const char* dbgName = GetName();
if (!CanConvert())
return false;
plLocation nodeLoc = GetLocation();//GetLocFromStrings(); // After this we can use GetLocation()
if (!nodeLoc.IsValid())
{
// If we are reconverting, we don't want to bother the user about a room.
// In most cases, if it doesn't have a room we are in the middle of creating
// it. We don't want to pop up a dialog at that point.
if (settings->fReconvert)
{
SetCanConvert(false);
return false;
}
if (!plGetLocationDlg::Instance().GetLocation(this, pErrMsg))
return false;
nodeLoc = GetLocation();
}
plSceneObject* pso;
plKey objKey;
// Handle this as a SceneObject
pso = new plSceneObject;
objKey = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetName()), pso, nodeLoc, GetLoadMask());
// Remember info in MaxNodeData block for later
plMaxNodeData *pDat = GetMaxNodeData();
pDat->SetKey(objKey);
pDat->SetSceneObject(pso);
CheckSynchOptions(pso);
return true;
}
bool plMaxNode::PrepareSkin(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if( !IFindBones(pErrMsg, settings) )
return false;
if( !NumBones() )
return true;
int i;
for( i = 0; i < NumBones(); i++ )
{
GetBone(i)->SetForceLocal(true);
GetBone(i)->SetDrawable(false);
}
return true;
}
bool plMaxNode::IFindBones(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if( !CanConvert() )
return false;
if (UserPropExists("Bone"))
{
AddBone(this);
SetForceLocal(true);
}
ISkin* skin = FindSkinModifier();
if( skin && skin->GetNumBones() )
{
char *dbgNodeName = GetName();
// BoneUpdate
//SetForceLocal(true);
int i;
for( i = 0; i < skin->GetNumBones(); i++ )
{
plMaxNode* bone = (plMaxNode*)skin->GetBone(i);
if( bone )
{
if( !bone->CanConvert() || !bone->GetMaxNodeData() )
{
if( pErrMsg->Set(true, GetName(), "Trouble connecting to bone %s - skipping", bone->GetName()).CheckAndAsk() )
SetDrawable(false);
}
else
{
AddBone(bone);
bone->SetForceLocal(true);
}
}
else
{
if( pErrMsg->Set(true, GetName(), "Trouble finding bone - skipping").CheckAndAsk() )
SetDrawable(false);
}
}
}
return true;
}
#include "plMaxMeshExtractor.h"
#include "plPhysXCooking.h"
#include "plPhysX/plPXStream.h"
#include "plPhysX/plSimulationMgr.h"
bool plMaxNode::MakePhysical(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
const char* dbgNodeName = GetName();
if( !CanConvert() )
return false;
if( !GetPhysical() )
return true;
plPhysicalProps *physProps = GetPhysicalProps();
if (!physProps->IsUsed())
return true;
//hsStatusMessageF("Making phys for %s", dbgNodeName);
plSimDefs::Group group = (plSimDefs::Group)physProps->GetGroup();
plSimDefs::Bounds bounds = (plSimDefs::Bounds)physProps->GetBoundsType();
float mass = physProps->GetMass();
plMaxNode *proxyNode = physProps->GetProxyNode();
if (!proxyNode)
proxyNode = this;
// We want to draw solid physicals only. If it is something avatars bounce off,
// set it to drawable.
if (settings->fPhysicalsOnly)
{
if (group == plSimDefs::kGroupStatic ||
group == plSimDefs::kGroupAvatarBlocker ||
group == plSimDefs::kGroupDynamic)
proxyNode->SetDrawable(true);
}
// If mass is zero and we're animated, set the mass to 1 so it will get a rigid
// body. Otherwise PhysX will make assumptions about the physical which will
// fail when it gets moved.
if (physProps->GetPhysAnim() && mass == 0.f)
mass = 1.f;
TSTR sdata;
plMaxNode* baseNode = this;
while (!baseNode->GetParentNode()->IsRootNode())
baseNode = (plMaxNode*)baseNode->GetParentNode();
plKey roomKey = baseNode->GetRoomKey();
if (!roomKey)
{
pErrMsg->Set(true, "Room Processing Error - Physics" "The Room that physics component %s is attached to should have already been\nprocessed.", GetName());
return false;
}
plMaxNode* subworld = physProps->GetSubworld();
PhysRecipe recipe;
recipe.mass = mass;
recipe.friction = physProps->GetFriction();
recipe.restitution = physProps->GetRestitution();
recipe.bounds = (plSimDefs::Bounds)physProps->GetBoundsType();
recipe.group = group;
recipe.reportsOn = physProps->GetReportGroup();
recipe.objectKey = GetKey();
recipe.sceneNode = roomKey;
recipe.worldKey = subworld ? subworld->GetKey() : nil;
plMaxMeshExtractor::NeutralMesh mesh;
plMaxMeshExtractor::Extract(mesh, proxyNode, bounds == plSimDefs::kBoxBounds, this);
if (subworld)
recipe.l2s = subworld->GetWorldToLocal44() * mesh.fL2W;
else
recipe.l2s = mesh.fL2W;
switch (bounds)
{
case plSimDefs::kBoxBounds:
{
hsPoint3 minV(FLT_MAX, FLT_MAX, FLT_MAX), maxV(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (int i = 0; i < mesh.fNumVerts; i++)
{
minV.fX = std::min(mesh.fVerts[i].fX, minV.fX);
minV.fY = std::min(mesh.fVerts[i].fY, minV.fY);
minV.fZ = std::min(mesh.fVerts[i].fZ, minV.fZ);
maxV.fX = std::max(mesh.fVerts[i].fX, maxV.fX);
maxV.fY = std::max(mesh.fVerts[i].fY, maxV.fY);
maxV.fZ = std::max(mesh.fVerts[i].fZ, maxV.fZ);
}
hsPoint3 width = maxV - minV;
recipe.bDimensions = width / 2;
recipe.bOffset = minV + (width / 2.f);
}
break;
case plSimDefs::kProxyBounds:
case plSimDefs::kExplicitBounds:
{
// if this is a detector then try to convert to a convex hull first... if that doesn't succeed then do it as an exact
if ( group == plSimDefs::kGroupDetector )
{
// try converting to a convex hull mesh
recipe.meshStream = plPhysXCooking::CookHull(mesh.fNumVerts, mesh.fVerts,false);
if (recipe.meshStream)
{
plPXStream pxs(recipe.meshStream);
recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
recipe.bounds = plSimDefs::kHullBounds;
// then test to see if the original mesh was convex (unless they said to skip 'em)
#ifdef WARNINGS_ON_CONCAVE_PHYSX_WORKAROUND
if ( !plPhysXCooking::fSkipErrors )
{
if ( !plPhysXCooking::TestIfConvex(recipe.convexMesh, mesh.fNumVerts, mesh.fVerts) )
{
int retStatus = pErrMsg->Set(true, "Physics Warning: PhysX workaround", "Detector region that is marked as exact and is concave but switching to convex hull for PhysX: %s", GetName()).CheckAskOrCancel();
pErrMsg->Set();
if ( retStatus == 1 ) // cancel?
plPhysXCooking::fSkipErrors = true;
}
}
#endif // WARNINGS_ON_CONCAVE_PHYSX_WORKAROUND
}
if (!recipe.meshStream)
{
if ( !pErrMsg->Set(true, "Physics Warning", "Detector region exact failed to be made a Hull, trying trimesh: %s", GetName()).Show() )
pErrMsg->Set();
recipe.meshStream = plPhysXCooking::CookTrimesh(mesh.fNumVerts, mesh.fVerts, mesh.fNumFaces, mesh.fFaces);
if (!recipe.meshStream)
{
pErrMsg->Set(true, "Physics Error", "Trimesh creation failed for physical %s", GetName()).Show();
return false;
}
plPXStream pxs(recipe.meshStream);
recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
}
}
else
{
recipe.meshStream = plPhysXCooking::CookTrimesh(mesh.fNumVerts, mesh.fVerts, mesh.fNumFaces, mesh.fFaces);
if (!recipe.meshStream)
{
pErrMsg->Set(true, "Physics Error", "Trimesh creation failed for physical %s", GetName()).Show();
return false;
}
plPXStream pxs(recipe.meshStream);
recipe.triMesh = plSimulationMgr::GetInstance()->GetSDK()->createTriangleMesh(pxs);
}
}
break;
case plSimDefs::kSphereBounds:
{
hsPoint3 minV(FLT_MAX, FLT_MAX, FLT_MAX), maxV(-FLT_MAX, -FLT_MAX, -FLT_MAX);
for (int i = 0; i < mesh.fNumVerts; i++)
{
minV.fX = std::min(mesh.fVerts[i].fX, minV.fX);
minV.fY = std::min(mesh.fVerts[i].fY, minV.fY);
minV.fZ = std::min(mesh.fVerts[i].fZ, minV.fZ);
maxV.fX = std::max(mesh.fVerts[i].fX, maxV.fX);
maxV.fY = std::max(mesh.fVerts[i].fY, maxV.fY);
maxV.fZ = std::max(mesh.fVerts[i].fZ, maxV.fZ);
}
hsPoint3 width = maxV - minV;
recipe.radius = std::max({ width.fX, width.fY, width.fZ });
recipe.radius /= 2.f;
recipe.offset = minV + (width / 2.f);
}
break;
case plSimDefs::kHullBounds:
{
if ( group == plSimDefs::kGroupDynamic )
{
recipe.meshStream = plPhysXCooking::IMakePolytope(mesh);
if (!recipe.meshStream)
{
pErrMsg->Set(true, "Physics Error", "polyTope-convexhull failed for physical %s", GetName()).Show();
return false;
}
}
else
{
recipe.meshStream = plPhysXCooking::CookHull(mesh.fNumVerts, mesh.fVerts,false);
if(!recipe.meshStream)
{
pErrMsg->Set(true, "Physics Error", "Convex hull creation failed for physical %s", GetName()).Show();
return false;
}
}
plPXStream pxs(recipe.meshStream);
recipe.convexMesh = plSimulationMgr::GetInstance()->GetSDK()->createConvexMesh(pxs);
}
break;
}
delete [] mesh.fFaces;
delete [] mesh.fVerts;
//
// Create the physical
//
plPXPhysical* physical = new plPXPhysical;
// add the object to the resource manager, keyed to the new name
plLocation nodeLoc = GetKey()->GetUoid().GetLocation();
plString objName = GetKey()->GetName();
plKey physKey = hsgResMgr::ResMgr()->NewKey(objName, physical, nodeLoc, GetLoadMask());
if (!physical->Init(recipe))
{
pErrMsg->Set(true, "Physics Error", "Physical creation failed for object %s", GetName()).Show();
physKey->RefObject();
physKey->UnRefObject();
return false;
}
physical->SetProperty(plSimulationInterface::kPinned, physProps->GetPinned());
physical->SetProperty(plSimulationInterface::kPhysAnim, physProps->GetPhysAnim());
physical->SetProperty(plSimulationInterface::kNoSynchronize, (physProps->GetNoSynchronize() != 0));
physical->SetProperty(plSimulationInterface::kStartInactive, (physProps->GetStartInactive() != 0));
physical->SetProperty(plSimulationInterface::kAvAnimPushable, (physProps->GetAvAnimPushable() != 0));
if(physProps->GetLOSBlockCamera())
physical->AddLOSDB(plSimDefs::kLOSDBCameraBlockers);
if(physProps->GetLOSBlockUI())
physical->AddLOSDB(plSimDefs::kLOSDBUIBlockers);
if(physProps->GetLOSBlockCustom())
physical->AddLOSDB(plSimDefs::kLOSDBCustom);
if(physProps->GetLOSUIItem())
physical->AddLOSDB(plSimDefs::kLOSDBUIItems);
if(physProps->GetLOSShootable())
physical->AddLOSDB(plSimDefs::kLOSDBShootableItems);
if(physProps->GetLOSAvatarWalkable())
physical->AddLOSDB(plSimDefs::kLOSDBAvatarWalkable);
if(physProps->GetLOSSwimRegion())
physical->AddLOSDB(plSimDefs::kLOSDBSwimRegion);
plSimulationInterface* si = new plSimulationInterface;
plKey pSiKey = hsgResMgr::ResMgr()->NewKey(objName, si, nodeLoc, GetLoadMask());
// link the simulation interface to the scene object
hsgResMgr::ResMgr()->AddViaNotify(pSiKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
// add the physical to the simulation interface
hsgResMgr::ResMgr()->AddViaNotify(physKey , new plIntRefMsg(pSiKey, plRefMsg::kOnCreate, 0, plIntRefMsg::kPhysical), plRefFlags::kActiveRef);
return true;
}
bool plMaxNode::MakeController(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if (!CanConvert())
return false;
bool forceLocal = hsControlConverter::Instance().ForceLocal(this);
// Rember the force Local setting
bool CurrForceLocal = GetForceLocal(); // dont want to clobber it with false if componentPass made it true
forceLocal = (CurrForceLocal || forceLocal) ? true : false; // if it was set before, or is true now, make it true...
SetForceLocal(forceLocal);
if( IsTMAnimated() && (!GetParentNode()->IsRootNode()) )
{
((plMaxNode*)GetParentNode())->SetForceLocal(true);
}
return true;
}
bool plMaxNode::MakeCoordinateInterface(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
const char* dbgNodeName = GetName();
if (!CanConvert())
return false;
plCoordinateInterface* ci = nil;
bool forceLocal = GetForceLocal();
bool needCI = (!GetParentNode()->IsRootNode())
|| NumberOfChildren()
|| forceLocal;
// If we have a transform, set up a coordinateinterface
if( needCI )
{
hsMatrix44 loc2Par = GetLocalToParent44();
hsMatrix44 par2Loc = GetParentToLocal44();
if( GetFilterInherit() )
ci = new plFilterCoordInterface;
else
ci = new plCoordinateInterface;
//-------------------------
// Get data from Node, then its key, then its name
//-------------------------
plKey pNodeKey = GetKey();
hsAssert(pNodeKey, "Missing key for this Object");
plString pName = pNodeKey->GetName();
plLocation nodeLoc = GetLocation();
plKey pCiKey = hsgResMgr::ResMgr()->NewKey(pName, ci,nodeLoc, GetLoadMask());
ci->SetLocalToParent(loc2Par, par2Loc);
bool usesPhysics = GetPhysicalProps()->IsUsed();
ci->SetProperty(plCoordinateInterface::kCanEverDelayTransform, !usesPhysics);
ci->SetProperty(plCoordinateInterface::kDelayedTransformEval, !usesPhysics);
hsgResMgr::ResMgr()->AddViaNotify(pCiKey, new plObjRefMsg(pNodeKey, plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
}
return true;
}
bool plMaxNode::MakeModifiers(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if (!CanConvert())
return false;
bool forceLocal = GetForceLocal();
const char *dbgNodeName = GetName();
bool addMods = (!GetParentNode()->IsRootNode())
|| forceLocal;
if (addMods)
{
// create / add modifiers
// mf horse hack testing ViewFace which is already obsolete
if ( UserPropExists("ViewFacing") )
{
plViewFaceModifier* pMod = new plViewFaceModifier;
if( UserPropExists("VFPivotFavorY") )
pMod->SetFlag(plViewFaceModifier::kPivotFavorY);
else if( UserPropExists("VFPivotY") )
pMod->SetFlag(plViewFaceModifier::kPivotY);
else if( UserPropExists("VFPivotTumble") )
pMod->SetFlag(plViewFaceModifier::kPivotTumble);
else
pMod->SetFlag(plViewFaceModifier::kPivotFace);
if( UserPropExists("VFScale") )
{
pMod->SetFlag(plViewFaceModifier::kScale);
TSTR sdata;
GetUserPropString("VFScale",sdata);
hsStringTokenizer toker;
toker.Reset(sdata, hsConverterUtils::fTagSeps);
int nGot = 0;
char* token;
hsVector3 scale;
scale.Set(1.f,1.f,1.f);
while( (nGot < 3) && (token = toker.next()) )
{
switch( nGot )
{
case 0:
scale.fZ = float(atof(token));
break;
case 1:
scale.fX = scale.fZ;
scale.fY = float(atof(token));
scale.fZ = 1.f;
break;
case 2:
scale.fZ = float(atof(token));
break;
}
nGot++;
}
pMod->SetScale(scale);
}
AddModifier(pMod, plString::FromUtf8(GetName()));
}
}
return true;
}
bool plMaxNode::MakeParentOrRoomConnection(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if (!CanConvert())
return false;
char *dbgNodeName = GetName();
plSceneObject *pso = GetSceneObject();
if( !GetParentNode()->IsRootNode() )
{
plKey parKey = GetParentKey();
plCoordinateInterface* ci = const_cast <plCoordinateInterface*> (pso->GetCoordinateInterface());
hsAssert(ci,"Missing CI");
plIntRefMsg* msg = new plIntRefMsg(parKey, plRefMsg::kOnCreate, -1, plIntRefMsg::kChildObject);
msg->SetRef(pso);
hsgResMgr::ResMgr()->AddViaNotify(msg, plRefFlags::kPassiveRef);
}
hsgResMgr::ResMgr()->AddViaNotify(pso->GetKey(), new plNodeRefMsg(GetRoomKey(), plRefMsg::kOnCreate, -1, plNodeRefMsg::kObject), plRefFlags::kActiveRef);
return true;
}
void plMaxNode::IWipeBranchDrawable(bool b)
{
SetDrawable(b);
for (int i = 0; i < NumberOfChildren(); i++)
{
plMaxNode *pChild = (plMaxNode *)GetChildNode(i);
pChild->IWipeBranchDrawable(b);
}
}
//// CanMakeMesh /////////////////////////////////////////////////////////////
// Returns true if MakeMesh() on this node will result in spans being stored
// on a drawable. Takes in the object pointer to avoid having to do redundant
// work to get it.
// 9.25.2001 mcn - Made public so components can figure out if this node is
// meshable.
bool plMaxNode::CanMakeMesh( Object *obj, plErrorMsg *pErrMsg, plConvertSettings *settings )
{
if( obj == nil )
return false;
if( UserPropExists( "Plasma2_Camera" ) )
return false;
if( !GetSwappableGeom() && !GetDrawable() )
return false;
if( GetParticleRelated() )
return false;
if( obj->CanConvertToType( triObjectClassID ) )
return true;
return false;
}
void ITestAdjacencyRecur(const hsTArray<int>* vertList, int iVert, hsBitVector& adjVerts)
{
adjVerts.SetBit(iVert);
int i;
for( i = 0; i < vertList[iVert].GetCount(); i++ )
{
if( !adjVerts.IsBitSet(vertList[iVert][i]) )
{
ITestAdjacencyRecur(vertList, vertList[iVert][i], adjVerts);
}
}
}
bool ITestAdjacency(const hsTArray<int>* vertList, int numVerts)
{
hsBitVector adjVerts;
ITestAdjacencyRecur(vertList, 0, adjVerts);
int i;
for( i = 0; i < numVerts; i++ )
{
if( !adjVerts.IsBitSet(i) )
return false;
}
return true;
}
int IsGeoSpanConvexExhaust(const plGeometrySpan* span)
{
// Brute force, check every point against every face
uint16_t* idx = span->fIndexData;
int numFaces = span->fNumIndices / 3;
uint32_t stride = span->GetVertexSize(span->fFormat);
uint8_t* vertData = span->fVertexData;
int numVerts = span->fNumVerts;
bool someIn = false;
bool someOut = false;
int i;
for( i = 0; i < numFaces; i++ )
{
// compute norm and dist for face
hsPoint3* pos[3];
pos[0] = (hsPoint3*)(vertData + idx[0] * stride);
pos[1] = (hsPoint3*)(vertData + idx[1] * stride);
pos[2] = (hsPoint3*)(vertData + idx[2] * stride);
hsVector3 edge01(pos[1], pos[0]);
hsVector3 edge02(pos[2], pos[0]);
hsVector3 faceNorm = edge01 % edge02;
hsFastMath::NormalizeAppr(faceNorm);
float faceDist = faceNorm.InnerProduct(pos[0]);
int j;
for( j = 0; j < numVerts; j++ )
{
hsPoint3* p = (hsPoint3*)(vertData + idx[0] * stride);
float dist = p->InnerProduct(faceNorm) - faceDist;
const float kSmall = 1.e-3f;
if( dist < -kSmall )
someIn = true;
else if( dist > kSmall )
someOut = true;
if( someIn && someOut )
return false;
}
idx += 3;
}
return true;
}
int IsGeoSpanConvex(plMaxNode* node, const plGeometrySpan* span)
{
static int skipTest = false;
if( skipTest )
return 0;
// May not be now, but could become.
if( span->fFormat & plGeometrySpan::kSkinWeightMask )
return 0;
// May not be now, but could become.
if( node->GetConcave() || node->UserPropExists("XXXWaterColor") )
return 0;
if( span->fMaterial && span->fMaterial->GetLayer(0) && (span->fMaterial->GetLayer(0)->GetMiscFlags() & hsGMatState::kMiscTwoSided) )
return 0;
int numVerts = span->fNumVerts;
if( !numVerts )
return 0;
int numFaces = span->fNumIndices / 3;
if( !numFaces )
return 0;
const int kSmallNumFaces = 20;
if( numFaces <= kSmallNumFaces )
return IsGeoSpanConvexExhaust(span);
hsTArray<int>* vertList = new hsTArray<int> [numVerts];
hsTArray<hsVector3>* normList = new hsTArray<hsVector3> [numVerts];
hsTArray<float>* distList = new hsTArray<float> [numVerts];
uint16_t* idx = span->fIndexData;
uint32_t stride = span->GetVertexSize(span->fFormat);
uint8_t* vertData = span->fVertexData;
// For each face
int iFace;
for( iFace = 0; iFace < numFaces; iFace++ )
{
// compute norm and dist for face
hsPoint3* pos[3];
pos[0] = (hsPoint3*)(vertData + idx[0] * stride);
pos[1] = (hsPoint3*)(vertData + idx[1] * stride);
pos[2] = (hsPoint3*)(vertData + idx[2] * stride);
hsVector3 edge01(pos[1], pos[0]);
hsVector3 edge02(pos[2], pos[0]);
hsVector3 faceNorm = edge01 % edge02;
hsFastMath::NormalizeAppr(faceNorm);
float faceDist = faceNorm.InnerProduct(pos[0]);
// For each vert
int iVtx;
for( iVtx = 0; iVtx < 3; iVtx++ )
{
int jVtx;
for( jVtx = 0; jVtx < 3; jVtx++ )
{
if( iVtx != jVtx )
{
// if idx[jVtx] not in list vertList[idx[iVtx]], add it
if( vertList[idx[iVtx]].kMissingIndex == vertList[idx[iVtx]].Find(idx[jVtx]) )
vertList[idx[iVtx]].Append(idx[jVtx]);
}
}
normList[idx[iVtx]].Append(faceNorm);
distList[idx[iVtx]].Append(faceDist);
}
idx += 3;
}
bool someIn = false;
bool someOut = false;
int i;
for( i = 0; i < numVerts; i++ )
{
int k;
for( k = 0; k < normList[i].GetCount(); k++ )
{
int j;
for( j = 0; j < vertList[i].GetCount(); j++ )
{
hsPoint3* pos = (hsPoint3*)(vertData + vertList[i][j] * stride);
float dist = pos->InnerProduct(normList[i][k]) - distList[i][k];
const float kSmall = 1.e-3f;
if( dist < -kSmall )
someIn = true;
else if( dist > kSmall )
someOut = true;
if( someIn && someOut )
goto cleanUp;
}
}
}
if( !ITestAdjacency(vertList, numVerts) )
someIn = someOut = true;
cleanUp:
delete [] vertList;
delete [] normList;
delete [] distList;
if( someIn && someOut )
return 0;
return someIn ? -1 : 1;
}
// Returns nil if there isn't a sceneobject and a drawinterface.
plDrawInterface* plMaxNode::GetDrawInterface()
{
plDrawInterface* di = nil;
plSceneObject* obj = GetSceneObject();
if( obj )
{
di = obj->GetVolatileDrawInterface();
}
return di;
}
bool plMaxNode::MakeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
hsTArray<plGeometrySpan *> spanArray;
plDrawInterface *newDI = nil;
bool gotMade = false;
bool haveAddedToSceneNode = false;
hsGMesh *myMesh = nil;
uint32_t i, triMeshIndex = (uint32_t)-1;
const char *dbgNodeName = GetName();
TSTR sdata;
hsStringTokenizer toker;
plLocation nodeLoc = GetLocation();
if (!GetSwappableGeom())
{
if (!CanConvert())
return false;
if( UserPropExists( "Plasma2_Camera" ) || !GetDrawable() )
{
SetMesh( nil );
return true;
}
}
if( GetSwappableGeomTarget() != (uint32_t)-1)
{
// This node has no geometry on export, but will have some added at runtime,
// so it needs a special drawInterface
plInstanceDrawInterface *newDI = new plInstanceDrawInterface;
newDI->fTargetID = GetSwappableGeomTarget();
plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() );
hsgResMgr::ResMgr()->AddViaNotify(pDiKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
plSwapSpansRefMsg *sMsg = new plSwapSpansRefMsg(pDiKey, plRefMsg::kOnCreate, -1, -1);
plDrawableSpans *drawable = IGetSceneNodeSpans(IGetDrawableSceneNode(pErrMsg), true, true);
hsgResMgr::ResMgr()->AddViaNotify(drawable->GetKey(), sMsg, plRefFlags::kActiveRef);
return true;
}
if( GetInstanced() )
{
hsTArray<plMaxNode *> nodes;
TimeValue t = hsConverterUtils::Instance().GetTime(GetInterface());
uint32_t numInstances = IBuildInstanceList( GetObjectRef(), t, nodes, true );
/// Instanced, find an iNode in the list that's been converted already
for( i = 0; i < numInstances; i++ )
{
if( nodes[ i ]->GetSceneObject() && nodes[ i ]->GetSceneObject()->GetDrawInterface() )
{
/// Found it!
if( !IMakeInstanceSpans( nodes[ i ], spanArray, pErrMsg, settings ) )
return false;
gotMade = true;
break;
}
}
/// If we didn't find anything, nothing's got converted yet, so convert the first one
/// like normal
}
// This has the side effect of calling SetMovable(true) if it should be and
// isn't already. So it needs to be before we make the mesh (and material).
// (Really, whatever makes it movable should do so then, but that has the potential
// to break other stuff, which I don't want to do 2 weeks before we ship).
bool movable = IsMovable();
if( !gotMade )
{
if( !plMeshConverter::Instance().CreateSpans( this, spanArray, !settings->fDoPreshade ) )
return false;
}
if( !spanArray.GetCount() )
return true;
for( i = 0; i < spanArray.GetCount(); i++ )
spanArray[i]->fMaxOwner = GetKey()->GetName();
uint32_t shadeFlags = 0;
if( GetNoPreShade() )
shadeFlags |= plGeometrySpan::kPropNoPreShade;
if( GetRunTimeLight() )
shadeFlags |= plGeometrySpan::kPropRunTimeLight;
if( GetNoShadow() )
shadeFlags |= plGeometrySpan::kPropNoShadow;
if( GetForceShadow() || GetAvatarSO() )
shadeFlags |= plGeometrySpan::kPropForceShadow;
if( GetReverseSort() )
shadeFlags |= plGeometrySpan::kPropReverseSort;
if( GetForceVisLOS() )
shadeFlags |= plGeometrySpan::kVisLOS;
if( shadeFlags )
{
for( i = 0; i < spanArray.GetCount(); i++ )
spanArray[ i ]->fProps |= shadeFlags;
}
bool DecalMat = false;
bool NonDecalMat = false;
for (i = 0; i < spanArray.GetCount(); i++)
{
if (spanArray[i]->fMaterial->IsDecal())
DecalMat = true;
else
NonDecalMat = true;
}
if (!(DecalMat ^ NonDecalMat))
{
for( i = 0; i < spanArray.GetCount(); i++ )
spanArray[ i ]->ClearBuffers();
if (pErrMsg->Set((plConvert::Instance().fWarned & plConvert::kWarnedDecalAndNonDecal) == 0, GetName(),
"This node has both regular and decal materials, and thus will be ignored.").CheckAskOrCancel())
{
plConvert::Instance().fWarned |= plConvert::kWarnedDecalAndNonDecal;
}
pErrMsg->Set(false);
return false;
}
bool isDecal = IsLegalDecal(false); // Don't complain about the parent
/// Get some stuff
bool forceLocal = GetForceLocal();
hsMatrix44 l2w = GetLocalToWorld44();
hsMatrix44 w2l = GetWorldToLocal44();
/// 4.17.2001 mcn - TEMP HACK to test fog by adding a key to a bogus fogEnviron object to ALL spans
/* plFogEnvironment *myFog = nil;
plKey myFogKey = hsgResMgr::ResMgr()->FindExportAlias( "HACK_FOG", plFogEnvironment::Index() );
if( myFogKey != nil )
myFog = plFogEnvironment::ConvertNoRef( myFogKey->GetObjectPtr() );
else
{
hsColorRGBA color;
color.Set( 0.5, 0.5, 1, 1 );
// Exp fog
myFog = new plFogEnvironment( plFogEnvironment::kExpFog, 700.f, 1.f, color );
myFogKey = hsgResMgr::ResMgr()->NewKey( "HACK_FOG", myFog, nodeLoc );
hsgResMgr::ResMgr()->AddExportAlias( "HACK_FOG", plFogEnvironment::Index(), myFogKey );
}
for( int j = 0; j < spanArray.GetCount(); j++ )
{
spanArray[ j ].fFogEnviron = myFog;
}
*/ /// 4.17.2001 mcn - TEMP HACK end
plDrawable* drawable = nil;
plSceneNode* tmpNode = nil;
/// Find the ice to add it to
if (GetSwappableGeom()) // We just want to make a geo span, not actually add it to a drawable(interface)
{
plMaxNode *drawableSource = (plMaxNode *)(GetParentNode()->IsRootNode() ? this : GetParentNode());
plSceneNode *tmpNode = drawableSource->IGetDrawableSceneNode(pErrMsg);
plDrawableSpans *drawable = IGetSceneNodeSpans(tmpNode, true, true);
ISetupBones(drawable, spanArray, l2w, w2l, pErrMsg, settings);
hsTArray<plGeometrySpan *> *swapSpans = &GetSwappableGeom()->fSpans;
for (i = 0; i < spanArray.GetCount(); i++)
swapSpans->Append(spanArray.Get(i));
plString tmpName = plFormat("{}_SMsh", GetName());
hsgResMgr::ResMgr()->NewKey(tmpName, GetSwappableGeom(), GetLocation(), GetLoadMask());
return true;
}
plMaxNode *nonDecalParent = this;
if( GetRoomKey() )
{
tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
if (isDecal) // If we're a decal, we just want to use our parent's drawable
{
plMaxNode *parent = (plMaxNode *)GetParentNode();
SetDecalLevel(parent->GetDecalLevel() + 1);
for( i = 0; i < spanArray.GetCount(); i++ )
spanArray[ i ]->fDecalLevel = GetDecalLevel();
}
{
/// Make a new drawInterface (will assign stuff to it later)
newDI = new plDrawInterface;
plKey pDiKey = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), newDI, nodeLoc, GetLoadMask() );
hsgResMgr::ResMgr()->AddViaNotify(pDiKey, new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
/// Attach the processed spans to the DI (through drawables)
IAssignSpansToDrawables( spanArray, newDI, pErrMsg, settings );
}
}
return true;
}
plSceneNode *plMaxNode::IGetDrawableSceneNode(plErrorMsg *pErrMsg)
{
plSceneNode *sn = nil;
sn = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
return sn;
}
//// IAssignSpansToDrawables /////////////////////////////////////////////////
// Given a span array, adds it to the node's drawables, creating them if
// necessary. Then it takes the resulting indices and drawable pointers
// and assigns them to the given drawInterface.
void plMaxNode::IAssignSpansToDrawables( hsTArray<plGeometrySpan *> &spanArray, plDrawInterface *di,
plErrorMsg *pErrMsg, plConvertSettings *settings )
{
hsTArray<plGeometrySpan *> opaqueArray, blendingArray, sortingArray;
plDrawableSpans *oSpans = nil, *bSpans = nil, *sSpans = nil;
int sCount, oCount, bCount, i;
plSceneNode *tmpNode = nil;
hsMatrix44 l2w = GetLocalToWorld44();
hsMatrix44 w2l = GetWorldToLocal44();
uint32_t oIndex = (uint32_t)-1, bIndex = (uint32_t)-1, sIndex = uint32_t(-1);
tmpNode = IGetDrawableSceneNode(pErrMsg);
/*
/// Get sceneNode. If we're itinerant and not the parent node, this won't just
/// be GetRoomKey()->GetObjectPtr()....
if( GetItinerant() && !GetParentNode()->IsRootNode() )
{
/// Step up to the top of the chain
plMaxNode *baseNode = this;
while( !baseNode->GetParentNode()->IsRootNode() )
baseNode = (plMaxNode *)baseNode->GetParentNode();
if( baseNode->GetItinerant() )
tmpNode = plSceneNode::ConvertNoRef( baseNode->GetRoomKey()->GetObjectPtr() );
else
{
tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
/// Warn, since we should only be itinerant if our parent is as well
pErrMsg->Set( true, "Warning", "Itinerant flag in child '%s' of non-itinerant tree. This should never happen. You should inform a programmer...", GetName() ).Show();
}
}
else
tmpNode = plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() );
*/
hsBitVector convexBits;
/// Separate the array into two arrays, one opaque and one blending
for( sCount = 0, oCount = 0, bCount = 0, i = 0; i < spanArray.GetCount(); i++ )
{
if( spanArray[ i ]->fProps & plGeometrySpan::kRequiresBlending )
{
bool needFaceSort = !GetNoFaceSort() && !IsGeoSpanConvex(this, spanArray[i]);
if( needFaceSort )
{
sCount++;
}
else
{
convexBits.SetBit(i);
bCount++;
}
}
else
oCount++;
}
// Done this way, since expanding an hsTArray has the nasty side effect of just copying data, which we don't
// want when we have memory pointers...
opaqueArray.SetCount( oCount );
blendingArray.SetCount( bCount );
sortingArray.SetCount( sCount );
for( sCount = 0, oCount = 0, bCount = 0, i = 0; i < spanArray.GetCount(); i++ )
{
if( spanArray[ i ]->fProps & plGeometrySpan::kRequiresBlending )
{
if( convexBits.IsBitSet(i) )
blendingArray[ bCount++ ] = spanArray[ i ];
else
sortingArray [ sCount++ ] = spanArray[ i ];
}
else
opaqueArray[ oCount++ ] = spanArray[ i ];
}
/// Get some drawable pointers
if( opaqueArray.GetCount() > 0 )
oSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, false ) );
if( blendingArray.GetCount() > 0 )
bSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, true, false ) );
if( sortingArray.GetCount() > 0 )
sSpans = plDrawableSpans::ConvertNoRef( IGetSceneNodeSpans( tmpNode, true, true ) );
if( oSpans != nil )
IAssignSpan( oSpans, opaqueArray, oIndex, l2w, w2l, pErrMsg, settings );
if( bSpans != nil )
IAssignSpan( bSpans, blendingArray, bIndex, l2w, w2l, pErrMsg, settings );
if( sSpans )
IAssignSpan( sSpans, sortingArray, sIndex, l2w, w2l, pErrMsg, settings );
/// Now assign to the interface
if( oSpans )
{
uint8_t iDraw = di->GetNumDrawables();
di->SetDrawable( iDraw, oSpans );
di->SetDrawableMeshIndex( iDraw, oIndex );
}
if( bSpans )
{
uint8_t iDraw = di->GetNumDrawables();
di->SetDrawable( iDraw, bSpans );
di->SetDrawableMeshIndex( iDraw, bIndex );
}
if( sSpans )
{
uint8_t iDraw = di->GetNumDrawables();
di->SetDrawable( iDraw, sSpans );
di->SetDrawableMeshIndex( iDraw, sIndex );
}
}
//// IAssignSpan /////////////////////////////////////////////////////////////
// Small utility function for IAssignSpansToDrawables, just does some of
// the low-down work that's identical for each drawable/spans/etc.
void plMaxNode::IAssignSpan( plDrawableSpans *drawable, hsTArray<plGeometrySpan *> &spanArray, uint32_t &index,
hsMatrix44 &l2w, hsMatrix44 &w2l,
plErrorMsg *pErrMsg, plConvertSettings *settings )
{
if( NumBones() )
ISetupBones( drawable, spanArray, l2w, w2l, pErrMsg, settings );
// Assign spans to the drawables, plus set the volatile flag on the
// drawables for the SceneViewer, just in case it hasn't been set yet
if( settings->fSceneViewer )
{
drawable->SetNativeProperty( plDrawable::kPropVolatile, true );
index = drawable->AppendDISpans( spanArray, index, false );
}
else
index = drawable->AddDISpans( spanArray, index );
if( GetItinerant() )
drawable->SetNativeProperty(plDrawable::kPropCharacter, true);
}
// Tiny helper for the function below
void SetSpansBoneInfo(hsTArray<plGeometrySpan *> &spanArray, uint32_t baseMatrix, uint32_t numMatrices)
{
int i;
for( i = 0; i < spanArray.GetCount(); i++ )
{
spanArray[ i ]->fBaseMatrix = baseMatrix;
spanArray[ i ]->fNumMatrices = numMatrices;
}
}
//// ISetupBones /////////////////////////////////////////////////////////////
// Adds the given bones to the given drawable, then sets up the given spans
// with the right indices and sets the initial bone positions.
void plMaxNode::ISetupBones(plDrawableSpans *drawable, hsTArray<plGeometrySpan *> &spanArray,
hsMatrix44 &l2w, hsMatrix44 &w2l,
plErrorMsg *pErrMsg, plConvertSettings *settings)
{
const char* dbgNodeName = GetName();
if( !NumBones() )
return;
plMaxBoneMap *boneMap = GetBoneMap();
if (boneMap && boneMap->GetBaseMatrixIndex(drawable) != (uint32_t)-1)
{
SetSpansBoneInfo(spanArray, boneMap->GetBaseMatrixIndex(drawable), boneMap->fNumBones);
return;
}
int baseMatrix, i;
uint8_t numBones = (boneMap ? boneMap->fNumBones : NumBones()) + 1;
plMaxNodeBase **boneArray = new plMaxNodeBase*[numBones];
if (boneMap)
boneMap->FillBoneArray(boneArray);
else
{
for (i = 0; i < NumBones(); i++)
{
boneArray[i] = GetBone(i);
}
}
hsTArray<hsMatrix44> initialB2W;
hsTArray<hsMatrix44> initialW2B;
initialB2W.SetCount(numBones);
initialW2B.SetCount(numBones);
hsTArray<hsMatrix44> initialL2B;
hsTArray<hsMatrix44> initialB2L;
initialL2B.SetCount(numBones);
initialB2L.SetCount(numBones);
initialB2W[0].Reset();
initialW2B[0].Reset();
initialL2B[0].Reset();
initialB2L[0].Reset();
for( i = 1; i < numBones; i++ )
{
hsMatrix44 b2w;
hsMatrix44 w2b;
hsMatrix44 l2b;
hsMatrix44 b2l;
plMaxNodeBase *bone = boneArray[i-1];
const char* dbgBoneName = bone->GetName();
Matrix3 localTM = bone->GetNodeTM(TimeValue(0));
b2w = Matrix3ToMatrix44(localTM);
b2w.GetInverse(&w2b);
l2b = w2b * l2w;
b2l = w2l * b2w;
initialB2W[i] = b2w;
initialW2B[i] = w2b;
initialL2B[i] = l2b;
initialB2L[i] = b2l;
}
// First, see if the bones are already set up appropriately.
// Appropriately means:
// a) Associated with the correct drawable (maybe others too, we don't care).
// b) InitialBone transforms match. If we (or another user of the same bone)
// are force localed, Our InitialBone won't match, because it also includes
// our transform as well as the bone's. If we've been flattened into world
// space, our transform is ident and we can share. This is the normal case
// in scene boning. So InitialBones have to match in count and matrix value.
baseMatrix = drawable->FindBoneBaseMatrix(initialL2B, GetSwappableGeom() != nil);
if( baseMatrix != uint32_t(-1) )
{
SetSpansBoneInfo(spanArray, baseMatrix, numBones);
delete [] boneArray;
return;
}
baseMatrix = drawable->AppendDIMatrixSpans(numBones);
SetSpansBoneInfo(spanArray, baseMatrix, numBones);
if (boneMap)
boneMap->SetBaseMatrixIndex(drawable, baseMatrix);
for( i = 1; i < numBones; i++ )
{
plMaxNodeBase *bone = boneArray[i-1];
plSceneObject* obj = bone->GetSceneObject();
const char *dbgBoneName = bone->GetName();
// Pick which drawable to point the DI to
uint8_t iDraw = 0;
/// Now create the actual bone DI, or grab it if it's already created
plDrawInterface *di = obj->GetVolatileDrawInterface();
if( di )
{
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
{
if( di->GetDrawable(iDraw) == drawable )
break;
}
}
else
{
plLocation nodeLoc = bone->GetLocation();
di = new plDrawInterface;
plKey diKey = hsgResMgr::ResMgr()->NewKey(GetKey()->GetName(), di, nodeLoc, GetLoadMask());
hsgResMgr::ResMgr()->AddViaNotify(diKey, new plObjRefMsg(obj->GetKey(), plRefMsg::kOnCreate, 0, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
}
if( di->GetNumDrawables() <= iDraw )
{
uint32_t diIndex = drawable->NewDIMatrixIndex();
di->SetDrawableMeshIndex(iDraw, diIndex);
di->SetDrawable(iDraw, drawable);
}
plDISpanIndex& skinIndices = drawable->GetDISpans(di->GetDrawableMeshIndex(iDraw));
skinIndices.Append(baseMatrix + i);
drawable->SetInitialBone(baseMatrix + i, initialL2B[i], initialB2L[i]);
di->SetTransform(initialB2W[i], initialW2B[i]);
}
delete [] boneArray;
}
//// IMakeInstanceSpans //////////////////////////////////////////////////////
// Given an instance node, instances the geoSpans that the node owns and
// stores them in the given array.
bool plMaxNode::IMakeInstanceSpans( plMaxNode *node, hsTArray<plGeometrySpan *> &spanArray,
plErrorMsg *pErrMsg, plConvertSettings *settings )
{
uint8_t iDraw;
int index, i;
plSceneObject *obj = node->GetSceneObject();
if( !obj )
return false;
const plDrawInterface *di = obj->GetDrawInterface();
if( !di )
return false;
bool setVisDists = false;
float minDist, maxDist;
if( hsMaterialConverter::HasVisDists(this, 0, minDist, maxDist) )
{
setVisDists = true;
}
index = 0;
spanArray.Reset();
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
{
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw));
if( !dr )
continue;
if( di->GetDrawableMeshIndex(iDraw) == (uint32_t)-1 )
continue;
plDISpanIndex disi = dr->GetDISpans(di->GetDrawableMeshIndex(iDraw));
spanArray.ExpandAndZero( spanArray.GetCount() + disi.fIndices.GetCount() );
for( i = 0; i < disi.fIndices.GetCount(); i++ )
{
spanArray[ index ] = new plGeometrySpan;
spanArray[ index ]->MakeInstanceOf( dr->GetGeometrySpan( disi.fIndices[ i ] ) );
if( setVisDists )
{
spanArray[ index ]->fMinDist = (minDist);
spanArray[ index ]->fMaxDist = (maxDist);
}
dr->GetGeometrySpan(disi.fIndices[i])->fProps |= plGeometrySpan::kInstanced;
spanArray[ index++ ]->fProps |= plGeometrySpan::kInstanced;
}
}
// Now that we have all of our instanced spans, we need to make sure we
// have the right materials. Why? There are some isolated cases (such as when "force
// material copy" is set) where we want to still instance the geometry but we want
// separate materials. In this case, GetMaterialArray() will return the right array of
// materials for our instanced node. However, since we've tossed everything except the
// final plGeometrySpans from MakeMesh(), we have to do a reverse lookup to see what
// materials get assigned to whom. GetMaterialArray() is guaranteed (according to Bob)
// to return materials in the same order for instanced nodes, so what we do is call
// GMA() for the old node and the new node (the old one should just be a lookup), then
// for each geoSpan look its old material up in the old array, find the matching material
// in the new array (i.e. same position) and assign that new material to the span.
#if 1 // Change this to 0 to just always use the same materials on instances (old, incorrect way)
Mtl *newMtl = GetMtl(), *origMtl = node->GetMtl();
if( newMtl != nil && newMtl == origMtl ) // newMtl should == origMtl, but check just in case
{
hsTArray<hsGMaterial *> oldMaterials, newMaterials;
if( hsMaterialConverter::IsMultiMat( newMtl ) )
{
for( i = 0; i < newMtl->NumSubMtls(); i++ )
{
hsMaterialConverter::Instance().GetMaterialArray( origMtl->GetSubMtl( i ), node, oldMaterials );
hsMaterialConverter::Instance().GetMaterialArray( newMtl->GetSubMtl( i ), this, newMaterials );
}
}
else
{
hsMaterialConverter::Instance().GetMaterialArray( origMtl, node, oldMaterials );
hsMaterialConverter::Instance().GetMaterialArray( newMtl, this, newMaterials );
}
/// Now we have two arrays to let us map, so walk through our geoSpans and translate them!
/// The good thing is that this is all done before the spans are added to the drawable,
/// so we don't have to worry about reffing or unreffing or any of that messiness; all of
/// that will be done for us as part of the normal AppendDISpans() process.
for( i = 0; i < spanArray.GetCount(); i++ )
{
int j;
// Find the span's original material
for( j = 0; j < oldMaterials.GetCount(); j++ )
{
if( spanArray[ i ]->fMaterial == oldMaterials[ j ] )
{
spanArray[ i ]->fMaterial = newMaterials[ j ];
break;
}
}
}
}
#endif
return true;
}
//// IBuildInstanceList //////////////////////////////////////////////////////
// For the given object, builds a list of all the iNodes that have that
// object as their object. Returns the total node count
uint32_t plMaxNode::IBuildInstanceList( Object *obj, TimeValue t, hsTArray<plMaxNode *> &nodes, bool beMoreAccurate )
{
Object *thisObj = EvalWorldState( t ).obj;
DependentIterator di( obj );
ReferenceMaker *rm;
plMaxNode *node;
plKey sceneNodeKey = GetRoomKey();
/// Use the DependentIterator to loop through all the dependents of the object,
/// looking for nodes that use it
nodes.Reset();
while( rm = di.Next() )
{
if( rm->SuperClassID() == BASENODE_CLASS_ID )
{
node = (plMaxNode *)rm;
if( node->EvalWorldState( t ).obj == thisObj )
{
// Note: we CANNOT instance across pages (i.e. sceneNodes), so we need to make sure this
// INode will be in the same page as our master object
// Also note: RoomKeys will be nil until we've finished the first component pass, so when
// we test this in ConvertValidate(), the keys will be nil and all objects will be "in the
// same room", even though they're not. This is not too bad, though, since the worst that
// could happen is the object gets forced local even when there ends up not being any other
// instances of it in the same page. Ooooh.
if( sceneNodeKey == node->GetRoomKey() )
{
// Make sure the materials generated for both of these nodes will be the same
if( IMaterialsMatch( node, beMoreAccurate ) )
nodes.Append( node );
}
}
}
}
return nodes.GetCount();
}
//// IMaterialsMatch /////////////////////////////////////////////////////////
// Given two nodes that are instances of each other, this function determines
// whether the resulting exported materials for both will be the same or not.
// If not, we need to not instance/share the geometry, since the UV channels
// could (and most likely will) be different.
// To test this, all we really need to do is check the return values of
// AlphaHackLayersNeeded(), since all the other material parameters will be
// identical due to these nodes being instances of each other.
bool plMaxNode::IMaterialsMatch( plMaxNode *otherNode, bool beMoreAccurate )
{
Mtl *mtl = GetMtl(), *otherMtl = otherNode->GetMtl();
if( mtl != otherMtl )
return false; // The two objects have different materials, no way we
// can try to instance them now
if( mtl == nil )
return true; // Both nodes have no material, works for me
// If we're not told to be accurate, then we just quit here. This is because
// in the early passes, we *can't* be more accurate, since we won't have all
// the info yet, so we don't bother checking it
if( !beMoreAccurate )
return true;
if( hsMaterialConverter::IsMultiMat( mtl ) )
{
int i;
for( i = 0; i < mtl->NumSubMtls(); i++ )
{
if( AlphaHackLayersNeeded( i ) != otherNode->AlphaHackLayersNeeded( i ) )
return false;
}
}
else
{
if( AlphaHackLayersNeeded( -1 ) != otherNode->AlphaHackLayersNeeded( -1 ) )
return false;
}
// They're close enough!
return true;
}
bool plMaxNode::ShadeMesh(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
const char* dbgNodeName = GetName();
hsTArray<plGeometrySpan *> spanArray;
if( !(CanConvert() && GetDrawable()) )
return true;
plSceneObject* obj = GetSceneObject();
if( !obj )
return true;
const plDrawInterface* di = obj->GetDrawInterface();
if( !di )
return true;
uint8_t iDraw;
for( iDraw = 0; iDraw < di->GetNumDrawables(); iDraw++ )
{
plDrawableSpans* dr = plDrawableSpans::ConvertNoRef(di->GetDrawable(iDraw));
if( !dr )
continue;
if( di->GetDrawableMeshIndex(iDraw) == (uint32_t)-1 )
continue;
plDISpanIndex disi = dr->GetDISpans(di->GetDrawableMeshIndex(iDraw));
int i;
for( i = 0; i < disi.fIndices.GetCount(); i++ )
{
spanArray.Append( dr->GetGeometrySpan( disi.fIndices[ i ] ) );
}
hsMatrix44 l2w = GetLocalToWorld44();
hsMatrix44 w2l = GetWorldToLocal44();
/// Shade the spans now
// Either do vertex shading or generate a light map.
if( GetLightMapComponent() )
{
plLightMapGen::Instance().MakeMaps(this, l2w, w2l, spanArray, pErrMsg, nil);
// Since they were already pointers to the geometry spans, we don't have
// to re-stuff them. Horray!
}
else
{
hsVertexShader::Instance().ShadeNode(this, l2w, w2l, spanArray);
}
if (settings && settings->fSceneViewer)
dr->RefreshDISpans(di->GetDrawableMeshIndex(iDraw));
}
return true;
}
bool plMaxNode::MakeOccluder(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if( !UserPropExists("Occluder") )
return true;
bool twoSided = UserPropExists("OccTwoSided");
bool isHole = UserPropExists("OccHole");
return ConvertToOccluder(pErrMsg, twoSided, isHole);
}
static void IRemoveCollinearPoints(hsTArray<Point3>& facePts)
{
int i;
for( i = 0; i < facePts.GetCount(); )
{
int j = i + 1 >= facePts.GetCount() ? 0 : i + 1;
int k = j + 1 >= facePts.GetCount() ? 0 : j + 1;
Point3 ab = FNormalize(facePts[i] - facePts[j]);
Point3 bc = FNormalize(facePts[j] - facePts[k]);
const float kDotCutoff = 1.f - 1.e-3f;
float dot = DotProd(ab, bc);
if( (dot < kDotCutoff) && (dot > -kDotCutoff) )
{
i++;
}
else
{
facePts.Remove(j);
}
}
}
bool plMaxNode::ConvertToOccluder(plErrorMsg* pErrMsg, bool twoSided, bool isHole)
{
if( !CanConvert() )
return false;
/// Get some stuff
plLocation nodeLoc = GetLocation();
bool moving = IsMovable();
if( moving )
moving++;
Matrix3 tmp(true);
Matrix3 maxL2V = GetLocalToVert(TimeValue(0));
Matrix3 maxV2L = GetVertToLocal(TimeValue(0));
hsTArray<plCullPoly> polys;
uint32_t polyInitFlags = plCullPoly::kNone;
if( isHole )
polyInitFlags |= plCullPoly::kHole;
else
if( twoSided )
polyInitFlags |= plCullPoly::kTwoSided;
Object *obj = EvalWorldState(TimeValue(0)).obj;
if( obj->CanConvertToType(triObjectClassID) )
{
TriObject *meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);
if( meshObj )
{
Mesh mesh(meshObj->mesh);
const float kNormThresh = M_PI / 20.f;
const float kEdgeThresh = M_PI / 20.f;
const float kBias = 0.1f;
const float kMaxEdge = -1.f;
const DWORD kOptFlags = OPTIMIZE_SAVESMOOTHBOUNDRIES;
mesh.Optimize(
kNormThresh, // threshold of normal differences to preserve
kEdgeThresh, // When the angle between adjacent surface normals is less than this value the auto edge is performed (if the OPTIMIZE_AUTOEDGE flag is set). This angle is specified in radians.
kBias, // Increasing the bias parameter keeps triangles from becoming degenerate. range [0..1] (0 = no bias).
kMaxEdge, // This will prevent the optimize function from creating edges longer than this value. If this parameter is <=0 no limit is placed on the length of the edges.
kOptFlags, // Let them input using smoothing groups, but nothing else.
NULL); // progress bar
MNMesh mnMesh(mesh);
mnMesh.EliminateCollinearVerts();
mnMesh.EliminateCoincidentVerts(0.1f);
// Documentation recommends MakeConvexPolyMesh over MakePolyMesh. Naturally, MakePolyMesh works better.
// mnMesh.MakeConvexPolyMesh();
mnMesh.MakePolyMesh();
mnMesh.MakeConvex();
// mnMesh.MakePlanar(1.f * M_PI / 180.f); // Completely ineffective. Winding up with majorly non-planar polys.
mnMesh.Transform(maxV2L);
polys.SetCount(mesh.getNumFaces());
polys.SetCount(0);
// Unfortunate problem here. Max is assuming that eventually this will get rendered, and so
// we need to avoid T-junctions. Fact is, T-junctions don't bother us at all, where-as colinear
// verts within a poly do (just as added overhead).
// So, to make this as painless (ha ha) as possible, we could detach each poly as we go to
// its own mnMesh, then eliminate colinear verts on that single poly mesh. Except
// EliminateCollinearVerts doesn't seem to actually do that. So we'll just have to
// manually detect and skip collinear verts.
hsTArray<Point3> facePts;
int i;
for( i = 0; i < mnMesh.numf; i++ )
{
MNFace& face = mnMesh.f[i];
facePts.SetCount(0);
int j;
for( j = 0; j < face.deg; j++ )
{
facePts.Append(mnMesh.v[face.vtx[j]].p);
}
IRemoveCollinearPoints(facePts);
if( facePts.GetCount() < 3 )
continue;
int lastAdded = 2;
plCullPoly* poly = polys.Push();
poly->fVerts.SetCount(0);
Point3 p;
hsPoint3 pt;
p = facePts[0];
pt.Set(p.x, p.y, p.z);
poly->fVerts.Append(pt);
p = facePts[1];
pt.Set(p.x, p.y, p.z);
poly->fVerts.Append(pt);
p = facePts[2];
pt.Set(p.x, p.y, p.z);
poly->fVerts.Append(pt);
for( j = lastAdded+1; j < facePts.GetCount(); j++ )
{
p = facePts[j];
pt.Set(p.x, p.y, p.z);
hsVector3 a = hsVector3(&pt, &poly->fVerts[0]);
hsVector3 b = hsVector3(&poly->fVerts[lastAdded], &poly->fVerts[0]);
hsVector3 c = hsVector3(&poly->fVerts[lastAdded-1], &poly->fVerts[0]);
hsVector3 aXb = a % b;
hsVector3 bXc = b % c;
hsFastMath::Normalize(aXb);
hsFastMath::Normalize(bXc);
float dotSq = aXb.InnerProduct(bXc);
dotSq *= dotSq;
const float kMinLenSq = 1.e-8f;
const float kMinDotFracSq = 0.998f * 0.998f;
float lenSq = aXb.MagnitudeSquared() * bXc.MagnitudeSquared();
if( lenSq < kMinLenSq )
continue;
// If not planar, move to new poly.
if( dotSq < lenSq * kMinDotFracSq )
{
poly->InitFromVerts(polyInitFlags);
poly = polys.Push();
plCullPoly* lastPoly = &polys[polys.GetCount()-2];
poly->fVerts.SetCount(0);
poly->fVerts.Append(lastPoly->fVerts[0]);
poly->fVerts.Append(lastPoly->fVerts[lastAdded]);
lastAdded = 1;
}
poly->fVerts.Append(pt);
lastAdded++;
}
poly->InitFromVerts(polyInitFlags);
}
}
}
if( polys.GetCount() )
{
plOccluder* occ = nil;
plMobileOccluder* mob = nil;
if( moving )
{
mob = new plMobileOccluder;
occ = mob;
}
else
{
occ = new plOccluder;
}
occ->SetPolyList(polys);
occ->ComputeFromPolys();
// Register it.
plString tmpName;
if( GetKey() && !GetKey()->GetName().IsEmpty() )
{
tmpName = plFormat("{}_Occluder", GetKey()->GetName());
}
else
{
static int numOcc = 0;
tmpName = plFormat("Occluder_{_04d}", numOcc);
}
plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, occ, nodeLoc, GetLoadMask() );
hsgResMgr::ResMgr()->AddViaNotify(occ->GetKey(), new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
}
return true;
}
bool plMaxNode::MakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
if (!CanConvert())
return false;
if (!GetRunTimeLight())
return true;
/// Get some stuff
plLocation nodeLoc = GetLocation();
bool forceLocal = GetForceLocal();
hsMatrix44 l2w = GetLocalToWorld44();
hsMatrix44 w2l = GetWorldToLocal44();
hsMatrix44 lt2l = GetVertToLocal44();
hsMatrix44 l2lt = GetLocalToVert44();
plLightInfo* liInfo = nil;
liInfo = IMakeLight(pErrMsg, settings);
if( liInfo )
{
// 12.03.01 mcn - Um, we want RT lights to affect static objects if they're animated. So
// why wasn't this here a long time ago? :~
if( IsMovable() || IsAnimatedLight() )
liInfo->SetProperty(plLightInfo::kLPMovable, true);
liInfo->SetTransform(l2w, w2l);
liInfo->SetLocalToLight(l2lt, lt2l);
plKey key = hsgResMgr::ResMgr()->NewKey( GetKey()->GetName(), liInfo, nodeLoc, GetLoadMask() );
hsgResMgr::ResMgr()->AddViaNotify(liInfo->GetKey(), new plObjRefMsg(GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kInterface), plRefFlags::kActiveRef);
// Only support projection for spots and dir lights for now.
if( plLimitedDirLightInfo::ConvertNoRef(liInfo) || plSpotLightInfo::ConvertNoRef(liInfo) )
{
// Have to do this after the drawable gets a key.
IGetProjection(liInfo, pErrMsg);
}
}
return true;
}
plLightInfo* plMaxNode::IMakeLight(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
plLightInfo* liInfo = nil;
Object *obj = EvalWorldState(timeVal).obj;
if( obj->ClassID() == Class_ID(OMNI_LIGHT_CLASS_ID, 0) )
liInfo = IMakeOmni(pErrMsg, settings);
else
if( (obj->ClassID() == Class_ID(SPOT_LIGHT_CLASS_ID, 0)) || (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
liInfo = IMakeSpot(pErrMsg, settings);
else
if( (obj->ClassID() == Class_ID(DIR_LIGHT_CLASS_ID, 0)) || (obj->ClassID() == Class_ID(TDIR_LIGHT_CLASS_ID, 0)) )
liInfo = IMakeDirectional(pErrMsg, settings);
else
if( obj->ClassID() == RTOMNI_LIGHT_CLASSID )
liInfo = IMakeRTOmni(pErrMsg, settings);
else
if( (obj->ClassID() == RTSPOT_LIGHT_CLASSID) ) //|| (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
liInfo = IMakeRTSpot(pErrMsg, settings);
else
if( (obj->ClassID() == RTDIR_LIGHT_CLASSID) ) //|| (obj->ClassID() == Class_ID(FSPOT_LIGHT_CLASS_ID, 0)) )
liInfo = IMakeRTDirectional(pErrMsg, settings);
else
if( obj->ClassID() == RTPDIR_LIGHT_CLASSID )
liInfo = IMakeRTProjDirectional( pErrMsg, settings );
return liInfo;
}
void plMaxNode::IGetLightAttenuation(plOmniLightInfo* liInfo, LightObject* light, LightState& ls)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
float attenConst, attenLinear, attenQuadratic;
float intens = ls.intens >= 0 ? ls.intens : -ls.intens;
float attenEnd = ls.attenEnd;
// Decay type 0:None, 1:Linear, 2:Squared
if( ls.useAtten )
{
switch(((GenLight*)light)->GetDecayType())
{
case 0:
case 1:
attenConst = 1.f;
attenLinear = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
attenQuadratic = 0;
break;
case 2:
attenConst = 1.f;
attenLinear = 0;
attenQuadratic = (intens * plSillyLightKonstants::GetFarPowerKonst() -1.f) / (attenEnd * attenEnd);
break;
case 3:
attenConst = intens;
attenLinear = 0.f;
attenQuadratic = 0.f;
liInfo->SetCutoffAttenuation( ( (GenLight *)light )->GetDecayRadius( timeVal ) );
break;
}
}
else
{
attenConst = 1.f;
attenLinear = 0.f;
attenQuadratic = 0.f;
}
liInfo->SetConstantAttenuation(attenConst);
liInfo->SetLinearAttenuation(attenLinear);
liInfo->SetQuadraticAttenuation(attenQuadratic);
}
bool plMaxNode::IGetRTLightAttenValues(IParamBlock2* ProperPB, float& attenConst, float& attenLinear, float& attenQuadratic, float &attenCutoff )
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, timeVal);
if( intens < 0 )
intens = -intens;
float attenEnd;
attenEnd = ProperPB->GetFloat(plRTLightBase::kAttenMaxFalloffEdit, timeVal);//ls.attenEnd;
// Decay Type New == 0 for Linear and 1 for Squared.... OLD and OBSOLETE:Decay type 0:None, 1:Linear, 2:Squared
// Oh, and now 2 = cutoff attenuation
if( ProperPB->GetInt(plRTLightBase::kUseAttenuationBool, timeVal))
{
switch(ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, timeVal))//((GenLight*)light)->GetDecayType())
{
case 0:
attenConst = 1.f;
attenLinear = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
if( attenLinear < 0 )
attenLinear = 0;
attenQuadratic = 0;
attenCutoff = attenEnd;
break;
case 1:
attenConst = 1.f;
attenLinear = 0;
attenQuadratic = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd * attenEnd);
if( attenQuadratic < 0 )
attenQuadratic = 0;
attenCutoff = attenEnd;
break;
case 2:
attenConst = intens;
attenLinear = 0.f;
attenQuadratic = 0.f;
attenCutoff = attenEnd;
break;
}
return true;
}
else
{
attenConst = 1.f;
attenLinear = 0.f;
attenQuadratic = 0.f;
attenCutoff = 0.f;
return true;
}
return false;
}
void plMaxNode::IGetRTLightAttenuation(plOmniLightInfo* liInfo, IParamBlock2* ProperPB)
{
float attenConst, attenLinear, attenQuadratic, attenCutoff;
if( IGetRTLightAttenValues(ProperPB, attenConst, attenLinear, attenQuadratic, attenCutoff) )
{
liInfo->SetConstantAttenuation(attenConst);
liInfo->SetLinearAttenuation(attenLinear);
liInfo->SetQuadraticAttenuation(attenQuadratic);
liInfo->SetCutoffAttenuation( attenCutoff );
}
}
void plMaxNode::IGetLightColors(plLightInfo* liInfo, LightObject* light, LightState& ls)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Point3 color = light->GetRGBColor(timeVal);
float intensity = light->GetIntensity(timeVal);
color *= intensity;
liInfo->SetAmbient(hsColorRGBA().Set(0,0,0,1.f));
if( ls.affectDiffuse )
liInfo->SetDiffuse(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
else
liInfo->SetDiffuse(hsColorRGBA().Set(0,0,0,intensity));
if( ls.affectSpecular )
liInfo->SetSpecular(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
else
liInfo->SetSpecular(hsColorRGBA().Set(0,0,0,intensity));
}
void plMaxNode::IGetRTLightColors(plLightInfo* liInfo, IParamBlock2* ProperPB)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Point3 color = ProperPB->GetPoint3(plRTLightBase::kLightColor, timeVal);//light->GetRGBColor(timeVal);
float intensity = ProperPB->GetFloat(plRTLightBase::kIntensity, timeVal); //light->GetIntensity(timeVal);
color *= intensity;
liInfo->SetAmbient(hsColorRGBA().Set(0,0,0,1.f));
if( ProperPB->GetInt( plRTLightBase::kAffectDiffuse, timeVal ) )
liInfo->SetDiffuse(hsColorRGBA().Set(color.x, color.y, color.z, intensity));
else
liInfo->SetDiffuse(hsColorRGBA().Set(0,0,0,intensity));
if( ProperPB->GetInt(plRTLightBase::kSpec, timeVal)) //ls.affectSpecular )
{
Color spec = ProperPB->GetColor(plRTLightBase::kSpecularColorSwatch);
liInfo->SetSpecular(hsColorRGBA().Set(spec.r, spec.g, spec.b, intensity));
}
else
liInfo->SetSpecular(hsColorRGBA().Set(0,0,0,intensity));
}
void plMaxNode::IGetCone(plSpotLightInfo* liInfo, LightObject* light, LightState& ls)
{
float inner = hsDegreesToRadians(ls.hotsize);
float outer = hsDegreesToRadians(ls.fallsize);
/// 4.26.2001 mcn - MAX gives us full angles, but we want to store half angles
liInfo->SetSpotInner( inner / 2.0f );
liInfo->SetSpotOuter( outer / 2.0f );
liInfo->SetFalloff(1.f);
}
void plMaxNode::IGetRTCone(plSpotLightInfo* liInfo, IParamBlock2* ProperPB)
{
//TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
float inner, outer;
inner = hsDegreesToRadians(ProperPB->GetFloat(plRTLightBase::kHotSpot, timeVal)); //ls.hotsize);
outer = hsDegreesToRadians(ProperPB->GetFloat(plRTLightBase::kFallOff, timeVal)); //ls.fallsize);
/// 4.26.2001 mcn - MAX gives us full angles, but we want to store half angles
liInfo->SetSpotInner( inner / 2.0f );
liInfo->SetSpotOuter( outer / 2.0f );
liInfo->SetFalloff(1.f);
}
plLightInfo* plMaxNode::IMakeSpot(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
LightObject *light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(SPOT_LIGHT_CLASS_ID,0));
if( !light )
light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(FSPOT_LIGHT_CLASS_ID,0));
LightState ls;
if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
{
pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
return nil;
}
plSpotLightInfo* spot = new plSpotLightInfo;
IGetLightColors(spot, light, ls);
IGetLightAttenuation(spot, light, ls);
IGetCone(spot, light, ls);
if( obj != light )
light->DeleteThis();
return spot;
}
plLightInfo* plMaxNode::IMakeOmni(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
LightObject *light = (LightObject*)obj->ConvertToType(timeVal,
Class_ID(OMNI_LIGHT_CLASS_ID,0));
LightState ls;
if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
{
pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
return nil;
}
plOmniLightInfo* omni = new plOmniLightInfo;
IGetLightAttenuation(omni, light, ls);
IGetLightColors(omni, light, ls);
if( obj != light )
light->DeleteThis();
return omni;
}
plLightInfo* plMaxNode::IMakeDirectional(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
LightObject *light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(DIR_LIGHT_CLASS_ID,0));
if( !light )
light = (LightObject*)obj->ConvertToType(timeVal, Class_ID(TDIR_LIGHT_CLASS_ID,0));
LightState ls;
if (!(REF_SUCCEED == light->EvalLightState(timeVal, Interval(timeVal, timeVal), &ls)))
{
pErrMsg->Set(true, GetName(), "Trouble evaluating light").CheckAndAsk();
return nil;
}
plLightInfo* plasLight = nil;
if( light->GetProjMap() )
{
plLimitedDirLightInfo* ldl = new plLimitedDirLightInfo;
float sz = light->GetFallsize(timeVal, FOREVER);
float depth = 1000.f;
ldl->SetWidth(sz);
ldl->SetHeight(sz);
ldl->SetDepth(depth);
plasLight = ldl;
}
else
{
plDirectionalLightInfo* direct = new plDirectionalLightInfo;
plasLight = direct;
}
IGetLightColors(plasLight, light, ls);
if( obj != light )
light->DeleteThis();
return plasLight;
}
plLightInfo* plMaxNode::IMakeRTSpot(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
Object *ThisObj = ((INode*)this)->GetObjectRef();
IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
if(!obj->CanConvertToType(RTSPOT_LIGHT_CLASSID))
{
pErrMsg->Set(true, GetName(), "Trouble evaluating light, improper classID").CheckAndAsk();
return nil;
}
plSpotLightInfo* spot = new plSpotLightInfo;
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
spot->SetProperty(plLightInfo::kDisable, true);
IGetRTLightColors(spot,ThisObjPB);
IGetRTLightAttenuation(spot,ThisObjPB);
IGetRTCone(spot, ThisObjPB);
//plSpotModifier* liMod = new plSpotModifier;
//GetRTLightColAnim(ThisObjPB, liMod);
//GetRTLightAttenAnim(ThisObjPB, liMod);
//GetRTConeAnim(ThisObjPB, liMod);
//IAttachRTLightModifier(liMod);
// if( obj != light )
// light->DeleteThis();
return spot;
}
plLightInfo* plMaxNode::IMakeRTOmni(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
Object *ThisObj = ((INode*)this)->GetObjectRef();
IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
plOmniLightInfo* omni = new plOmniLightInfo;
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
omni->SetProperty(plLightInfo::kDisable, true);
IGetRTLightAttenuation(omni, ThisObjPB);
IGetRTLightColors(omni, ThisObjPB);
//plOmniModifier* liMod = new plOmniModifier;
//GetRTLightColAnim(ThisObjPB, liMod);
//GetRTLightAttenAnim(ThisObjPB, liMod);
//IAttachRTLightModifier(liMod);
// if( obj != light )
// light->DeleteThis();
return omni;
}
plLightInfo* plMaxNode::IMakeRTDirectional(plErrorMsg* pErrMsg, plConvertSettings* settings)
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
Object *ThisObj = ((INode*)this)->GetObjectRef();
IParamBlock2* ThisObjPB = ThisObj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
plDirectionalLightInfo* direct = new plDirectionalLightInfo;
if(!ThisObjPB->GetInt(plRTLightBase::kLightOn))
direct->SetProperty(plLightInfo::kDisable, true);
IGetRTLightColors(direct, ThisObjPB);
//plLightModifier* liMod = new plLightModifier;
//GetRTLightColAnim(ThisObjPB, liMod);
//IAttachRTLightModifier(liMod);
// if( obj != light )
// light->DeleteThis();
return direct;
}
//// IMakeRTProjDirectional //////////////////////////////////////////////////
// Conversion function for RT Projected Directional lights
plLightInfo *plMaxNode::IMakeRTProjDirectional( plErrorMsg *pErrMsg, plConvertSettings *settings )
{
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
Object *ThisObj = ((INode*)this)->GetObjectRef();
IParamBlock2 *mainPB = ThisObj->GetParamBlockByID( plRTLightBase::kBlkMain );
IParamBlock2 *projPB = ThisObj->GetParamBlockByID( plRTProjDirLight::kBlkProj );
plLimitedDirLightInfo *light = new plLimitedDirLightInfo;
light->SetWidth( projPB->GetFloat( plRTProjDirLight::kWidth ) );
light->SetHeight( projPB->GetFloat( plRTProjDirLight::kHeight ) );
light->SetDepth( projPB->GetFloat( plRTProjDirLight::kRange ) );
if( !mainPB->GetInt( plRTLightBase::kLightOn ) )
light->SetProperty( plLightInfo::kDisable, true );
IGetRTLightColors( light, mainPB );
//plLightModifier *liMod = new plLightModifier;
//GetRTLightColAnim( mainPB, liMod );
//IAttachRTLightModifier(liMod);
return light;
}
bool plMaxNode::IGetProjection(plLightInfo* li, plErrorMsg* pErrMsg)
{
bool persp = false;
TimeValue timeVal = hsConverterUtils::Instance().GetTime(GetInterface());
Object *obj = EvalWorldState(timeVal).obj;
LightObject *light = (LightObject*)obj->ConvertToType(timeVal, RTSPOT_LIGHT_CLASSID);
if( light )
persp = true;
if( !light )
light = (LightObject*)obj->ConvertToType(timeVal, RTPDIR_LIGHT_CLASSID);
if( !light )
return false;
bool retVal = false;
Texmap* projMap = light->GetProjMap();
if( !projMap )
return false;
plConvert& convert = plConvert::Instance();
if( projMap->ClassID() != LAYER_TEX_CLASS_ID )
{
if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedWrongProj), GetName(),
"Only Plasma Layers supported for projection").CheckAskOrCancel() )
{
convert.fWarned |= plConvert::kWarnedWrongProj;
}
pErrMsg->Set(false);
return false;
}
IParamBlock2 *pb = nil;
Class_ID cid = obj->ClassID();
// Get the paramblock
if (cid == RTSPOT_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
else if (cid == RTOMNI_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
else if (cid == RTDIR_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
else if (cid == RTPDIR_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTProjDirLight::kBlkProj);
// Have the layer converter process this layer directly
plLayerConverter::Instance().MuteWarnings();
plLayerInterface* proj = plLayerConverter::Instance().ConvertTexmap( projMap, this, 0, true, false );
plLayerConverter::Instance().UnmuteWarnings();
if( proj )
{
plLayer* projLay = plLayer::ConvertNoRef(proj->BottomOfStack());
if( projLay && projLay->GetTexture() )
{
if( persp )
projLay->SetMiscFlags(projLay->GetMiscFlags() | hsGMatState::kMiscPerspProjection);
else
projLay->SetMiscFlags(projLay->GetMiscFlags() | hsGMatState::kMiscOrthoProjection);
projLay->SetUVWSrc(projLay->GetUVWSrc() | plLayerInterface::kUVWPosition);
projLay->SetClampFlags(hsGMatState::kClampTexture);
projLay->SetZFlags(hsGMatState::kZNoZWrite);
switch( pb->GetInt(plRTLightBase::kProjTypeRadio) )
{
default:
case plRTLightBase::kIlluminate:
projLay->SetBlendFlags(hsGMatState::kBlendMult);
li->SetProperty(plLightInfo::kLPOverAll, false);
break;
case plRTLightBase::kAdd:
projLay->SetBlendFlags(hsGMatState::kBlendAdd);
li->SetProperty(plLightInfo::kLPOverAll, true);
break;
case plRTLightBase::kMult:
projLay->SetBlendFlags(hsGMatState::kBlendMult | hsGMatState::kBlendInvertColor | hsGMatState::kBlendInvertFinalColor);
li->SetProperty(plLightInfo::kLPOverAll, true);
break;
case plRTLightBase::kMADD:
projLay->SetBlendFlags(hsGMatState::kBlendMADD);
li->SetProperty(plLightInfo::kLPOverAll, true);
break;
}
hsgResMgr::ResMgr()->AddViaNotify(proj->GetKey(), new plGenRefMsg(li->GetKey(), plRefMsg::kOnCreate, 0, 0), plRefFlags::kActiveRef);
li->SetShadowCaster(false);
li->SetProperty(plLightInfo::kLPMovable, true);
retVal = true;
}
else
{
char buff[256];
if( projMap && projMap->GetName() && *projMap->GetName() )
sprintf(buff, "Can't find projected bitmap - %s", projMap->GetName());
else
sprintf(buff, "Can't find projected bitmap - <unknown>");
if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedMissingProj), GetName(),
buff).CheckAskOrCancel() )
convert.fWarned |= plConvert::kWarnedMissingProj;
pErrMsg->Set(false);
retVal = false;
}
}
else
{
if( pErrMsg->Set(!(convert.fWarned & plConvert::kWarnedWrongProj), GetName(),
"Failure to convert projection map - check type.").CheckAskOrCancel() )
convert.fWarned |= plConvert::kWarnedWrongProj;
pErrMsg->Set(false);
retVal = false;
}
if( light != obj )
light->DeleteThis();
return retVal;
}
/*
bool plMaxNode::IAttachRTLightModifier(plLightModifier* liMod)
{
if( liMod->HasAnima() )
{
liMod->DefaultAnimation();
CreateModifierKey(liMod, "_ANIMA");
AddModifier(liMod);
}
else
{
delete liMod;
return false;
}
return true;
}
*/
bool plMaxNode::IsAnimatedLight()
{
Object *obj = GetObjectRef();
if (!obj)
return false;
const char* dbgNodeName = GetName();
Class_ID cid = obj->ClassID();
if (!(cid == RTSPOT_LIGHT_CLASSID ||
cid == RTOMNI_LIGHT_CLASSID ||
cid == RTDIR_LIGHT_CLASSID ||
cid == RTPDIR_LIGHT_CLASSID))
return false;
IParamBlock2 *pb = nil;
// Get the paramblock
if (cid == RTSPOT_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkSpotLight);
else if (cid == RTOMNI_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkOmniLight);
else if (cid == RTDIR_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkTSpotLight);
else if (cid == RTPDIR_LIGHT_CLASSID)
pb = obj->GetParamBlockByID(plRTLightBase::kBlkMain);
hsControlConverter& cc = hsControlConverter::Instance();
// Is the color animated?
Control *colorCtl = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kLightColor ) );
if (colorCtl && cc.HasKeyTimes(colorCtl))
return true;
// Is the specularity animated?
Control *specCtl = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kSpecularColorSwatch ) );
if (specCtl && cc.HasKeyTimes(specCtl))
return true;
// Is the attenuation animated? (Spot and Omni lights only)
if (cid == RTSPOT_LIGHT_CLASSID || cid == RTOMNI_LIGHT_CLASSID)
{
Control *falloffCtl = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kAttenMaxFalloffEdit ) );
if (falloffCtl && cc.HasKeyTimes(falloffCtl))
return true;
}
// Is the cone animated? (Spot only)
if (cid == RTSPOT_LIGHT_CLASSID)
{
Control *innerCtl = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kHotSpot ) );
if (innerCtl && cc.HasKeyTimes(innerCtl))
return true;
Control *outerCtl = GetParamBlock2Controller(pb, ParamID( plRTLightBase::kFallOff ) );
if (outerCtl && cc.HasKeyTimes(outerCtl))
return true;
}
return false;
}
void plMaxNode::GetRTLightAttenAnim(IParamBlock2* ProperPB, plAGAnim *anim)
{
if( ProperPB->GetInt(plRTLightBase::kUseAttenuationBool, TimeValue(0)) )
{
Control* falloffCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kAttenMaxFalloffEdit));
if( falloffCtl )
{
plLeafController* subCtl;
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
subCtl = hsControlConverter::Instance().MakeScalarController(falloffCtl, this);
else
subCtl = hsControlConverter::Instance().MakeScalarController(falloffCtl, this,
anim->GetStart(), anim->GetEnd());
if( subCtl )
{
if( ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0)) == 2 )
{
// Animation of a cutoff attenuation, which only needs a scalar channel
plOmniCutoffApplicator *app = new plOmniCutoffApplicator();
app->SetChannelName(GetName());
plScalarControllerChannel *chan = new plScalarControllerChannel(subCtl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(subCtl->GetLength());
}
else
{
bool distSq = ProperPB->GetInt(plRTLightBase::kAttenTypeRadio, TimeValue(0));
int i;
for( i = 0; i < subCtl->GetNumKeys(); i++ )
{
hsScalarKey *key = subCtl->GetScalarKey(i);
if (key)
{
float attenEnd = key->fValue;
TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME;
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
float newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
if( distSq )
newVal /= attenEnd;
key->fValue = newVal;
}
hsBezScalarKey *bezKey = subCtl->GetBezScalarKey(i);
if (bezKey)
{
float attenEnd = bezKey->fValue;
TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME;
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
float newVal = (intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / attenEnd;
if( distSq )
newVal /= attenEnd;
/// From the chain rule, fix our tangents.
bezKey->fInTan *= -(intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd*attenEnd);
if( distSq )
bezKey->fInTan *= 2.f / attenEnd;
bezKey->fOutTan *= -(intens * plSillyLightKonstants::GetFarPowerKonst() - 1.f) / (attenEnd*attenEnd);
if( distSq )
bezKey->fOutTan *= 2.f / attenEnd;
bezKey->fValue = newVal;
}
}
plAGApplicator *app;
if (distSq)
app = new plOmniSqApplicator;
else
app = new plOmniApplicator;
app->SetChannelName(GetName());
plScalarControllerChannel *chan = new plScalarControllerChannel(subCtl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(subCtl->GetLength());
float attenConst, attenLinear, attenQuadratic, attenCutoff;
IGetRTLightAttenValues(ProperPB, attenConst, attenLinear, attenQuadratic, attenCutoff);
plOmniLightInfo *info = plOmniLightInfo::ConvertNoRef(GetSceneObject()->GetGenericInterface(plOmniLightInfo::Index()));
if (info)
{
hsPoint3 initAtten(attenConst, attenLinear, attenQuadratic);
info->SetConstantAttenuation(attenConst);
info->SetLinearAttenuation(attenLinear);
info->SetQuadraticAttenuation(attenQuadratic);
}
else
hsAssert(false, "Failed to find light info");
}
}
}
}
}
void plMaxNode::IAdjustRTColorByIntensity(plController* ctl, IParamBlock2* ProperPB)
{
plLeafController* simp = plLeafController::ConvertNoRef(ctl);
plCompoundController* comp;
if( simp )
{
int i;
for( i = 0; i < simp->GetNumKeys(); i++ )
{
hsPoint3Key* key = simp->GetPoint3Key(i);
if (key)
{
TimeValue tv = key->fFrame * MAX_TICKS_PER_FRAME;
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
key->fValue *= intens;
}
hsBezPoint3Key* bezKey = simp->GetBezPoint3Key(i);
if (bezKey)
{
TimeValue tv = bezKey->fFrame * MAX_TICKS_PER_FRAME;
float intens = ProperPB->GetFloat(plRTLightBase::kIntensity, tv);
bezKey->fInTan *= intens;
bezKey->fOutTan *= intens;
bezKey->fValue *= intens;
}
}
}
else if( comp = plCompoundController::ConvertNoRef(ctl) )
{
int j;
for( j = 0; j < 3; j++ )
{
IAdjustRTColorByIntensity(comp->GetController(j), ProperPB);
}
}
}
void plMaxNode::GetRTLightColAnim(IParamBlock2* ProperPB, plAGAnim *anim)
{
Control* ambientCtl = nil; // Ambient not currently supported
Control* colorCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kLightColor));
Control* specCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kSpecularColorSwatch));
plPointControllerChannel *chan;
if( ambientCtl )
{
plController* ctl;
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this);
else
ctl = hsControlConverter::Instance().MakeColorController(ambientCtl, this, anim->GetStart(), anim->GetEnd());
if( ctl )
{
plLightAmbientApplicator *app = new plLightAmbientApplicator();
app->SetChannelName(GetName());
chan = new plPointControllerChannel(ctl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(ctl->GetLength());
}
}
if( colorCtl )
{
plController* ctl;
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
ctl = hsControlConverter::Instance().MakeColorController(colorCtl, this);
else
ctl = hsControlConverter::Instance().MakeColorController(colorCtl, this, anim->GetStart(), anim->GetEnd());
if( ctl )
{
IAdjustRTColorByIntensity(ctl, ProperPB);
plLightDiffuseApplicator *app = new plLightDiffuseApplicator();
app->SetChannelName(GetName());
chan = new plPointControllerChannel(ctl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(ctl->GetLength());
}
}
if( specCtl )
{
plController* ctl;
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
ctl = hsControlConverter::Instance().MakeColorController(specCtl, this);
else
ctl = hsControlConverter::Instance().MakeColorController(specCtl, this, anim->GetStart(), anim->GetEnd());
if( ctl )
{
plLightSpecularApplicator *app = new plLightSpecularApplicator();
app->SetChannelName(GetName());
chan = new plPointControllerChannel(ctl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(ctl->GetLength());
}
}
}
void plMaxNode::GetRTConeAnim(IParamBlock2* ProperPB, plAGAnim *anim)
{
Control* innerCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kHotSpot));
Control* outerCtl = GetParamBlock2Controller(ProperPB, ParamID(plRTLightBase::kFallOff));
plScalarControllerChannel *chan;
if( innerCtl )
{
plLeafController* ctl;
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this);
else
ctl = hsControlConverter::Instance().MakeScalarController(innerCtl, this, anim->GetStart(), anim->GetEnd());
if( ctl )
{
plSpotInnerApplicator *app = new plSpotInnerApplicator();
app->SetChannelName(GetName());
chan = new plScalarControllerChannel(ctl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(ctl->GetLength());
}
}
if( outerCtl )
{
plController* ctl;
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this);
else
ctl = hsControlConverter::Instance().MakeScalarController(outerCtl, this, anim->GetStart(), anim->GetEnd());
if( ctl )
{
plSpotOuterApplicator *app = new plSpotOuterApplicator();
app->SetChannelName(GetName());
chan = new plScalarControllerChannel(ctl);
app->SetChannel(chan);
anim->AddApplicator(app);
if (!anim->GetName().Compare(ENTIRE_ANIMATION_NAME))
anim->ExtendToLength(ctl->GetLength());
}
}
}
plXImposterComp* plMaxNode::GetXImposterComp()
{
int count = NumAttachedComponents();
int i;
for( i = 0; i < count; i++ )
{
// See if any are a x-imposter component.
plComponentBase *comp = GetAttachedComponent(i);
if( comp && (comp->ClassID() == XIMPOSTER_COMP_CID) )
{
plXImposterComp* ximp = (plXImposterComp*)comp;
return ximp;
}
}
return nil;
}
Point3 plMaxNode::GetFlexibility()
{
uint32_t count = NumAttachedComponents();
// Go through all the components attached to this node
for (uint32_t i = 0; i < count; i++)
{
// See if any are a flexibility component.
plComponentBase *comp = GetAttachedComponent(i);
if( comp && (comp->ClassID() == FLEXIBILITY_COMP_CID) )
{
plFlexibilityComponent* flex = (plFlexibilityComponent*)comp;
return flex->GetFlexibility();
}
}
return Point3(0.f, 0.f, 0.f);
}
plLightMapComponent* plMaxNode::GetLightMapComponent()
{
uint32_t count = NumAttachedComponents();
// Go through all the components attached to this node
for (uint32_t i = 0; i < count; i++)
{
// See if any are a flexibility component.
plComponentBase *comp = GetAttachedComponent(i);
if( comp && (comp->ClassID() == LIGHTMAP_COMP_CID) )
{
plLightMapComponent* lmap = (plLightMapComponent*)comp;
return lmap;
}
}
return nil;
}
plDrawableCriteria plMaxNode::GetDrawableCriteria(bool needBlending, bool needSorting)
{
plRenderLevel level = needBlending ? GetRenderLevel(needBlending) : plRenderLevel::OpaqueRenderLevel();
if( GetSortAsOpaque() )
level.Set(plRenderLevel::kOpaqueMajorLevel, level.Minor());
uint32_t crit = 0;
if( needBlending )
{
if( needSorting && !GetNoFaceSort() )
crit |= plDrawable::kCritSortFaces;
if( !GetNoSpanSort() )
crit |= plDrawable::kCritSortSpans;
}
if( GetItinerant() )
crit |= plDrawable::kCritCharacter;
plDrawableCriteria retVal(crit, level, GetLoadMask());
if( GetEnviron() )
retVal.fType |= plDrawable::kEnviron;
if( GetEnvironOnly() )
retVal.fType &= ~plDrawable::kNormal;
return retVal;
}
//// IGetSceneNodeSpans //////////////////////////////////////////////////////
// Gets the required drawableSpans from a sceneNode. Creates a new one
// if it can't find one.
plDrawableSpans *plMaxNode::IGetSceneNodeSpans( plSceneNode *node, bool needBlending, bool needSorting )
{
plDrawableSpans *spans;
plString tmpName;
plLocation nodeLoc = GetLocation();
if( !needBlending )
needSorting = false;
plDrawableCriteria crit = GetDrawableCriteria(needBlending, needSorting);
spans = plDrawableSpans::ConvertNoRef( node->GetMatchingDrawable( crit ) );
if( spans != nil )
{
if( GetNoSpanReSort() )
{
spans->SetNativeProperty(plDrawable::kPropNoReSort, true);
}
return spans;
}
/// Couldn't find--create and return it
spans = new plDrawableSpans;
if( needBlending )
{
/// Blending (deferred) spans
spans->SetCriteria( crit );
tmpName = plFormat("{}_{_08x}_{x}BlendSpans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria);
}
else
{
/// Normal spans
spans->SetCriteria( crit );
tmpName = plFormat("{}_{_08x}_{x}Spans", node->GetKeyName(), crit.fLevel.fLevel, crit.fCriteria);
}
if (GetSwappableGeomTarget() != (uint32_t)-1 || GetSwappableGeom()) // We intend to swap geometry with this node... flag the drawable as volatile
{
if( GetItinerant() )
spans->SetNativeProperty(plDrawable::kPropCharacter, true);
spans->SetNativeProperty( plDrawable::kPropVolatile, true );
}
// Add a key for the spans
plKey key = hsgResMgr::ResMgr()->NewKey( tmpName, spans, nodeLoc, GetLoadMask() );
spans->SetSceneNode(node->GetKey());
/// Created! Return it now...
if( GetNoSpanReSort() )
spans->SetNativeProperty(plDrawable::kPropNoReSort, true);
return spans;
}
bool plMaxNode::SetupPropertiesPass(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
// TEMP
if (IsComponent())
return false;
// End TEMP
bool ret = true;
uint32_t count = NumAttachedComponents();
// Go through all the components attached to this node
for (uint32_t i = 0; i < count; i++)
{
// For each one, call the requested function. If any of the attached components
// return false this function will return false.
plComponentBase *comp = GetAttachedComponent(i);
if (comp->IsExternal())
{
if (!((plComponentExt*)comp)->SetupProperties(this, &gComponentTools, pErrMsg))
ret = false;
}
else
{
if (!((plComponent*)comp)->SetupProperties(this, pErrMsg))
ret = false;
}
}
if( ret )
{
// Now loop through all the plPassMtlBase-derived materials that are applied to this node
Mtl *mtl = GetMtl();
if( mtl != nil && !GetParticleRelated() )
{
if( hsMaterialConverter::IsMultiMat( mtl ) || hsMaterialConverter::IsMultipassMat( mtl ) || hsMaterialConverter::IsCompositeMat( mtl ) )
{
int i;
for (i = 0; i < mtl->NumSubMtls(); i++)
{
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl->GetSubMtl( i ) );
if( pass != nil )
{
if( !pass->SetupProperties( this, pErrMsg ) )
ret = false;
}
}
}
else
{
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl );
if( pass != nil )
{
if( !pass->SetupProperties( this, pErrMsg ) )
ret = false;
}
}
}
}
if( ret )
{
plMaxNode* parent = (plMaxNode*)GetParentNode();
if( parent && IsLegalDecal(false) )
{
AddRenderDependency(parent);
SetNoSpanSort(true);
SetNoFaceSort(true);
SetNoDeferDraw(true);
}
}
return ret;
}
bool plMaxNode::FirstComponentPass(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
// TEMP
if (IsComponent())
return false;
// End TEMP
bool ret = true;
if (!CanConvert())
return ret;
uint32_t count = NumAttachedComponents();
// Go through all the components attached to this node
for (uint32_t i = 0; i < count; i++)
{
// For each one, call the requested function. If any of the attached components
// return false this function will return false.
plComponentBase *comp = GetAttachedComponent(i);
if (comp->IsExternal())
{
if (!((plComponentExt*)comp)->PreConvert(this, &gComponentTools, pErrMsg))
ret = false;
}
else
{
if (!((plComponent*)comp)->PreConvert(this, pErrMsg))
ret = false;
}
}
return ret;
}
bool plMaxNode::ConvertComponents(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
// TEMP
if (IsComponent())
return false;
// End TEMP
bool ret = true;
char *dbgNodeName = GetName();
if (!CanConvert())
return ret;
uint32_t count = NumAttachedComponents();
// Go through all the components attached to this node
for (uint32_t i = 0; i < count; i++)
{
// For each one, call the requested function. If any of the attached components
// return false this function will return false.
plComponentBase *comp = GetAttachedComponent(i);
if (comp->IsExternal())
{
if (!((plComponentExt*)comp)->Convert(this, &gComponentTools, pErrMsg))
ret = false;
}
else
{
if (!((plComponent*)comp)->Convert(this, pErrMsg))
ret = false;
}
}
return ret;
}
bool plMaxNode::DeInitComponents(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
// TEMP
if (IsComponent())
return false;
// End TEMP
bool ret = true;
char *dbgNodeName = GetName();
if (!CanConvert())
return ret;
uint32_t count = NumAttachedComponents();
// Go through all the components attached to this node
for (uint32_t i = 0; i < count; i++)
{
// For each one, call the requested function. If any of the attached components
// return false this function will return false.
plComponentBase *comp = GetAttachedComponent(i);
if (comp->IsExternal())
{
if (!((plComponentExt*)comp)->DeInit(this, &gComponentTools, pErrMsg))
ret = false;
}
else
{
if (!((plComponent*)comp)->DeInit(this, pErrMsg))
ret = false;
}
}
if( ret )
{
// Now loop through all the plPassMtlBase-derived materials that are applied to this node
// So we can call ConvertDeInit() on them
Mtl *mtl = GetMtl();
if( mtl != nil && !GetParticleRelated() )
{
if( hsMaterialConverter::IsMultiMat( mtl ) || hsMaterialConverter::IsMultipassMat( mtl ) || hsMaterialConverter::IsCompositeMat( mtl ) )
{
int i;
for (i = 0; i < mtl->NumSubMtls(); i++)
{
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl->GetSubMtl( i ) );
if( pass != nil )
{
if( !pass->ConvertDeInit( this, pErrMsg ) )
ret = false;
}
}
}
else
{
plPassMtlBase *pass = plPassMtlBase::ConvertToPassMtl( mtl );
if( pass != nil )
{
if( !pass->ConvertDeInit( this, pErrMsg ) )
ret = false;
}
}
}
}
return ret;
}
bool plMaxNode::ClearData(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaAgeChunk);
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaDistChunk);
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaRoomChunk);
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaMaxNodeDataChunk);
// RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaSceneViewerChunk);
RemoveAppDataChunk(PLASMA_MAX_CLASSID, GUP_CLASS_ID, kPlasmaLightChunk);
return true;
}
// HASAGMOD
// Little special-purpose thing to see if a node has an animation graph modifier on it.
plAGModifier *plMaxNode::HasAGMod()
{
char *name = GetName();
if (CanConvert())
{
plSceneObject *SO = GetSceneObject();
int numMods = SO->GetNumModifiers();
for (int i = 0; i < numMods; i++)
{
const plModifier *mod = SO->GetModifier(i);
if(plAGModifier::ConvertNoRef(mod)) {
return (plAGModifier *)mod;
}
}
}
return nil;
}
plAGMasterMod *plMaxNode::GetAGMasterMod()
{
char *name = GetName();
if (CanConvert())
{
plSceneObject *SO = GetSceneObject();
int numMods = SO->GetNumModifiers();
for (int i = 0; i < numMods; i++)
{
const plModifier *mod = SO->GetModifier(i);
if(plAGMasterMod::ConvertNoRef(mod)) {
return (plAGMasterMod *)mod;
}
}
}
return nil;
}
// SETUPBONESALIASESRECUR
void plMaxNode::SetupBonesAliasesRecur(const char *rootName)
{
if(CanConvert()) {
if (!HasAGMod()) {
plString nameToUse;
// parse UserPropsBuf for entire BoneName line
char localName[256];
TSTR propsBuf;
GetUserPropBuffer(propsBuf);
char* start=strstr(propsBuf, "BoneName=");
if (!start)
start=strstr(propsBuf, "bonename=");
const int len = strlen("BoneName=");
if(start && UserPropExists("BoneName"))
{
start+=len;
int i=0;
while(*start != '\n' && *start)
{
hsAssert(i<256, "localName overflow");
localName[i++]=*start++;
}
localName[i]=0;
nameToUse = plString::FromUtf8(localName);
}
else
{
plString nodeName = plString::FromUtf8(GetName());
// char str[256];
// sprintf(str, "Missing 'BoneName=foo' UserProp, on object %s, using node name", nodeName ? nodeName : "?");
// hsAssert(false, str);
nameToUse = nodeName;
}
/* char aliasName[256];
sprintf(aliasName, "%s_%s", rootName, nameToUse);
plUoid* uoid = hsgResMgr::ResMgr()->FindAlias(aliasName, plSceneObject::Index());
if( !uoid )
{
plAliasModifier* pAlMod = new plAliasModifier;
pAlMod->SetAlias(aliasName);
AddModifier(pAlMod);
}
*/
plAGModifier *mod = new plAGModifier(nameToUse);
AddModifier(mod, plString::FromUtf8(GetName()));
}
}
int j = 0;
for( j = 0; j < NumberOfChildren(); j++ )
((plMaxNode*)GetChildNode(j))->SetupBonesAliasesRecur(rootName);
}
void plMaxNode::SetDISceneNodeSpans( plDrawInterface *di, bool needBlending )
{
// This poorly named function is currently only used by ParticleComponent.
// di->GetNumDrawables() will always be zero. In general, particles only
// need a blending drawable, which can always be index zero since it's the only
// one.
di->SetDrawable( di->GetNumDrawables(),
IGetSceneNodeSpans(plSceneNode::ConvertNoRef( GetRoomKey()->GetObjectPtr() ),
needBlending));
}
plMaxNode* plMaxNode::GetBonesRoot()
{
ISkin* skin = FindSkinModifier();
if( !skin )
return nil;
INode* bone = skin->GetBone(0);
if( !bone )
return nil;
while( !bone->GetParentNode()->IsRootNode() )
bone = bone->GetParentNode();
plMaxNode* boneRoot = (plMaxNode*)bone;
if( !(boneRoot && boneRoot->CanConvert()) )
return nil;
return boneRoot;
}
void plMaxNode::GetBonesRootsRecur(hsTArray<plMaxNode*>& nodes)
{
plMaxNode* bRoot = GetBonesRoot();
if( bRoot )
{
int idx = nodes.Find(bRoot);
if( idx == nodes.kMissingIndex )
nodes.Append(bRoot);
}
int i;
for( i = 0; i < NumberOfChildren(); i++ )
((plMaxNode*)GetChildNode(i))->GetBonesRootsRecur(nodes);
}
plSceneObject* plMaxNode::MakeCharacterHierarchy(plErrorMsg *pErrMsg)
{
plSceneObject* playerRoot = GetSceneObject();
if( pErrMsg->Set(playerRoot->GetDrawInterface() != nil, GetName(), "Non-helper as player root").CheckAndAsk() )
return nil;
const char *playerRootName = GetName();
hsTArray<plMaxNode*> bonesRoots;
int i;
for( i = 0; i < NumberOfChildren(); i++ )
((plMaxNode*)GetChildNode(i))->GetBonesRootsRecur(bonesRoots);
if( pErrMsg->Set(bonesRoots.GetCount() > 1, playerRootName, "Found multiple bones hierarchies").CheckAndAsk() )
return nil;
if( bonesRoots.GetCount() )
{
bonesRoots[0]->SetupBonesAliasesRecur(playerRootName);
plSceneObject* boneRootObj = bonesRoots[0]->GetSceneObject();
if( pErrMsg->Set(boneRootObj == nil, playerRootName, "No scene object for the bones root").CheckAndAsk() )
return nil;
if( boneRootObj != playerRoot )
hsMessageBox("This avatar's bone hierarchy does not have the avatar root node linked as a parent. "
"This may cause the avatar draw incorrectly.", playerRootName, hsMessageBoxNormal);
}
return playerRoot;
}
// Takes all bones found on this node (and any descendents) and sets up a single palette
void plMaxNode::SetupBoneHierarchyPalette(plMaxBoneMap *bones /* = nil */)
{
const char* dbgNodeName = GetName();
if( !CanConvert() )
return;
if (GetBoneMap())
return;
if (bones == nil)
{
bones = new plMaxBoneMap();
bones->fOwner = this;
}
if (UserPropExists("Bone"))
bones->AddBone(this);
int i;
for (i = 0; i < NumBones(); i++)
bones->AddBone(GetBone(i));
SetBoneMap(bones);
for (i = 0; i < NumberOfChildren(); i++)
((plMaxNode*)GetChildNode(i))->SetupBoneHierarchyPalette(bones);
// If we were the one to start this whole thing, then sort all the bones now that
// they've been added.
if (bones->fOwner == this)
bones->SortBones();
}
bool plMaxNode::IsLegalDecal(bool checkParent /* = true */)
{
Mtl *mtl = GetMtl();
if (mtl == nil || GetParticleRelated())
return false;
if (hsMaterialConverter::IsMultiMat(mtl))
{
int i;
for (i = 0; i < mtl->NumSubMtls(); i++)
{
if (!hsMaterialConverter::IsDecalMat(mtl->GetSubMtl(i)))
return false;
}
}
else if (!hsMaterialConverter::IsDecalMat(mtl))
return false;
return true;
}
int plMaxNode::NumUVWChannels()
{
bool deleteIt;
TriObject* triObj = GetTriObject(deleteIt);
if( triObj )
{
Mesh* mesh = &(triObj->mesh);
// note: There's been a bit of a change with UV accounting. There are two variables, numChannels
// and numBlendChannels. The first represents texture UV channels MAX gives us, the latter is
// the number of extra blending channels the current material uses to handle its effects.
// numMaps includes map #0, which is the vertex colors. So subtract 1 to get the # of uv maps...
int numChannels = mesh->getNumMaps() - 1;
if( numChannels > plGeometrySpan::kMaxNumUVChannels )
numChannels = plGeometrySpan::kMaxNumUVChannels;
/// Check the mapping channels. See, MAX likes to tell us we have mapping channels
/// but the actual channel pointer is nil. When MAX tries to render the scene, it says that the
/// object in question has no UV channel. So apparently we have to check numChannels *and*
/// that each mapChannel is non-nil...
int i;
for( i = 0; i < numChannels; i++ )
{
// i + 1 is exactly what IGenerateUVs uses, so I'm not questioning it...
if( mesh->mapFaces( i + 1 ) == nil )
{
numChannels = i;
break;
}
}
int numUsed = hsMaterialConverter::MaxUsedUVWSrc(this, GetMtl());
plLightMapComponent* lmc = GetLightMapComponent();
if( lmc )
{
if( (lmc->GetUVWSrc() < numChannels) && (lmc->GetUVWSrc() >= numUsed) )
numUsed = lmc->GetUVWSrc() + 1;
}
if( numChannels > numUsed )
numChannels = numUsed;
if( GetWaterDecEnv() )
numChannels = 3;
if( deleteIt )
triObj->DeleteThis();
return numChannels;
}
return 0;
}
//// IGet/SetCachedAlphaHackValue ////////////////////////////////////////////
// Pair of functions to handle accessing the TArray cache on plMaxNodeData.
// See AlphaHackLayersNeeded() for details.
int plMaxNode::IGetCachedAlphaHackValue( int iSubMtl )
{
plMaxNodeData *pDat = GetMaxNodeData();
if( pDat == nil )
return -1;
hsTArray<int> *cache = pDat->GetAlphaHackLayersCache();
if( cache == nil )
return -1;
iSubMtl++;
if( iSubMtl >= cache->GetCount() )
return -1;
return (*cache)[ iSubMtl ];
}
void plMaxNode::ISetCachedAlphaHackValue( int iSubMtl, int value )
{
plMaxNodeData *pDat = GetMaxNodeData();
if( pDat == nil )
return;
hsTArray<int> *cache = pDat->GetAlphaHackLayersCache();
if( cache == nil )
{
cache = new hsTArray<int>;
pDat->SetAlphaHackLayersCache( cache );
}
iSubMtl++;
if( iSubMtl >= cache->GetCount() )
{
int i = cache->GetCount();
cache->ExpandAndZero( iSubMtl + 1 );
for( ; i < cache->GetCount(); i++ )
(*cache)[ i ] = -1;
}
(*cache)[ iSubMtl ] = value;
}
//// AlphaHackLayersNeeded ///////////////////////////////////////////////////
// Updated 8.13.02 mcn - Turns out this function is actually very slow, and
// it also happens to be used a lot in testing instanced objects and whether
// they really can be instanced or not. Since the return value of this
// function will be constant after the SetupProperties() pass (and undefined
// before), we cache the value now after the first time we calculate it.
// Note: mf said that putting long comments in are good so long as most of
// them aren't obscenities, so I'm trying to keep the #*$&(*#$ obscenities
// to a minimum here.
int plMaxNode::AlphaHackLayersNeeded(int iSubMtl)
{
const char* dbgNodeName = GetName();
int cached = IGetCachedAlphaHackValue( iSubMtl );
if( cached != -1 )
return cached;
int numVtxOpacChanAvail = VtxAlphaNotAvailable() ? 0 : 1;
int numVtxOpacChanNeeded = hsMaterialConverter::NumVertexOpacityChannelsRequired(this, iSubMtl);
cached = numVtxOpacChanNeeded - numVtxOpacChanAvail;
ISetCachedAlphaHackValue( iSubMtl, cached );
return cached;
}
// Will our lighting pay attention to vertex alpha values?
bool plMaxNode::VtxAlphaNotAvailable()
{
if( NonVtxPreshaded() || GetParticleRelated())
return false;
return true;
}
bool plMaxNode::NonVtxPreshaded()
{
if( GetForceMatShade() )
return false;
if( GetAvatarSO() != nil ||
hsMaterialConverter::Instance().HasMaterialDiffuseOrOpacityAnimation(this) )
return false;
if( GetRunTimeLight() && !hsMaterialConverter::Instance().HasEmissiveLayer(this) )
return true;
return( GetLightMapComponent() != nil );
}
TriObject* plMaxNode::GetTriObject(bool& deleteIt)
{
// Get da object
Object *obj = EvalWorldState(TimeValue(0)).obj;
if( obj == nil )
return nil;
if( !obj->CanConvertToType(triObjectClassID) )
return nil;
// Convert to triMesh object
TriObject *meshObj = (TriObject *)obj->ConvertToType(TimeValue(0), triObjectClassID);
if( meshObj == nil )
return nil;
deleteIt = meshObj != obj;
return meshObj;
}
//// GetNextSoundIdx /////////////////////////////////////////////////////////
// Starting at 0, returns an incrementing index for each maxNode. Useful for
// assigning indices to sound objects attached to the node.
uint32_t plMaxNode::GetNextSoundIdx( void )
{
uint32_t idx = GetSoundIdxCounter();
SetSoundIdxCounter( idx + 1 );
return idx;
}
//// IsPhysical //////////////////////////////////////////////////////////////
// Fun temp hack function to tell if a maxNode is physical. Useful after
// preConvert (checks for a physical on the simInterface)
bool plMaxNode::IsPhysical( void )
{
if( GetSceneObject() && GetSceneObject()->GetSimulationInterface() &&
GetSceneObject()->GetSimulationInterface()->GetPhysical() )
return true;
return false;
}
plPhysicalProps *plMaxNode::GetPhysicalProps()
{
plMaxNodeData *pDat = GetMaxNodeData();
if (pDat)
return pDat->GetPhysicalProps();
return nil;
}
//// FindPageKey /////////////////////////////////////////////////////////////
// Little helper function. Calls FindKey() in the resManager using the location (page) of this node
plKey plMaxNode::FindPageKey( uint16_t classIdx, const plString &name )
{
return hsgResMgr::ResMgr()->FindKey( plUoid( GetLocation(), classIdx, name ) );
}
const char *plMaxNode::GetAgeName()
{
int i;
for (i = 0; i < NumAttachedComponents(); i++)
{
plComponentBase *comp = GetAttachedComponent(i);
if (comp->ClassID() == PAGEINFO_CID)
return ((plPageInfoComponent*)comp)->GetAgeName();
}
return nil;
}
// create a list of keys used by the run-time interface for things like
// determining cursor changes, what kind of object this is, etc.
// we're doing this here because multiple logic triggers can be attached to a
// single object and tracking down all their run-time counterpart objects (who might
// need a message sent to them) is a huge pain and very ugly. This will capture anything
// important in a single list.
bool plMaxNode::MakeIfaceReferences(plErrorMsg *pErrMsg, plConvertSettings *settings)
{
bool ret = true;
char *dbgNodeName = GetName();
if (!CanConvert())
return ret;
uint32_t count = GetSceneObject()->GetNumModifiers();
hsTArray<plKey> keys;
// Go through all the modifiers attached to this node's scene object
// and grab keys for objects who we would need to send interface messages to
for (uint32_t i = 0; i < count; i++)
{
const plModifier* pMod = GetSceneObject()->GetModifier(i);
// right now all we care about are these, but I guarentee you we will
// care about more as the interface gets more complex
const plPickingDetector* pDet = plPickingDetector::ConvertNoRef(pMod);
const plLogicModifier* pLog = plLogicModifier::ConvertNoRef(pMod);
if( pDet )
{
for (int j = 0; j < pDet->GetNumReceivers(); j++)
keys.Append(pDet->GetReceiver(j));
}
else
if( pLog )
{
keys.Append(pLog->GetKey());
}
}
// if there is anything there, create an 'interface object modifier' which simply stores
// the list in a handy form
if (keys.Count())
{
plInterfaceInfoModifier* pMod = new plInterfaceInfoModifier;
plKey modifierKey = hsgResMgr::ResMgr()->NewKey(plString::FromUtf8(GetName()), pMod, GetLocation(), GetLoadMask());
hsgResMgr::ResMgr()->AddViaNotify(modifierKey, new plObjRefMsg(GetSceneObject()->GetKey(), plRefMsg::kOnCreate, -1, plObjRefMsg::kModifier), plRefFlags::kActiveRef);
for(int i = 0; i < keys.Count(); i++)
pMod->AddRefdKey(keys[i]);
}
return ret;
}