From 99a64342e92b634780c67a242e4b693b650257f0 Mon Sep 17 00:00:00 2001 From: parax0 Date: Sat, 19 Mar 2016 20:30:42 -0600 Subject: [PATCH] Added support for script instance cloning --- src/Core/Resource/Script/CLink.h | 17 ++-- src/Core/Resource/Script/IProperty.cpp | 16 ++-- src/Core/Resource/Script/IProperty.h | 27 +++--- src/Editor/CGizmo.cpp | 6 +- src/Editor/Editor.pro | 6 +- src/Editor/INodeEditor.cpp | 29 +++++-- src/Editor/INodeEditor.h | 1 + src/Editor/Undo/CCloneSelectionCommand.cpp | 97 ++++++++++++++++++++++ src/Editor/Undo/CCloneSelectionCommand.h | 22 +++++ src/Editor/Undo/UndoCommands.h | 1 + 10 files changed, 186 insertions(+), 36 deletions(-) create mode 100644 src/Editor/Undo/CCloneSelectionCommand.cpp create mode 100644 src/Editor/Undo/CCloneSelectionCommand.h diff --git a/src/Core/Resource/Script/CLink.h b/src/Core/Resource/Script/CLink.h index c1a4d12e..93088645 100644 --- a/src/Core/Resource/Script/CLink.h +++ b/src/Core/Resource/Script/CLink.h @@ -113,15 +113,16 @@ public: } // Accessors - u32 State() const { return mStateID; } - u32 Message() const { return mMessageID; } - u32 SenderID() const { return mSenderID; } - u32 ReceiverID() const { return mReceiverID; } - CScriptObject* Sender() const { return mpArea->InstanceByID(mSenderID); } - CScriptObject* Receiver() const { return mpArea->InstanceByID(mReceiverID); } + inline CGameArea* Area() const { return mpArea; } + inline u32 State() const { return mStateID; } + inline u32 Message() const { return mMessageID; } + inline u32 SenderID() const { return mSenderID; } + inline u32 ReceiverID() const { return mReceiverID; } + inline CScriptObject* Sender() const { return mpArea->InstanceByID(mSenderID); } + inline CScriptObject* Receiver() const { return mpArea->InstanceByID(mReceiverID); } - void SetState(u32 StateID) { mStateID = StateID; } - void SetMessage(u32 MessageID) { mMessageID = MessageID; } + inline void SetState(u32 StateID) { mStateID = StateID; } + inline void SetMessage(u32 MessageID) { mMessageID = MessageID; } }; diff --git a/src/Core/Resource/Script/IProperty.cpp b/src/Core/Resource/Script/IProperty.cpp index c026141a..6abf5adc 100644 --- a/src/Core/Resource/Script/IProperty.cpp +++ b/src/Core/Resource/Script/IProperty.cpp @@ -99,13 +99,8 @@ void CPropertyStruct::Copy(const IProperty *pkProp) { const CPropertyStruct *pkSource = static_cast(pkProp); - for (auto it = mProperties.begin(); it != mProperties.end(); it++) - delete *it; - - mProperties.resize(pkSource->mProperties.size()); - for (u32 iSub = 0; iSub < mProperties.size(); iSub++) - mProperties[iSub] = pkSource->mProperties[iSub]->Clone(Instance(), this); + mProperties[iSub]->Copy(pkSource->mProperties[iSub]); } bool CPropertyStruct::ShouldCook() @@ -196,6 +191,15 @@ CPropertyStruct* CPropertyStruct::StructByIDString(const TIDString& rkStr) const } // ************ CArrayProperty ************ +void CArrayProperty::Copy(const IProperty *pkProp) +{ + const CArrayProperty *pkSource = static_cast(pkProp); + Resize(pkSource->Count()); + + for (u32 iSub = 0; iSub < mProperties.size(); iSub++) + mProperties[iSub]->Copy(pkSource->mProperties[iSub]); +} + bool CArrayProperty::ShouldCook() { return (mpTemplate->CookPreference() == eNeverCook ? false : true); diff --git a/src/Core/Resource/Script/IProperty.h b/src/Core/Resource/Script/IProperty.h index 7bc1de0a..0e83d1a4 100644 --- a/src/Core/Resource/Script/IProperty.h +++ b/src/Core/Resource/Script/IProperty.h @@ -1,8 +1,6 @@ #ifndef IPROPERTY #define IPROPERTY -/* This header file declares some classes used to track script object properties - * IProperty, TTypedProperty (and typedefs), CPropertyStruct, and CArrayProperty */ #include "EPropertyType.h" #include "IPropertyValue.h" #include "Core/Resource/CResource.h" @@ -21,7 +19,6 @@ typedef TString TIDString; /* * IProperty is the base class, containing just some virtual function definitions - * Virtual destructor is mainly there to make cleanup easy; don't need to cast to delete */ class IProperty { @@ -69,6 +66,15 @@ public: /* * TTypedProperty is a template subclass for actual properties. */ +#define IMPLEMENT_PROPERTY_CLONE(ClassName) \ + virtual IProperty* Clone(CScriptObject *pInstance, CPropertyStruct *pParent) const \ + { \ + if (!pParent) pParent = mpParent; \ + ClassName *pOut = new ClassName(mpTemplate, pInstance, pParent); \ + pOut->Copy(this); \ + return pOut; \ + } + template class TTypedProperty : public IProperty { @@ -94,14 +100,7 @@ public: mValue.Set(pkCast->mValue.Get()); } - virtual TTypedProperty* Clone(class CScriptObject *pInstance, CPropertyStruct *pParent) const - { - if (!pParent) pParent = mpParent; - - TTypedProperty *pOut = new TTypedProperty(mpTemplate, pInstance, pParent); - pOut->Copy(this); - return pOut; - } + IMPLEMENT_PROPERTY_CLONE(TTypedProperty) virtual bool Matches(const IProperty *pkProp) const { @@ -138,6 +137,7 @@ class TStringProperty : public TTypedProperty, eMayaSplinePr { public: IMPLEMENT_PROPERTY_CTORS(TMayaSplineProperty, std::vector) + IMPLEMENT_PROPERTY_CLONE(TMayaSplineProperty) virtual bool MatchesDefault() { return Get().empty(); } }; @@ -256,6 +259,8 @@ public: EPropertyType Type() const { return eArrayProperty; } static inline EPropertyType StaticType() { return eArrayProperty; } + virtual void Copy(const IProperty *pkProp); + virtual IProperty* Clone(CScriptObject *pInstance, CPropertyStruct *pParent) const { if (!pParent) pParent = mpParent; diff --git a/src/Editor/CGizmo.cpp b/src/Editor/CGizmo.cpp index 4a0aa961..70690706 100644 --- a/src/Editor/CGizmo.cpp +++ b/src/Editor/CGizmo.cpp @@ -362,7 +362,7 @@ bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera) if (!mHasTransformed && (mDeltaTranslation != CVector3f::skZero)) mHasTransformed = true; - return true; + return mHasTransformed; } } @@ -415,7 +415,7 @@ bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera) if (!mHasTransformed && (rotAmount != 0.f)) mHasTransformed = true; - return true; + return mHasTransformed; } // Scale @@ -484,7 +484,7 @@ bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera) if (!mHasTransformed && (scaleAmount != 1.f)) mHasTransformed = true; - return true; + return mHasTransformed; } return false; diff --git a/src/Editor/Editor.pro b/src/Editor/Editor.pro index 908fe0e9..eac9a516 100644 --- a/src/Editor/Editor.pro +++ b/src/Editor/Editor.pro @@ -154,7 +154,8 @@ HEADERS += \ WorldEditor/CTemplateMimeData.h \ WorldEditor/CTemplateListView.h \ CSelectionIterator.h \ - Undo/ObjReferences.h + Undo/ObjReferences.h \ + Undo/CCloneSelectionCommand.h # Source Files SOURCES += \ @@ -211,7 +212,8 @@ SOURCES += \ Undo/CDeleteLinksCommand.cpp \ Undo/CEditLinkCommand.cpp \ Undo/CDeleteSelectionCommand.cpp \ - Undo/CCreateInstanceCommand.cpp + Undo/CCreateInstanceCommand.cpp \ + Undo/CCloneSelectionCommand.cpp # UI Files FORMS += \ diff --git a/src/Editor/INodeEditor.cpp b/src/Editor/INodeEditor.cpp index 7b346b83..ae180924 100644 --- a/src/Editor/INodeEditor.cpp +++ b/src/Editor/INodeEditor.cpp @@ -13,6 +13,7 @@ INodeEditor::INodeEditor(QWidget *pParent) , mGizmoTransforming(false) , mTranslateSpace(eWorldTransform) , mRotateSpace(eWorldTransform) + , mCloneState(eNotCloning) { // Create undo actions QAction *pUndoAction = mUndoStack.createUndoAction(this); @@ -85,6 +86,7 @@ bool INodeEditor::IsGizmoVisible() void INodeEditor::BeginGizmoTransform() { mGizmoTransforming = true; + if ((qApp->keyboardModifiers() & Qt::ShiftModifier) != 0) mCloneState = eReadyToClone; foreach (QAction *pAction, mGizmoActions) pAction->setEnabled(false); @@ -97,12 +99,20 @@ void INodeEditor::EndGizmoTransform() foreach (QAction *pAction, mGizmoActions) pAction->setEnabled(true); - if (mGizmo.Mode() == CGizmo::eTranslate) - mUndoStack.push(CTranslateNodeCommand::End()); - else if (mGizmo.Mode() == CGizmo::eRotate) - mUndoStack.push(CRotateNodeCommand::End()); - else if (mGizmo.Mode() == CGizmo::eScale) - mUndoStack.push(CScaleNodeCommand::End()); + if (mGizmo.HasTransformed()) + { + if (mGizmo.Mode() == CGizmo::eTranslate) + mUndoStack.push(CTranslateNodeCommand::End()); + else if (mGizmo.Mode() == CGizmo::eRotate) + mUndoStack.push(CRotateNodeCommand::End()); + else if (mGizmo.Mode() == CGizmo::eScale) + mUndoStack.push(CScaleNodeCommand::End()); + } + + if (mCloneState == eCloning) + mUndoStack.endMacro(); + + mCloneState = eNotCloning; } ETransformSpace INodeEditor::CurrentTransformSpace() @@ -251,6 +261,13 @@ void INodeEditor::OnSelectionModified() void INodeEditor::OnGizmoMoved() { + if (mCloneState == eReadyToClone) + { + mUndoStack.beginMacro("Clone"); + mUndoStack.push(new CCloneSelectionCommand(this)); + mCloneState = eCloning; + } + switch (mGizmo.Mode()) { case CGizmo::eTranslate: diff --git a/src/Editor/INodeEditor.h b/src/Editor/INodeEditor.h index eef511f6..471c2cf9 100644 --- a/src/Editor/INodeEditor.h +++ b/src/Editor/INodeEditor.h @@ -34,6 +34,7 @@ protected: bool mGizmoTransforming; ETransformSpace mTranslateSpace; ETransformSpace mRotateSpace; + enum { eNotCloning, eReadyToClone, eCloning } mCloneState; // Gizmo widgets QActionGroup *mpGizmoGroup; diff --git a/src/Editor/Undo/CCloneSelectionCommand.cpp b/src/Editor/Undo/CCloneSelectionCommand.cpp new file mode 100644 index 00000000..384eaffd --- /dev/null +++ b/src/Editor/Undo/CCloneSelectionCommand.cpp @@ -0,0 +1,97 @@ +#include "CCloneSelectionCommand.h" +#include "Editor/CSelectionIterator.h" + +CCloneSelectionCommand::CCloneSelectionCommand(INodeEditor *pEditor) + : IUndoCommand("Clone") + , mpEditor(qobject_cast(pEditor)) // todo: fix this! bad assumption! (clone handling code is in INodeEditor but active area is in CWorldEditor) +{ + mOriginalSelection = mpEditor->Selection()->SelectedNodeList(); + + for (CSelectionIterator It(mpEditor->Selection()); It; ++It) + { + if (It->NodeType() == eScriptNode) + mNodesToClone << *It; + } +} + +void CCloneSelectionCommand::undo() +{ + QList ClonedNodes = mClonedNodes.DereferenceList(); + mpEditor->Selection()->Clear(); + + foreach (CSceneNode *pNode, ClonedNodes) + { + CScriptObject *pInst = static_cast(pNode)->Object(); + + mpEditor->NotifyNodeAboutToBeDeleted(pNode); + mpEditor->Scene()->DeleteNode(pNode); + mpEditor->ActiveArea()->DeleteInstance(pInst); + mpEditor->NotifyNodeDeleted(); + } + + mClonedNodes.clear(); + mpEditor->Selection()->SetSelectedNodes(mOriginalSelection.DereferenceList()); +} + +void CCloneSelectionCommand::redo() +{ + QList ToClone = mNodesToClone.DereferenceList(); + QList ClonedNodes; + QList ToCloneInstanceIDs; + QList ClonedInstanceIDs; + + foreach (CSceneNode *pNode, ToClone) + { + mpEditor->NotifyNodeAboutToBeSpawned(); + CScriptNode *pScript = static_cast(pNode); + CScriptObject *pInstance = pScript->Object(); + + CScriptObject *pCloneInst = mpEditor->ActiveArea()->SpawnInstance(pInstance->Template(), pInstance->Layer()); + pCloneInst->Properties()->Copy(pInstance->Properties()); + pCloneInst->EvaluateProperties(); + + CScriptNode *pCloneNode = mpEditor->Scene()->CreateScriptNode(pCloneInst); + pCloneNode->SetName(pScript->Name()); + pCloneNode->SetPosition(pScript->LocalPosition()); + pCloneNode->SetRotation(pScript->LocalRotation()); + pCloneNode->SetScale(pScript->LocalScale()); + pCloneNode->OnLoadFinished(); + + ToCloneInstanceIDs << pInstance->InstanceID(); + ClonedInstanceIDs << pCloneInst->InstanceID(); + ClonedNodes << pCloneNode; + mClonedNodes << pCloneNode; + mpEditor->NotifyNodeSpawned(pCloneNode); + } + + // Clone outgoing links from source object; incoming ones are discarded + QList LinkedInstances; + + for (int iNode = 0; iNode < ClonedNodes.size(); iNode++) + { + CScriptObject *pSrc = static_cast(ToClone[iNode])->Object(); + CScriptObject *pClone = static_cast(ClonedNodes[iNode])->Object(); + + for (u32 iLink = 0; iLink < pSrc->NumLinks(eOutgoing); iLink++) + { + CLink *pSrcLink = pSrc->Link(eOutgoing, iLink); + + // If we're cloning the receiver then target the cloned receiver instead of the original one. + u32 ReceiverID = pSrcLink->ReceiverID(); + if (ToCloneInstanceIDs.contains(ReceiverID)) + ReceiverID = ClonedInstanceIDs[ToCloneInstanceIDs.indexOf(ReceiverID)]; + + CLink *pCloneLink = new CLink(pSrcLink->Area(), pSrcLink->State(), pSrcLink->Message(), pClone->InstanceID(), ReceiverID); + pCloneLink->Sender()->AddLink(eOutgoing, pCloneLink); + pCloneLink->Receiver()->AddLink(eIncoming, pCloneLink); + + if (!LinkedInstances.contains(pCloneLink->Sender())) + LinkedInstances << pCloneLink->Sender(); + if (!LinkedInstances.contains(pCloneLink->Receiver())) + LinkedInstances << pCloneLink->Receiver(); + } + } + + mpEditor->OnLinksModified(LinkedInstances); + mpEditor->Selection()->SetSelectedNodes(mClonedNodes.DereferenceList()); +} diff --git a/src/Editor/Undo/CCloneSelectionCommand.h b/src/Editor/Undo/CCloneSelectionCommand.h new file mode 100644 index 00000000..8bb12891 --- /dev/null +++ b/src/Editor/Undo/CCloneSelectionCommand.h @@ -0,0 +1,22 @@ +#ifndef CCLONESELECTIONCOMMAND_H +#define CCLONESELECTIONCOMMAND_H + +#include "IUndoCommand.h" +#include "ObjReferences.h" +#include "Editor/WorldEditor/CWorldEditor.h" + +class CCloneSelectionCommand : public IUndoCommand +{ + CWorldEditor *mpEditor; + CNodePtrList mOriginalSelection; + CNodePtrList mNodesToClone; + CNodePtrList mClonedNodes; + +public: + CCloneSelectionCommand(INodeEditor *pEditor); + void undo(); + void redo(); + bool AffectsCleanState() const { return true; } +}; + +#endif // CCLONESELECTIONCOMMAND_H diff --git a/src/Editor/Undo/UndoCommands.h b/src/Editor/Undo/UndoCommands.h index 27bfbfc9..3b5e8c62 100644 --- a/src/Editor/Undo/UndoCommands.h +++ b/src/Editor/Undo/UndoCommands.h @@ -2,6 +2,7 @@ #define UNDOCOMMANDS #include "CCreateInstanceCommand.h" +#include "CCloneSelectionCommand.h" #include "CTranslateNodeCommand.h" #include "CRotateNodeCommand.h"