From 0c50ce89f38df4824eae15bd374a666ac28df1b4 Mon Sep 17 00:00:00 2001 From: D-AIRY <admin@ds-servers.com> Date: Sun, 15 Dec 2024 00:47:54 +0300 Subject: [PATCH] TerraX support for grouping --- proj/terrax/vs2013/terrax.vcxproj | 6 + proj/terrax/vs2013/terrax.vcxproj.filters | 18 ++ source/terrax/CommandBuildModel.cpp | 7 +- source/terrax/CommandDuplicate.cpp | 23 +- source/terrax/CommandGroup.cpp | 178 +++++++++++ source/terrax/CommandGroup.h | 52 ++++ source/terrax/CommandPaste.cpp | 77 ++++- source/terrax/CommandPaste.h | 11 + source/terrax/CommandUngroup.cpp | 101 ++++++ source/terrax/CommandUngroup.h | 39 +++ source/terrax/GroupObject.cpp | 361 ++++++++++++++++++++++ source/terrax/GroupObject.h | 110 +++++++ source/terrax/ProxyObject.cpp | 12 +- source/terrax/SceneTreeWindow.cpp | 79 ++++- source/terrax/SceneTreeWindow.h | 2 + source/terrax/UndoManager.cpp | 3 +- source/terrax/mainWindow.cpp | 232 ++++++++++++-- source/terrax/resource.h | Bin 11833 -> 24580 bytes source/terrax/resource/toolbar1.bmp | Bin 838 -> 1080 bytes source/terrax/resource/toolbar2.bmp | Bin 838 -> 1080 bytes source/terrax/terrax.cpp | 355 ++++++++++++++++----- source/terrax/terrax.h | 48 ++- source/terrax/terrax.rc | Bin 48900 -> 49534 bytes 23 files changed, 1569 insertions(+), 145 deletions(-) create mode 100644 source/terrax/CommandGroup.cpp create mode 100644 source/terrax/CommandGroup.h create mode 100644 source/terrax/CommandUngroup.cpp create mode 100644 source/terrax/CommandUngroup.h create mode 100644 source/terrax/GroupObject.cpp create mode 100644 source/terrax/GroupObject.h diff --git a/proj/terrax/vs2013/terrax.vcxproj b/proj/terrax/vs2013/terrax.vcxproj index 94f6063c1..3714e7454 100644 --- a/proj/terrax/vs2013/terrax.vcxproj +++ b/proj/terrax/vs2013/terrax.vcxproj @@ -214,6 +214,7 @@ <ClCompile Include="..\..\..\source\terrax\CommandDelete.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandDestroyModel.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandDuplicate.cpp" /> + <ClCompile Include="..\..\..\source\terrax\CommandGroup.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandModifyModel.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandMove.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandPaste.cpp" /> @@ -221,6 +222,7 @@ <ClCompile Include="..\..\..\source\terrax\CommandRotate.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandScale.cpp" /> <ClCompile Include="..\..\..\source\terrax\CommandSelect.cpp" /> + <ClCompile Include="..\..\..\source\terrax\CommandUngroup.cpp" /> <ClCompile Include="..\..\..\source\terrax\CurveEditorDialog.cpp" /> <ClCompile Include="..\..\..\source\terrax\CurveEditorGraphNode.cpp" /> <ClCompile Include="..\..\..\source\terrax\CurveEditorGraphNodeData.cpp" /> @@ -234,6 +236,7 @@ <ClCompile Include="..\..\..\source\terrax\GradientPreviewGraphNode.cpp" /> <ClCompile Include="..\..\..\source\terrax\GradientPreviewGraphNodeData.cpp" /> <ClCompile Include="..\..\..\source\terrax\Grid.cpp" /> + <ClCompile Include="..\..\..\source\terrax\GroupObject.cpp" /> <ClCompile Include="..\..\..\source\terrax\LevelOpenDialog.cpp" /> <ClCompile Include="..\..\..\source\terrax\mainWindow.cpp" /> <ClCompile Include="..\..\..\source\terrax\MaterialBrowser.cpp" /> @@ -271,6 +274,7 @@ <ClInclude Include="..\..\..\source\terrax\CommandDelete.h" /> <ClInclude Include="..\..\..\source\terrax\CommandDestroyModel.h" /> <ClInclude Include="..\..\..\source\terrax\CommandDuplicate.h" /> + <ClInclude Include="..\..\..\source\terrax\CommandGroup.h" /> <ClInclude Include="..\..\..\source\terrax\CommandModifyModel.h" /> <ClInclude Include="..\..\..\source\terrax\CommandMove.h" /> <ClInclude Include="..\..\..\source\terrax\CommandPaste.h" /> @@ -278,6 +282,7 @@ <ClInclude Include="..\..\..\source\terrax\CommandRotate.h" /> <ClInclude Include="..\..\..\source\terrax\CommandScale.h" /> <ClInclude Include="..\..\..\source\terrax\CommandSelect.h" /> + <ClInclude Include="..\..\..\source\terrax\CommandUngroup.h" /> <ClInclude Include="..\..\..\source\terrax\CurveEditorDialog.h" /> <ClInclude Include="..\..\..\source\terrax\CurveEditorGraphNode.h" /> <ClInclude Include="..\..\..\source\terrax\CurveEditorGraphNodeData.h" /> @@ -290,6 +295,7 @@ <ClInclude Include="..\..\..\source\terrax\GizmoScale.h" /> <ClInclude Include="..\..\..\source\terrax\GradientPreviewGraphNode.h" /> <ClInclude Include="..\..\..\source\terrax\GradientPreviewGraphNodeData.h" /> + <ClInclude Include="..\..\..\source\terrax\GroupObject.h" /> <ClInclude Include="..\..\..\source\terrax\ICompoundObject.h" /> <ClInclude Include="..\..\..\source\terrax\LevelOpenDialog.h" /> <ClInclude Include="..\..\..\source\terrax\MaterialBrowser.h" /> diff --git a/proj/terrax/vs2013/terrax.vcxproj.filters b/proj/terrax/vs2013/terrax.vcxproj.filters index f6cb0656d..c7f15f1fc 100644 --- a/proj/terrax/vs2013/terrax.vcxproj.filters +++ b/proj/terrax/vs2013/terrax.vcxproj.filters @@ -207,6 +207,15 @@ <ClCompile Include="..\..\..\source\terrax\SceneTreeWindow.cpp"> <Filter>Source Files\windows</Filter> </ClCompile> + <ClCompile Include="..\..\..\source\terrax\GroupObject.cpp"> + <Filter>Source Files</Filter> + </ClCompile> + <ClCompile Include="..\..\..\source\terrax\CommandGroup.cpp"> + <Filter>Source Files\cmd</Filter> + </ClCompile> + <ClCompile Include="..\..\..\source\terrax\CommandUngroup.cpp"> + <Filter>Source Files\cmd</Filter> + </ClCompile> </ItemGroup> <ItemGroup> <ResourceCompile Include="..\..\..\source\terrax\terrax.rc"> @@ -400,6 +409,15 @@ <ClInclude Include="..\..\..\source\terrax\ICompoundObject.h"> <Filter>Header Files</Filter> </ClInclude> + <ClInclude Include="..\..\..\source\terrax\GroupObject.h"> + <Filter>Header Files</Filter> + </ClInclude> + <ClInclude Include="..\..\..\source\terrax\CommandGroup.h"> + <Filter>Header Files\cmd</Filter> + </ClInclude> + <ClInclude Include="..\..\..\source\terrax\CommandUngroup.h"> + <Filter>Header Files\cmd</Filter> + </ClInclude> </ItemGroup> <ItemGroup> <Image Include="..\..\..\source\terrax\resource\new.bmp"> diff --git a/source/terrax/CommandBuildModel.cpp b/source/terrax/CommandBuildModel.cpp index 1584fbb7a..f3ff59d42 100644 --- a/source/terrax/CommandBuildModel.cpp +++ b/source/terrax/CommandBuildModel.cpp @@ -39,7 +39,12 @@ bool XMETHODCALLTYPE CCommandBuildModel::exec() { if((*i.first)->isSelected()) { - m_aObjLocations.push_back({*i.first, *i.second}); + void *pData = NULL; + (*i.first)->getInternalData(&X_IS_COMPOUND_GUID, &pData); + if(!pData) + { + m_aObjLocations.push_back({*i.first, *i.second}); + } } } diff --git a/source/terrax/CommandDuplicate.cpp b/source/terrax/CommandDuplicate.cpp index e5d2e5d8a..52493d9e7 100644 --- a/source/terrax/CommandDuplicate.cpp +++ b/source/terrax/CommandDuplicate.cpp @@ -27,15 +27,15 @@ bool XMETHODCALLTYPE CCommandDuplicate::undo() void CCommandDuplicate::initialize() { - for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i) - { - IXEditorObject *pObj = g_pLevelObjects[i]; + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ if(pObj->isSelected()) { processObject(pObj); + return(XEOR_SKIP_CHILDREN); } - } - + return(XEOR_CONTINUE); + }); + for(UINT i = 0, l = g_apProxies.size(); i < l; ++i) { CProxyObject *pObj = g_apProxies[i]; @@ -48,6 +48,19 @@ void CCommandDuplicate::initialize() } } } + + for(UINT i = 0, l = g_apGroups.size(); i < l; ++i) + { + CGroupObject *pObj = g_apGroups[i]; + if(pObj->isSelected()) + { + UINT uGroup = m_commandPaste.addGroup(*pObj->getGUID()); + for(UINT j = 0, jl = pObj->getObjectCount(); j < jl; ++j) + { + m_commandPaste.addGroupObject(uGroup, *pObj->getObject(j)->getGUID()); + } + } + } } void CCommandDuplicate::processObject(IXEditorObject *pObj) diff --git a/source/terrax/CommandGroup.cpp b/source/terrax/CommandGroup.cpp new file mode 100644 index 000000000..d0e2d75f0 --- /dev/null +++ b/source/terrax/CommandGroup.cpp @@ -0,0 +1,178 @@ +#include "CommandGroup.h" +#include <common/aastring.h> + +extern AssotiativeArray<AAString, IXEditable*> g_mEditableSystems; + +CCommandGroup::CCommandGroup() +{ +} + +CCommandGroup::~CCommandGroup() +{ + mem_release(m_pGroup); +} + +bool XMETHODCALLTYPE CCommandGroup::exec() +{ + if(!m_isLocationsSaved) + { + IXEditorObject *pObj; + const Map<IXEditorObject*, ICompoundObject*>::Node *pNode; + for(auto i = g_mObjectsLocation.begin(); i; ++i) + { + if((*i.first)->isSelected() && !(*i.second)->isSelected()) + { + m_aObjLocations.push_back({*i.first, *i.second}); + } + } + + bool canHaveCommonParent = true; + fora(i, g_pLevelObjects) + { + if(g_pLevelObjects[i]->isSelected()) + { + canHaveCommonParent = false; + break; + } + } + + if(canHaveCommonParent) + { + Array<ICompoundObject*> aPath; + ICompoundObject *pParent; + fora(i, m_aObjLocations) + { + pParent = m_aObjLocations[i].pLocation; + while(pParent) + { + if(i == 0) + { + aPath.push_back(pParent); + } + else + { + int idx = aPath.indexOf(pParent); + if(idx >= 0) + { + while(idx--) + { + aPath.erase(0); + } + break; + } + } + pParent = XGetObjectParent(pParent); + } + + if(i != 0 && !pParent) + { + aPath.clear(); + break; + } + } + + if(aPath.size()) + { + fora(i, aPath) + { + // Groups cannot be inside proxies + void *pData = NULL; + aPath[i]->getInternalData(&X_IS_PROXY_GUID, &pData); + if(!pData) + { + m_pCommonParent = aPath[i]; + break; + } + } + } + } + + m_isLocationsSaved = true; + } + + if(!m_pGroup) + { + m_pGroup = new CGroupObject(); + m_pGroup->setPos(m_vPos); + } + + g_pEditor->addObject(m_pGroup); + + if(m_pCommonParent) + { + m_pCommonParent->addChildObject(m_pGroup); + } + + fora(i, m_aObjLocations) + { + ObjLocation &loc = m_aObjLocations[i]; + loc.pLocation->removeChildObject(loc.pObj); + } + + IXEditorObject *pObject; + forar(i, g_pLevelObjects) + { + pObject = g_pLevelObjects[i]; + if(pObject->isSelected()) + { + m_pGroup->addChildObject(pObject); + } + } + + //g_pEditor->addObject(m_pGroup); + + m_pGroup->setSelected(true); + + add_ref(m_pGroup); + g_apGroups.push_back(m_pGroup); + + //TODO("Find deepest common parent to place group into"); + + return(true); +} +bool XMETHODCALLTYPE CCommandGroup::undo() +{ + int idx = g_apGroups.indexOf(m_pGroup); + assert(idx >= 0); + if(idx >= 0) + { + mem_release(g_apGroups[idx]); + g_apGroups.erase(idx); + } + + //m_pProxy->setSelected(false); + + + // destroy proxy + //m_pProxy->reset(); + + IXEditorObject *pObj; + for(int i = (int)m_pGroup->getObjectCount() - 1; i >= 0; --i) + { + pObj = m_pGroup->getObject(i); + m_pGroup->removeChildObject(pObj); + + if(m_aObjLocations.indexOf(pObj, [](const ObjLocation &a, IXEditorObject *pB){ + return(a.pObj == pB); + }) < 0) + { + g_pEditor->onObjectAdded(pObj); + } + } + // restore object locations + fora(i, m_aObjLocations) + { + ObjLocation &loc = m_aObjLocations[i]; + loc.pLocation->addChildObject(loc.pObj); + } + + m_pGroup->setSelected(false); + + if(m_pCommonParent) + { + m_pCommonParent->removeChildObject(m_pGroup); + } + g_pEditor->removeObject(m_pGroup); + + return(true); +} diff --git a/source/terrax/CommandGroup.h b/source/terrax/CommandGroup.h new file mode 100644 index 000000000..d37541a6c --- /dev/null +++ b/source/terrax/CommandGroup.h @@ -0,0 +1,52 @@ +#ifndef _COMMAND_GROUP_H_ +#define _COMMAND_GROUP_H_ + +#include <xcommon/editor/IXEditorExtension.h> +#include "terrax.h" + +//#include <common/assotiativearray.h> +//#include <common/string.h> +//#include <xcommon/editor/IXEditable.h> + +//#include "CommandCreate.h" +#include "GroupObject.h" + +class CCommandGroup final: public IXUnknownImplementation<IXEditorCommand> +{ +public: + CCommandGroup(); + ~CCommandGroup(); + + bool XMETHODCALLTYPE exec() override; + bool XMETHODCALLTYPE undo() override; + + const char* XMETHODCALLTYPE getText() override + { + return("group"); + } + + bool XMETHODCALLTYPE isEmpty() override + { + return(false); + } + +private: + CGroupObject *m_pGroup = NULL; + + struct ObjLocation + { + IXEditorObject *pObj; + ICompoundObject *pLocation; + }; + Array<ObjLocation> m_aObjLocations; + + ICompoundObject *m_pCommonParent = NULL; + + float3_t m_vPos; + + bool m_isLocationsSaved = false; + bool m_isCenterFound = false; + +}; + +#endif diff --git a/source/terrax/CommandPaste.cpp b/source/terrax/CommandPaste.cpp index 130e1b621..f7c59ec80 100644 --- a/source/terrax/CommandPaste.cpp +++ b/source/terrax/CommandPaste.cpp @@ -10,10 +10,12 @@ bool XMETHODCALLTYPE CCommandPaste::exec() m_pCommandSelect = new CCommandSelect(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { m_pCommandSelect->addDeselected(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); } @@ -45,6 +47,7 @@ bool XMETHODCALLTYPE CCommandPaste::exec() _proxy_obj &po = m_aProxies[i]; po.pProxy->setDstObject(m_mapGuids[po.guid]); + m_mapGuids[po.guid] = *po.pProxy->getGUID(); fora(j, po.aObjects) { IXEditorObject *pObj = XFindObjectByGUID(m_mapGuids[po.aObjects[j]]); @@ -56,20 +59,51 @@ bool XMETHODCALLTYPE CCommandPaste::exec() po.pProxy->build(); g_pEditor->addObject(po.pProxy); + po.pProxy->setSelected(true); add_ref(po.pProxy); g_apProxies.push_back(po.pProxy); } + fora(i, m_aGroups) + { + _group_obj &go = m_aGroups[i]; + + go.pGroup = (CGroupObject*)XFindObjectByGUID(m_mapGuids[go.guid]); + + fora(j, go.aObjects) + { + IXEditorObject *pObj = XFindObjectByGUID(m_mapGuids[go.aObjects[j]]); + if(pObj) + { + go.pGroup->addChildObject(pObj); + } + } + + go.pGroup->setSelected(true); + } + XUpdatePropWindow(); return(m_aObjects.size() != 0); } bool XMETHODCALLTYPE CCommandPaste::undo() { + forar(i, m_aGroups) + { + CGroupObject *pGroup = m_aGroups[i].pGroup; + + while(pGroup->getObjectCount()) + { + pGroup->removeChildObject(pGroup->getObject(0)); + } + } + forar(i, m_aProxies) { CProxyObject *pProxy = m_aProxies[i].pProxy; + m_mapGuids[m_aProxies[i].guid] = *pProxy->getTargetObject()->getGUID(); + int idx = g_apProxies.indexOf(pProxy); assert(idx >= 0); if(idx >= 0) @@ -87,8 +121,7 @@ bool XMETHODCALLTYPE CCommandPaste::undo() pProxy->reset(); } - - + _paste_obj *pObj; forar(i, m_aObjects) { @@ -120,20 +153,31 @@ CCommandPaste::~CCommandPaste() UINT CCommandPaste::addObject(const char *szTypeName, const char *szClassName, const float3 &vPos, const float3 &vScale, const SMQuaternion &qRotate, const XGUID &oldGUID) { - const AssotiativeArray<AAString, IXEditable*>::Node *pNode; - if(!g_mEditableSystems.KeyExists(AAString(szTypeName), &pNode)) + _paste_obj obj; + if(!fstrcmp(szTypeName, "TerraX")) { - LibReport(REPORT_MSG_LEVEL_ERROR, "Unknown object type %s, skipping!", szTypeName); - return(UINT_MAX); + if(!fstrcmp(szClassName, "Group")) + { + obj.pObject = new CGroupObject(); + } + } + else + { + const AssotiativeArray<AAString, IXEditable*>::Node *pNode; + if(!g_mEditableSystems.KeyExists(AAString(szTypeName), &pNode)) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Unknown object type %s, skipping!\n", szTypeName); + return(UINT_MAX); + } + obj.pObject = (*(pNode->Val))->newObject(szClassName); } - _paste_obj obj; - obj.pObject = (*(pNode->Val))->newObject(szClassName); if(!obj.pObject) { - LibReport(REPORT_MSG_LEVEL_ERROR, "Cannot create object type %s/%s, skipping!", szTypeName, szClassName); + LibReport(REPORT_MSG_LEVEL_ERROR, "Cannot create object type %s/%s, skipping!\n", szTypeName, szClassName); return(UINT_MAX); } + obj.vPos = vPos; obj.vScale = vScale; obj.qRotate = qRotate; @@ -164,3 +208,16 @@ void CCommandPaste::addProxyObject(UINT uProxy, const XGUID &guid) m_aProxies[uProxy].aObjects.push_back(guid); } + +UINT CCommandPaste::addGroup(const XGUID &guid) +{ + m_aGroups.push_back({guid, NULL}); + + return(m_aGroups.size() - 1); +} +void CCommandPaste::addGroupObject(UINT uGroup, const XGUID &guid) +{ + assert(uGroup < m_aGroups.size()); + + m_aGroups[uGroup].aObjects.push_back(guid); +} diff --git a/source/terrax/CommandPaste.h b/source/terrax/CommandPaste.h index 49cf50404..e962111a4 100644 --- a/source/terrax/CommandPaste.h +++ b/source/terrax/CommandPaste.h @@ -33,6 +33,9 @@ public: UINT addProxy(const XGUID &guid); void addProxyObject(UINT uProxy, const XGUID &guid); + UINT addGroup(const XGUID &guid); + void addGroupObject(UINT uGroup, const XGUID &guid); + protected: struct _paste_obj { @@ -57,6 +60,14 @@ protected: Array<_proxy_obj> m_aProxies; Map<XGUID, XGUID> m_mapGuids; + + struct _group_obj + { + XGUID guid; + CGroupObject *pGroup; + Array<XGUID> aObjects; + }; + Array<_group_obj> m_aGroups; }; #endif diff --git a/source/terrax/CommandUngroup.cpp b/source/terrax/CommandUngroup.cpp new file mode 100644 index 000000000..46dc5f0a7 --- /dev/null +++ b/source/terrax/CommandUngroup.cpp @@ -0,0 +1,101 @@ +#include "CommandUngroup.h" + +CCommandUngroup::CCommandUngroup(CGroupObject *pObject): + m_pGroup(pObject) +{ + add_ref(m_pGroup); + + IXEditorObject *pObj; + for(UINT i = 0, l = pObject->getObjectCount(); i < l; ++i) + { + pObj = pObject->getObject(i); + add_ref(pObj); + m_aObjects.push_back(pObj); + } +} + +CCommandUngroup::~CCommandUngroup() +{ + fora(i, m_aObjects) + { + mem_release(m_aObjects[i]); + } + mem_release(m_pGroup); +} + +bool XMETHODCALLTYPE CCommandUngroup::exec() +{ + ICompoundObject *pParent = XGetObjectParent(m_pGroup); + + fora(i, m_aObjects) + { + m_pGroup->removeChildObject(m_aObjects[i]); + if(pParent) + { + pParent->addChildObject(m_aObjects[i]); + } + else + { + g_pEditor->onObjectAdded(m_aObjects[i]); + } + } + + /* + int idx = g_apGroups.indexOf(m_pGroup); + assert(idx >= 0); + if(idx >= 0) + { + mem_release(g_apGroups[idx]); + g_apGroups.erase(idx); + } + + //m_pProxy->setSelected(false); + + g_pEditor->removeObject(m_pGroup); + */ + return(true); +} +bool XMETHODCALLTYPE CCommandUngroup::undo() +{ + ICompoundObject *pParent; + fora(i, m_aObjects) + { + pParent = XGetObjectParent(m_aObjects[i]); + SAFE_CALL(pParent, removeChildObject, m_aObjects[i]); + m_pGroup->addChildObject(m_aObjects[i]); + } + + /* + fora(i, m_aModels) + { + IXEditorModel *pMdl = m_aModels[i]; + assert(!g_apLevelModels.KeyExists(*pMdl->getGUID())); + g_apLevelModels[*pMdl->getGUID()] = pMdl; + add_ref(pMdl); + pMdl->restore(); + } + fora(i, aObjModels) + { + ObjModel &om = aObjModels[i]; + om.pModel->addObject(om.pObj); + } + + fora(i, m_aModels) + { + IXEditorModel *pMdl = m_aModels[i]; + m_pProxy->addSrcModel(*pMdl->getGUID()); + } + + bool res = m_pProxy->setDstObject(*m_pEntity->getGUID()); + assert(res); + m_pProxy->build(); + + g_pEditor->addObject(m_pProxy); + + //m_pProxy->setSelected(true); + + add_ref(m_pProxy); + g_apProxies.push_back(m_pProxy); + */ + return(true); +} diff --git a/source/terrax/CommandUngroup.h b/source/terrax/CommandUngroup.h new file mode 100644 index 000000000..0ce86b27a --- /dev/null +++ b/source/terrax/CommandUngroup.h @@ -0,0 +1,39 @@ +#ifndef _COMMAND_UNGROUP_H_ +#define _COMMAND_UNGROUP_H_ + +#include <xcommon/editor/IXEditorExtension.h> +#include "terrax.h" + +#include <common/assotiativearray.h> +#include <common/string.h> +#include <xcommon/editor/IXEditable.h> + +#include "CommandDelete.h" +#include "ProxyObject.h" + +class CCommandUngroup final: public IXUnknownImplementation<IXEditorCommand> +{ +public: + CCommandUngroup(CGroupObject *pObj); + ~CCommandUngroup(); + + bool XMETHODCALLTYPE exec() override; + bool XMETHODCALLTYPE undo() override; + + const char* XMETHODCALLTYPE getText() override + { + return("ungroup"); + } + + bool XMETHODCALLTYPE isEmpty() override + { + return(false); + } + +private: + CGroupObject *m_pGroup = NULL; + + Array<IXEditorObject*> m_aObjects; +}; + +#endif diff --git a/source/terrax/GroupObject.cpp b/source/terrax/GroupObject.cpp new file mode 100644 index 000000000..5beb78e13 --- /dev/null +++ b/source/terrax/GroupObject.cpp @@ -0,0 +1,361 @@ +#include "GroupObject.h" + +#include <xcommon/resource/IXResourceManager.h> +#include <xcommon/IXModelWriter.h> +#include "terrax.h" +#include "CommandDelete.h" +#include <common/aastring.h> +#include <core/sxcore.h> + +extern AssotiativeArray<AAString, IXEditable*> g_mEditableSystems; + +CGroupObject::CGroupObject() +{ + XCreateGUID(&m_guid); +} + +CGroupObject::CGroupObject(const XGUID &guid) +{ + m_guid = guid; +} + +CGroupObject::~CGroupObject() +{ + fora(i, m_aObjects) + { + mem_release(m_aObjects[i].pObj); + } +} + +void XMETHODCALLTYPE CGroupObject::setPos(const float3_t &pos) +{ + m_vPos = pos; + + fora(i, m_aObjects) + { + SrcObject &o = m_aObjects[i]; + o.pObj->setPos((float3)(m_vPos + m_qOrient * o.vOffset)); + } +} + +void XMETHODCALLTYPE CGroupObject::setSize(const float3_t &vSize) +{ + float3 vMin, vMax, vScale; + getBound(&vMin, &vMax); + vScale = vSize / (vMax - vMin); + fora(i, m_aObjects) + { + SrcObject &o = m_aObjects[i]; + o.vOffset = m_qOrient.Conjugate() * (float3)((o.pObj->getPos() - m_vPos) * vScale); + o.pObj->setPos((float3)(m_vPos + m_qOrient * o.vOffset)); + o.pObj->getBound(&vMin, &vMax); + //printf("%.2f %.2f %.2f : %.2f %.2f %.2f\n", vMin.x, vMin.y, vMin.z, vMax.x, vMax.y, vMax.z); + o.pObj->setSize((float3)(vScale * (vMax - vMin))); + } +} + +void XMETHODCALLTYPE CGroupObject::setOrient(const SMQuaternion &orient) +{ + m_qOrient = orient; + //m_pTargetObject->setOrient(orient); + fora(i, m_aObjects) + { + SrcObject &o = m_aObjects[i]; + o.pObj->setOrient(orient * o.qOffset); + o.pObj->setPos((float3)(m_vPos + m_qOrient * o.vOffset)); + } +} + +float3_t XMETHODCALLTYPE CGroupObject::getPos() +{ + float3 vMin, vMax; + getBound(&vMin, &vMax); + + m_vPos = (vMax + vMin) * 0.5f; + + fora(i, m_aObjects) + { + m_aObjects[i].vOffset = m_qOrient.Conjugate() * (m_aObjects[i].pObj->getPos() - m_vPos); + } + + return(m_vPos); +} + +SMQuaternion XMETHODCALLTYPE CGroupObject::getOrient() +{ + fora(i, m_aObjects) + { + m_aObjects[i].qOffset = m_aObjects[i].pObj->getOrient() * m_qOrient.Conjugate(); + } + + return(m_qOrient); +} + +void XMETHODCALLTYPE CGroupObject::getBound(float3 *pvMin, float3 *pvMax) +{ + if(!m_aObjects.size()) + { + *pvMin = 0.0f; + *pvMax = 0.0f; + return; + } + + float3 vMin, vMax; + + m_aObjects[0].pObj->getBound(pvMin, pvMax); + for(UINT i = 1, l = m_aObjects.size(); i < l; ++i) + { + m_aObjects[i].pObj->getBound(&vMin, &vMax); + *pvMin = SMVectorMin(*pvMin, vMin); + *pvMax = SMVectorMax(*pvMax, vMax); + } +} + +void XMETHODCALLTYPE CGroupObject::render(bool is3D, bool bRenderSelection, IXGizmoRenderer *pGizmoRenderer) +{ + fora(i, m_aObjects) + { + m_aObjects[i].pObj->render(is3D, bRenderSelection, pGizmoRenderer); + } +} + +bool XMETHODCALLTYPE CGroupObject::rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut, float3 *pvNormal, ID *pidMtrl, bool bReturnNearestPoint) +{ + if(bReturnNearestPoint) + { + float fBestDist = FLT_MAX; + float3 vPoint, vNormal; + ID idMtrl = -1; + float fDist; + bool isFound = false; + + fora(i, m_aObjects) + { + if(m_aObjects[i].pObj->rayTest(vStart, vEnd, &vPoint, &vNormal, &idMtrl, bReturnNearestPoint)) + { + fDist = SMVector3Length2(vPoint - vStart); + if(fDist < fBestDist) + { + fBestDist = fBestDist; + if(pvOut) + { + *pvOut = vPoint; + } + if(pvNormal) + { + *pvNormal = vNormal; + } + if(pidMtrl) + { + *pidMtrl = idMtrl; + } + } + + isFound = true; + } + } + + return(isFound); + } + else + { + fora(i, m_aObjects) + { + if(m_aObjects[i].pObj->rayTest(vStart, vEnd, pvOut, pvNormal, pidMtrl, bReturnNearestPoint)) + { + return(true); + } + } + } + + return(false); +} + +void XMETHODCALLTYPE CGroupObject::remove() +{ + int idx = g_apGroups.indexOf(this); + assert(idx >= 0); + if(idx >= 0) + { + mem_release(g_apGroups[idx]); + g_apGroups.erase(idx); + } + + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ + g_mObjectsLocation.erase(pObj); + mem_release(pObj); + return(XEOR_SKIP_CHILDREN); + }, this); + + m_isRemoved = true; + fora(i, m_aObjects) + { + m_aObjects[i].pObj->remove(); + } +} +void XMETHODCALLTYPE CGroupObject::preSetup() +{ + //m_pTargetObject->preSetup(); +} +void XMETHODCALLTYPE CGroupObject::postSetup() +{ + //m_pTargetObject->preSetup(); +} + +void XMETHODCALLTYPE CGroupObject::create() +{ + int idx = g_apGroups.indexOf(this); + assert(idx < 0); + if(idx < 0) + { + add_ref(this); + g_apGroups.push_back(this); + } + + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ + g_mObjectsLocation[pObj] = pParent; + add_ref(pObj); + return(XEOR_SKIP_CHILDREN); + }, this); + + + m_isRemoved = false; + fora(i, m_aObjects) + { + m_aObjects[i].pObj->create(); + } +} + +void XMETHODCALLTYPE CGroupObject::setKV(const char *szKey, const char *szValue) +{ + if(!fstrcmp(szKey, "guid")) + { + XGUIDFromString(&m_guid, szValue); + } + else if(!fstrcmp(szKey, "name")) + { + m_sName = szValue; + } +} +const char* XMETHODCALLTYPE CGroupObject::getKV(const char *szKey) +{ + if(!fstrcmp(szKey, "guid")) + { + char tmp[64]; + XGUIDToSting(m_guid, tmp, sizeof(tmp)); + m_sGUID = tmp; + return(m_sGUID.c_str()); + } + else if(!fstrcmp(szKey, "name")) + { + return(m_sName.c_str()); + } + return(NULL); +} +const X_PROP_FIELD* XMETHODCALLTYPE CGroupObject::getPropertyMeta(UINT uKey) +{ + static X_PROP_FIELD s_prop0 = {"guid", "GUID", XPET_TEXT, NULL, "", true}; + static X_PROP_FIELD s_prop1 = {"name", "Name", XPET_TEXT, NULL, ""}; + switch(uKey) + { + case 0: + return(&s_prop0); + case 1: + return(&s_prop1); + } + return(NULL); +} +UINT XMETHODCALLTYPE CGroupObject::getProperyCount() +{ + return(2); +} + +const char* XMETHODCALLTYPE CGroupObject::getTypeName() +{ + return("TerraX"); +} +const char* XMETHODCALLTYPE CGroupObject::getClassName() +{ + return("Group"); +} + +void XMETHODCALLTYPE CGroupObject::setSelected(bool set) +{ + m_isSelected = set; + + fora(i, m_aObjects) + { + m_aObjects[i].pObj->setSelected(set); + } +} + +void XMETHODCALLTYPE CGroupObject::setSimulationMode(bool set) +{ + fora(i, m_aObjects) + { + m_aObjects[i].pObj->setSimulationMode(set); + } +} + +void CGroupObject::addChildObject(IXEditorObject *pObject) +{ + assert(pObject != this); + + ICompoundObject *pOldContainer = XTakeObject(pObject, this); + assert(pOldContainer == NULL); + //add_ref(pObject); + m_aObjects.push_back({pObject, m_qOrient.Conjugate() * (pObject->getPos() - m_vPos), pObject->getOrient() * m_qOrient.Conjugate()}); + + g_pEditor->onObjectAdded(pObject); +} +void CGroupObject::removeChildObject(IXEditorObject *pObject) +{ + ICompoundObject *pOldContainer = XTakeObject(pObject, NULL); + assert(pOldContainer == this); + + int idx = m_aObjects.indexOf(pObject, [](const SrcObject &a, IXEditorObject *b){ + return(a.pObj == b); + }); + assert(idx >= 0); + if(idx >= 0) + { + m_aObjects.erase(idx); + + g_pEditor->onObjectRemoved(pObject); + + //mem_release(pObject); + + if(!m_aObjects.size()) + { + CCommandDelete *pCmd = new CCommandDelete(); + pCmd->addObject(this); + XAttachCommand(pCmd); + } + } +} + +UINT CGroupObject::getObjectCount() +{ + return(m_aObjects.size()); +} +IXEditorObject* CGroupObject::getObject(UINT id) +{ + assert(id < m_aObjects.size()); + if(id < m_aObjects.size()) + { + return(m_aObjects[id].pObj); + } + return(NULL); +} + +void XMETHODCALLTYPE CGroupObject::getInternalData(const XGUID *pGUID, void **ppOut) +{ + if(*pGUID == X_IS_GROUP_GUID || *pGUID == X_IS_COMPOUND_GUID) + { + *ppOut = (void*)1; + } + else + { + BaseClass::getInternalData(pGUID, ppOut); + } +} diff --git a/source/terrax/GroupObject.h b/source/terrax/GroupObject.h new file mode 100644 index 000000000..1e51d9fcf --- /dev/null +++ b/source/terrax/GroupObject.h @@ -0,0 +1,110 @@ +#ifndef __GROUPOBJECT_H +#define __GROUPOBJECT_H + +#include <xcommon/editor/IXEditorObject.h> +#include <xcommon/editor/IXEditable.h> +#include "ICompoundObject.h" +#include <common/string.h> + + +// {B92F6791-0F82-4A47-BA46-6319149FEFED} +#define X_IS_GROUP_GUID DEFINE_XGUID(0xb92f6791, 0xf82, 0x4a47, 0xba, 0x46, 0x63, 0x19, 0x14, 0x9f, 0xef, 0xed) + + +//############################################################################# + +class CGroupObject final: public IXUnknownImplementation<ICompoundObject> +{ + DECLARE_CLASS(CGroupObject, IXUnknownImplementation<ICompoundObject>); +public: + CGroupObject(); + CGroupObject(const XGUID &guid); + ~CGroupObject(); + + void XMETHODCALLTYPE setPos(const float3_t &pos) override; + void XMETHODCALLTYPE setOrient(const SMQuaternion &orient) override; + void XMETHODCALLTYPE setSize(const float3_t &vSize) override; + + void XMETHODCALLTYPE getBound(float3 *pvMin, float3 *pvMax) override; + + void XMETHODCALLTYPE render(bool is3D, bool bRenderSelection, IXGizmoRenderer *pGizmoRenderer) override; + + bool XMETHODCALLTYPE rayTest(const float3 &vStart, const float3 &vEnd, float3 *pvOut = NULL, float3 *pvNormal = NULL, ID *pidMtrl = NULL, bool bReturnNearestPoint = false) override; + + void XMETHODCALLTYPE remove() override; + void XMETHODCALLTYPE create() override; + void XMETHODCALLTYPE preSetup() override; + void XMETHODCALLTYPE postSetup() override; + + void XMETHODCALLTYPE setKV(const char *szKey, const char *szValue) override; + const char* XMETHODCALLTYPE getKV(const char *szKey) override; + const X_PROP_FIELD* XMETHODCALLTYPE getPropertyMeta(UINT uKey) override; + UINT XMETHODCALLTYPE getProperyCount() override; + + const char* XMETHODCALLTYPE getTypeName() override; + const char* XMETHODCALLTYPE getClassName() override; + + float3_t XMETHODCALLTYPE getPos() override; + + SMQuaternion XMETHODCALLTYPE getOrient() override; + + bool XMETHODCALLTYPE isSelected() override + { + return(m_isSelected); + } + void XMETHODCALLTYPE setSelected(bool set) override; + + IXTexture* XMETHODCALLTYPE getIcon() override + { + return(NULL); + } + + void XMETHODCALLTYPE setSimulationMode(bool set) override; + + bool XMETHODCALLTYPE hasVisualModel() override + { + return(true); + } + + const XGUID* XMETHODCALLTYPE getGUID() override + { + return(&m_guid); + } + + void XMETHODCALLTYPE getInternalData(const XGUID *pGUID, void **ppOut) override; + + void addChildObject(IXEditorObject *pObject) override; + void removeChildObject(IXEditorObject *pObject) override; + + UINT getObjectCount() override; + IXEditorObject* getObject(UINT id) override; + + /*bool isRemoved() + { + return(m_isRemoved); + } + */ +private: + XGUID m_guid; + + bool m_isSelected = false; + + bool m_isRemoved = false; + + float3_t m_vPos; + SMQuaternion m_qOrient; + + struct SrcObject + { + IXEditorObject *pObj; + float3_t vOffset; + SMQuaternion qOffset; + }; + + Array<SrcObject> m_aObjects; + + String m_sGUID; + String m_sName; +}; + +#endif diff --git a/source/terrax/ProxyObject.cpp b/source/terrax/ProxyObject.cpp index ec0616104..20d1e4131 100644 --- a/source/terrax/ProxyObject.cpp +++ b/source/terrax/ProxyObject.cpp @@ -140,6 +140,7 @@ void XMETHODCALLTYPE CProxyObject::remove() XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ g_mObjectsLocation.erase(pObj); mem_release(pObj); + return(XEOR_SKIP_CHILDREN); }, this); m_isRemoved = true; @@ -171,6 +172,7 @@ void XMETHODCALLTYPE CProxyObject::create() XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ g_mObjectsLocation[pObj] = pParent; add_ref(pObj); + return(XEOR_SKIP_CHILDREN); }, this); @@ -235,6 +237,10 @@ bool CProxyObject::setDstObject(const XGUID &guid) if(pObj) { m_pTargetObject = pObj; + + ICompoundObject *pParent = XGetObjectParent(pObj); + SAFE_CALL(pParent, removeChildObject, pObj); + int idx = g_pLevelObjects.indexOf(pObj); assert(idx >= 0); if(idx >= 0) @@ -253,7 +259,7 @@ bool CProxyObject::setDstObject(const XGUID &guid) char tmp[64], tmp2[64]; XGUIDToSting(guid, tmp, sizeof(tmp)); XGUIDToSting(m_guid, tmp2, sizeof(tmp2)); - LibReport(REPORT_MSG_LEVEL_ERROR, "Cannot set the object %s as the proxy %s target object. The щиоусе is not found\n", tmp, tmp2); + LibReport(REPORT_MSG_LEVEL_ERROR, "Cannot set the object %s as the proxy %s target object. The object is not found\n", tmp, tmp2); return(false); } void CProxyObject::addSrcModel(const XGUID &guid) @@ -453,6 +459,8 @@ void CProxyObject::addChildObject(IXEditorObject *pObject) m_aObjects.push_back({pObject, m_qOrient.Conjugate() * (pObject->getPos() - m_vPos), pObject->getOrient() * m_qOrient.Conjugate()}); m_aModels[idx].pModel->addObject(pObject); + + g_pEditor->onObjectAdded(pObject); } } void CProxyObject::removeChildObject(IXEditorObject *pObject) @@ -478,6 +486,8 @@ void CProxyObject::removeChildObject(IXEditorObject *pObject) m_aModels[idx].pModel->removeObject(pObject); } + g_pEditor->onObjectRemoved(pObject); + mem_release(pObject); } } diff --git a/source/terrax/SceneTreeWindow.cpp b/source/terrax/SceneTreeWindow.cpp index e39d400f3..e423352cd 100644 --- a/source/terrax/SceneTreeWindow.cpp +++ b/source/terrax/SceneTreeWindow.cpp @@ -44,6 +44,9 @@ CSceneTreeWindow::CSceneTreeWindow(CEditor *pEditor, IXCore *pCore): m_pTreeMenu->addItem("Copy\tCtrl+C", "copy"); m_pTreeMenu->addItem("Delete\tDel", "delete"); m_pTreeMenu->addSeparator(); + m_pTreeMenu->addItem("Group\tCtrl+G", "group"); + m_pTreeMenu->addItem("Ungroup\tCtrl+U", "ungroup"); + m_pTreeMenu->addSeparator(); m_pTreeMenu->addItem("To Object\tCtrl+T", "to_object"); m_pTreeMenu->addItem("To World\tCtrl+Shift+T", "to_world"); m_pTreeMenu->addSeparator(); @@ -75,6 +78,9 @@ CSceneTreeWindow::CSceneTreeWindow(CEditor *pEditor, IXCore *pCore): pAccelTable->addItem({XAF_VIRTKEY, KEY_F2}, "rename"); pAccelTable->addItem({XAF_CTRL | XAF_VIRTKEY, KEY_E}, "center_on_selection"); pAccelTable->addItem({XAF_CTRL | XAF_SHIFT | XAF_VIRTKEY, KEY_E}, "go_to_selection"); + pAccelTable->addItem({XAF_CTRL | XAF_VIRTKEY, KEY_G}, "group"); + pAccelTable->addItem({XAF_CTRL | XAF_VIRTKEY, KEY_U}, "ungroup"); + pAccelTable->addItem({XAF_CTRL | XAF_SHIFT | XAF_VIRTKEY, KEY_G}, "ungroup"); m_pWindow->setAcceleratorTable(pAccelTable); mem_release(pAccelTable); @@ -117,6 +123,12 @@ CSceneTreeWindow::CSceneTreeWindow(CEditor *pEditor, IXCore *pCore): m_pWindow->addCommand("rename", [](void *pCtx){ ((CSceneTreeWindow*)pCtx)->m_pTree->editSelectedNode(); }, this); + m_pWindow->addCommand("group", [](void *pCtx){ + ((CSceneTreeWindow*)pCtx)->sendParentCommand(ID_TOOLS_GROUP); + }, this); + m_pWindow->addCommand("ungroup", [](void *pCtx){ + ((CSceneTreeWindow*)pCtx)->sendParentCommand(ID_TOOLS_UNGROUP); + }, this); onResize(); @@ -369,6 +381,11 @@ static int CompareNodes(const CSceneTreeAdapter::TreeNode &a, const CSceneTreeAd return(cmp); } +CSceneTreeAdapter::CSceneTreeAdapter() +{ + m_rootNode.isExpanded = true; +} + void CSceneTreeAdapter::setTree(IUITree *pTree) { m_pTree = pTree; @@ -617,6 +634,10 @@ bool CSceneTreeAdapter::isNodeSelected(UITreeNodeHandle hNode) bool CSceneTreeAdapter::onNodeExpanded(UITreeNodeHandle hNode, bool isExpanded, bool isRecursive) { + if(!hNode) + { + return(true); + } TreeNode *pNode = findTreeNode((IXEditorObject*)hNode); assert(pNode); if(pNode) @@ -665,6 +686,7 @@ bool CSceneTreeAdapter::onNodeSelected(UITreeNodeHandle hNode, bool isSelected, pCmd->addDeselected(pObj); } } + return(XEOR_CONTINUE); }); } @@ -705,6 +727,7 @@ bool CSceneTreeAdapter::onMultiSelected(UITreeNodeHandle *aNodes, UINT uNodeCoun pCmd->addDeselected(pObj); } } + return(XEOR_CONTINUE); }); } @@ -734,10 +757,12 @@ void CSceneTreeAdapter::onNodeEdited(UITreeNodeHandle hNode, const char *szNewTe { CCommandProperties *pCmd = new CCommandProperties(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { pCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); //pCmd->addObject((IXEditorObject*)hNode); pCmd->setKV("name", szNewText); @@ -756,19 +781,49 @@ void CSceneTreeAdapter::ensureExpanded(UITreeNodeHandle hNode) } } -void CSceneTreeAdapter::onObjectsetChanged() +static void SaveExpansionState(CSceneTreeAdapter::TreeNode *pNode, Map<IXEditorObject*, bool> *pMap) +{ + if(pNode->isExpanded) + { + (*pMap)[pNode->pObject] = true; + } + fora(i, pNode->aChildren) + { + SaveExpansionState(&pNode->aChildren[i], pMap); + } +} + +static void RestoreExpansionState(CSceneTreeAdapter::TreeNode *pNode, Map<IXEditorObject*, bool> *pMap, CSceneTreeAdapter *pAdapter) { - m_rootNode.aChildren.clearFast(); - m_rootNode.aChildren.reserve(g_pLevelObjects.size()); + if(pMap->KeyExists(pNode->pObject)) + { + pAdapter->onNodeExpanded((UITreeNodeHandle)pNode->pObject, true, false); + + fora(i, pNode->aChildren) + { + RestoreExpansionState(&pNode->aChildren[i], pMap, pAdapter); + } + } +} +void CSceneTreeAdapter::onObjectsetChanged() +{ if(m_hasFilter) { + m_rootNode.aChildren.clearFast(); + m_rootNode.aChildren.reserve(g_pLevelObjects.size()); // load filtered recursive bool hasItems = false; loadFiltered(&m_rootNode, NULL, &hasItems); } else { + Map<IXEditorObject*, bool> mapExpansionState; + SaveExpansionState(&m_rootNode, &mapExpansionState); + + m_rootNode.aChildren.clearFast(); + m_rootNode.aChildren.reserve(g_pLevelObjects.size()); + TreeNode tmp; fora(i, g_pLevelObjects) @@ -779,17 +834,16 @@ void CSceneTreeAdapter::onObjectsetChanged() tmp.pObject = pObj; m_rootNode.aChildren.push_back(tmp); - /* - //Func(pObj, isProxy ? true : false, pWhere); - - if(isProxy) + if(mapExpansionState.KeyExists(pObj)) { - ((CProxyObject*)pObj)->getObjectCount(); - }*/ + onNodeExpanded((UITreeNodeHandle)pObj, true, false); + } } sortChildren(&m_rootNode); + + RestoreExpansionState(&m_rootNode, &mapExpansionState, this); } m_pTree->notifyDatasetChanged(); @@ -847,10 +901,13 @@ bool CSceneTreeAdapter::hasSelection() { bool hasSelection = false; XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(!hasSelection && pObj->isSelected()) + if(pObj->isSelected()) { hasSelection = true; + + return(XEOR_STOP); } + return(XEOR_CONTINUE); }); return(hasSelection); diff --git a/source/terrax/SceneTreeWindow.h b/source/terrax/SceneTreeWindow.h index 29cf96453..aaaa81185 100644 --- a/source/terrax/SceneTreeWindow.h +++ b/source/terrax/SceneTreeWindow.h @@ -10,6 +10,8 @@ class ICompoundObject; class CSceneTreeAdapter final: public IUITreeAdapter { public: + CSceneTreeAdapter(); + void setTree(IUITree *pTree); UINT getColumnCount() override; diff --git a/source/terrax/UndoManager.cpp b/source/terrax/UndoManager.cpp index d658b5b5c..17b0c63a7 100644 --- a/source/terrax/UndoManager.cpp +++ b/source/terrax/UndoManager.cpp @@ -84,13 +84,12 @@ bool CUndoManager::execCommand(IXEditorCommand *pCommand, bool bSaveForUndo) ++m_isInCommandContext; if(!pCommand->isEmpty() && pCommand->exec()) { - if(aAttachedCommands.size()) { // create new command container CCommandContainer *pContainer = new CCommandContainer(); pContainer->addCommand(pCommand); - fora(i, aAttachedCommands) + for(UINT i = 0; i < aAttachedCommands.size(); ++i) // size can change during iteration { aAttachedCommands[i]->exec(); pContainer->addCommand(aAttachedCommands[i]); diff --git a/source/terrax/mainWindow.cpp b/source/terrax/mainWindow.cpp index 1ebdac59a..6996e46fb 100644 --- a/source/terrax/mainWindow.cpp +++ b/source/terrax/mainWindow.cpp @@ -40,6 +40,8 @@ #include "CommandBuildModel.h" #include "CommandDestroyModel.h" #include "CommandModifyModel.h" +#include "CommandGroup.h" +#include "CommandUngroup.h" #include "PropertyWindow.h" @@ -197,10 +199,12 @@ public: m_pPropsCmd = new CCommandProperties(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { m_pPropsCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); for(UINT i = 0, l = g_pPropWindow->getCustomTabCount(); i < l; ++i) @@ -542,10 +546,12 @@ static void DeleteSelection() { CCommandDelete *pDelCmd = new CCommandDelete(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { pDelCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); XExecCommand(pDelCmd); } @@ -667,6 +673,37 @@ static void ToClipboard(bool isCut = false) pConfig->set("meta", "proxy_count", szSection); + uCount = 0; + for(UINT i = 0, l = g_apGroups.size(); i < l; ++i) + { + CGroupObject *pObj = g_apGroups[i]; + if(pObj->isSelected()) + { + sprintf(szSection, "group_%u", uCount); + + XGUIDToSting(*pObj->getGUID(), szTmp, sizeof(szTmp)); + pConfig->set(szSection, "guid", szTmp); + + UINT uObjCount = 0; + sprintf(szTmp, "%u", pObj->getObjectCount()); + pConfig->set(szSection, "o_count", szTmp); + + for(UINT i = 0, l = pObj->getObjectCount(); i < l; ++i) + { + XGUIDToSting(*pObj->getObject(i)->getGUID(), szTmp, sizeof(szTmp)); + sprintf(szKey, "o_%u", uObjCount); + pConfig->set(szSection, szKey, szTmp); + ++uObjCount; + } + + ++uCount; + } + } + + sprintf(szSection, "%u", uCount); + pConfig->set("meta", "group_count", szSection); + + sprintf(szSection, "%f %f %f", g_xState.vSelectionBoundMin.x, g_xState.vSelectionBoundMin.y, g_xState.vSelectionBoundMin.z); pConfig->set("meta", "aabb_min", szSection); sprintf(szSection, "%f %f %f", g_xState.vSelectionBoundMax.x, g_xState.vSelectionBoundMax.y, g_xState.vSelectionBoundMax.z); @@ -896,6 +933,36 @@ guid = {9D7D2E62-24C7-42B7-8D83-8448FC4604F0} } } + szVal = pConfig->getKey("meta", "group_count"); + if(szVal) + { + sscanf(szVal, "%u", &uCount); + for(UINT i = 0; i < uCount; ++i) + { + sprintf(szSection, "group_%u", i); + const char *szTmp; + XGUID guid; + UINT uObjCount; + if( + (szTmp = pConfig->getKey(szSection, "guid")) && XGUIDFromString(&guid, szTmp) + && (szTmp = pConfig->getKey(szSection, "o_count")) && sscanf(szTmp, "%u", &uObjCount) + ) + { + char szKey[64]; + UINT uGroup = pCmd->addGroup(guid); + for(UINT j = 0; j < uObjCount; ++j) + { + sprintf(szKey, "o_%u", j); + if((szTmp = pConfig->getKey(szSection, szKey)) && XGUIDFromString(&guid, szTmp)) + { + pCmd->addGroupObject(uGroup, guid); + } + } + } + + } + } + XExecCommand(pCmd); } } @@ -1332,6 +1399,21 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) EnableMenuItem(hMenu, ID_EDIT_COPY, hasSelection ? MF_ENABLED : MF_DISABLED); EnableMenuItem(hMenu, ID_EDIT_DELETE, hasSelection ? MF_ENABLED : MF_DISABLED); EnableMenuItem(hMenu, ID_EDIT_PASTE, GetFileAttributesA(g_szClipboardFile) != ~0 ? MF_ENABLED : MF_DISABLED); + EnableMenuItem(hMenu, ID_TOOLS_CONVERTTOENTITY, hasSelection && IsWindowEnabled(g_hButtonToEntityWnd) ? MF_ENABLED : MF_DISABLED); + EnableMenuItem(hMenu, ID_TOOLS_CONVERTTOSTATIC, hasSelection ? MF_ENABLED : MF_DISABLED); + EnableMenuItem(hMenu, ID_TOOLS_GROUP, hasSelection ? MF_ENABLED : MF_DISABLED); + + bool hasGroupSelected = false; + fora(i, g_apGroups) + { + if(g_apGroups[i]->isSelected()) + { + hasGroupSelected = true; + break; + } + } + + EnableMenuItem(hMenu, ID_TOOLS_UNGROUP, hasGroupSelected ? MF_ENABLED : MF_DISABLED); } XUpdateUndoRedo(); @@ -1911,6 +1993,46 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) } break; + case ID_TOOLS_GROUP: + XExecCommand(new CCommandGroup()); + break; + + case ID_TOOLS_UNGROUP: + //if(!g_xConfig.m_bIgnoreGroups) + { + CCommandContainer *pContainer = NULL; + fora(i, g_apGroups) + { + CGroupObject *pGroup = g_apGroups[i]; + if(pGroup->isSelected()) + { + ICompoundObject *pParent = pGroup; + bool bSkip = false; + while((pParent = XGetObjectParent(pParent))) + { + if(pParent->isSelected()) + { + bSkip = true; + break; + } + } + if(!bSkip) + { + if(!pContainer) + { + pContainer = new CCommandContainer(); + } + pContainer->addCommand(new CCommandUngroup(pGroup)); + } + } + } + if(pContainer) + { + XExecCommand(pContainer); + } + } + break; + case ID_HELP_SKYXENGINEWIKI: ShellExecute(0, 0, "https://wiki.skyxengine.com", 0, 0, SW_SHOW); break; @@ -1924,10 +2046,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CCommandSelect *pCmdUnselect = new CCommandSelect(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { pCmdUnselect->addDeselected(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); g_pUndoManager->execCommand(pCmdUnselect); } @@ -1937,10 +2061,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CCommandSelect *pCmdSelect = new CCommandSelect(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(!pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(!pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { pCmdSelect->addSelected(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); g_pUndoManager->execCommand(pCmdSelect); } @@ -2069,10 +2195,12 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { CCommandRotate *pCmd = new CCommandRotate(GetKeyState(VK_SHIFT) < 0); XEnumerateObjects([pCmd](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { pCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); X_2D_VIEW xCurView = g_xConfig.m_x2DView[g_xState.activeWindow]; @@ -2128,6 +2256,8 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) hasUnselectedChild = true; } } + + return(XEOR_CONTINUE); }, (ICompoundObject*)pObj); if(hasUnselectedChild) { @@ -2152,6 +2282,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) pCmdUnselect->addDeselected(pObj); } } + return(XEOR_CONTINUE); }, (ICompoundObject*)pObj); if(pObj->isSelected()) { @@ -2177,6 +2308,7 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) pCmdUnselect->addDeselected(pParent); } } + return(XEOR_CONTINUE); }, (ICompoundObject*)pObj); } } @@ -2190,12 +2322,14 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { pCmdUnselect->addDeselected(pObj); } + return(XEOR_CONTINUE); }, (ICompoundObject*)pObj); pCmdUnselect->addSelected(pObj); } } } + return(XEOR_CONTINUE); }); pCmdUnselect->setIGMode(CCommandSelect::IGM_ENABLE); @@ -2896,6 +3030,7 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP } } } + return(XEOR_CONTINUE); }); if(bUse) @@ -3052,7 +3187,8 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP g_aRaytracedItems.clearFast(); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent) + //if(g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent) + if(!g_xConfig.m_bIgnoreGroups || !isProxy) { float fDist2 = -1.0f; if(!pObj->hasVisualModel()) @@ -3087,6 +3223,8 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP g_aRaytracedItems.push_back({fDist2, pObj}); } } + + return(XEOR_CONTINUE); }); g_aRaytracedItems.quickSort([](const SelectItem &a, const SelectItem &b){ @@ -3123,6 +3261,7 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP } } } + return(XEOR_CONTINUE); }); s_aRaytracedItems.quickSort([](const SelectItem2 &a, const SelectItem2 &b){ @@ -3267,10 +3406,12 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP s_pScaleCmd->setTransformDir(dirs[g_xConfig.m_x2DView[g_xState.activeWindow]][i]); s_pScaleCmd->setStartPos(vStartPos); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { s_pScaleCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); } else if(g_xState.xformType == X2DXF_ROTATE) @@ -3280,10 +3421,12 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP s_pRotateCmd->setStartOrigin((g_xState.vSelectionBoundMax + g_xState.vSelectionBoundMin) * 0.5f * vMask, float3(1.0f) - vMask); s_pRotateCmd->setStartPos(vStartPos); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { s_pRotateCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); } bHandled = true; @@ -3308,7 +3451,7 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP bool wasSel = false; XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(!(g_xConfig.m_bIgnoreGroups && isProxy)) + //if(!(g_xConfig.m_bIgnoreGroups && isProxy)) { bool sel = XIsClicked(pObj->getPos()); @@ -3355,6 +3498,7 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP } } } + return(XEOR_CONTINUE); }); if(bUse) @@ -3386,25 +3530,44 @@ LRESULT CALLBACK RenderWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lP s_pMoveCmd->setStartPos(XSnapToGrid(vStartPos)); bool bReferenceFound = false; - + IXEditorObject *pReferenceObject = NULL; float3 vBoundMin, vBoundMax; XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ if(pObj->isSelected()) { - if(g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent) - { - s_pMoveCmd->addObject(pObj); - } - if(!bReferenceFound && g_xConfig.m_bSnapGrid) + s_pMoveCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); + } + return(XEOR_CONTINUE); + }); + + if(g_xConfig.m_bSnapGrid) + { + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ + if(pObj->isSelected()) { - pObj->getBound(&vBoundMin, &vBoundMax); - if(XIsMouseInBound(g_xState.activeWindow, vBoundMin, vBoundMax)) + if((!bReferenceFound || (pReferenceObject == pParent))) { - bReferenceFound = true; + pObj->getBound(&vBoundMin, &vBoundMax); + if(XIsMouseInBound(g_xState.activeWindow, vBoundMin, vBoundMax)) + { + bReferenceFound = true; + pReferenceObject = pObj; + + if(!isProxy) + { + return(XEOR_STOP); + } + } + else + { + return(XEOR_SKIP_CHILDREN); + } } } - } - }); + return(XEOR_CONTINUE); + }); + } if(!bReferenceFound) { @@ -3917,11 +4080,13 @@ void XFrameRun(float fDeltaTime) if(g_uSelectedIndex == ~0 && !g_isSelectionCtrl) { XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + //if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups || !pParent)) { pObj->setSelected(false); g_pSelectCmd->addDeselected(pObj); } + return(XEOR_CONTINUE); }); } @@ -4430,7 +4595,7 @@ void XUpdatePropWindow() UINT uSelectedCount = 0; XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { ++uSelectedCount; if(!szFirstType) @@ -4490,7 +4655,9 @@ void XUpdatePropWindow() mProps[AAString(pField->szKey)] = {*pField, true, pObj->getKV(pField->szKey)}; } } + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); XCleanupUnreferencedPropGizmos(); @@ -4590,10 +4757,12 @@ void XMETHODCALLTYPE CGizmoMoveCallback::onStart(IXEditorGizmoMove *pGizmo) m_pCmd = new CCommandMove(GetKeyState(VK_SHIFT) < 0); m_pCmd->setStartPos(pGizmo->getPos()); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { m_pCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); } void XMETHODCALLTYPE CGizmoMoveCallback::onEnd(IXEditorGizmoMove *pGizmo) @@ -4626,10 +4795,12 @@ void XMETHODCALLTYPE CGizmoRotateCallback::onStart(const float3_t &vAxis, IXEdit m_pCmd->setStartOrigin(pGizmo->getPos(), vAxis); m_pCmd->setStartPos(pGizmo->getPos() + vStartOffset); XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)) + if(pObj->isSelected()/* && (g_xConfig.m_bIgnoreGroups ? !isProxy : !pParent)*/) { m_pCmd->addObject(pObj); + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); pGizmo->setOrient(SMQuaternion()); @@ -4649,6 +4820,11 @@ void CheckToolbarButton(int iCmd, BOOL isChecked) SendMessage(g_hToolbarWnd, TB_CHECKBUTTON, iCmd, MAKELPARAM(isChecked, 0)); } +void EnableToolbarButton(int iCmd, BOOL isChecked) +{ + SendMessage(g_hToolbarWnd, TB_ENABLEBUTTON, iCmd, MAKELPARAM(isChecked, 0)); +} + void CheckXformButton(X_2DXFORM_TYPE type, bool isChecked) { int iCmd = 0; @@ -4672,14 +4848,14 @@ HWND CreateToolbar(HWND hWndParent) { // Declare and initialize local constants. const int ImageListID = 0; - const int numButtons = 4; + const int numButtons = 8; const int bitmapSize = 16; const DWORD buttonStyles = BTNS_AUTOSIZE; // Create the toolbar. HWND hWndToolbar = CreateWindowEx(0, TOOLBARCLASSNAME, NULL, - WS_CHILD | TBSTYLE_WRAPABLE | TBSTYLE_LIST | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS, 0, 0, 0, 0, + WS_CHILD | /*TBSTYLE_WRAPABLE | */TBSTYLE_LIST | TBSTYLE_FLAT | TBSTYLE_TOOLTIPS, 0, 0, 0, 0, hWndParent, NULL, hInst, NULL); //SetWindowLong(hWndToolbar, GWL_EXSTYLE, GetWindowLong(hWndToolbar, GWL_EXSTYLE) | TBSTYLE_EX_MIXEDBUTTONS); @@ -4732,6 +4908,8 @@ HWND CreateToolbar(HWND hWndParent) {MAKELONG(1, ImageListID), ID_XFORM_TRANSLATE, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Move [W]"}, {MAKELONG(2, ImageListID), ID_XFORM_ROTATE, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Rotate [R]"}, {0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0}, + {MAKELONG(6, ImageListID), ID_TOOLS_GROUP, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Group selected [Ctrl+G]"}, + {MAKELONG(7, ImageListID), ID_TOOLS_UNGROUP, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Ungroup selected [Ctrl+U]"}, {MAKELONG(5, ImageListID), ID_IGNORE_GROUPS, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Toggle group ignore [Ctrl+W]"}, {0, 0, TBSTATE_ENABLED, BTNS_SEP, 0L, 0}, {MAKELONG(3, ImageListID), ID_LEVEL_RUN, TBSTATE_ENABLED, buttonStyles, {0}, 0, (INT_PTR)"Run [F5]"} @@ -4746,7 +4924,7 @@ HWND CreateToolbar(HWND hWndParent) SendMessage(hWndToolbar, TB_SETEXTENDEDSTYLE, 0, (LPARAM)TBSTYLE_EX_MIXEDBUTTONS); ShowWindow(hWndToolbar, TRUE); - return hWndToolbar; + return(hWndToolbar); } void XSetXformType(X_2DXFORM_TYPE type) diff --git a/source/terrax/resource.h b/source/terrax/resource.h index d5ba81e5ad59d168a6b76c7d8032749b41f9af8c..bdbd2937659c1c2521c71074b5341ca191f4b3a6 100644 GIT binary patch literal 24580 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 literal 11833 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 diff --git a/source/terrax/resource/toolbar1.bmp b/source/terrax/resource/toolbar1.bmp index aa1f7466d2b9315dfbfdf34a4cb6bf69abf3b6da..dbb1f0b437a3419ade41cafc9834107c00921d22 100644 GIT binary patch delta 355 zcmX@cwu8gi$=8B~0Sw9*7#K7d7#JED7#R2&7#J8CAQFd|85o4PAsDQ1qH!^Qk-8EX zm6w+nFff!)Tx!U#UJeFD<>l!GXO6H>ylN<*4v{S?FE1-NbHt&1G8>~2UpYv&ybLB} z#%KUmcjkygQF$6j(xH5E8c2|VfdMR7US3dM?od8?8lxdU&lv}hK_ENX%L~dU9|I}m zIpa`X?ob3W7bG$H8${5By}SS<=m2upWHly`ZWjgy28bQyY3Y;0m<$Be*~?4V%gZ4O z)62^zw}Au;%FDrmAd5kQ+n9{G8Q4Jvfvhc?{DjGf2kzkl28PK(%qBw6Fi`?217QXR E0KM07M*si- delta 113 zcmdnNag5E_$=8jU0Sw9*7#K7d7#I>57#R2&7#J8CSis^Jn7|a65MW?n5SVCOJaL=B z#BT<Z%@~a)S1{^L-o|J!`5U9*WOF9{$z@CileaM$PX5lMKiQ1gU~(C=@#LM%x|6># G8vp<}q!~;A diff --git a/source/terrax/resource/toolbar2.bmp b/source/terrax/resource/toolbar2.bmp index 710d69c5e27e3c5948ab841bcdd9cbcc3619d9b2..5051572bc72dbbd0c0b1a1af2b48dcd827182f2d 100644 GIT binary patch delta 360 zcmX@cwu8gi$=8B~0Sw9*7#K7d7#JED7#R2&7#J8CAQFd|85o4PAsDQ1qH!_*2Yv=H z`v3p`2L^`!6PFtD^Zy5f5C8x3e_&wXpLo?!fFC0J;s5^+9~c-I{!eCOG~xqk;Q#*t zCS=BF09D8E;XeaNlHvd4G>{<JGKLQjD;WMyp2lbhau>)TkewiU@-apOF{mKO3XskJ z|NjU1VDcPBEdeAc29WC}t1%h!LCl5N^q*mJD3c*r9mug@SMvY=Ke>&`KmcSIir_XT nV{QhpH$irPnEZsvhzHF55AqHJ!(<_56Cr58fUN^r$-n>rmZpVF delta 120 zcmdnNag5E_$=8jU0Sw9*7#K7d7#I>57#R2&7#J8CSis^Jn7|a65MW?n5SVCOJaL=B z#BT<Z%@~a)S1{^L-o|J!`5U9*WOF8cw*UV@`X~Qk)SkSZ$zbw#CjH4~%m$Oon2jgz LWY(ShjoAPI_e2}t diff --git a/source/terrax/terrax.cpp b/source/terrax/terrax.cpp index 8d4994851..56df65475 100644 --- a/source/terrax/terrax.cpp +++ b/source/terrax/terrax.cpp @@ -75,6 +75,7 @@ Map<AAString, IXEditable*> g_mEditableSystems; Map<XGUID, IXEditorModel*> g_apLevelModels; Map<IXEditorObject*, ICompoundObject*> g_mObjectsLocation; Array<CProxyObject*> g_apProxies; +Array<CGroupObject*> g_apGroups; //SGeom_GetCountModels() Array<IXEditorImporter*> g_pEditorImporters; @@ -1005,6 +1006,12 @@ int main(int argc, char **argv) mem_release(g_apProxies[i]); } g_apProxies.clear(); + + fora(i, g_apGroups) + { + mem_release(g_apGroups[i]); + } + g_apGroups.clear(); for(Map<XGUID, IXEditorModel*>::Iterator i = g_apLevelModels.begin(); i; ++i) { @@ -1075,72 +1082,118 @@ int main(int argc, char **argv) pCfg->save(); mem_release(pCfg); - // save proxies - sprintf(szPathLevel, "levels/%s/editor/proxies.json", pData->szLevelName); - + + // save groups + sprintf(szPathLevel, "levels/%s/editor/groups.json", pData->szLevelName); + IFile *pFile = pFS->openFile(szPathLevel, FILE_MODE_WRITE); if(!pFile) { LibReport(REPORT_MSG_LEVEL_ERROR, "Unable to save data '%s'\n", szPathLevel); - return; } - - pFile->writeText("[\n"); - - sprintf(szPathLevel, "levels/%s/models", pData->szLevelName); - pFS->deleteDirectory(szPathLevel); - //IFileIterator *pIter = pFS->getFileList(szPathLevel, "dse"); - //const char *szFile; - //while((szFile = pIter->next())) - //{ - // //printf("%s\n", szFile); - // pFS->deleteFile(szFile); - //} - - bool isFirst = true; - char tmp[64]; - fora(i, g_apProxies) + else { - CProxyObject *pProxy = g_apProxies[i]; - if(pProxy->isRemoved()) + pFile->writeText("[\n"); + + bool isFirst = true; + char tmp[64]; + fora(i, g_apGroups) { - continue; - } - pFile->writeText("\t%s{\n", isFirst ? "" : ","); - isFirst = false; + CGroupObject *pGroup = g_apGroups[i]; - XGUIDToSting(*pProxy->getGUID(), tmp, sizeof(tmp)); - pFile->writeText("\t\t\"guid\": \"%s\"\n", tmp); - XGUIDToSting(*pProxy->getTargetObject()->getGUID(), tmp, sizeof(tmp)); - pFile->writeText("\t\t,\"t\": \"%s\"\n", tmp); - pFile->writeText("\t\t,\"s\": [\n", tmp); + pFile->writeText("\t%s{\n", isFirst ? "" : ","); + isFirst = false; - // TODO don't save empty models! + XGUIDToSting(*pGroup->getGUID(), tmp, sizeof(tmp)); + pFile->writeText("\t\t\"guid\": \"%s\"\n", tmp); + pFile->writeText("\t\t,\"name\": \"%s\"\n", pGroup->getKV("name")); - for(UINT j = 0, jl = pProxy->getModelCount(); j < jl; ++j) - { - XGUIDToSting(*pProxy->getModel(j)->getGUID(), tmp, sizeof(tmp)); - pFile->writeText("\t\t\t%s\"%s\"\n", j == 0 ? "" : ",", tmp); - } + pFile->writeText("\t\t,\"o\": [\n", tmp); + + for(UINT j = 0, jl = pGroup->getObjectCount(); j < jl; ++j) + { + XGUIDToSting(*pGroup->getObject(j)->getGUID(), tmp, sizeof(tmp)); + pFile->writeText("\t\t\t%s\"%s\"\n", j == 0 ? "" : ",", tmp); + } - pFile->writeText("\t\t]\n\t\t,\"o\": [\n", tmp); + pFile->writeText("\t\t]\n", tmp); - for(UINT j = 0, jl = pProxy->getObjectCount(); j < jl; ++j) - { - XGUIDToSting(*pProxy->getObject(j)->getGUID(), tmp, sizeof(tmp)); - pFile->writeText("\t\t\t%s\"%s\"\n", j == 0 ? "" : ",", tmp); + pFile->writeText("\t}\n"); } - pFile->writeText("\t\t]\n", tmp); + pFile->writeText("]\n"); + + mem_release(pFile); + } - pFile->writeText("\t}\n"); - pProxy->saveModel(); + // save proxies + sprintf(szPathLevel, "levels/%s/editor/proxies.json", pData->szLevelName); + + pFile = pFS->openFile(szPathLevel, FILE_MODE_WRITE); + if(!pFile) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Unable to save data '%s'\n", szPathLevel); } + else + { + pFile->writeText("[\n"); + + sprintf(szPathLevel, "levels/%s/models", pData->szLevelName); + pFS->deleteDirectory(szPathLevel); + //IFileIterator *pIter = pFS->getFileList(szPathLevel, "dse"); + //const char *szFile; + //while((szFile = pIter->next())) + //{ + // //printf("%s\n", szFile); + // pFS->deleteFile(szFile); + //} + + bool isFirst = true; + char tmp[64]; + fora(i, g_apProxies) + { + CProxyObject *pProxy = g_apProxies[i]; + if(pProxy->isRemoved()) + { + continue; + } + pFile->writeText("\t%s{\n", isFirst ? "" : ","); + isFirst = false; - pFile->writeText("\n]\n"); + XGUIDToSting(*pProxy->getGUID(), tmp, sizeof(tmp)); + pFile->writeText("\t\t\"guid\": \"%s\"\n", tmp); + XGUIDToSting(*pProxy->getTargetObject()->getGUID(), tmp, sizeof(tmp)); + pFile->writeText("\t\t,\"t\": \"%s\"\n", tmp); + pFile->writeText("\t\t,\"s\": [\n", tmp); - mem_release(pFile); + // TODO don't save empty models! + + for(UINT j = 0, jl = pProxy->getModelCount(); j < jl; ++j) + { + XGUIDToSting(*pProxy->getModel(j)->getGUID(), tmp, sizeof(tmp)); + pFile->writeText("\t\t\t%s\"%s\"\n", j == 0 ? "" : ",", tmp); + } + + pFile->writeText("\t\t]\n\t\t,\"o\": [\n", tmp); + + for(UINT j = 0, jl = pProxy->getObjectCount(); j < jl; ++j) + { + XGUIDToSting(*pProxy->getObject(j)->getGUID(), tmp, sizeof(tmp)); + pFile->writeText("\t\t\t%s\"%s\"\n", j == 0 ? "" : ",", tmp); + } + + pFile->writeText("\t\t]\n", tmp); + + pFile->writeText("\t}\n"); + + pProxy->saveModel(); + } + + pFile->writeText("]\n"); + + mem_release(pFile); + } } break; @@ -1259,7 +1312,120 @@ int main(int argc, char **argv) if(!isLoaded) { - LibReport(REPORT_MSG_LEVEL_ERROR, "Unable to load '%s'\n", szFile); + LibReport(REPORT_MSG_LEVEL_WARNING, "Unable to load '%s'\n", szFile); + } + } + + sprintf(szFile, "levels/%s/editor/groups.json", pData->szLevelName); + + pFile = Core_GetIXCore()->getFileSystem()->openFile(szFile, FILE_MODE_READ); + if(pFile) + { + size_t sizeFile = pFile->getSize(); + char *szJSON = new char[sizeFile + 1]; + pFile->readBin(szJSON, sizeFile); + szJSON[sizeFile] = 0; + + bool isLoaded = false; + + IXJSON *pJSON = (IXJSON*)Core_GetIXCore()->getPluginManager()->getInterface(IXJSON_GUID); + IXJSONItem *pRoot; + if(pJSON->parse(szJSON, &pRoot)) + { + IXJSONArray *pArr = pRoot->asArray(); + if(pArr) + { + Array<CGroupObject*> aGroups(pArr->size()); + const char *szGUID = NULL; + const char *szName = NULL; + XGUID guid; + + for(UINT j = 0, jl = pArr->size(); j < jl; ++j) + { + aGroups[j] = NULL; + + IXJSONObject *pGroupObj = pArr->at(j)->asObject(); + if(pGroupObj) + { + IXJSONItem *pGuidItem = pGroupObj->getItem("guid"); + if(!pGuidItem || !(szGUID = pGuidItem->getString()) || !XGUIDFromString(&guid, szGUID)) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid group '%u' in '%s'. Invalid GUID\n", j, szFile); + continue; + } + + IXJSONItem *pNameItem = pGroupObj->getItem("name"); + if(!pNameItem || !(szName = pNameItem->getString())) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid group '%u' in '%s'. Invalid name\n", j, szFile); + continue; + } + + IXJSONItem *pOItem = pGroupObj->getItem("o"); + IXJSONArray *pOArr = NULL; + if(!pOItem || !(pOArr = pOItem->asArray())) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid group '%u' in '%s'. Missing 'o' key\n", j, szFile); + continue; + } + + if(!pOArr->size()) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid group '%u' in '%s'. No child objects\n", j, szFile); + continue; + } + + CGroupObject *pGroup = new CGroupObject(guid); + pGroup->setKV("name", szName); + + g_apGroups.push_back(pGroup); + + add_ref(pGroup); + g_pLevelObjects.push_back(pGroup); + + aGroups[j] = pGroup; + } + } + + for(UINT j = 0, jl = pArr->size(); j < jl; ++j) + { + CGroupObject *pGroup = aGroups[j]; + if(pGroup) + { + IXJSONArray *pOArr = pArr->at(j)->asObject()->getItem("o")->asArray(); + + for(UINT k = 0, kl = pOArr->size(); k < kl; ++k) + { + szGUID = pOArr->at(k)->getString(); + if(!szGUID || !XGUIDFromString(&guid, szGUID)) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid object '%u' guid in group '%u' in '%s'. '%s'\n", k, j, szFile, szGUID ? szGUID : ""); + continue; + } + + IXEditorObject *pObj = XFindObjectByGUID(guid); + if(!pObj) + { + LibReport(REPORT_MSG_LEVEL_ERROR, "Invalid object '%u' in group '%u' in '%s'. '%s'. No object with GUID found.\n", k, j, szFile, szGUID ? szGUID : ""); + continue; + } + pGroup->addChildObject(pObj); + + isLoaded = true; + } + } + } + } + + mem_release(pRoot); + } + + mem_delete_a(szJSON); + mem_release(pFile); + + if(!isLoaded) + { + LibReport(REPORT_MSG_LEVEL_WARNING, "Unable to load '%s'\n", szFile); } } @@ -1577,6 +1743,7 @@ void XRender3D() { pObj->render(true, true, g_pSelectionRenderer); } + return(XEOR_CONTINUE); }); g_pSelectionRenderer->render(false, false); @@ -1590,6 +1757,7 @@ void XRender3D() { pObj->render(true, false, g_pUnselectedRenderer); } + return(XEOR_CONTINUE); }); g_pUnselectedRenderer->render(false, false); @@ -1679,25 +1847,25 @@ void XRender3D() { UINT uHandlerCount = 0; pvData = NULL; - for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i) - { - float3_t vPos = g_pLevelObjects[i]->getPos(); - //@TODO: Add visibility check + + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ + float3_t vPos = pObj->getPos(); + TODO("Add visibility check"); /*if(fViewportBorders.x > vPos.x || fViewportBorders.z < vPos.x || fViewportBorders.y < vPos.z) // not visible { - continue; + continue; }*/ - //if(isSelected != g_pLevelObjects[i]->isSelected() || (!isSelected && (g_pLevelObjects[i]->hasVisualModel() || g_pLevelObjects[i]->getIcon()))) - if(g_pLevelObjects[i]->hasVisualModel() || isSelected != g_pLevelObjects[i]->isSelected() || (!isSelected && g_pLevelObjects[i]->getIcon())) + + if(pObj->hasVisualModel() || isSelected != pObj->isSelected() || (!isSelected && pObj->getIcon())) { - continue; + return(XEOR_CONTINUE); } if(!pvData && !g_xRenderStates.pHandlerInstanceVB->lock((void**)&pvData, GXBL_WRITE)) { - break; + return(XEOR_STOP); } float3 vMin, vMax; - g_pLevelObjects[i]->getBound(&vMin, &vMax); + pObj->getBound(&vMin, &vMax); pvData[uHandlerCount++] = {(float3)((vMax + vMin) * 0.5f), (float3)(vMax - vMin)}; if(uHandlerCount == X_MAX_HANDLERS_PER_DIP) @@ -1707,7 +1875,10 @@ void XRender3D() pvData = NULL; uHandlerCount = 0; } - } + + return(XEOR_CONTINUE); + }); + if(pvData) { g_xRenderStates.pHandlerInstanceVB->unlock(); @@ -1774,6 +1945,7 @@ void XRender3D() icon.vPos = pObj->getPos(); aIcons.push_back(icon); } + return(XEOR_CONTINUE); }); aIcons.quickSort([](const Icon &a, const Icon &b){ return(a.pTexture < b.pTexture); @@ -1882,6 +2054,7 @@ void XRender2D(IXCamera *pCamera, X_2D_VIEW view, float fScale, bool preScene, b { pObj->render(false, true, g_pSelectionRenderer); } + return(XEOR_CONTINUE); }); g_isRenderedSelection3D = false; @@ -1900,6 +2073,7 @@ void XRender2D(IXCamera *pCamera, X_2D_VIEW view, float fScale, bool preScene, b { pObj->render(false, false, g_pUnselectedRenderer); } + return(XEOR_CONTINUE); }); g_isRenderedUnselected3D = false; @@ -1950,16 +2124,16 @@ void XRender2D(IXCamera *pCamera, X_2D_VIEW view, float fScale, bool preScene, b }*/ if(isSelected != pObj->isSelected()) { - return; + return(XEOR_CONTINUE); } if(isProxy) { - return; + return(XEOR_CONTINUE); } if(!pvData && !g_xRenderStates.pHandlerInstanceVB->lock((void**)&pvData, GXBL_WRITE)) { - return; + return(XEOR_CONTINUE); } pvData[uHandlerCount++] = vPos; @@ -1970,6 +2144,7 @@ void XRender2D(IXCamera *pCamera, X_2D_VIEW view, float fScale, bool preScene, b pvData = NULL; uHandlerCount = 0; } + return(XEOR_CONTINUE); }); if(pvData) @@ -2018,25 +2193,24 @@ void XRender2D(IXCamera *pCamera, X_2D_VIEW view, float fScale, bool preScene, b { UINT uHandlerCount = 0; pvData = NULL; - for(UINT i = 0, l = g_pLevelObjects.size(); i < l; ++i) - { - float3_t vPos = g_pLevelObjects[i]->getPos(); - //@TODO: Add visibility check + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ + float3_t vPos = pObj->getPos(); + TODO("Add visibility check"); /*if(fViewportBorders.x > vPos.x || fViewportBorders.z < vPos.x || fViewportBorders.y < vPos.z) // not visible { continue; }*/ - //if(isSelected != g_pLevelObjects[i]->isSelected() || (!isSelected && (g_pLevelObjects[i]->hasVisualModel() || g_pLevelObjects[i]->getIcon()))) - if(g_pLevelObjects[i]->hasVisualModel() || isSelected != g_pLevelObjects[i]->isSelected()) + + if(pObj->hasVisualModel() || isSelected != pObj->isSelected()) { - continue; + return(XEOR_CONTINUE); } if(!pvData && !g_xRenderStates.pHandlerInstanceVB->lock((void**)&pvData, GXBL_WRITE)) { - break; + return(XEOR_STOP); } float3 vMin, vMax; - g_pLevelObjects[i]->getBound(&vMin, &vMax); + pObj->getBound(&vMin, &vMax); pvData[uHandlerCount++] = {(float3)((vMax + vMin) * 0.5f), (float3)(vMax - vMin)}; if(uHandlerCount == X_MAX_HANDLERS_PER_DIP) @@ -2046,7 +2220,10 @@ void XRender2D(IXCamera *pCamera, X_2D_VIEW view, float fScale, bool preScene, b pvData = NULL; uHandlerCount = 0; } - } + + return(XEOR_CONTINUE); + }); + if(pvData) { g_xRenderStates.pHandlerInstanceVB->unlock(); @@ -2436,7 +2613,7 @@ void XUpdateSelectionBound() float3 vMin, vMax; XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ - if(pObj->isSelected() && !(g_xConfig.m_bIgnoreGroups && isProxy)) + if(pObj->isSelected()/* && !(g_xConfig.m_bIgnoreGroups && isProxy)*/) { pObj->getBound(&vMin, &vMax); if(!g_xState.bHasSelection) @@ -2450,8 +2627,25 @@ void XUpdateSelectionBound() g_xState.vSelectionBoundMax = (float3)SMVectorMax(g_xState.vSelectionBoundMax, vMax); g_xState.vSelectionBoundMin = (float3)SMVectorMin(g_xState.vSelectionBoundMin, vMin); } + return(XEOR_SKIP_CHILDREN); } + return(XEOR_CONTINUE); }); + + bool hasGroupSelected = false; + if(g_xState.bHasSelection) + { + fora(i, g_apGroups) + { + if(g_apGroups[i]->isSelected()) + { + hasGroupSelected = true; + break; + } + } + } + EnableToolbarButton(ID_TOOLS_GROUP, g_xState.bHasSelection); + EnableToolbarButton(ID_TOOLS_UNGROUP, hasGroupSelected); } bool XRayCast(X_WINDOW_POS wnd) @@ -2487,6 +2681,7 @@ bool XRayCast(X_WINDOW_POS wnd) { res = true; } + return(XEOR_CONTINUE); }); return(false); } @@ -2746,15 +2941,17 @@ ICompoundObject* XGetObjectParent(IXEditorObject *pObject) IXEditorObject* XFindObjectByGUID(const XGUID &guid) { - fora(i, g_pLevelObjects) - { - if(*g_pLevelObjects[i]->getGUID() == guid) + IXEditorObject *pFoundObj = NULL; + XEnumerateObjects([&](IXEditorObject *pObj, bool isProxy, ICompoundObject *pParent){ + if(*pObj->getGUID() == guid) { - return(g_pLevelObjects[i]); + pFoundObj = pObj; + return(XEOR_STOP); } - } + return(XEOR_CONTINUE); + }); - return(NULL); + return(pFoundObj); } void BeginMaterialEdit(const char *szMaterialName) diff --git a/source/terrax/terrax.h b/source/terrax/terrax.h index d5e581939..0f512ce83 100644 --- a/source/terrax/terrax.h +++ b/source/terrax/terrax.h @@ -36,6 +36,7 @@ #include "MaterialEditor.h" #include "Editor.h" #include "ProxyObject.h" +#include "GroupObject.h" enum X_VIEWPORT_LAYOUT { @@ -216,9 +217,17 @@ extern Array<IXEditorObject*> g_pLevelObjects; extern Map<XGUID, IXEditorModel*> g_apLevelModels; extern Map<IXEditorObject*, ICompoundObject*> g_mObjectsLocation; extern Array<CProxyObject*> g_apProxies; +extern Array<CGroupObject*> g_apGroups; + +enum XENUMERATE_OBJECTS_RESULT +{ + XEOR_CONTINUE, + XEOR_SKIP_CHILDREN, + XEOR_STOP +}; template<typename T, class L> -void XEnumerateObjects(const T &Func, L *pWhere) +XENUMERATE_OBJECTS_RESULT XEnumerateObjects(const T &Func, L *pWhere) { void *isProxy; if(pWhere) @@ -228,10 +237,19 @@ void XEnumerateObjects(const T &Func, L *pWhere) IXEditorObject *pObj = pWhere->getObject(i); isProxy = NULL; pObj->getInternalData(&X_IS_COMPOUND_GUID, &isProxy); - Func(pObj, isProxy ? true : false, pWhere); - if(isProxy) + XENUMERATE_OBJECTS_RESULT res = Func(pObj, isProxy ? true : false, pWhere); + if(res == XEOR_STOP) + { + return(XEOR_STOP); + } + + if(isProxy && res != XEOR_SKIP_CHILDREN) { - XEnumerateObjects(Func, (ICompoundObject*)pObj); + res = XEnumerateObjects(Func, (ICompoundObject*)pObj); + if(res == XEOR_STOP) + { + return(XEOR_STOP); + } } } } @@ -242,19 +260,30 @@ void XEnumerateObjects(const T &Func, L *pWhere) IXEditorObject *pObj = g_pLevelObjects[i]; isProxy = NULL; pObj->getInternalData(&X_IS_COMPOUND_GUID, &isProxy); - Func(pObj, isProxy ? true : false, pWhere); - if(isProxy) + XENUMERATE_OBJECTS_RESULT res = Func(pObj, isProxy ? true : false, pWhere); + if(res == XEOR_STOP) { - XEnumerateObjects(Func, (ICompoundObject*)pObj); + return(XEOR_STOP); + } + + if(isProxy && res != XEOR_SKIP_CHILDREN) + { + res = XEnumerateObjects(Func, (ICompoundObject*)pObj); + if(res == XEOR_STOP) + { + return(XEOR_STOP); + } } } } + + return(XEOR_CONTINUE); } template<typename T> -void XEnumerateObjects(const T &Func) +XENUMERATE_OBJECTS_RESULT XEnumerateObjects(const T &Func) { - XEnumerateObjects(Func, (ICompoundObject*)NULL); + return(XEnumerateObjects(Func, (ICompoundObject*)NULL)); } void XDrawBorder(GXCOLOR color, const float3_t &vA, const float3_t &vB, const float3_t &vC, const float3_t &vD, float fViewportScale = 0.01f); @@ -292,6 +321,7 @@ IXEditorObject* XFindObjectByGUID(const XGUID &guid); ICompoundObject* XGetObjectParent(IXEditorObject *pObject); void CheckToolbarButton(int iCmd, BOOL isChecked); +void EnableToolbarButton(int iCmd, BOOL isChecked); int DivDpi(int iUnscaled, UINT uCurrentDpi); diff --git a/source/terrax/terrax.rc b/source/terrax/terrax.rc index d9c1bca1a02a3743b3ee50789a73c7297ad200ab..906103b7f896e6a69aee0def76275d68924939ea 100644 GIT binary patch delta 553 zcmZqq$Mmm>c|(F6e+)wjgEK=3LlHv`gEoWv<b~YIlMDFRChN%aOkN@vf+in2c_Ftv zNPe@9{5^h2cZMQ{e1=kn0tP(BaJn-DG59ltG6YOMC})n#z{xuDQj?3g5++a3XWJ~G zFTp(dnw{8WC!?~-*X%?lUoffz(`m*PVEUVJ6`1ZYfyj%QLg*!?5OFgzh`M7?I?Nm* z{{%`GS%CSIEFsD!Sc18e<*Xp|8Y_q~cGeL30+<$?oMHpvf3ukfcHTPMHZbjH*E0E< z-7+wHnmtI3*kmn-8ZiHWLkXDna|G!TnatwU0CxHurzS9M=G-)S+f=d1mz?V+Z<{JJ zxxl4nG8glr$$HzlHqUXL!U*R)aK9ok*&&#V(S5R`gfb|CkV4jwOLX!FZwF>2hES-i z+~g<TKFmrCFx7ICOMG-D&x%x=JTH-NGMk6o<_8HnOi<H#Ht$RRW-{4`O@`Bep@P8x L1UH|ZVXg)M(L2P3 delta 369 zcmey@#N6_aX+whC<VD;@lP4r9O!ksDo1DPIHu;?#*W^XqdXp8BR5p9bAK>5YW+2Br z`I?c$<O<^oFwJ682d1Z)K=?|gRbc)G(=sq^Wd@;-m_fvY%t7j;CO<F-iAzn+u_&2* z%}8qUHz?g;3E^{DLFA`dLHG*R5PFq0M8Aa%ggyqQB_;>i)`0DMWIGQ`PqFI&(+2i! zVEUB(Brsjz09H525#sDgjv#9!Cd)W2083wRY5>zI&P`zYmvhr(g&9(lC%M#3R+ynM zS;w_zG8^-<$qH#)n?u~DFv2-gJg!Jg7D(ZnEKtBQd7p>W=AdL9CXnoAg)}D9$xYc( Qj0TexlZ7|O%vMqZ03+g+zW@LL -- GitLab