Added support for script instance cloning

This commit is contained in:
parax0 2016-03-19 20:30:42 -06:00
parent 3d6993a1ec
commit 99a64342e9
10 changed files with 186 additions and 36 deletions

View File

@ -113,15 +113,16 @@ public:
} }
// Accessors // Accessors
u32 State() const { return mStateID; } inline CGameArea* Area() const { return mpArea; }
u32 Message() const { return mMessageID; } inline u32 State() const { return mStateID; }
u32 SenderID() const { return mSenderID; } inline u32 Message() const { return mMessageID; }
u32 ReceiverID() const { return mReceiverID; } inline u32 SenderID() const { return mSenderID; }
CScriptObject* Sender() const { return mpArea->InstanceByID(mSenderID); } inline u32 ReceiverID() const { return mReceiverID; }
CScriptObject* Receiver() const { return mpArea->InstanceByID(mReceiverID); } inline CScriptObject* Sender() const { return mpArea->InstanceByID(mSenderID); }
inline CScriptObject* Receiver() const { return mpArea->InstanceByID(mReceiverID); }
void SetState(u32 StateID) { mStateID = StateID; } inline void SetState(u32 StateID) { mStateID = StateID; }
void SetMessage(u32 MessageID) { mMessageID = MessageID; } inline void SetMessage(u32 MessageID) { mMessageID = MessageID; }
}; };

View File

