/*
** Command & Conquer Generals Zero Hour(tm)
** Copyright 2025 Electronic Arts 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 .
*/
/***********************************************************************************************
*** C O N F I D E N T I A L --- W E S T W O O D S T U D I O S ***
***********************************************************************************************
* *
* Project Name : Commando Tools - WWSkin *
* *
* $Archive:: /Commando/Code/Tools/max2w3d/skin.cpp $*
* *
* $Author:: Greg_h $*
* *
* $Modtime:: 4/24/01 5:15p $*
* *
* $Revision:: 13 $*
* *
*---------------------------------------------------------------------------------------------*
* Functions: *
* - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - */
#include "skin.h"
#include "dllmain.h"
#include "max.h"
#include "simpmod.h"
#include "simpobj.h"
#include "resource.h"
#include "skindata.h"
#include "bpick.h"
#include "namedsel.h"
#include "boneicon.h"
#if defined W3D_MAX4 //defined as in the project (.dsp)
static GenSubObjType _SubObjectTypeVertex(1);
#endif
/*
** Static functions
*/
static BOOL CALLBACK _sot_dialog_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
static BOOL CALLBACK _skeleton_dialog_thunk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
static BOOL CALLBACK _bone_influence_dialog_thunk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam);
static TriObject * Get_Tri_Object(TimeValue t,ObjectState & os,Interval & valid,BOOL & needsdel);
static float Bone_Distance(INode * bone,TimeValue time,const Point3 & vertex);
/*
** Static variables
*/
HWND SkinWSMObjectClass::SotHWND = NULL;
HWND SkinWSMObjectClass::SkeletonHWND = NULL;
HWND SkinWSMObjectClass::BoneListHWND = NULL;
IObjParam * SkinWSMObjectClass::InterfacePtr = NULL;
ICustButton * SkinWSMObjectClass::AddBonesButton = NULL;
ICustButton * SkinWSMObjectClass::RemoveBonesButton = NULL;
ISpinnerControl * SkinWSMObjectClass::BasePoseSpin = NULL;
/*******************************************************************************
**
** Class Descriptor for SkinWSMObjectClass
**
*******************************************************************************/
class SkinWSMObjectClassDesc:public ClassDesc
{
public:
int IsPublic() { return 1; }
void * Create(BOOL loading = FALSE) { return new SkinWSMObjectClass; }
const TCHAR * ClassName() { return _T("WWSkin"); }
SClass_ID SuperClassID() { return WSM_OBJECT_CLASS_ID; }
Class_ID ClassID() { return SKIN_OBJ_CLASS_ID; }
const TCHAR* Category() { return _T("Westwood Space Warps"); }
};
static SkinWSMObjectClassDesc _SkinWSMObjectDesc;
ClassDesc * Get_Skin_Obj_Desc() { return &_SkinWSMObjectDesc; }
/*******************************************************************************
**
** Class Descriptor for the SkinModifier
**
*******************************************************************************/
class SkinModClassDesc:public ClassDesc
{
public:
int IsPublic() { return 0; }
void * Create(BOOL loading = FALSE) { return new SkinModifierClass; }
const TCHAR * ClassName() { return _T("WWSkin"); }
SClass_ID SuperClassID() { return WSM_CLASS_ID; }
Class_ID ClassID() { return SKIN_MOD_CLASS_ID; }
const TCHAR * Category() { return _T("Westwood Space Warps"); }
};
static SkinModClassDesc _SkinModDesc;
ClassDesc * Get_Skin_Mod_Desc() { return &_SkinModDesc; }
/*******************************************************************************
**
** SkinWSMObjectCreateCallback
** A class derived from CreateMouseCallBack to handle
** the user input during the creation phase of the SkinWSMObject.
**
*******************************************************************************/
class SkinWSMObjectCreateCallBack : public CreateMouseCallBack
{
public:
int proc( ViewExp * vpt,int msg, int point, int flags, IPoint2 m, Matrix3 & mat)
{
if (msg == MOUSE_POINT) {
Point3 pos = vpt->GetPointOnCP(m);
mat.IdentityMatrix();
mat.SetTrans(pos);
return CREATE_STOP;
}
return TRUE;
}
};
static SkinWSMObjectCreateCallBack _SkinCreateCB;
/*******************************************************************************
**
** SkinWSMObjectClass
**
*******************************************************************************/
SkinWSMObjectClass::SkinWSMObjectClass()
{
/*
** Initialize class variables to default state!
*/
MeshBuilt = FALSE;
BoneSelectionMode = BONE_SEL_MODE_NONE;
BoneTab.SetCount(0);
BasePoseFrame = 0;
pblock = NULL;
}
SkinWSMObjectClass::~SkinWSMObjectClass(void)
{
assert(!((InterfacePtr == NULL) && (SotHWND != NULL)));
if (SotHWND != NULL) {
InterfacePtr->UnRegisterDlgWnd(SotHWND);
InterfacePtr->DeleteRollupPage(SotHWND);
SotHWND = NULL;
}
assert(!((InterfacePtr == NULL) && (SkeletonHWND != NULL)));
if (SkeletonHWND != NULL) {
InterfacePtr->UnRegisterDlgWnd(SkeletonHWND);
InterfacePtr->DeleteRollupPage(SkeletonHWND);
SkeletonHWND = NULL;
}
}
void SkinWSMObjectClass::BeginEditParams(IObjParam *ip, ULONG flags,Animatable *prev)
{
SimpleWSMObject::BeginEditParams(ip,flags,prev);
/*
** save off a copy of the interface pointer
*/
InterfacePtr = ip;
/*
** Install the "supports objects of type" rollup
*/
if (SotHWND == NULL) {
SotHWND = ip->AddRollupPage(
AppInstance,
MAKEINTRESOURCE(IDD_SKIN_SOT),
_sot_dialog_proc,
Get_String(IDS_SOT),
(LPARAM)InterfacePtr,
APPENDROLL_CLOSED);
} else {
SetWindowLong(SotHWND,GWL_USERDATA,(LPARAM)ip);
}
/*
** Install the skeleton rollup
*/
if (SkeletonHWND == NULL) {
SkeletonHWND = InterfacePtr->AddRollupPage(
AppInstance,
MAKEINTRESOURCE(IDD_SKELETON_PARAMETERS),
_skeleton_dialog_thunk,
Get_String(IDS_SKELETON_PARAMETERS),
(LPARAM)this,
0);
} else {
SetWindowLong(SkeletonHWND,GWL_USERDATA,(LPARAM)this);
}
Update_Bone_List();
}
void SkinWSMObjectClass::EndEditParams(IObjParam *ip, ULONG flags,Animatable *next)
{
SimpleWSMObject::EndEditParams(ip,flags,next);
if (flags & END_EDIT_REMOVEUI) {
/*
** Remove the Sot rollup
*/
if (SotHWND != NULL) {
InterfacePtr->UnRegisterDlgWnd(SotHWND);
InterfacePtr->DeleteRollupPage(SotHWND);
SotHWND = NULL;
}
/*
** Remove the info rollup
*/
if (SkeletonHWND != NULL) {
InterfacePtr->UnRegisterDlgWnd(SkeletonHWND);
InterfacePtr->DeleteRollupPage(SkeletonHWND);
SkeletonHWND = NULL;
}
}
/*
** get rid of our copy of the interface pointer
*/
InterfacePtr = NULL;
}
RefTargetHandle SkinWSMObjectClass::Clone(RemapDir & remap)
{
/*
** create another SkinWSMObject and return it.
*/
SkinWSMObjectClass * sobj = new SkinWSMObjectClass();
return(sobj);
}
RefTargetHandle SkinWSMObjectClass::GetReference(int i)
{
/*
** return reference "i". If i==0, the reference belongs
** to SimpleWSMObject so thunk down to it.
*/
if (i < SimpleWSMObject::NumRefs()) {
return SimpleWSMObject::GetReference(i);
}
/*
** The rest of the references are ours.
*/
int boneidx = To_Bone_Index(i);
return BoneTab[boneidx];
}
void SkinWSMObjectClass::SetReference(int i, RefTargetHandle rtarg)
{
if (i < SimpleWSMObject::NumRefs()) {
SimpleWSMObject::SetReference(i,rtarg);
} else {
int boneidx = To_Bone_Index(i);
assert(boneidx >= 0);
assert(boneidx < BoneTab.Count());
BoneTab[boneidx] = (INode *)rtarg;
}
}
RefResult SkinWSMObjectClass::NotifyRefChanged(Interval changeInt,RefTargetHandle hTarget,PartID& partID, RefMessage message)
{
int i;
switch (message) {
case REFMSG_TARGET_DELETED:
for (i=0; isetVerts(a, b, c);
f->setSmGroup(0);
f->setEdgeVisFlags(1,1,1);
}
void SkinWSMObjectClass::User_Picked_Bone(INode * node)
{
// TODO: Undo/Redo!
switch (BoneSelectionMode) {
case BONE_SEL_MODE_ADD:
Add_Bone(node);
break;
case BONE_SEL_MODE_REMOVE:
Remove_Bone(node);
break;
default:
assert(0);
}
Set_Bone_Selection_Mode(BONE_SEL_MODE_NONE);
Update_Bone_List();
}
void SkinWSMObjectClass::User_Picked_Bones(INodeTab & nodetab)
{
// TODO: Undo/Redo!
switch (BoneSelectionMode) {
case BONE_SEL_MODE_ADD_MANY:
Add_Bones(nodetab);
break;
case BONE_SEL_MODE_REMOVE_MANY:
Remove_Bones(nodetab);
break;
default:
assert(0);
}
Set_Bone_Selection_Mode(BONE_SEL_MODE_NONE);
Update_Bone_List();
}
void SkinWSMObjectClass::Set_Bone_Selection_Mode(int mode)
{
assert(mode >= BONE_SEL_MODE_NONE);
assert(mode <= BONE_SEL_MODE_REMOVE_MANY);
/*
** store the selection mode
*/
BoneSelectionMode = mode;
/*
** update the dialog box buttons
*/
AddBonesButton->SetCheck(mode == BONE_SEL_MODE_ADD_MANY);
RemoveBonesButton->SetCheck(mode == BONE_SEL_MODE_REMOVE_MANY);
}
int SkinWSMObjectClass::Add_Bone(INode * node)
{
int refidx;
int boneidx;
/*
** If we already have this bone, just return
*/
boneidx = Find_Bone(node);
if (boneidx != -1) {
return boneidx;
}
/*
** Otherwise, look for a NULL bone and we'll re-use
** its slot. This happens when a user removes a bone or
** a bone in the scene is deleted.
*/
boneidx = Find_Bone(NULL);
if (boneidx != -1) {
refidx = To_Ref_Index(boneidx);
MakeRefByID(FOREVER,refidx,node);
return boneidx;
}
/*
** If we made it here, add the bone to the end of the
** reference array.
*/
BoneTab.Append(1,&node);
boneidx = BoneTab.Count() - 1;
refidx = To_Ref_Index(boneidx);
MakeRefByID(FOREVER,refidx,node);
return boneidx;
}
void SkinWSMObjectClass::Add_Bones(INodeTab & nodetab)
{
/*
** Add each bone individually
*/
for (int i=0; iGetName());
}
}
}
int SkinWSMObjectClass::Find_Bone(INode * node)
{
for (int i=0; i 0) {
isave->BeginChunk(NUM_BONES_CHUNK);
isave->Write(&numbones,sizeof(ULONG),&nb);
isave->EndChunk();
}
return IO_OK;
}
IOResult SkinWSMObjectClass::Load(ILoad * iload)
{
SimpleWSMObject::Load(iload);
IOResult res;
ULONG nb;
int level = -1;
while (IO_OK==(res=iload->OpenChunk())) {
switch (iload->CurChunkID()) {
case NUM_BONES_CHUNK: {
ULONG numbones;
res = iload->Read(&numbones,sizeof(numbones),&nb);
BoneTab.SetCount(numbones);
for (int i=0; iCloseChunk();
if (res!=IO_OK) {
return res;
}
}
return IO_OK;
}
int SkinWSMObjectClass::Find_Closest_Bone(const Point3 & vertex)
{
float mindist = 10000.0f;
int minindex = -1;
TimeValue basetime = Get_Base_Pose_Time();
for (int boneidx = 0; boneidx < BoneTab.Count(); boneidx++) {
if (BoneTab[boneidx] == NULL) continue;
float bonedist = Bone_Distance(BoneTab[boneidx],basetime,vertex);
if (bonedist < mindist) {
mindist = bonedist;
minindex = boneidx;
}
}
return minindex;
}
/*******************************************************************************
**
** SkinModifierClass
**
*******************************************************************************/
SkinModifierClass::SkinModifierClass(void)
{
Default_Init();
}
SkinModifierClass::SkinModifierClass(INode * node,SkinWSMObjectClass * skin_obj)
{
Default_Init();
/*
** Make the reference to the space warp node.
*/
MakeRefByID(FOREVER,NODE_REF,node);
/*
** Make reference to the WSMObject
*/
MakeRefByID(FOREVER,OBJ_REF,skin_obj);
}
void SkinModifierClass::Default_Init(void)
{
SubObjSelLevel = VERTEX_SEL_LEVEL;
WSMObjectRef = NULL;
WSMNodeRef = NULL;
InterfacePtr = NULL;
BoneInfluenceHWND = NULL;
LinkButton = NULL;
LinkByNameButton = NULL;
AutoLinkButton = NULL;
UnLinkButton = NULL;
}
RefTargetHandle SkinModifierClass::Clone(RemapDir & remap)
{
SkinModifierClass * newmod = new SkinModifierClass(WSMNodeRef,(SkinWSMObjectClass *)WSMObjectRef);
return newmod;
}
void SkinModifierClass::BeginEditParams(IObjParam * ip, ULONG flags,Animatable * prev)
{
static int i=0;
i++;
/*
** Grab a copy of the interface pointer
*/
InterfacePtr = ip;
/*
** allocate the selection command mode for use in vertex selection
*/
SelectMode = new SelectModBoxCMode(this,InterfacePtr);
/*
** register the desired sub-object selection types.
*/
const TCHAR * ptype[] = { "Vertices" };
#if defined W3D_MAX4 //defined as in the project (.dsp)
InterfacePtr->SetSubObjectLevel(1);
#else
//---This call is obsolete from version 4.
InterfacePtr->RegisterSubObjectTypes( ptype, 1);
#endif
/*
** Restore the selection level.
*/
ip->SetSubObjectLevel(SubObjSelLevel);
}
void SkinModifierClass::EndEditParams(IObjParam *ip, ULONG flags,Animatable *next)
{
/*
** just checking...
*/
assert(ip == InterfacePtr);
/*
** Make sure we clear out the pick mode
*/
InterfacePtr->ClearPickMode();
/*
** remove and deallocate the selection command mode
*/
InterfacePtr->DeleteMode(SelectMode);
if (SelectMode ) delete SelectMode;
SelectMode = NULL;
/*
** Remove the rollup window(s) if needed
*/
if (flags & END_EDIT_REMOVEUI) {
Remove_Bone_Influence_Dialog();
}
/*
** Make sure we don't hang onto an invalid interface
*/
InterfacePtr = NULL;
}
Interval SkinModifierClass::Get_Validity(TimeValue t)
{
/*
** Start with an infinite interval and chop it down
** using the validity intervals of each of the controlling bones
*/
Interval valid = FOREVER;
/*
** Now intersect the validity with the validities of all of
** the controlling bones.
*/
SkinWSMObjectClass * obj = (SkinWSMObjectClass *)Get_WSMObject();
// for (int i=0; iNum_Bones(); i++) {
// valid &= obj->Get_Bone(i)->tmValid(); //TODO: is this right?
// }
// return valid;
return Interval(t,t+1); //KLUDGE - only valid for this frame
}
RefTargetHandle SkinModifierClass::GetReference(int i)
{
switch (i) {
case OBJ_REF: return WSMObjectRef;
case NODE_REF: return WSMNodeRef;
default: return NULL;
}
}
void SkinModifierClass::SetReference(int i, RefTargetHandle rtarg)
{
switch (i) {
case OBJ_REF: WSMObjectRef = (SkinWSMObjectClass *)rtarg; break;
case NODE_REF: WSMNodeRef = (INode *)rtarg; break;
}
}
RefResult SkinModifierClass::NotifyRefChanged(Interval changeInt, RefTargetHandle hTarget, PartID& partID, RefMessage message)
{
switch (message) {
case REFMSG_TARGET_DELETED:
/*
** This means the WSM node is being deleted. As a result,
** we must delete ourselves.
*/
DeleteMe(); // also deletes all refs and
// sends REFMSG_TARGET_DELETED to all Dependents
return REF_STOP;
}
return(REF_SUCCEED);
}
void SkinModifierClass::ModifyObject(TimeValue t, ModContext & mc, ObjectState * os, INode * node)
{
/*
** Get a TriObject from the object state
*/
assert(os->obj->IsSubClassOf(triObjectClassID));
TriObject *triobj = (TriObject *)os->obj;
/*
** Get the skin data from the ModContext.
*/
SkinDataClass * skindata = (SkinDataClass *)mc.localData;
/*
** If there is no skin data, allocate it
** Also, do an initial auto attach.
*/
if (skindata == NULL) {
mc.localData = skindata = new SkinDataClass(&triobj->mesh);
}
if (!skindata->IsValid()) {
skindata->Validate(&triobj->mesh);
}
/*
** If in vertex selection mode, tell the mesh to display the
** selected vertices and turn on vertex tick marks. Otherwise
** make sure vertex tick marks are off.
*/
if (SubObjSelLevel == VERTEX_SEL_LEVEL) {
triobj->mesh.vertSel = skindata->VertSel;
triobj->mesh.SetDispFlag(DISP_VERTTICKS|DISP_SELVERTS);
if (triobj->mesh.selLevel != MESH_VERTEX) {
triobj->mesh.selLevel = MESH_VERTEX;
}
} else {
triobj->mesh.selLevel = MESH_OBJECT;
triobj->mesh.ClearDispFlag(DISP_VERTTICKS|DISP_SELVERTS);
}
/*
** Loop through the points in the deformable object
*/
for (int vidx = 0; vidx < triobj->NumPoints(); vidx++) {
// TODO: Allow multiple bone influences here...
// issues - UI to set the weights, rebalance weights whenever
// a bone is deleted, should also then never get NULL bones
// and remove the need to check for NULL bones in this routine...
/*
** Get a pointer to the bone that this vertex is attached to
*/
InfluenceStruct * inf = &(skindata->VertData[vidx]);
int boneidx = inf->BoneIdx[0];
if ((boneidx != -1) && (boneidx < WSMObjectRef->Num_Bones())) {
INode * bone = WSMObjectRef->Get_Bone(inf->BoneIdx[0]);
if (bone == NULL) {
/*
** this bone has gone away for some reason so
** clear this vert's bone influence index
*/
inf->BoneIdx[0] = -1;
} else {
/*
** Ok, got the bone, now transform the point and
** give it back to the mesh
*/
Point3 pnew;
Matrix3 tm;
pnew = triobj->GetPoint(vidx);
if (os->GetTM()) {
tm = *(os->GetTM());
} else {
tm.IdentityMatrix();
}
pnew = tm * pnew;
TimeValue basetime = WSMObjectRef->Get_Base_Pose_Time();
Matrix3 basetm = bone->GetObjectTM(basetime);
Matrix3 curtm = bone->GetObjectTM(t);
pnew = (pnew * Inverse(basetm)) * curtm;
pnew = Inverse(tm) * pnew;
triobj->SetPoint(vidx,pnew);
}
}
}
/*
** Tell the object that points were changed
*/
triobj->PointsWereChanged();
/*
** Set the validity of the updated geometry data
*/
triobj->UpdateValidity(GEOM_CHAN_NUM,Get_Validity(t));
}
IOResult SkinModifierClass::Save(ISave * isave)
{
ULONG nb;
Modifier::Save(isave);
/*
** Save the sub object selection level
*/
short sl = SubObjSelLevel;
isave->BeginChunk(SEL_LEVEL_CHUNK);
isave->Write(&sl,sizeof(short),&nb);
isave->EndChunk();
return IO_OK;
}
IOResult SkinModifierClass::Load(ILoad * iload)
{
Modifier::Load(iload);
IOResult res;
ULONG nb;
int level = -1;
while (IO_OK==(res=iload->OpenChunk())) {
switch (iload->CurChunkID()) {
case SEL_LEVEL_CHUNK: {
short sl;
res = iload->Read(&sl,sizeof(short),&nb);
SubObjSelLevel = sl;
}
break;
}
iload->CloseChunk();
if (res!=IO_OK) {
return res;
}
}
return IO_OK;
}
IOResult SkinModifierClass::SaveLocalData(ISave *isave, LocalModData *ld)
{
SkinDataClass * skindata = (SkinDataClass *)ld;
return skindata->Save(isave);
}
IOResult SkinModifierClass::LoadLocalData(ILoad *iload, LocalModData **pld)
{
/*
** Create a new SkinDataClass
*/
if (*pld==NULL) {
*pld = (SkinDataClass *) new SkinDataClass();
}
SkinDataClass * newskin = (SkinDataClass *)*pld;
/*
** Initialize it from ILoad...
*/
return newskin->Load(iload);
}
void SkinModifierClass::ActivateSubobjSel(int level, XFormModes & modes)
{
/*
** Storing the current sub-object selection level
*/
SubObjSelLevel = level;
/*
** Set the appropriate command mode. We only want selection.
*/
switch (SubObjSelLevel)
{
case OBJECT_SEL_LEVEL:
Remove_Bone_Influence_Dialog();
break;
case VERTEX_SEL_LEVEL: // Modifying Vertices
modes = XFormModes(NULL,NULL,NULL,NULL,NULL,SelectMode);
Install_Bone_Influence_Dialog();
break;
}
/*
** Put our named subobject selection sets into the drop down list
*/
Create_Named_Selection_Sets();
/*
** Notify our dependents that the subselection type,
** and the display have changed
*/
NotifyDependents(FOREVER, PART_SUBSEL_TYPE|PART_DISPLAY, REFMSG_CHANGE);
/*
** Notify the pipeline that the selection level has changed.
*/
InterfacePtr->PipeSelLevelChanged();
/*
** Notify our dependents that the selection channel,
** display attributes, and subselection type channels have changed
*/
NotifyDependents(FOREVER, SELECT_CHANNEL|DISP_ATTRIB_CHANNEL|SUBSEL_TYPE_CHANNEL, REFMSG_CHANGE);
}
int SkinModifierClass::HitTest
(
TimeValue t,
INode * inode,
int type,
int crossing,
int flags,
IPoint2 * p,
ViewExp * vpt,
ModContext * mc
)
{
Interval valid = FOREVER;
int needsdel;
int savedLimits;
int res = 0;
HitRegion hr;
Matrix3 mat;
MakeHitRegion(hr,type, crossing,4,p);
mat = inode->GetObjectTM(t);
/*
** Set up the graphics window to do the hit test
*/
GraphicsWindow *gw = vpt->getGW();
gw->setHitRegion(&hr);
gw->setTransform(mat);
gw->setRndLimits(((savedLimits = gw->getRndLimits()) | GW_PICK) & ~GW_ILLUM);
if (1 /*IgnoreBackfaces*/) {
gw->setRndLimits(gw->getRndLimits() | GW_BACKCULL);
} else {
gw->setRndLimits(gw->getRndLimits() & ~GW_BACKCULL);
}
gw->clearHitCode();
/*
** Do the hit test!
*/
SubObjHitList hitlist;
MeshSubHitRec * rec;
ObjectState os = inode->EvalWorldState(InterfacePtr->GetTime());
TriObject * tobj = Get_Tri_Object(InterfacePtr->GetTime(),os,valid,needsdel);
res = tobj->mesh.SubObjectHitTest(gw,gw->getMaterial(),&hr,flags | SUBHIT_VERTS,hitlist);
/*
** Record all of the hits
*/
rec = hitlist.First();
while (rec)
{
/*
** rec->index is the index of vertex which was hit!
** Remember that we are always turning on vertex hit testing;
** if we were testing for edges, index would be the edge index.
*/
vpt->LogHit(inode,mc,rec->dist,rec->index,NULL);
rec = rec->Next();
}
/*
** Cleanup
*/
gw->setRndLimits(savedLimits);
if (needsdel) {
tobj->DeleteThis();
}
return res;
}
void SkinModifierClass::SelectSubComponent(HitRecord *hitRec, BOOL selected, BOOL all, BOOL invert)
{
SkinDataClass * skindata = NULL;
int count = 0;
switch (SubObjSelLevel) {
case VERTEX_SEL_LEVEL:
while (hitRec) {
skindata = (SkinDataClass *)hitRec->modContext->localData;
/*
** Undo/Redo functionality
*/
#if 0
if (theHold.Holding() && !SelData->held) {
theHold.Put(new SubSelRestore(this,SelData));
}
theHold.Accept(_T("Select Vertex"));
#endif
BitArray * array = &(skindata->VertSel);
if (all & invert) {
/*
** hitRec->hitInfo is the MeshSubHitRec::index that was stored in the
** HitTest method through LogHit
*/
if ((*array)[hitRec->hitInfo]) {
array->Clear(hitRec->hitInfo);
} else {
array->Set(hitRec->hitInfo,selected);
}
} else {
array->Set(hitRec->hitInfo,selected);
}
if (!all) break;
hitRec = hitRec->Next();
}
break;
}
NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
}
void SkinModifierClass::ClearSelection(int selLevel)
{
int needsdel = 0;
Interval valid = FOREVER;
ModContextList mcList;
INodeTab nodes;
if (!InterfacePtr ) return;
InterfacePtr->GetModContexts(mcList,nodes);
InterfacePtr->ClearCurNamedSelSet();
for (int i = 0; i < mcList.Count(); i++) {
SkinDataClass * skindata = (SkinDataClass *)mcList[i]->localData;
if (skindata==NULL) continue;
ObjectState os = nodes[i]->EvalWorldState(InterfacePtr->GetTime());
TriObject * tobj = Get_Tri_Object(InterfacePtr->GetTime(),os,valid,needsdel);
switch (SubObjSelLevel) {
#if 0
case OBJECT_SEL_LEVEL:
assert(0);
return;
#endif
case VERTEX_SEL_LEVEL:
#if 0 // undo/redo
if (theHold.Holding()) {
theHold.Put(new VertexSelRestore(meshData,this));
}
#endif
tobj->mesh.vertSel.ClearAll();
skindata->VertSel.ClearAll();
break;
}
if (needsdel) {
tobj->DeleteThis();
}
}
/*
** Get rid of the temporary copies of the INodes.
*/
nodes.DisposeTemporary();
/*
** Tell our dependents that the selection set has changed
*/
NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
}
void SkinModifierClass::SelectAll(int selLevel)
{
int needsdel = 0;
Interval valid = FOREVER;
ModContextList mclist;
INodeTab nodes;
if (!InterfacePtr) return;
InterfacePtr->GetModContexts(mclist,nodes);
InterfacePtr->ClearCurNamedSelSet();
for (int i = 0; i < mclist.Count(); i++) {
SkinDataClass * skindata = (SkinDataClass *)mclist[i]->localData;
if (skindata==NULL) continue;
ObjectState os = nodes[i]->EvalWorldState(InterfacePtr->GetTime());
TriObject * tobj = Get_Tri_Object(InterfacePtr->GetTime(),os,valid,needsdel);
switch (SubObjSelLevel) {
case OBJECT_SEL_LEVEL:
assert(0);
return;
case VERTEX_SEL_LEVEL:
#if 0 // undo/redo
if (theHold.Holding()) {
theHold.Put(new VertexSelRestore(meshData,this));
}
#endif
tobj->mesh.vertSel.SetAll();
skindata->VertSel.SetAll();
break;
}
if (needsdel) {
tobj->DeleteThis();
}
}
/*
** Get rid of the temporary copies of the INodes.
*/
nodes.DisposeTemporary();
/*
** Tell our dependents that the selection set has changed
*/
NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
}
void SkinModifierClass::InvertSelection(int selLevel)
{
int needsdel = 0;
Interval valid = FOREVER;
ModContextList mclist;
INodeTab nodes;
if (!InterfacePtr) return;
InterfacePtr->GetModContexts(mclist,nodes);
InterfacePtr->ClearCurNamedSelSet();
for (int i = 0; i < mclist.Count(); i++) {
SkinDataClass * skindata = (SkinDataClass *)mclist[i]->localData;
if (skindata==NULL) continue;
ObjectState os = nodes[i]->EvalWorldState(InterfacePtr->GetTime());
TriObject * tobj = Get_Tri_Object(InterfacePtr->GetTime(),os,valid,needsdel);
switch (SubObjSelLevel) {
case OBJECT_SEL_LEVEL:
assert(0);
return;
case VERTEX_SEL_LEVEL:
#if 0 // undo/redo
if (theHold.Holding()) {
theHold.Put(new VertexSelRestore(meshData,this));
}
#endif
for (int j=0; jmesh.vertSel.GetSize(); j++) {
if (tobj->mesh.vertSel[j]) tobj->mesh.vertSel.Clear(j);
else tobj->mesh.vertSel.Set(j);
}
skindata->VertSel = tobj->mesh.vertSel;
break;
}
if (needsdel) {
tobj->DeleteThis();
}
}
/*
** Get rid of the temporary copies of the INodes.
*/
nodes.DisposeTemporary();
/*
** Tell our dependents that the selection set has changed
*/
NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
}
void SkinModifierClass::User_Picked_Bone(INode * node)
{
assert(InterfacePtr != NULL);
/*
** Get a pointer to the ModContext and SkinData for
** the mesh currently being messed with.
*/
ModContext * mc = NULL;
ModContextList mclist;
INodeTab nodelist;
InterfacePtr->GetModContexts(mclist,nodelist);
/*
** This seems wrong... But I always get only one ModContext and
** it is the one that I want so I'll just use it...
** I believe that OS Modifiers can get multiple ones but WS modifiers
** don't
*/
mc = mclist[0];
assert(mc != NULL);
SkinDataClass * skindata = (SkinDataClass *)(mc->localData);
/*
** Add this bone to the influences of all selected vertices
*/
int boneidx = WSMObjectRef->Find_Bone(node);
assert(boneidx != -1);
skindata->Add_Influence(boneidx);
/*
** Recreate all of the named selection sets!
*/
Create_Named_Selection_Sets();
/*
** Update dependents and redraw the views.
*/
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
InterfacePtr->RedrawViews(InterfacePtr->GetTime());
}
void SkinModifierClass::User_Picked_Bones(INodeTab & nodetab)
{
/*
** One by one, add the selected bones to the influences of
** all selected vertices.
*/
for (int i=0; iGetModContexts(mclist,nodes);
for (int i = 0; i < mclist.Count(); i++) {
SkinDataClass * skindata = (SkinDataClass *)mclist[i]->localData;
if (!skindata) continue;
int index = skindata->VertSelSets.Find_Set(setname);
if (index < 0) continue;
int needsdel;
Interval valid;
ObjectState os = nodes[i]->EvalWorldState(InterfacePtr->GetTime());
TriObject * tobj = Get_Tri_Object(InterfacePtr->GetTime(),os,valid,needsdel);
Mesh * mesh = &(tobj->mesh);
// TODO: undo redo
#if 0
if (theHold.Holding()) {
theHold.Put(new VertexSelRestore(meshData,this));
}
#endif
if (skindata->VertSelSets[index].GetSize() != mesh->getNumVerts()) {
skindata->VertSelSets[index].SetSize(mesh->getNumVerts(),TRUE);
}
mesh->vertSel = skindata->VertSelSets[index];
skindata->VertSel = mesh->vertSel;
if (needsdel) {
tobj->DeleteThis();
}
}
nodes.DisposeTemporary();
NotifyDependents(FOREVER, PART_SELECT, REFMSG_CHANGE);
InterfacePtr->RedrawViews(InterfacePtr->GetTime());
}
void SkinModifierClass::NewSetFromCurSel(TSTR &setname)
{
Install_Named_Selection_Sets();
}
void SkinModifierClass::RemoveSubSelSet(TSTR &setname)
{
Install_Named_Selection_Sets();
}
void SkinModifierClass::Create_Named_Selection_Sets(void)
{
/*
** This function creates a named selection set of vertices
** for each bone in the skeleton.
*/
if (InterfacePtr == NULL) return;
SkinWSMObjectClass * skinobj = WSMObjectRef;
if (skinobj == NULL) return;
ModContextList mclist;
INodeTab nodes;
InterfacePtr->GetModContexts(mclist,nodes);
SkinDataClass * skindata = (SkinDataClass *)mclist[0]->localData;
if (skindata == NULL) return;
/*
** Clear out the old selection sets
*/
skindata->VertSelSets.Reset();
/*
** Create and add a set for each bone
*/
for (int boneidx = 0; boneidx < skinobj->Num_Bones(); boneidx++) {
if (skinobj->Get_Bone(boneidx) != NULL) {
BitArray boneverts;
boneverts.SetSize(skindata->VertData.Count());
for (int vertidx = 0; vertidx < skindata->VertData.Count(); vertidx++) {
if (skindata->VertData[vertidx].BoneIdx[0] == boneidx) boneverts.Set(vertidx);
else boneverts.Clear(vertidx);
}
TSTR bonename = skinobj->Get_Bone(boneidx)->GetName();
skindata->VertSelSets.Append_Set(boneverts,bonename);
}
}
Install_Named_Selection_Sets();
nodes.DisposeTemporary();
}
void SkinModifierClass::Install_Named_Selection_Sets(void)
{
/*
** If we are in sub-object selection mode add the sets
** to the drop down box.
*/
if ((SubObjSelLevel == VERTEX_SEL_LEVEL) && (InterfacePtr != NULL)) {
ModContextList mclist;
INodeTab nodes;
InterfacePtr->GetModContexts(mclist,nodes);
SkinDataClass * skindata = (SkinDataClass *)mclist[0]->localData;
if (skindata == NULL) return;
InterfacePtr->ClearSubObjectNamedSelSets();
for (int i=0; i < skindata->VertSelSets.Count(); i++) {
InterfacePtr->AppendSubObjectNamedSelSet(*skindata->VertSelSets.Names[i]);
}
nodes.DisposeTemporary();
}
}
void SkinModifierClass::Auto_Attach_Verts(BOOL all)
{
assert(InterfacePtr);
/*
** Get the skin data.
*/
ModContextList mclist;
INodeTab nodes;
InterfacePtr->GetModContexts(mclist,nodes);
SkinDataClass * skindata = (SkinDataClass *)mclist[0]->localData;
if (skindata == NULL) return;
/*
** get the skin WSM object.
*/
SkinWSMObjectClass * skinobj = WSMObjectRef;
if (skinobj == NULL) return;
/*
** Get a triobject representing the object state in the base pose.
*/
Interval valid;
BOOL needsdel;
TimeValue basetime = WSMObjectRef->Get_Base_Pose_Time();
ObjectState os = nodes[0]->EvalWorldState(basetime);
TriObject * triobj = Get_Tri_Object(basetime,os,valid,needsdel);
/*
** Attach each selected vertex (or all of them) to their closest bone.
*/
for (int vertidx = 0; vertidx < skindata->VertData.Count(); vertidx++){
if (skindata->VertSel[vertidx] || all) {
Point3 vert = triobj->GetPoint(vertidx);
if (os.GetTM()) vert = vert * (*os.GetTM());
int boneidx = skinobj->Find_Closest_Bone(vert);
skindata->VertData[vertidx].Set_Influence(boneidx);
}
}
/*
** Re-create the named selection sets
*/
Create_Named_Selection_Sets();
/*
** Update dependents and redraw the views.
*/
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
InterfacePtr->RedrawViews(InterfacePtr->GetTime());
/*
** Cleanup...
*/
nodes.DisposeTemporary();
if (needsdel) {
triobj->DeleteThis();
}
}
void SkinModifierClass::Unlink_Verts(void)
{
assert(InterfacePtr);
/*
** Get the skin data.
*/
ModContextList mclist;
INodeTab nodes;
InterfacePtr->GetModContexts(mclist,nodes);
SkinDataClass * skindata = (SkinDataClass *)mclist[0]->localData;
if (skindata == NULL) return;
/*
** Unlink each selected vertex (give them bone index -1)
*/
for (int vertidx = 0; vertidx < skindata->VertData.Count(); vertidx++){
if (skindata->VertSel[vertidx]) {
skindata->VertData[vertidx].Set_Influence(-1);
}
}
/*
** Re-create the named selection sets
*/
Create_Named_Selection_Sets();
/*
** Update dependents and redraw the views.
*/
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
InterfacePtr->RedrawViews(InterfacePtr->GetTime());
/*
** Cleanup...
*/
nodes.DisposeTemporary();
}
/****************************************************************************
**
** DIALOG BOX JUNK
**
****************************************************************************/
void SkinModifierClass::Install_Bone_Influence_Dialog(void)
{
if (BoneInfluenceHWND != NULL) return;
/*
** loading resource string for the name of the dialog
*/
static int loaded = 0;
static TCHAR string[MAX_STRING_LENGTH];
if (!loaded) {
LoadString(AppInstance,IDS_BONE_INFLUENCE_PARAMS,string,MAX_STRING_LENGTH);
loaded = 1;
}
/*
** Put up the UI that is used to assign vertices to bones
*/
BoneInfluenceHWND = InterfacePtr->AddRollupPage(
AppInstance,
MAKEINTRESOURCE(IDD_BONE_INFLUENCE_PARAMS),
_bone_influence_dialog_thunk,
string,
(LPARAM)this,
0);
}
void SkinModifierClass::Remove_Bone_Influence_Dialog(void)
{
/*
** If it is currently up, remove the bone influences dialog
*/
if (BoneInfluenceHWND != NULL) {
InterfacePtr->UnRegisterDlgWnd(BoneInfluenceHWND);
InterfacePtr->DeleteRollupPage(BoneInfluenceHWND);
BoneInfluenceHWND = NULL;
}
}
/*********************************************************************************
*
* _sot_dialog_proc
*
*********************************************************************************/
static BOOL CALLBACK _sot_dialog_proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
IObjParam *ip = (IObjParam*)GetWindowLong(hWnd,GWL_USERDATA);
switch (message) {
case WM_INITDIALOG:
SetWindowLong(hWnd,GWL_USERDATA,lParam);
break;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MOUSEMOVE:
if (ip) ip->RollupMouseMessage(hWnd,message,wParam,lParam);
return FALSE;
default:
return FALSE;
}
return TRUE;
}
/*********************************************************************************
*
* _skeleton_dialog_proc
*
*********************************************************************************/
static BOOL CALLBACK _skeleton_dialog_thunk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
SkinWSMObjectClass * skinobj = (SkinWSMObjectClass *)GetWindowLong(hWnd,GWL_USERDATA);
if (!skinobj && message != WM_INITDIALOG) return FALSE;
if (message == WM_INITDIALOG) {
skinobj = (SkinWSMObjectClass *)lParam;
SetWindowLong(hWnd,GWL_USERDATA,(LONG)skinobj);
}
return skinobj->Skeleton_Dialog_Proc(hWnd,message,wParam,lParam);
}
BOOL SkinWSMObjectClass::Skeleton_Dialog_Proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch (message) {
case WM_INITDIALOG:
BoneListHWND = GetDlgItem(hWnd,IDC_BONE_LIST);
/*
** Intitialize the add bone and remove bone check buttons
*/
AddBonesButton = GetICustButton(GetDlgItem(hWnd, IDC_ADD_BONES_BUTTON));
RemoveBonesButton = GetICustButton(GetDlgItem(hWnd, IDC_REMOVE_BONES_BUTTON));
AddBonesButton->SetType(CBT_CHECK);
AddBonesButton->SetHighlightColor(GREEN_WASH);
AddBonesButton->SetTooltip(TRUE, _T("Add bones by name"));
RemoveBonesButton->SetType(CBT_CHECK);
RemoveBonesButton->SetHighlightColor(GREEN_WASH);
RemoveBonesButton->SetTooltip(TRUE, _T("Remove bones by name"));
/*
** Initialize the "Base Pose Frame" spinner
*/
BasePoseSpin = GetISpinner(GetDlgItem(hWnd, IDC_BASE_POSE_SPIN));
BasePoseSpin->SetLimits(0,9999, FALSE);
BasePoseSpin->SetValue(0,FALSE);
BasePoseSpin->SetResetValue(0);
BasePoseSpin->LinkToEdit(GetDlgItem(hWnd,IDC_BASE_POSE_EDIT),EDITTYPE_INT);
return TRUE;
case WM_DESTROY:
ReleaseICustButton(AddBonesButton);
ReleaseICustButton(RemoveBonesButton);
ReleaseISpinner(BasePoseSpin);
AddBonesButton = NULL;
RemoveBonesButton = NULL;
BasePoseSpin = NULL;
BoneListHWND = NULL;
return FALSE;
case CC_SPINNER_CHANGE:
switch (LOWORD(wParam))
{
case IDC_BASE_POSE_SPIN:
BasePoseFrame = BasePoseSpin->GetIVal();
break;
}
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
InterfacePtr->RedrawViews(InterfacePtr->GetTime(),REDRAW_INTERACTIVE);
return TRUE;
case CC_SPINNER_BUTTONUP:
NotifyDependents(FOREVER, PART_ALL, REFMSG_CHANGE);
InterfacePtr->RedrawViews(InterfacePtr->GetTime(),REDRAW_END);
return TRUE;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MOUSEMOVE:
InterfacePtr->RollupMouseMessage(hWnd,message,wParam,lParam);
return FALSE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_ADD_BONES_BUTTON:
TheBonePicker.Set_User(this);
Set_Bone_Selection_Mode(BONE_SEL_MODE_ADD_MANY);
InterfacePtr->DoHitByNameDialog(&TheBonePicker);
Set_Bone_Selection_Mode(BONE_SEL_MODE_NONE);
break;
case IDC_REMOVE_BONES_BUTTON:
TheBonePicker.Set_User(this,FALSE,&(BoneTab));
Set_Bone_Selection_Mode(BONE_SEL_MODE_REMOVE_MANY);
InterfacePtr->DoHitByNameDialog(&TheBonePicker);
Set_Bone_Selection_Mode(BONE_SEL_MODE_NONE);
break;
}
default:
return FALSE;
}
}
/*********************************************************************************
*
* Bone_Influence_Dialog_Proc
*
*********************************************************************************/
static BOOL CALLBACK _bone_influence_dialog_thunk(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
SkinModifierClass * skinmod = (SkinModifierClass *)GetWindowLong(hWnd,GWL_USERDATA);
if (!skinmod && message != WM_INITDIALOG) return FALSE;
if (message == WM_INITDIALOG) {
skinmod = (SkinModifierClass *)lParam;
SetWindowLong(hWnd,GWL_USERDATA,(LONG)skinmod);
}
return skinmod->Bone_Influence_Dialog_Proc(hWnd,message,wParam,lParam);
}
BOOL SkinModifierClass::Bone_Influence_Dialog_Proc(HWND hWnd,UINT message,WPARAM wParam,LPARAM lParam)
{
switch (message) {
case WM_INITDIALOG:
/*
** Intitialize the bone influence buttons
*/
LinkButton = GetICustButton(GetDlgItem(hWnd, IDC_LINK_BUTTON));
LinkByNameButton = GetICustButton(GetDlgItem(hWnd, IDC_LINK_BY_NAME_BUTTON));
AutoLinkButton = GetICustButton(GetDlgItem(hWnd, IDC_AUTO_LINK_BUTTON));
UnLinkButton = GetICustButton(GetDlgItem(hWnd, IDC_UNLINK_BUTTON));
LinkButton->SetType(CBT_PUSH);
LinkButton->SetTooltip(TRUE, _T("Link Vertices to a bone by selecting the bone"));
LinkByNameButton->SetType(CBT_PUSH);
LinkByNameButton->SetTooltip(TRUE, _T("Link Vertices to a bone by name"));
AutoLinkButton->SetType(CBT_PUSH);
AutoLinkButton->SetTooltip(TRUE, _T("Link Vertices to nearest bone"));
UnLinkButton->SetType(CBT_PUSH);
UnLinkButton->SetTooltip(TRUE, _T("Unlink selected vertices"));
return TRUE;
case WM_DESTROY:
ReleaseICustButton(LinkButton);
ReleaseICustButton(LinkByNameButton);
ReleaseICustButton(AutoLinkButton);
ReleaseICustButton(UnLinkButton);
LinkButton = NULL;
LinkByNameButton = NULL;
AutoLinkButton = NULL;
UnLinkButton = NULL;
return FALSE;
case WM_LBUTTONDOWN:
case WM_LBUTTONUP:
case WM_MOUSEMOVE:
InterfacePtr->RollupMouseMessage(hWnd,message,wParam,lParam);
return FALSE;
case WM_COMMAND:
switch (LOWORD(wParam))
{
case IDC_LINK_BUTTON:
{
/*
** user picks a bone out of the scene to link to.
*/
assert(WSMObjectRef != NULL);
INodeTab * bonetab = &(WSMObjectRef->Get_Bone_List());
TheBonePicker.Set_User(this,TRUE,bonetab);
InterfacePtr->SetPickMode(&TheBonePicker);
break;
}
case IDC_LINK_BY_NAME_BUTTON:
{
/*
** pop up a bone selection dialog
*/
assert(WSMObjectRef != NULL);
INodeTab * bonetab = &(WSMObjectRef->Get_Bone_List());
TheBonePicker.Set_User(this,TRUE,bonetab);
InterfacePtr->DoHitByNameDialog(&TheBonePicker);
break;
}
case IDC_AUTO_LINK_BUTTON:
{
Auto_Attach_Verts();
break;
}
case IDC_UNLINK_BUTTON:
{
Unlink_Verts();
break;
}
}
default:
return FALSE;
}
}
static TriObject * Get_Tri_Object(TimeValue t,ObjectState & os,Interval & valid,BOOL & needsdel)
{
needsdel = FALSE;
valid &= os.Validity(t);
if (os.obj->IsSubClassOf(triObjectClassID)) {
return (TriObject *)os.obj;
} else {
if (os.obj->CanConvertToType(triObjectClassID)) {
Object * oldObj = os.obj;
TriObject * tobj = (TriObject *)os.obj->ConvertToType(t,triObjectClassID);
needsdel = (tobj != oldObj);
return tobj;
}
}
return NULL;
}
float Bone_Distance(INode * bone,TimeValue time,const Point3 & vertex)
{
/*
** Average the pivot point of this bone with the pivot points of
** all of its children.
*/
Point3 icenter = bone->GetObjectTM(time).GetTrans();
for (int ci=0; ciNumberOfChildren(); ci++) {
icenter += bone->GetChildNode(ci)->GetObjectTM(time).GetTrans();
}
icenter = icenter / (float)(bone->NumberOfChildren() + 1);
return Length(icenter - vertex);
}
#if defined W3D_MAX4 //defined as in the project (.dsp)
int SkinModifierClass::NumSubObjTypes()
{
return 1;
}
////////////////////////////////////////////////////////////////////////////////////////
ISubObjType *SkinModifierClass::GetSubObjType(int i)
{
static bool _initialized = false;
if(!_initialized){
_initialized = true;
_SubObjectTypeVertex.SetName("Vertices");
}
if(i == -1){
if(GetSubObjectLevel() > 0){
return GetSubObjType(GetSubObjectLevel()-1);
}
}
return &_SubObjectTypeVertex;
}
#endif