diff --git a/proj/terrax/vs2013/terrax.vcxproj b/proj/terrax/vs2013/terrax.vcxproj index 94f6063c1cb5fba4a5dda7a1af413e43d4dd0bf4..3714e7454c5446f0fc1fa17a452affecb38d0674 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 f6cb0656ddbd482409af3bae3430120d2995f584..c7f15f1fc8863e186cc481aa6a876468a8a27a9d 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 1584fbb7af4534ec00bfad615cbbb8e7f777fb4d..f3ff59d42f8b94b22cca96ca04f36814cf8e700e 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 e5d2e5d8a01cd5b7cab33e3ffe769e738f483460..52493d9e7ab3fb43310f1242b128a0bc319dcde9 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 0000000000000000000000000000000000000000..d0e2d75f0c7a864f352cd79a4a7b02b7bae19272 --- /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 0000000000000000000000000000000000000000..d37541a6c2a1ff1d15f4e7c3d464a98c80d7950a --- /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 130e1b62170354dd9081a6185b09119bb7504183..f7c59ec80d51c334304af87323bab912382229a3 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 49cf504045c6378470509936aa248878759066e2..e962111a4113fb21ce6f30c4602ac4b44a7da60a 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 0000000000000000000000000000000000000000..46dc5f0a7377f5fbcf8bc7443b0463cb8408c206 --- /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 0000000000000000000000000000000000000000..0ce86b27ad97699a9c7f75868c4051658bc85d45 --- /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 0000000000000000000000000000000000000000..5beb78e13a37a1a825bd2c00fd5406de316446e1 --- /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 0000000000000000000000000000000000000000..1e51d9fcf376eb7efdf1b92f6b1b1fc00e3a08f0 --- /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 ec0616104b832504b2fc8af3c48fecccfeeb1985..20d1e4131093faeec19e352d7cb0dd2ff5562ad3 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 e39d400f37f5dd7e13754f59c3769884f6dcc8a3..e423352cd9a8e8f859e8d1a6a0d01168e734bdb7 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 29cf9645329c4de1b24a0562992b17c573c5a249..aaaa811854359c9bf52037b825b8e46b531877ac 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 d658b5b5c1773fa23f4568723e98f858644842f8..17b0c63a7a19bbddc6010be16ef6509fc1443f69 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 1ebdac59add1623670cf478be2ec56f2f78dc866..6996e46fb3b992688fdb054b67f5426a06bdd360 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 Binary files a/source/terrax/resource.h and b/source/terrax/resource.h differ diff --git a/source/terrax/resource/toolbar1.bmp b/source/terrax/resource/toolbar1.bmp index aa1f7466d2b9315dfbfdf34a4cb6bf69abf3b6da..dbb1f0b437a3419ade41cafc9834107c00921d22 100644 Binary files a/source/terrax/resource/toolbar1.bmp and b/source/terrax/resource/toolbar1.bmp differ diff --git a/source/terrax/resource/toolbar2.bmp b/source/terrax/resource/toolbar2.bmp index 710d69c5e27e3c5948ab841bcdd9cbcc3619d9b2..5051572bc72dbbd0c0b1a1af2b48dcd827182f2d 100644 Binary files a/source/terrax/resource/toolbar2.bmp and b/source/terrax/resource/toolbar2.bmp differ diff --git a/source/terrax/terrax.cpp b/source/terrax/terrax.cpp index 8d4994851eec23ad2b8ec8059b42a42c70b969eb..56df654753d9174e57e199ded9859da02a34a061 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 d5e581939a63c8620e99eadb05ebb0c181df7e47..0f512ce832315f45f4e01dfa3faa42037ee384f1 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 Binary files a/source/terrax/terrax.rc and b/source/terrax/terrax.rc differ