@ -99,13 +99,8 @@ void CPropertyStruct::Copy(const IProperty *pkProp)
{ {
const CPropertyStruct *pkSource = static_cast<const CPropertyStruct*>(pkProp); const CPropertyStruct *pkSource = static_cast<const CPropertyStruct*>(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++) 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() bool CPropertyStruct::ShouldCook()
@ -196,6 +191,15 @@ CPropertyStruct* CPropertyStruct::StructByIDString(const TIDString& rkStr) const
} }
// ************ CArrayProperty ************ // ************ CArrayProperty ************
void CArrayProperty::Copy(const IProperty *pkProp)
{
const CArrayProperty *pkSource = static_cast<const CArrayProperty*>(pkProp);
Resize(pkSource->Count());
for (u32 iSub = 0; iSub < mProperties.size(); iSub++)
mProperties[iSub]->Copy(pkSource->mProperties[iSub]);
}
bool CArrayProperty::ShouldCook() bool CArrayProperty::ShouldCook()
{ {
return (mpTemplate->CookPreference() == eNeverCook ? false : true); return (mpTemplate->CookPreference() == eNeverCook ? false : true);

View File

@ -1,8 +1,6 @@
#ifndef IPROPERTY #ifndef IPROPERTY
#define 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 "EPropertyType.h"
#include "IPropertyValue.h" #include "IPropertyValue.h"
#include "Core/Resource/CResource.h" #include "Core/Resource/CResource.h"
@ -21,7 +19,6 @@ typedef TString TIDString;
/* /*
* IProperty is the base class, containing just some virtual function definitions * 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 class IProperty
{ {
@ -69,6 +66,15 @@ public:
/* /*
* TTypedProperty is a template subclass for actual properties. * 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 <typename ValueType, EPropertyType TypeEnum, class ValueClass> template <typename ValueType, EPropertyType TypeEnum, class ValueClass>
class TTypedProperty : public IProperty class TTypedProperty : public IProperty
{ {
@ -94,14 +100,7 @@ public:
mValue.Set(pkCast->mValue.Get()); mValue.Set(pkCast->mValue.Get());
} }
virtual TTypedProperty* Clone(class CScriptObject *pInstance, CPropertyStruct *pParent) const IMPLEMENT_PROPERTY_CLONE(TTypedProperty)
{
if (!pParent) pParent = mpParent;
TTypedProperty *pOut = new TTypedProperty(mpTemplate, pInstance, pParent);
pOut->Copy(this);
return pOut;
}
virtual bool Matches(const IProperty *pkProp) const virtual bool Matches(const IProperty *pkProp) const
{ {
@ -138,6 +137,7 @@ class TStringProperty : public TTypedProperty<TString, eStringProperty, CStringV
{ {
public: public:
IMPLEMENT_PROPERTY_CTORS(TStringProperty, TString) IMPLEMENT_PROPERTY_CTORS(TStringProperty, TString)
IMPLEMENT_PROPERTY_CLONE(TStringProperty)
virtual bool MatchesDefault() { return Get().IsEmpty(); } virtual bool MatchesDefault() { return Get().IsEmpty(); }
virtual bool ShouldCook() { return true; } virtual bool ShouldCook() { return true; }
}; };
@ -146,6 +146,7 @@ class TFileProperty : public TTypedProperty<CResourceInfo, eFileProperty, CFileV
{ {
public: public:
IMPLEMENT_PROPERTY_CTORS(TFileProperty, CResourceInfo) IMPLEMENT_PROPERTY_CTORS(TFileProperty, CResourceInfo)
IMPLEMENT_PROPERTY_CLONE(TFileProperty)
virtual bool MatchesDefault() { return !Get().IsValid(); } virtual bool MatchesDefault() { return !Get().IsValid(); }
virtual bool ShouldCook() { return true; } virtual bool ShouldCook() { return true; }
}; };
@ -154,6 +155,7 @@ class TCharacterProperty : public TTypedProperty<CAnimationParameters, eCharacte
{ {
public: public:
IMPLEMENT_PROPERTY_CTORS(TCharacterProperty, CAnimationParameters) IMPLEMENT_PROPERTY_CTORS(TCharacterProperty, CAnimationParameters)
IMPLEMENT_PROPERTY_CLONE(TCharacterProperty)
virtual bool MatchesDefault() { return Get().AnimSet() == nullptr; } virtual bool MatchesDefault() { return Get().AnimSet() == nullptr; }
virtual bool ShouldCook() { return true; } virtual bool ShouldCook() { return true; }
}; };
@ -162,6 +164,7 @@ class TMayaSplineProperty : public TTypedProperty<std::vector<u8>, eMayaSplinePr
{ {
public: public:
IMPLEMENT_PROPERTY_CTORS(TMayaSplineProperty, std::vector<u8>) IMPLEMENT_PROPERTY_CTORS(TMayaSplineProperty, std::vector<u8>)
IMPLEMENT_PROPERTY_CLONE(TMayaSplineProperty)
virtual bool MatchesDefault() { return Get().empty(); } virtual bool MatchesDefault() { return Get().empty(); }
}; };
@ -256,6 +259,8 @@ public:
EPropertyType Type() const { return eArrayProperty; } EPropertyType Type() const { return eArrayProperty; }
static inline EPropertyType StaticType() { return eArrayProperty; } static inline EPropertyType StaticType() { return eArrayProperty; }
virtual void Copy(const IProperty *pkProp);
virtual IProperty* Clone(CScriptObject *pInstance, CPropertyStruct *pParent) const virtual IProperty* Clone(CScriptObject *pInstance, CPropertyStruct *pParent) const
{ {
if (!pParent) pParent = mpParent; if (!pParent) pParent = mpParent;

View File

@ -362,7 +362,7 @@ bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera)
if (!mHasTransformed && (mDeltaTranslation != CVector3f::skZero)) if (!mHasTransformed && (mDeltaTranslation != CVector3f::skZero))
mHasTransformed = true; mHasTransformed = true;
return true; return mHasTransformed;
} }
} }
@ -415,7 +415,7 @@ bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera)
if (!mHasTransformed && (rotAmount != 0.f)) if (!mHasTransformed && (rotAmount != 0.f))
mHasTransformed = true; mHasTransformed = true;
return true; return mHasTransformed;
} }
// Scale // Scale
@ -484,7 +484,7 @@ bool CGizmo::TransformFromInput(const CRay& ray, CCamera& camera)
if (!mHasTransformed && (scaleAmount != 1.f)) if (!mHasTransformed && (scaleAmount != 1.f))
mHasTransformed = true; mHasTransformed = true;
return true; return mHasTransformed;
} }
return false; return false;

View File

@ -154,7 +154,8 @@ HEADERS += \
WorldEditor/CTemplateMimeData.h \ WorldEditor/CTemplateMimeData.h \
WorldEditor/CTemplateListView.h \ WorldEditor/CTemplateListView.h \
CSelectionIterator.h \ CSelectionIterator.h \
Undo/ObjReferences.h Undo/ObjReferences.h \
Undo/CCloneSelectionCommand.h
# Source Files # Source Files
SOURCES += \ SOURCES += \
@ -211,7 +212,8 @@ SOURCES += \
Undo/CDeleteLinksCommand.cpp \ Undo/CDeleteLinksCommand.cpp \
Undo/CEditLinkCommand.cpp \ Undo/CEditLinkCommand.cpp \
Undo/CDeleteSelectionCommand.cpp \ Undo/CDeleteSelectionCommand.cpp \
Undo/CCreateInstanceCommand.cpp Undo/CCreateInstanceCommand.cpp \
Undo/CCloneSelectionCommand.cpp
# UI Files # UI Files
FORMS += \ FORMS += \

View File

@ -13,6 +13,7 @@ INodeEditor::INodeEditor(QWidget *pParent)
, mGizmoTransforming(false) , mGizmoTransforming(false)
, mTranslateSpace(eWorldTransform) , mTranslateSpace(eWorldTransform)
, mRotateSpace(eWorldTransform) , mRotateSpace(eWorldTransform)
, mCloneState(eNotCloning)
{ {
// Create undo actions // Create undo actions
QAction *pUndoAction = mUndoStack.createUndoAction(this); QAction *pUndoAction = mUndoStack.createUndoAction(this);
@ -85,6 +86,7 @@ bool INodeEditor::IsGizmoVisible()
void INodeEditor::BeginGizmoTransform() void INodeEditor::BeginGizmoTransform()
{ {
mGizmoTransforming = true; mGizmoTransforming = true;
if ((qApp->keyboardModifiers() & Qt::ShiftModifier) != 0) mCloneState = eReadyToClone;
foreach (QAction *pAction, mGizmoActions) foreach (QAction *pAction, mGizmoActions)
pAction->setEnabled(false); pAction->setEnabled(false);
@ -97,6 +99,8 @@ void INodeEditor::EndGizmoTransform()
foreach (QAction *pAction, mGizmoActions) foreach (QAction *pAction, mGizmoActions)
pAction->setEnabled(true); pAction->setEnabled(true);
if (mGizmo.HasTransformed())
{
if (mGizmo.Mode() == CGizmo::eTranslate) if (mGizmo.Mode() == CGizmo::eTranslate)
mUndoStack.push(CTranslateNodeCommand::End()); mUndoStack.push(CTranslateNodeCommand::End());
else if (mGizmo.Mode() == CGizmo::eRotate) else if (mGizmo.Mode() == CGizmo::eRotate)
@ -105,6 +109,12 @@ void INodeEditor::EndGizmoTransform()
mUndoStack.push(CScaleNodeCommand::End()); mUndoStack.push(CScaleNodeCommand::End());
} }
if (mCloneState == eCloning)
mUndoStack.endMacro();
mCloneState = eNotCloning;
}
ETransformSpace INodeEditor::CurrentTransformSpace() ETransformSpace INodeEditor::CurrentTransformSpace()
{ {
switch (mGizmo.Mode()) switch (mGizmo.Mode())
@ -251,6 +261,13 @@ void INodeEditor::OnSelectionModified()
void INodeEditor::OnGizmoMoved() void INodeEditor::OnGizmoMoved()
{ {
if (mCloneState == eReadyToClone)
{
mUndoStack.beginMacro("Clone");
mUndoStack.push(new CCloneSelectionCommand(this));
mCloneState = eCloning;
}
switch (mGizmo.Mode()) switch (mGizmo.Mode())
{ {
case CGizmo::eTranslate: case CGizmo::eTranslate:

View File

@ -34,6 +34,7 @@ protected:
bool mGizmoTransforming; bool mGizmoTransforming;
ETransformSpace mTranslateSpace; ETransformSpace mTranslateSpace;
ETransformSpace mRotateSpace; ETransformSpace mRotateSpace;
enum { eNotCloning, eReadyToClone, eCloning } mCloneState;
// Gizmo widgets // Gizmo widgets
QActionGroup *mpGizmoGroup; QActionGroup *mpGizmoGroup;

View File

@ -0,0 +1,97 @@
#include "CCloneSelectionCommand.h"
#include "Editor/CSelectionIterator.h"
CCloneSelectionCommand::CCloneSelectionCommand(INodeEditor *pEditor)
: IUndoCommand("Clone")
, mpEditor(qobject_cast<CWorldEditor*>(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<CSceneNode*> ClonedNodes = mClonedNodes.DereferenceList();
mpEditor->Selection()->Clear();
foreach (CSceneNode *pNode, ClonedNodes)
{
CScriptObject *pInst = static_cast<CScriptNode*>(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<CSceneNode*> ToClone = mNodesToClone.DereferenceList();
QList<CSceneNode*> ClonedNodes;
QList<u32> ToCloneInstanceIDs;
QList<u32> ClonedInstanceIDs;
foreach (CSceneNode *pNode, ToClone)
{
mpEditor->NotifyNodeAboutToBeSpawned();
CScriptNode *pScript = static_cast<CScriptNode*>(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<CScriptObject*> LinkedInstances;
for (int iNode = 0; iNode < ClonedNodes.size(); iNode++)
{
CScriptObject *pSrc = static_cast<CScriptNode*>(ToClone[iNode])->Object();
CScriptObject *pClone = static_cast<CScriptNode*>(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());
}

View File

@ -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

View File

@ -2,6 +2,7 @@
#define UNDOCOMMANDS #define UNDOCOMMANDS
#include "CCreateInstanceCommand.h" #include "CCreateInstanceCommand.h"
#include "CCloneSelectionCommand.h"
#include "CTranslateNodeCommand.h" #include "CTranslateNodeCommand.h"
#include "CRotateNodeCommand.h" #include "CRotateNodeCommand.h"