539 lines
14 KiB
C++
539 lines
14 KiB
C++
/*
|
|
** Command & Conquer Renegade(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 <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
// BoneMgrDialog.cpp : implementation file
|
|
//
|
|
|
|
#include "StdAfx.H"
|
|
#include "W3DView.H"
|
|
#include "BoneMgrDialog.H"
|
|
#include "HTree.H"
|
|
#include "AssetMgr.H"
|
|
#include "Utils.H"
|
|
#include "MainFrm.H"
|
|
#include "W3DViewDoc.H"
|
|
#include "DataTreeView.H"
|
|
//#include "HModel.H"
|
|
|
|
#ifdef _DEBUG
|
|
#define new DEBUG_NEW
|
|
#undef THIS_FILE
|
|
static char THIS_FILE[] = __FILE__;
|
|
#endif
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// BoneMgrDialogClass
|
|
//
|
|
BoneMgrDialogClass::BoneMgrDialogClass
|
|
(
|
|
RenderObjClass *prender_obj,
|
|
CWnd *pparent
|
|
)
|
|
: m_pBaseModel (prender_obj),
|
|
m_pBackupModel (NULL),
|
|
m_bAttach (true),
|
|
CDialog (BoneMgrDialogClass::IDD, pparent)
|
|
{
|
|
//{{AFX_DATA_INIT(BoneMgrDialogClass)
|
|
// NOTE: the ClassWizard will add member initialization here
|
|
//}}AFX_DATA_INIT
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// DoDataExchange
|
|
//
|
|
void
|
|
BoneMgrDialogClass::DoDataExchange (CDataExchange* pDX)
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
//{{AFX_DATA_MAP(BoneMgrDialogClass)
|
|
DDX_Control(pDX, IDC_OBJECT_COMBO, m_ObjectCombo);
|
|
DDX_Control(pDX, IDC_BONE_TREE, m_BoneTree);
|
|
//}}AFX_DATA_MAP
|
|
return ;
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(BoneMgrDialogClass, CDialog)
|
|
//{{AFX_MSG_MAP(BoneMgrDialogClass)
|
|
ON_NOTIFY(TVN_SELCHANGED, IDC_BONE_TREE, OnSelchangedBoneTree)
|
|
ON_CBN_SELCHANGE(IDC_OBJECT_COMBO, OnSelchangeObjectCombo)
|
|
ON_WM_DESTROY()
|
|
ON_BN_CLICKED(IDC_ATTACH_BUTTON, OnAttachButton)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// OnInitDialog
|
|
//
|
|
BOOL
|
|
BoneMgrDialogClass::OnInitDialog (void)
|
|
{
|
|
// Allow the base class to process this message
|
|
CDialog::OnInitDialog ();
|
|
|
|
// Make a backup of this hierarchy in case we need to restore it.
|
|
m_pBackupModel = m_pBaseModel->Clone ();
|
|
|
|
// Create an icon imagelist for the tree control
|
|
CImageList *pimagelist = new CImageList;
|
|
pimagelist->Create (16, 16, ILC_COLOR | ILC_MASK, 2, 2);
|
|
|
|
// Load this icon and add it to our imagelist
|
|
pimagelist->Add ((HICON)::LoadImage (::AfxGetResourceHandle (),
|
|
MAKEINTRESOURCE (IDI_FOLDER),
|
|
IMAGE_ICON,
|
|
16,
|
|
16,
|
|
LR_SHARED));
|
|
|
|
pimagelist->Add ((HICON)::LoadImage (::AfxGetResourceHandle (),
|
|
MAKEINTRESOURCE (IDI_OBJECT),
|
|
IMAGE_ICON,
|
|
16,
|
|
16,
|
|
LR_SHARED));
|
|
|
|
m_BoneTree.SetImageList (pimagelist, TVSIL_NORMAL);
|
|
|
|
// Get the hierarchy tree for this model so we can enumerate bone's
|
|
// and subobjects.
|
|
HTREEITEM hfirst_item = NULL;
|
|
|
|
// Loop through all the bones in this model
|
|
int bone_count = m_pBaseModel->Get_Num_Bones ();
|
|
for (int index = 0; index < bone_count; index ++) {
|
|
const char *pbone_name = m_pBaseModel->Get_Bone_Name (index);
|
|
|
|
// Add this bone to the tree control
|
|
HTREEITEM hbone_item = m_BoneTree.InsertItem (pbone_name, 0, 0);
|
|
Fill_Bone_Item (hbone_item, index);
|
|
|
|
// Is this the first item we've added to the tree?
|
|
if (hfirst_item == NULL) {
|
|
hfirst_item = hbone_item;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Sort the tree control
|
|
//
|
|
m_BoneTree.SortChildren (TVI_ROOT);
|
|
|
|
// Build a list of all the render objects currently loaded
|
|
CW3DViewDoc *pdoc = (CW3DViewDoc *)((CMainFrame *)::AfxGetMainWnd())->GetActiveDocument ();
|
|
CDataTreeView *pdata_tree = pdoc->GetDataTreeView ();
|
|
DynamicVectorClass <CString> asset_list;
|
|
pdata_tree->Build_Render_Object_List (asset_list);
|
|
|
|
// Add this render object list to the combobox
|
|
for (index = 0; index < asset_list.Count (); index ++) {
|
|
m_ObjectCombo.AddString (asset_list[index]);
|
|
}
|
|
|
|
// Select the first entry in the combobox
|
|
m_ObjectCombo.SetCurSel (0);
|
|
OnSelchangeObjectCombo ();
|
|
|
|
// Select the first item in the tree
|
|
m_BoneTree.SelectItem (hfirst_item);
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////
|
|
//
|
|
// Fill_Bone_Item
|
|
//
|
|
void
|
|
BoneMgrDialogClass::Fill_Bone_Item
|
|
(
|
|
HTREEITEM hbone_item,
|
|
int bone_index
|
|
)
|
|
{
|
|
// Create a new instance of the hmodel which we can use
|
|
// to compare with the supplied hmodel and determine
|
|
// which 'bones-models' are new.
|
|
const char *orig_model_name = m_pBaseModel->Get_Base_Model_Name ();
|
|
orig_model_name = (orig_model_name == NULL) ? m_pBaseModel->Get_Name () : orig_model_name;
|
|
RenderObjClass *porig_model = WW3DAssetManager::Get_Instance()->Create_Render_Obj (orig_model_name);
|
|
|
|
// Build a list of nodes that are contained in the vanilla model
|
|
DynamicVectorClass <RenderObjClass *> orig_node_list;
|
|
for (int index = 0;
|
|
index < porig_model->Get_Num_Sub_Objects_On_Bone (bone_index);
|
|
index ++) {
|
|
RenderObjClass *psubobj = porig_model->Get_Sub_Object_On_Bone (index, bone_index);
|
|
if (psubobj != NULL) {
|
|
orig_node_list.Add (psubobj);
|
|
}
|
|
}
|
|
|
|
// Build a list of nodes that are contained in this bone
|
|
DynamicVectorClass <RenderObjClass *> node_list;
|
|
for (index = 0;
|
|
index < m_pBaseModel->Get_Num_Sub_Objects_On_Bone (bone_index);
|
|
index ++) {
|
|
RenderObjClass *psubobj = m_pBaseModel->Get_Sub_Object_On_Bone (index, bone_index);
|
|
if (psubobj != NULL) {
|
|
node_list.Add (psubobj);
|
|
}
|
|
}
|
|
|
|
if (node_list.Count () > 0) {
|
|
|
|
// Add the subobjects to the tree control
|
|
for (int node_index = 0; node_index < node_list.Count (); node_index ++) {
|
|
RenderObjClass *psubobject = node_list[node_index];
|
|
ASSERT (psubobject != NULL);
|
|
|
|
// Is this subobject new? (i.e. not in a 'vanilla' instance?)
|
|
if (psubobject != NULL &&
|
|
(Is_Object_In_List (psubobject->Get_Name (), orig_node_list) == false)) {
|
|
m_BoneTree.InsertItem (psubobject->Get_Name (), 1, 1, hbone_item);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Free our hold on the render objs in the original node list
|
|
for (index = 0; index < orig_node_list.Count (); index ++) {
|
|
MEMBER_RELEASE (orig_node_list[index]);
|
|
}
|
|
|
|
// Free our hold on the render objs in the node list
|
|
for (index = 0; index < node_list.Count (); index ++) {
|
|
MEMBER_RELEASE (node_list[index]);
|
|
}
|
|
|
|
MEMBER_RELEASE (porig_model);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Is_Object_In_List
|
|
//
|
|
bool
|
|
BoneMgrDialogClass::Is_Object_In_List
|
|
(
|
|
const char *passet_name,
|
|
DynamicVectorClass <RenderObjClass *> &node_list
|
|
)
|
|
{
|
|
// Assume failure
|
|
bool retval = false;
|
|
|
|
// Loop through the nodes in the list until we've found the one
|
|
// were are looking for.
|
|
for (int node_index = 0; (node_index < node_list.Count ()) && (retval == false); node_index ++) {
|
|
RenderObjClass *prender_obj = node_list[node_index];
|
|
|
|
// Is this the render object we were looking for?
|
|
if (prender_obj != NULL &&
|
|
::lstrcmpi (prender_obj->Get_Name (), passet_name) == 0) {
|
|
retval = true;
|
|
}
|
|
}
|
|
|
|
// Return the true/false result code
|
|
return retval;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OnSelchangedBoneTree
|
|
//
|
|
void
|
|
BoneMgrDialogClass::OnSelchangedBoneTree
|
|
(
|
|
NMHDR *pNMHDR,
|
|
LRESULT *pResult
|
|
)
|
|
{
|
|
// Make the dialog controls reflect the new selection
|
|
NM_TREEVIEW *pNMTreeView = (NM_TREEVIEW *)pNMHDR;
|
|
Update_Controls (pNMTreeView->itemNew.hItem);
|
|
|
|
(*pResult) = 0;
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OnSelchangeObjectCombo
|
|
//
|
|
void
|
|
BoneMgrDialogClass::OnSelchangeObjectCombo (void)
|
|
{
|
|
// Get the name of the currently selected render object
|
|
CString name;
|
|
int index = m_ObjectCombo.GetCurSel ();
|
|
m_ObjectCombo.GetLBText (index, name);
|
|
|
|
// Change the text of the 'Attach' button based on whether or not
|
|
// this render object is already attached to the current bone.
|
|
if (Is_Render_Obj_Already_Attached (name)) {
|
|
SetDlgItemText (IDC_ATTACH_BUTTON, "&Remove");
|
|
m_bAttach = false;
|
|
} else {
|
|
SetDlgItemText (IDC_ATTACH_BUTTON, "&Attach");
|
|
m_bAttach = true;
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Is_Render_Obj_Already_Attached
|
|
//
|
|
bool
|
|
BoneMgrDialogClass::Is_Render_Obj_Already_Attached (const CString &name)
|
|
{
|
|
// Assume not already attached
|
|
bool retval = false;
|
|
|
|
HTREEITEM htree_item = m_BoneTree.GetSelectedItem ();
|
|
HTREEITEM hparent_item = m_BoneTree.GetParentItem (htree_item);
|
|
htree_item = (hparent_item != NULL) ? hparent_item : htree_item;
|
|
if (htree_item != NULL) {
|
|
|
|
// Loop through all the children of this bone
|
|
for (HTREEITEM hchild_item = m_BoneTree.GetChildItem (htree_item);
|
|
(hchild_item != NULL) && (retval == false);
|
|
hchild_item = m_BoneTree.GetNextSiblingItem (hchild_item)) {
|
|
|
|
// Is this the render object we were looking for?
|
|
CString child_name = m_BoneTree.GetItemText (hchild_item);
|
|
if (name.CompareNoCase (child_name) == 0) {
|
|
retval = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Return the true/false result code
|
|
return retval;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Update_Controls
|
|
//
|
|
void
|
|
BoneMgrDialogClass::Update_Controls (HTREEITEM selected_item)
|
|
{
|
|
// Get the name of the currently selected item
|
|
CString name = m_BoneTree.GetItemText (selected_item);
|
|
bool bis_bone = (m_BoneTree.GetParentItem (selected_item) == NULL);
|
|
|
|
// Did the user select a bone name?
|
|
if (bis_bone) {
|
|
m_BoneName = name;
|
|
} else {
|
|
|
|
// Select this render object in the combobox
|
|
int index = m_ObjectCombo.FindStringExact (-1, name);
|
|
m_ObjectCombo.SetCurSel ((index != CB_ERR) ? index : 0);
|
|
|
|
// The bone name is the name of the parent item of the selected item.
|
|
m_BoneName = m_BoneTree.GetItemText (m_BoneTree.GetParentItem (selected_item));
|
|
}
|
|
|
|
OnSelchangeObjectCombo ();
|
|
|
|
// Change the text of the group box to reflect the bone name
|
|
CString text;
|
|
text.Format ("Bone: %s", m_BoneName);
|
|
SetDlgItemText (IDC_BONE_GROUPBOX, text);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OnDestroy
|
|
//
|
|
void
|
|
BoneMgrDialogClass::OnDestroy (void)
|
|
{
|
|
// Free the state image list we associated with the control
|
|
CImageList *pimagelist = m_BoneTree.GetImageList (TVSIL_NORMAL);
|
|
m_BoneTree.SetImageList (NULL, TVSIL_NORMAL);
|
|
SAFE_DELETE (pimagelist);
|
|
|
|
// Allow the base class to process this message
|
|
CDialog::OnDestroy ();
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OnOK
|
|
//
|
|
void
|
|
BoneMgrDialogClass::OnOK (void)
|
|
{
|
|
// Simply forget about the backup we made
|
|
MEMBER_RELEASE (m_pBackupModel);
|
|
|
|
// Update the hierarchy's cached information to reflect the new settings
|
|
CW3DViewDoc *pdoc = (CW3DViewDoc *)((CMainFrame *)::AfxGetMainWnd())->GetActiveDocument ();
|
|
pdoc->Update_Aggregate_Prototype (*m_pBaseModel);
|
|
|
|
// Allow the base class to process this message
|
|
CDialog::OnOK ();
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OnCancel
|
|
//
|
|
void
|
|
BoneMgrDialogClass::OnCancel (void)
|
|
{
|
|
CWaitCursor wait_cursor;
|
|
|
|
// Display the backup hierarchy
|
|
CW3DViewDoc *pdoc = (CW3DViewDoc *)((CMainFrame *)::AfxGetMainWnd())->GetActiveDocument ();
|
|
pdoc->DisplayObject (m_pBackupModel, false, false);
|
|
|
|
// Allow the base class to process this message
|
|
CDialog::OnCancel ();
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// OnAttachButton
|
|
//
|
|
void
|
|
BoneMgrDialogClass::OnAttachButton (void)
|
|
{
|
|
// Get the name of the currently selected render object
|
|
CString name;
|
|
int index = m_ObjectCombo.GetCurSel ();
|
|
m_ObjectCombo.GetLBText (index, name);
|
|
|
|
// Lookup the currently selected bone item
|
|
HTREEITEM hbone_item = Get_Current_Bone_Item ();
|
|
|
|
// Should we attach or remove the render object?
|
|
if (m_bAttach) {
|
|
|
|
// Create an instance of the render object and attach it to the bone
|
|
RenderObjClass *prender_obj = WW3DAssetManager::Get_Instance()->Create_Render_Obj (name);
|
|
if (prender_obj != NULL) {
|
|
m_pBaseModel->Add_Sub_Object_To_Bone (prender_obj, m_BoneName);
|
|
m_BoneTree.InsertItem (name, 1, 1, hbone_item);
|
|
MEMBER_RELEASE (prender_obj);
|
|
}
|
|
|
|
} else {
|
|
|
|
// Loop through all the subobjects on the bone
|
|
bool found = false;
|
|
int bone_index = m_pBaseModel->Get_Bone_Index (m_BoneName);
|
|
int count = m_pBaseModel->Get_Num_Sub_Objects_On_Bone (bone_index);
|
|
for (int index = 0; (index < count) && !found; index ++) {
|
|
|
|
// Is this the subobject we were looking for?
|
|
RenderObjClass *psub_obj = m_pBaseModel->Get_Sub_Object_On_Bone (index, bone_index);
|
|
if ((psub_obj != NULL) &&
|
|
(::lstrcmpi (psub_obj->Get_Name (), name) == 0)) {
|
|
|
|
// Remove this subobject from the bone
|
|
m_pBaseModel->Remove_Sub_Object (psub_obj);
|
|
found = true;
|
|
}
|
|
|
|
// Release our hold on this pointer
|
|
MEMBER_RELEASE (psub_obj);
|
|
}
|
|
|
|
// Remove the object from our UI
|
|
Remove_Object_From_Bone (hbone_item, name);
|
|
}
|
|
|
|
// Refresh the UI state
|
|
m_BoneTree.InvalidateRect (NULL, TRUE);
|
|
m_BoneTree.UpdateWindow ();
|
|
Update_Controls (hbone_item);
|
|
return ;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Get_Current_Bone_Item
|
|
//
|
|
HTREEITEM
|
|
BoneMgrDialogClass::Get_Current_Bone_Item (void)
|
|
{
|
|
// Get the currently selected item and its parent
|
|
HTREEITEM htree_item = m_BoneTree.GetSelectedItem ();
|
|
HTREEITEM hparent_item = m_BoneTree.GetParentItem (htree_item);
|
|
|
|
// Return the bone item
|
|
return (hparent_item != NULL) ? hparent_item : htree_item;
|
|
}
|
|
|
|
|
|
///////////////////////////////////////////////////////////////////////////////////
|
|
//
|
|
// Remove_Object_From_Bone
|
|
//
|
|
void
|
|
BoneMgrDialogClass::Remove_Object_From_Bone
|
|
(
|
|
HTREEITEM bone_item,
|
|
const CString &name
|
|
)
|
|
{
|
|
// Loop through all the children of this bone
|
|
for (HTREEITEM hchild_item = m_BoneTree.GetChildItem (bone_item);
|
|
(hchild_item != NULL);
|
|
hchild_item = m_BoneTree.GetNextSiblingItem (hchild_item)) {
|
|
|
|
// Is this the render object we were looking for?
|
|
CString child_name = m_BoneTree.GetItemText (hchild_item);
|
|
if (name.CompareNoCase (child_name) == 0) {
|
|
m_BoneTree.DeleteItem (hchild_item);
|
|
break ;
|
|
}
|
|
}
|
|
|
|
return ;
|
|
}
|
|
|