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