You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2855 lines
81 KiB
C++
2855 lines
81 KiB
C++
/*
|
|
* Copyright (c) Contributors to the Open 3D Engine Project.
|
|
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
|
*
|
|
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
|
*
|
|
*/
|
|
|
|
|
|
// Description : CBaseObject implementation.
|
|
|
|
|
|
#include "EditorDefs.h"
|
|
|
|
#include "BaseObject.h"
|
|
|
|
// Azframework
|
|
#include <AzFramework/Terrain/TerrainDataRequestBus.h> // for AzFramework::Terrain::TerrainDataRequests
|
|
|
|
// AzToolsFramework
|
|
#include <AzToolsFramework/API/ComponentEntityObjectBus.h> // for ComponentEntityObjectRequestBus
|
|
|
|
// Editor
|
|
#include "Settings.h"
|
|
#include "Viewport.h"
|
|
#include "DisplaySettings.h"
|
|
#include "Undo/Undo.h"
|
|
#include "UsedResources.h"
|
|
#include "GizmoManager.h"
|
|
#include "Include/IIconManager.h"
|
|
#include "Objects/SelectionGroup.h"
|
|
#include "Objects/ObjectManager.h"
|
|
#include "ViewManager.h"
|
|
#include "IEditorImpl.h"
|
|
#include "GameEngine.h"
|
|
#include <IEntityRenderState.h>
|
|
#include <IStatObj.h>
|
|
// To use the Andrew's algorithm in order to make convex hull from the points, this header is needed.
|
|
#include "Util/GeometryUtil.h"
|
|
|
|
namespace {
|
|
QColor kLinkColorParent = QColor(0, 255, 255);
|
|
QColor kLinkColorChild = QColor(0, 0, 255);
|
|
QColor kLinkColorGray = QColor(128, 128, 128);
|
|
}
|
|
|
|
extern CObjectManager* g_pObjectManager;
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//! Undo object for CBaseObject.
|
|
class CUndoBaseObject
|
|
: public IUndoObject
|
|
{
|
|
public:
|
|
CUndoBaseObject(CBaseObject* pObj, const char* undoDescription);
|
|
|
|
protected:
|
|
int GetSize() override { return sizeof(*this); }
|
|
QString GetDescription() override { return m_undoDescription; };
|
|
QString GetObjectName() override;
|
|
|
|
void Undo(bool bUndo) override;
|
|
void Redo() override;
|
|
|
|
protected:
|
|
QString m_undoDescription;
|
|
GUID m_guid;
|
|
XmlNodeRef m_undo;
|
|
XmlNodeRef m_redo;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//! Undo object for CBaseObject that only stores its transform, color, area and minSpec
|
|
class CUndoBaseObjectMinimal
|
|
: public IUndoObject
|
|
{
|
|
public:
|
|
CUndoBaseObjectMinimal(CBaseObject* obj, const char* undoDescription, int flags);
|
|
|
|
protected:
|
|
int GetSize() override { return sizeof(*this); }
|
|
QString GetDescription() override { return m_undoDescription; };
|
|
QString GetObjectName() override;
|
|
|
|
void Undo(bool bUndo) override;
|
|
void Redo() override;
|
|
|
|
private:
|
|
struct StateStruct
|
|
{
|
|
Vec3 pos;
|
|
Quat rotate;
|
|
Vec3 scale;
|
|
QColor color;
|
|
float area;
|
|
int minSpec;
|
|
};
|
|
|
|
void SetTransformsFromState(CBaseObject* pObject, const StateStruct& state, bool bUndo);
|
|
|
|
GUID m_guid;
|
|
QString m_undoDescription;
|
|
StateStruct m_undoState;
|
|
StateStruct m_redoState;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//! Undo object for attach/detach changes
|
|
class CUndoAttachBaseObject
|
|
: public IUndoObject
|
|
{
|
|
public:
|
|
CUndoAttachBaseObject(CBaseObject* pAttachedObject, bool bKeepPos, bool bAttach)
|
|
: m_attachedObjectGUID(pAttachedObject->GetId())
|
|
, m_parentObjectGUID(pAttachedObject->GetParent()->GetId())
|
|
, m_bKeepPos(bKeepPos)
|
|
, m_bAttach(bAttach) {}
|
|
|
|
void Undo([[maybe_unused]] bool bUndo) override
|
|
{
|
|
if (m_bAttach)
|
|
{
|
|
Detach();
|
|
}
|
|
else
|
|
{
|
|
Attach();
|
|
}
|
|
}
|
|
|
|
void Redo() override
|
|
{
|
|
if (m_bAttach)
|
|
{
|
|
Attach();
|
|
}
|
|
else
|
|
{
|
|
Detach();
|
|
}
|
|
}
|
|
|
|
private:
|
|
void Attach()
|
|
{
|
|
CObjectManager* pObjectManager = static_cast<CObjectManager*>(GetIEditor()->GetObjectManager());
|
|
CBaseObject* pObject = pObjectManager->FindObject(m_attachedObjectGUID);
|
|
CBaseObject* pParentObject = pObjectManager->FindObject(m_parentObjectGUID);
|
|
|
|
if (pObject && pParentObject)
|
|
{
|
|
pParentObject->AttachChild(pObject, m_bKeepPos);
|
|
}
|
|
}
|
|
|
|
void Detach()
|
|
{
|
|
CObjectManager* pObjectManager = static_cast<CObjectManager*>(GetIEditor()->GetObjectManager());
|
|
CBaseObject* pObject = pObjectManager->FindObject(m_attachedObjectGUID);
|
|
|
|
if (pObject)
|
|
{
|
|
pObject->DetachThis(m_bKeepPos);
|
|
}
|
|
}
|
|
|
|
int GetSize() override { return sizeof(CUndoAttachBaseObject); }
|
|
QString GetDescription() override { return "Attachment Changed"; }
|
|
|
|
GUID m_attachedObjectGUID;
|
|
GUID m_parentObjectGUID;
|
|
bool m_bKeepPos;
|
|
bool m_bAttach;
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CUndoBaseObject::CUndoBaseObject(CBaseObject* obj, const char* undoDescription)
|
|
{
|
|
// Stores the current state of this object.
|
|
assert(obj != 0);
|
|
m_undoDescription = undoDescription;
|
|
m_guid = obj->GetId();
|
|
|
|
m_redo = nullptr;
|
|
m_undo = XmlHelpers::CreateXmlNode("Undo");
|
|
CObjectArchive ar(GetIEditor()->GetObjectManager(), m_undo, false);
|
|
ar.bUndo = true;
|
|
obj->Serialize(ar);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
QString CUndoBaseObject::GetObjectName()
|
|
{
|
|
if (CBaseObject* obj = GetIEditor()->GetObjectManager()->FindObject(m_guid))
|
|
{
|
|
return obj->GetName();
|
|
}
|
|
return QString();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CUndoBaseObject::Undo(bool bUndo)
|
|
{
|
|
CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid);
|
|
if (!pObject)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetIEditor()->SuspendUndo();
|
|
|
|
if (bUndo)
|
|
{
|
|
m_redo = XmlHelpers::CreateXmlNode("Redo");
|
|
// Save current object state.
|
|
CObjectArchive ar(GetIEditor()->GetObjectManager(), m_redo, false);
|
|
ar.bUndo = true;
|
|
pObject->Serialize(ar);
|
|
}
|
|
|
|
// Undo object state.
|
|
CObjectArchive ar(GetIEditor()->GetObjectManager(), m_undo, true);
|
|
ar.bUndo = true;
|
|
pObject->Serialize(ar);
|
|
|
|
GetIEditor()->ResumeUndo();
|
|
|
|
using namespace AzToolsFramework;
|
|
ComponentEntityObjectRequestBus::Event(pObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CUndoBaseObject::Redo()
|
|
{
|
|
CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid);
|
|
if (!pObject)
|
|
{
|
|
return;
|
|
}
|
|
|
|
GetIEditor()->SuspendUndo();
|
|
|
|
CObjectArchive ar(GetIEditor()->GetObjectManager(), m_redo, true);
|
|
ar.bUndo = true;
|
|
|
|
pObject->Serialize(ar);
|
|
|
|
GetIEditor()->ResumeUndo();
|
|
|
|
using namespace AzToolsFramework;
|
|
ComponentEntityObjectRequestBus::Event(pObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CUndoBaseObjectMinimal::CUndoBaseObjectMinimal(CBaseObject* pObj, const char* undoDescription, [[maybe_unused]] int flags)
|
|
{
|
|
// Stores the current state of this object.
|
|
assert(pObj != nullptr);
|
|
m_undoDescription = undoDescription;
|
|
m_guid = pObj->GetId();
|
|
|
|
ZeroStruct(m_redoState);
|
|
|
|
m_undoState.pos = pObj->GetPos();
|
|
m_undoState.rotate = pObj->GetRotation();
|
|
m_undoState.scale = pObj->GetScale();
|
|
m_undoState.color = pObj->GetColor();
|
|
m_undoState.area = pObj->GetArea();
|
|
m_undoState.minSpec = pObj->GetMinSpec();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
QString CUndoBaseObjectMinimal::GetObjectName()
|
|
{
|
|
CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid);
|
|
if (!pObject)
|
|
{
|
|
return QString();
|
|
}
|
|
|
|
return pObject->GetName();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CUndoBaseObjectMinimal::Undo(bool bUndo)
|
|
{
|
|
CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid);
|
|
if (!pObject || pObject->GetType() == OBJTYPE_DUMMY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (bUndo)
|
|
{
|
|
m_redoState.pos = pObject->GetPos();
|
|
m_redoState.scale = pObject->GetScale();
|
|
m_redoState.rotate = pObject->GetRotation();
|
|
m_redoState.color = pObject->GetColor();
|
|
m_redoState.area = pObject->GetArea();
|
|
m_redoState.minSpec = pObject->GetMinSpec();
|
|
}
|
|
|
|
SetTransformsFromState(pObject, m_undoState, bUndo);
|
|
|
|
pObject->ChangeColor(m_undoState.color);
|
|
pObject->SetArea(m_undoState.area);
|
|
pObject->SetMinSpec(m_undoState.minSpec, false);
|
|
|
|
using namespace AzToolsFramework;
|
|
ComponentEntityObjectRequestBus::Event(pObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CUndoBaseObjectMinimal::Redo()
|
|
{
|
|
CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(m_guid);
|
|
if (!pObject || pObject->GetType() == OBJTYPE_DUMMY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
SetTransformsFromState(pObject, m_redoState, true);
|
|
|
|
pObject->ChangeColor(m_redoState.color);
|
|
pObject->SetArea(m_redoState.area);
|
|
pObject->SetMinSpec(m_redoState.minSpec, false);
|
|
|
|
using namespace AzToolsFramework;
|
|
ComponentEntityObjectRequestBus::Event(pObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CUndoBaseObjectMinimal::SetTransformsFromState(CBaseObject* pObject, const StateStruct& state, bool bUndo)
|
|
{
|
|
uint32 flags = eObjectUpdateFlags_Undo;
|
|
if (!bUndo)
|
|
{
|
|
flags |= eObjectUpdateFlags_UserInputUndo;
|
|
}
|
|
|
|
pObject->SetPos(state.pos, flags);
|
|
pObject->SetScale(state.scale, flags);
|
|
pObject->SetRotation(state.rotate, flags);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CObjectCloneContext::AddClone(CBaseObject* pFromObject, CBaseObject* pToObject)
|
|
{
|
|
m_objectsMap[pFromObject] = pToObject;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject* CObjectCloneContext::FindClone(CBaseObject* pFromObject)
|
|
{
|
|
CBaseObject* pTarget = stl::find_in_map(m_objectsMap, pFromObject, (CBaseObject*) nullptr);
|
|
return pTarget;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
GUID CObjectCloneContext::ResolveClonedID(REFGUID guid)
|
|
{
|
|
CBaseObject* pObject = GetIEditor()->GetObjectManager()->FindObject(guid);
|
|
CBaseObject* pClonedTarget = FindClone(pObject);
|
|
if (!pClonedTarget)
|
|
{
|
|
pClonedTarget = pObject; // If target not cloned, link to original target.
|
|
}
|
|
if (pClonedTarget)
|
|
{
|
|
return pClonedTarget->GetId();
|
|
}
|
|
return GUID_NULL;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// CBaseObject implementation.
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject::CBaseObject()
|
|
: m_pos(0, 0, 0)
|
|
, m_rotate(IDENTITY)
|
|
, m_scale(1, 1, 1)
|
|
, m_guid(GUID_NULL)
|
|
, m_floorNumber(-1)
|
|
, m_flags(0)
|
|
, m_nTextureIcon(0)
|
|
, m_color(QColor(255, 255, 255))
|
|
, m_worldTM(IDENTITY)
|
|
, m_lookat(nullptr)
|
|
, m_lookatSource(nullptr)
|
|
, m_flattenArea(0.f)
|
|
, m_classDesc(nullptr)
|
|
, m_numRefs(0)
|
|
, m_parent(nullptr)
|
|
, m_bInSelectionBox(false)
|
|
, m_pTransformDelegate(nullptr)
|
|
, m_bMatrixInWorldSpace(false)
|
|
, m_bMatrixValid(false)
|
|
, m_bWorldBoxValid(false)
|
|
, m_nMaterialLayersMask(0)
|
|
, m_nMinSpec(0)
|
|
, m_vDrawIconPos(0, 0, 0)
|
|
, m_nIconFlags(0)
|
|
, m_hideOrder(CBaseObject::s_invalidHiddenID)
|
|
{
|
|
m_worldBounds.min.Set(0, 0, 0);
|
|
m_worldBounds.max.Set(0, 0, 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
IObjectManager* CBaseObject::GetObjectManager() const
|
|
{
|
|
return g_pObjectManager;
|
|
};
|
|
|
|
|
|
void CBaseObject::SetClassDesc(CObjectClassDesc* classDesc)
|
|
{
|
|
m_classDesc = classDesc;
|
|
}
|
|
|
|
//! Initialize Object.
|
|
bool CBaseObject::Init([[maybe_unused]] IEditor* ie, CBaseObject* prev, [[maybe_unused]] const QString& file)
|
|
{
|
|
SetFlags(m_flags & (~OBJFLAG_DELETED));
|
|
|
|
if (prev != nullptr)
|
|
{
|
|
SetUniqueName(prev->GetName());
|
|
SetLocalTM(prev->GetPos(), prev->GetRotation(), prev->GetScale());
|
|
SetArea(prev->GetArea());
|
|
SetColor(prev->GetColor());
|
|
m_nMaterialLayersMask = prev->m_nMaterialLayersMask;
|
|
SetMinSpec(prev->GetMinSpec(), false);
|
|
|
|
// Copy all basic variables.
|
|
EnableUpdateCallbacks(false);
|
|
CopyVariableValues(prev);
|
|
EnableUpdateCallbacks(true);
|
|
OnSetValues();
|
|
}
|
|
|
|
m_nTextureIcon = m_classDesc->GetTextureIconId();
|
|
if (m_classDesc->RenderTextureOnTop())
|
|
{
|
|
SetFlags(OBJFLAG_SHOW_ICONONTOP);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject::~CBaseObject()
|
|
{
|
|
for (Childs::iterator c = m_childs.begin(); c != m_childs.end(); c++)
|
|
{
|
|
CBaseObject* child = *c;
|
|
child->m_parent = nullptr;
|
|
}
|
|
m_childs.clear();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::Done()
|
|
{
|
|
DetachThis();
|
|
|
|
// From children
|
|
DetachAll();
|
|
|
|
SetLookAt(nullptr);
|
|
if (m_lookatSource)
|
|
{
|
|
m_lookatSource->SetLookAt(nullptr);
|
|
}
|
|
SetFlags(m_flags | OBJFLAG_DELETED);
|
|
|
|
NotifyListeners(CBaseObject::ON_DELETE);
|
|
m_eventListeners.clear();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetName(const QString& name)
|
|
{
|
|
if (name == m_name)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StoreUndo("Name");
|
|
|
|
// Notification is expensive and not required if this is during construction.
|
|
bool notify = (!m_name.isEmpty());
|
|
|
|
m_name = name;
|
|
GetObjectManager()->RegisterObjectName(name);
|
|
SetModified(false);
|
|
|
|
if (notify)
|
|
{
|
|
NotifyListeners(ON_RENAME);
|
|
static_cast<CObjectManager*>(GetIEditor()->GetObjectManager())->NotifyObjectListeners(this, ON_RENAME);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetUniqueName(const QString& name)
|
|
{
|
|
SetName(GetObjectManager()->GenerateUniqueObjectName(name));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::GenerateUniqueName()
|
|
{
|
|
if (m_classDesc)
|
|
{
|
|
SetUniqueName(m_classDesc->ClassName());
|
|
}
|
|
else
|
|
{
|
|
SetUniqueName("Object");
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const QString& CBaseObject::GetName() const
|
|
{
|
|
return m_name;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
QString CBaseObject::GetWarningsText() const
|
|
{
|
|
QString warnings;
|
|
|
|
if (gSettings.viewports.bShowScaleWarnings)
|
|
{
|
|
const EScaleWarningLevel scaleWarningLevel = GetScaleWarningLevel();
|
|
if (scaleWarningLevel == eScaleWarningLevel_Rescaled)
|
|
{
|
|
warnings += "\\n Warning: Object Scale is not 100%.";
|
|
}
|
|
else if (scaleWarningLevel == eScaleWarningLevel_RescaledNonUniform)
|
|
{
|
|
warnings += "\\n Warning: Object has non-uniform scale.";
|
|
}
|
|
}
|
|
|
|
if (gSettings.viewports.bShowRotationWarnings)
|
|
{
|
|
const ERotationWarningLevel rotationWarningLevel = GetRotationWarningLevel();
|
|
|
|
if (rotationWarningLevel == eRotationWarningLevel_Rotated)
|
|
{
|
|
warnings += "\\n Warning: Object is rotated.";
|
|
}
|
|
else if (rotationWarningLevel == eRotationWarningLevel_RotatedNonRectangular)
|
|
{
|
|
warnings += "\\n Warning: Object is rotated non-orthogonally.";
|
|
}
|
|
}
|
|
|
|
return warnings;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsSameClass(CBaseObject* obj)
|
|
{
|
|
return GetClassDesc() == obj->GetClassDesc();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::SetPos(const Vec3& pos, int flags)
|
|
{
|
|
const Vec3 currentPos = GetPos();
|
|
|
|
bool equal = false;
|
|
if (flags & eObjectUpdateFlags_MoveTool) // very sensitive in case of the move tool
|
|
{
|
|
equal = IsVectorsEqual(currentPos, pos, 0.0f);
|
|
}
|
|
else // less sensitive for others
|
|
{
|
|
equal = IsVectorsEqual(currentPos, pos);
|
|
}
|
|
|
|
if (equal)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if position is bad.
|
|
if (fabs(pos.x) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
fabs(pos.y) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
fabs(pos.z) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
!_finite(pos.x) || !_finite(pos.y) || !_finite(pos.z))
|
|
{
|
|
CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING,
|
|
"Object %s, SetPos called with invalid position: (%f,%f,%f)",
|
|
GetName().toUtf8().data(), pos.x, pos.y, pos.z);
|
|
return false;
|
|
}
|
|
|
|
OnBeforeAreaChange();
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const bool bPositionDelegated = m_pTransformDelegate && m_pTransformDelegate->IsPositionDelegated();
|
|
if (m_pTransformDelegate && (flags & eObjectUpdateFlags_Animated) == 0)
|
|
{
|
|
m_pTransformDelegate->SetTransformDelegatePos(pos);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
if (!bPositionDelegated && (flags & eObjectUpdateFlags_RestoreUndo) == 0 && (flags & eObjectUpdateFlags_Animated) == 0)
|
|
{
|
|
StoreUndo("Position", true, flags);
|
|
}
|
|
|
|
if (!bPositionDelegated)
|
|
{
|
|
m_pos = pos;
|
|
}
|
|
|
|
if (!(flags & eObjectUpdateFlags_DoNotInvalidate))
|
|
{
|
|
InvalidateTM(flags | eObjectUpdateFlags_PositionChanged);
|
|
}
|
|
|
|
SetModified(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::SetRotation(const Quat& rotate, int flags)
|
|
{
|
|
const Quat currentRotate = GetRotation();
|
|
|
|
if (currentRotate.w == rotate.w && currentRotate.v.x == rotate.v.x
|
|
&& currentRotate.v.y == rotate.v.y && currentRotate.v.z == rotate.v.z)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (flags & eObjectUpdateFlags_ScaleTool)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
OnBeforeAreaChange();
|
|
|
|
const bool bRotationDelegated = m_pTransformDelegate && m_pTransformDelegate->IsRotationDelegated();
|
|
if (m_pTransformDelegate && (flags & eObjectUpdateFlags_Animated) == 0)
|
|
{
|
|
m_pTransformDelegate->SetTransformDelegateRotation(rotate);
|
|
}
|
|
|
|
if (!bRotationDelegated && (flags & eObjectUpdateFlags_RestoreUndo) == 0 && (flags & eObjectUpdateFlags_Animated) == 0)
|
|
{
|
|
StoreUndo("Rotate", true, flags);
|
|
}
|
|
|
|
if (!bRotationDelegated)
|
|
{
|
|
m_rotate = rotate;
|
|
}
|
|
|
|
if (m_bMatrixValid && !(flags & eObjectUpdateFlags_DoNotInvalidate))
|
|
{
|
|
InvalidateTM(flags | eObjectUpdateFlags_RotationChanged);
|
|
}
|
|
|
|
SetModified(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::SetScale(const Vec3& scale, int flags)
|
|
{
|
|
if (IsVectorsEqual(GetScale(), scale))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if scale is bad.
|
|
if (scale.x < 0.01f || scale.y < 0.01f || scale.z < 0.01f)
|
|
{
|
|
CryWarning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, "Object %s, SetScale called with invalid scale: (%f,%f,%f)", GetName().toUtf8().data(), scale.x, scale.y, scale.z);
|
|
return false;
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
OnBeforeAreaChange();
|
|
|
|
const bool bScaleDelegated = m_pTransformDelegate && m_pTransformDelegate->IsScaleDelegated();
|
|
if (m_pTransformDelegate && (flags & eObjectUpdateFlags_Animated) == 0)
|
|
{
|
|
m_pTransformDelegate->SetTransformDelegateScale(scale);
|
|
}
|
|
|
|
if (!bScaleDelegated && (flags & eObjectUpdateFlags_RestoreUndo) == 0 && (flags & eObjectUpdateFlags_Animated) == 0)
|
|
{
|
|
StoreUndo("Scale", true, flags);
|
|
}
|
|
|
|
if (!bScaleDelegated)
|
|
{
|
|
m_scale = scale;
|
|
}
|
|
|
|
if (m_bMatrixValid && !(flags & eObjectUpdateFlags_DoNotInvalidate))
|
|
{
|
|
InvalidateTM(flags | eObjectUpdateFlags_ScaleChanged);
|
|
}
|
|
|
|
SetModified(true);
|
|
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const Vec3 CBaseObject::GetPos() const
|
|
{
|
|
if (!m_pTransformDelegate)
|
|
{
|
|
return m_pos;
|
|
}
|
|
|
|
return m_pTransformDelegate->GetTransformDelegatePos(m_pos);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const Quat CBaseObject::GetRotation() const
|
|
{
|
|
if (!m_pTransformDelegate)
|
|
{
|
|
return m_rotate;
|
|
}
|
|
|
|
return m_pTransformDelegate->GetTransformDelegateRotation(m_rotate);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const Vec3 CBaseObject::GetScale() const
|
|
{
|
|
if (!m_pTransformDelegate)
|
|
{
|
|
return m_scale;
|
|
}
|
|
|
|
return m_pTransformDelegate->GetTransformDelegateScale(m_scale);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::ChangeColor(const QColor& color)
|
|
{
|
|
if (color == m_color)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StoreUndo("Color", true);
|
|
|
|
SetColor(color);
|
|
SetModified(false);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetColor(const QColor& color)
|
|
{
|
|
m_color = color;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetArea(float area)
|
|
{
|
|
if (m_flattenArea == area)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StoreUndo("Area", true);
|
|
|
|
m_flattenArea = area;
|
|
SetModified(false);
|
|
};
|
|
|
|
//! Get bounding box of object in world coordinate space.
|
|
void CBaseObject::GetBoundBox(AABB& box)
|
|
{
|
|
if (!m_bWorldBoxValid)
|
|
{
|
|
GetLocalBounds(m_worldBounds);
|
|
if (!m_worldBounds.IsReset() && !m_worldBounds.IsEmpty())
|
|
{
|
|
m_worldBounds.SetTransformedAABB(GetWorldTM(), m_worldBounds);
|
|
m_bWorldBoxValid = true;
|
|
}
|
|
}
|
|
box = m_worldBounds;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::GetLocalBounds(AABB& box)
|
|
{
|
|
box.min.Set(0, 0, 0);
|
|
box.max.Set(0, 0, 0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetModified(bool)
|
|
{
|
|
}
|
|
|
|
void CBaseObject::DrawDefault(DisplayContext& dc, const QColor& labelColor)
|
|
{
|
|
Vec3 wp = GetWorldPos();
|
|
|
|
bool bDisplaySelectionHelper = false;
|
|
if (!CanBeDrawn(dc, bDisplaySelectionHelper))
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Draw link between parent and child.
|
|
if (dc.flags & DISPLAY_LINKS)
|
|
{
|
|
if (GetParent())
|
|
{
|
|
dc.DrawLine(GetParentAttachPointWorldTM().GetTranslation(), wp, IsFrozen() ? kLinkColorGray : kLinkColorParent, IsFrozen() ? kLinkColorGray : kLinkColorChild);
|
|
}
|
|
size_t nChildCount = GetChildCount();
|
|
for (size_t i = 0; i < nChildCount; ++i)
|
|
{
|
|
const CBaseObject* pChild = GetChild(i);
|
|
dc.DrawLine(pChild->GetParentAttachPointWorldTM().GetTranslation(), pChild->GetWorldPos(), pChild->IsFrozen() ? kLinkColorGray : kLinkColorParent, pChild->IsFrozen() ? kLinkColorGray : kLinkColorChild);
|
|
}
|
|
}
|
|
|
|
// Draw Bounding box
|
|
if (dc.flags & DISPLAY_BBOX)
|
|
{
|
|
AABB box;
|
|
GetBoundBox(box);
|
|
dc.SetColor(Vec3(1, 1, 1));
|
|
dc.DrawWireBox(box.min, box.max);
|
|
}
|
|
|
|
if (IsHighlighted())
|
|
{
|
|
DrawHighlight(dc);
|
|
}
|
|
|
|
if (IsSelected())
|
|
{
|
|
DrawArea(dc);
|
|
|
|
CSelectionGroup* pSelection = GetObjectManager()->GetSelection();
|
|
|
|
// If the number of selected object is over 2, the merged boundbox should be used to render the measurement axis.
|
|
if (!pSelection || (pSelection && pSelection->GetCount() == 1))
|
|
{
|
|
DrawDimensions(dc);
|
|
}
|
|
}
|
|
|
|
if (bDisplaySelectionHelper)
|
|
{
|
|
DrawSelectionHelper(dc, wp, labelColor, 1.0f);
|
|
}
|
|
else if (!(dc.flags & DISPLAY_HIDENAMES))
|
|
{
|
|
DrawLabel(dc, wp, labelColor);
|
|
}
|
|
|
|
SetDrawTextureIconProperties(dc, wp);
|
|
DrawTextureIcon(dc, wp);
|
|
DrawWarningIcons(dc, wp);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawDimensions(DisplayContext&, AABB*)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawSelectionHelper(DisplayContext& dc, const Vec3& pos, const QColor& labelColor, [[maybe_unused]] float alpha)
|
|
{
|
|
DrawLabel(dc, pos, labelColor);
|
|
|
|
dc.SetColor(GetColor());
|
|
if (IsHighlighted() || IsSelected() || IsInSelectionBox())
|
|
{
|
|
dc.SetColor(dc.GetSelectedColor());
|
|
}
|
|
|
|
uint32 nPrevState = dc.GetState();
|
|
dc.DepthTestOff();
|
|
float r = dc.view->GetScreenScaleFactor(pos) * 0.006f;
|
|
dc.DrawWireBox(pos - Vec3(r, r, r), pos + Vec3(r, r, r));
|
|
dc.SetState(nPrevState);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetDrawTextureIconProperties(DisplayContext& dc, const Vec3& pos, float alpha, int texIconFlags)
|
|
{
|
|
if (gSettings.viewports.bShowIcons || gSettings.viewports.bShowSizeBasedIcons)
|
|
{
|
|
if (IsHighlighted())
|
|
{
|
|
dc.SetColor(QColor(255, 120, 0), 0.8f * alpha);
|
|
}
|
|
else if (IsSelected())
|
|
{
|
|
dc.SetSelectedColor(alpha);
|
|
}
|
|
else if (IsFrozen())
|
|
{
|
|
dc.SetFreezeColor();
|
|
}
|
|
else
|
|
{
|
|
dc.SetColor(QColor(255, 255, 255), alpha);
|
|
}
|
|
|
|
m_vDrawIconPos = pos;
|
|
|
|
int nIconFlags = texIconFlags;
|
|
if (CheckFlags(OBJFLAG_SHOW_ICONONTOP))
|
|
{
|
|
Vec3 objectPos = GetWorldPos();
|
|
|
|
AABB box;
|
|
GetBoundBox(box);
|
|
m_vDrawIconPos.z = (m_vDrawIconPos.z - objectPos.z) + box.max.z;
|
|
nIconFlags |= DisplayContext::TEXICON_ALIGN_BOTTOM;
|
|
}
|
|
m_nIconFlags = nIconFlags;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawTextureIcon(DisplayContext& dc, [[maybe_unused]] const Vec3& pos, [[maybe_unused]] float alpha)
|
|
{
|
|
if (m_nTextureIcon && (gSettings.viewports.bShowIcons || gSettings.viewports.bShowSizeBasedIcons))
|
|
{
|
|
dc.DrawTextureLabel(GetTextureIconDrawPos(), OBJECT_TEXTURE_ICON_SIZEX, OBJECT_TEXTURE_ICON_SIZEY, GetTextureIcon(), GetTextureIconFlags());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawWarningIcons(DisplayContext& dc, const Vec3& pos)
|
|
{
|
|
// Don't draw warning icons if they are beyond draw distance
|
|
if ((dc.camera->GetPosition() - pos).GetLength() > gSettings.viewports.fWarningIconsDrawDistance)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (gSettings.viewports.bShowIcons || gSettings.viewports.bShowSizeBasedIcons)
|
|
{
|
|
const int warningIconSizeX = OBJECT_TEXTURE_ICON_SIZEX / 2;
|
|
const int warningIconSizeY = OBJECT_TEXTURE_ICON_SIZEY / 2;
|
|
|
|
const int iconOffsetX = m_nTextureIcon ? (-OBJECT_TEXTURE_ICON_SIZEX / 2) : 0;
|
|
const int iconOffsetY = m_nTextureIcon ? (-OBJECT_TEXTURE_ICON_SIZEY / 2) : 0;
|
|
|
|
if (gSettings.viewports.bShowScaleWarnings)
|
|
{
|
|
const EScaleWarningLevel scaleWarningLevel = GetScaleWarningLevel();
|
|
|
|
if (scaleWarningLevel != eScaleWarningLevel_None)
|
|
{
|
|
dc.SetColor(QColor(255, scaleWarningLevel == eScaleWarningLevel_RescaledNonUniform ? 50 : 255, 50), 1.0f);
|
|
dc.DrawTextureLabel(GetTextureIconDrawPos(), warningIconSizeX, warningIconSizeY,
|
|
GetIEditor()->GetIconManager()->GetIconTexture(eIcon_ScaleWarning), GetTextureIconFlags(),
|
|
-warningIconSizeX / 2, iconOffsetX - (warningIconSizeY / 2));
|
|
}
|
|
}
|
|
|
|
if (gSettings.viewports.bShowRotationWarnings)
|
|
{
|
|
const ERotationWarningLevel rotationWarningLevel = GetRotationWarningLevel();
|
|
if (rotationWarningLevel != eRotationWarningLevel_None)
|
|
{
|
|
dc.SetColor(QColor(255, rotationWarningLevel == eRotationWarningLevel_RotatedNonRectangular ? 50 : 255, 50), 1.0f);
|
|
dc.DrawTextureLabel(GetTextureIconDrawPos(), warningIconSizeX, warningIconSizeY,
|
|
GetIEditor()->GetIconManager()->GetIconTexture(eIcon_RotationWarning), GetTextureIconFlags(),
|
|
warningIconSizeX / 2, iconOffsetY - (warningIconSizeY / 2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawLabel(DisplayContext& dc, const Vec3& pos, const QColor& lC, float alpha, float size)
|
|
{
|
|
QColor labelColor = lC;
|
|
|
|
AABB box;
|
|
GetBoundBox(box);
|
|
|
|
//p.z = box.max.z + 0.2f;
|
|
if ((dc.flags & DISPLAY_2D) && labelColor == QColor(255, 255, 255))
|
|
{
|
|
labelColor = QColor(0, 0, 0);
|
|
}
|
|
|
|
float camDist = dc.camera->GetPosition().GetDistance(pos);
|
|
float maxDist = dc.settings->GetLabelsDistance();
|
|
if (camDist < dc.settings->GetLabelsDistance() || (dc.flags & DISPLAY_SELECTION_HELPERS))
|
|
{
|
|
float range = maxDist / 2.0f;
|
|
Vec3 c(static_cast<f32>(labelColor.redF()), static_cast<f32>(labelColor.greenF()), static_cast<f32>(labelColor.redF()));
|
|
if (IsSelected())
|
|
{
|
|
c = Vec3(static_cast<f32>(dc.GetSelectedColor().redF()), static_cast<f32>(dc.GetSelectedColor().greenF()), static_cast<f32>(dc.GetSelectedColor().blueF()));
|
|
}
|
|
|
|
float col[4] = { c.x, c.y, c.z, 1 };
|
|
if (dc.flags & DISPLAY_SELECTION_HELPERS)
|
|
{
|
|
if (IsHighlighted())
|
|
{
|
|
c = Vec3(static_cast<f32>(dc.GetSelectedColor().redF()), static_cast<f32>(dc.GetSelectedColor().greenF()), static_cast<f32>(dc.GetSelectedColor().blueF()));
|
|
}
|
|
col[0] = c.x;
|
|
col[1] = c.y;
|
|
col[2] = c.z;
|
|
}
|
|
else if (camDist > range)
|
|
{
|
|
col[3] = col[3] * (1.0f - (camDist - range) / range);
|
|
}
|
|
|
|
dc.SetColor(col[0], col[1], col[2], col[3] * alpha);
|
|
dc.DrawTextLabel(pos, size, GetName().toUtf8().data());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawHighlight(DisplayContext& dc)
|
|
{
|
|
if (!m_nTextureIcon)
|
|
{
|
|
AABB box;
|
|
GetLocalBounds(box);
|
|
|
|
dc.PushMatrix(GetWorldTM());
|
|
dc.DrawWireBox(box.min, box.max);
|
|
dc.SetLineWidth(1);
|
|
dc.PopMatrix();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawBudgetUsage(DisplayContext& dc, const QColor& color)
|
|
{
|
|
AABB box;
|
|
GetLocalBounds(box);
|
|
|
|
dc.SetColor(color);
|
|
|
|
dc.PushMatrix(GetWorldTM());
|
|
dc.DrawWireBox(box.min, box.max);
|
|
dc.PopMatrix();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DrawAxis([[maybe_unused]] DisplayContext& dc, [[maybe_unused]] const Vec3& pos, [[maybe_unused]] float size)
|
|
{
|
|
/*
|
|
dc.renderer->EnableDepthTest(false);
|
|
Vec3 x(size,0,0);
|
|
Vec3 y(0,size,0);
|
|
Vec3 z(0,0,size);
|
|
|
|
bool bWorldSpace = false;
|
|
if (dc.flags & DISPLAY_WORLDSPACEAXIS)
|
|
bWorldSpace = true;
|
|
|
|
Matrix tm = GetWorldTM();
|
|
Vec3 org = tm.TransformPoint( pos );
|
|
|
|
if (!bWorldSpace)
|
|
{
|
|
tm.NoScale();
|
|
x = tm.TransformVector(x);
|
|
y = tm.TransformVector(y);
|
|
z = tm.TransformVector(z);
|
|
}
|
|
|
|
float fScreenScale = dc.view->GetScreenScaleFactor(org);
|
|
x = x * fScreenScale;
|
|
y = y * fScreenScale;
|
|
z = z * fScreenScale;
|
|
|
|
float col[4] = { 1,1,1,1 };
|
|
float hcol[4] = { 1,0,0,1 };
|
|
dc.renderer->DrawLabelEx( org+x,1.2f,col,true,true,"X" );
|
|
dc.renderer->DrawLabelEx( org+y,1.2f,col,true,true,"Y" );
|
|
dc.renderer->DrawLabelEx( org+z,1.2f,col,true,true,"Z" );
|
|
|
|
Vec3 colX(1,0,0),colY(0,1,0),colZ(0,0,1);
|
|
if (s_highlightAxis)
|
|
{
|
|
float col[4] = { 1,0,0,1 };
|
|
if (s_highlightAxis == 1)
|
|
{
|
|
colX.Set(1,1,0);
|
|
dc.renderer->DrawLabelEx( org+x,1.2f,col,true,true,"X" );
|
|
}
|
|
if (s_highlightAxis == 2)
|
|
{
|
|
colY.Set(1,1,0);
|
|
dc.renderer->DrawLabelEx( org+y,1.2f,col,true,true,"Y" );
|
|
}
|
|
if (s_highlightAxis == 3)
|
|
{
|
|
colZ.Set(1,1,0);
|
|
dc.renderer->DrawLabelEx( org+z,1.2f,col,true,true,"Z" );
|
|
}
|
|
}
|
|
|
|
x = x * 0.8f;
|
|
y = y * 0.8f;
|
|
z = z * 0.8f;
|
|
float fArrowScale = fScreenScale * 0.07f;
|
|
dc.SetColor( colX );
|
|
dc.DrawArrow( org,org+x,fArrowScale );
|
|
dc.SetColor( colY );
|
|
dc.DrawArrow( org,org+y,fArrowScale );
|
|
dc.SetColor( colZ );
|
|
dc.DrawArrow( org,org+z,fArrowScale );
|
|
|
|
//dc.DrawLine( org,org+x,colX,colX );
|
|
//dc.DrawLine( org,org+y,colY,colY );
|
|
//dc.DrawLine( org,org+z,colZ,colZ );
|
|
|
|
dc.renderer->EnableDepthTest(true);
|
|
///dc.SetColor( 0,1,1,1 );
|
|
//dc.DrawLine( p,p+dc.view->m_constructionPlane.m_normal*10.0f );
|
|
*/
|
|
}
|
|
|
|
void CBaseObject::DrawArea(DisplayContext& dc)
|
|
{
|
|
float area = m_flattenArea;
|
|
if (area > 0)
|
|
{
|
|
dc.SetColor(QColor(5, 5, 255), 1.f); // make it different color from the AI sight radius
|
|
Vec3 wp = GetWorldPos();
|
|
float z = GetIEditor()->GetTerrainElevation(wp.x, wp.y);
|
|
if (fabs(wp.z - z) < 5)
|
|
{
|
|
dc.DrawTerrainCircle(wp, area, 0.2f);
|
|
}
|
|
else
|
|
{
|
|
dc.DrawCircle(wp, area);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::CanBeDrawn(const DisplayContext& dc, bool& outDisplaySelectionHelper) const
|
|
{
|
|
bool bResult = true;
|
|
outDisplaySelectionHelper = false;
|
|
|
|
if (dc.flags & DISPLAY_SELECTION_HELPERS)
|
|
{
|
|
// Check if this object type is masked for selection.
|
|
if ((GetType() & gSettings.objectSelectMask) && !IsFrozen())
|
|
{
|
|
if (IsSkipSelectionHelper())
|
|
{
|
|
return bResult;
|
|
}
|
|
if (CanBeHightlighted())
|
|
{
|
|
outDisplaySelectionHelper = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Object helpers should not be displayed when object is not for selection.
|
|
bResult = false;
|
|
}
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsInCameraView(const CCamera& camera)
|
|
{
|
|
AABB bbox;
|
|
GetBoundBox(bbox);
|
|
return (camera.IsAABBVisible_F(AABB(bbox.min, bbox.max)));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
float CBaseObject::GetCameraVisRatio(const CCamera& camera)
|
|
{
|
|
AABB bbox;
|
|
GetBoundBox(bbox);
|
|
|
|
static const float defaultVisRatio = 1000.0f;
|
|
|
|
const float objectHeightSq = max(1.0f, (bbox.max - bbox.min).GetLengthSquared());
|
|
const float camdistSq = (bbox.min - camera.GetPosition()).GetLengthSquared();
|
|
float visRatio = defaultVisRatio;
|
|
if (camdistSq > FLT_EPSILON)
|
|
{
|
|
visRatio = objectHeightSq / camdistSq;
|
|
}
|
|
|
|
return visRatio;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
int CBaseObject::MouseCreateCallback(CViewport* view, EMouseEvent event, QPoint& point, int flags)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Editor);
|
|
|
|
if (event == eMouseMove || event == eMouseLDown)
|
|
{
|
|
Vec3 pos;
|
|
if (GetIEditor()->GetAxisConstrains() != AXIS_TERRAIN)
|
|
{
|
|
pos = view->MapViewToCP(point);
|
|
}
|
|
else
|
|
{
|
|
// Snap to terrain.
|
|
bool hitTerrain;
|
|
pos = view->ViewToWorld(point, &hitTerrain);
|
|
if (hitTerrain)
|
|
{
|
|
pos.z = GetIEditor()->GetTerrainElevation(pos.x, pos.y) + 1.0f;
|
|
}
|
|
pos = view->SnapToGrid(pos);
|
|
}
|
|
SetPos(pos);
|
|
|
|
if (event == eMouseLDown)
|
|
{
|
|
return MOUSECREATE_OK;
|
|
}
|
|
}
|
|
|
|
if (event == eMouseWheel)
|
|
{
|
|
float angle = 1;
|
|
Quat rot = GetRotation();
|
|
rot.SetRotationXYZ(Ang3(0.f, 0.f, rot.GetRotZ() + DEG2RAD(flags > 0 ? angle * (-1) : angle)));
|
|
SetRotation(rot);
|
|
}
|
|
return MOUSECREATE_CONTINUE;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::OnEvent(ObjectEvent event)
|
|
{
|
|
switch (event)
|
|
{
|
|
case EVENT_CONFIG_SPEC_CHANGE:
|
|
UpdateVisibility(!IsHidden());
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetShared([[maybe_unused]] bool bShared)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetHidden(bool bHidden, uint64 hiddenID, bool bAnimated)
|
|
{
|
|
if (CheckFlags(OBJFLAG_HIDDEN) != bHidden)
|
|
{
|
|
if (!bAnimated)
|
|
{
|
|
StoreUndo("Hide Object");
|
|
}
|
|
|
|
if (bHidden)
|
|
{
|
|
SetFlags(OBJFLAG_HIDDEN);
|
|
}
|
|
else
|
|
{
|
|
ClearFlags(OBJFLAG_HIDDEN);
|
|
}
|
|
|
|
m_hideOrder = hiddenID;
|
|
UpdateVisibility(!IsHidden());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetFrozen(bool bFrozen)
|
|
{
|
|
if (CheckFlags(OBJFLAG_FROZEN) != bFrozen)
|
|
{
|
|
StoreUndo("Freeze Object");
|
|
if (bFrozen)
|
|
{
|
|
SetFlags(OBJFLAG_FROZEN);
|
|
}
|
|
else
|
|
{
|
|
ClearFlags(OBJFLAG_FROZEN);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetHighlight(bool bHighlight)
|
|
{
|
|
if (bHighlight)
|
|
{
|
|
SetFlags(OBJFLAG_HIGHLIGHT);
|
|
}
|
|
else
|
|
{
|
|
ClearFlags(OBJFLAG_HIGHLIGHT);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetSelected(bool bSelect)
|
|
{
|
|
if (bSelect)
|
|
{
|
|
SetFlags(OBJFLAG_SELECTED);
|
|
NotifyListeners(ON_SELECT);
|
|
|
|
//CLogFile::FormatLine( "Selected: %s, ID=%u",(const char*)m_name,m_id );
|
|
}
|
|
else
|
|
{
|
|
ClearFlags(OBJFLAG_SELECTED);
|
|
NotifyListeners(ON_UNSELECT);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsHiddenBySpec() const
|
|
{
|
|
if (!gSettings.bApplyConfigSpecInEditor)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
return (m_nMinSpec != 0 && gSettings.editorConfigSpec != 0 && m_nMinSpec > static_cast<uint32>(gSettings.editorConfigSpec));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//! Returns true if object hidden.
|
|
bool CBaseObject::IsHidden() const
|
|
{
|
|
return (CheckFlags(OBJFLAG_HIDDEN)) ||
|
|
(m_classDesc && (gSettings.objectHideMask & GetType()));
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//! Returns true if object frozen.
|
|
bool CBaseObject::IsFrozen() const
|
|
{
|
|
return CheckFlags(OBJFLAG_FROZEN);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsSelectable() const
|
|
{
|
|
// Not selectable if frozen.
|
|
return IsFrozen() ? false : true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::Serialize(CObjectArchive& ar)
|
|
{
|
|
XmlNodeRef xmlNode = ar.node;
|
|
|
|
ITransformDelegate* pTransformDelegate = m_pTransformDelegate;
|
|
m_pTransformDelegate = nullptr;
|
|
|
|
if (ar.bLoading)
|
|
{
|
|
// Loading.
|
|
if (ar.ShouldResetInternalMembers())
|
|
{
|
|
m_flags = 0;
|
|
m_flattenArea = 0.0f;
|
|
m_nMinSpec = 0;
|
|
m_scale.Set(1.0f, 1.0f, 1.0f);
|
|
}
|
|
|
|
int flags = 0;
|
|
int oldFlags = m_flags;
|
|
|
|
QString name = m_name;
|
|
QString mtlName;
|
|
|
|
Vec3 pos = m_pos;
|
|
Vec3 scale = m_scale;
|
|
Quat quat = m_rotate;
|
|
Ang3 angles(0, 0, 0);
|
|
uint32 nMinSpec = m_nMinSpec;
|
|
|
|
QColor color = m_color;
|
|
float flattenArea = m_flattenArea;
|
|
|
|
GUID parentId = GUID_NULL;
|
|
GUID lookatId = GUID_NULL;
|
|
|
|
xmlNode->getAttr("Name", name);
|
|
xmlNode->getAttr("Pos", pos);
|
|
if (!xmlNode->getAttr("Rotate", quat))
|
|
{
|
|
// Backward compatibility.
|
|
if (xmlNode->getAttr("Angles", angles))
|
|
{
|
|
angles = DEG2RAD(angles);
|
|
//angles.z += gf_PI/2;
|
|
quat.SetRotationXYZ(angles);
|
|
}
|
|
}
|
|
|
|
xmlNode->getAttr("Scale", scale);
|
|
xmlNode->getAttr("ColorRGB", color);
|
|
xmlNode->getAttr("FlattenArea", flattenArea);
|
|
xmlNode->getAttr("Flags", flags);
|
|
xmlNode->getAttr("Parent", parentId);
|
|
xmlNode->getAttr("LookAt", lookatId);
|
|
xmlNode->getAttr("Material", mtlName);
|
|
xmlNode->getAttr("MinSpec", nMinSpec);
|
|
xmlNode->getAttr("FloorNumber", m_floorNumber);
|
|
|
|
if (nMinSpec <= CONFIG_VERYHIGH_SPEC) // Ignore invalid values.
|
|
{
|
|
m_nMinSpec = nMinSpec;
|
|
}
|
|
|
|
bool bHidden = flags & OBJFLAG_HIDDEN;
|
|
bool bFrozen = flags & OBJFLAG_FROZEN;
|
|
|
|
m_flags = flags;
|
|
m_flags &= ~OBJFLAG_PERSISTMASK;
|
|
m_flags |= (oldFlags) & (~OBJFLAG_PERSISTMASK);
|
|
//SetFlags( flags & OBJFLAG_PERSISTMASK );
|
|
m_flags &= ~OBJFLAG_SHARED; // clear shared flag
|
|
m_flags &= ~OBJFLAG_DELETED; // clear deleted flag
|
|
|
|
if (ar.bUndo)
|
|
{
|
|
DetachThis(false);
|
|
}
|
|
|
|
if (name != m_name)
|
|
{
|
|
// This may change object id.
|
|
SetName(name);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if position is bad.
|
|
if (fabs(pos.x) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
fabs(pos.y) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
fabs(pos.z) > AZ::Constants::MaxFloatBeforePrecisionLoss)
|
|
{
|
|
// File Not found.
|
|
CErrorRecord err;
|
|
err.error = QStringLiteral("Object %1 have invalid position (%2,%3,%4)").arg(GetName()).arg(pos.x).arg(pos.y).arg(pos.z);
|
|
err.pObject = this;
|
|
err.severity = CErrorRecord::ESEVERITY_WARNING;
|
|
GetIEditor()->GetErrorReport()->ReportError(err);
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
if (!ar.bUndo)
|
|
{
|
|
SetLocalTM(pos, quat, scale);
|
|
}
|
|
else
|
|
{
|
|
SetLocalTM(pos, quat, scale, eObjectUpdateFlags_Undo);
|
|
}
|
|
|
|
SetColor(color);
|
|
SetArea(flattenArea);
|
|
SetFrozen(bFrozen);
|
|
SetHidden(bHidden);
|
|
|
|
ar.SetResolveCallback(this, parentId, [this](CBaseObject* parent) { ResolveParent(parent); });
|
|
ar.SetResolveCallback(this, lookatId, [this](CBaseObject* target) { SetLookAt(target); });
|
|
|
|
InvalidateTM(0);
|
|
SetModified(false);
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
if (ar.bUndo)
|
|
{
|
|
// If we are selected update UI Panel.
|
|
xmlNode->getAttr("HideOrder", m_hideOrder);
|
|
}
|
|
|
|
// We reseted the min spec and deserialized it so set it internally
|
|
if (ar.ShouldResetInternalMembers())
|
|
{
|
|
SetMinSpec(m_nMinSpec);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Saving.
|
|
|
|
// This attributed only readed by ObjectManager.
|
|
xmlNode->setAttr("Type", GetTypeName().toUtf8().data());
|
|
|
|
xmlNode->setAttr("Id", m_guid);
|
|
|
|
xmlNode->setAttr("Name", GetName().toUtf8().data());
|
|
xmlNode->setAttr("HideOrder", m_hideOrder);
|
|
|
|
if (m_parent)
|
|
{
|
|
xmlNode->setAttr("Parent", m_parent->GetId());
|
|
}
|
|
|
|
if (m_lookat)
|
|
{
|
|
xmlNode->setAttr("LookAt", m_lookat->GetId());
|
|
}
|
|
|
|
if (!IsEquivalent(GetPos(), Vec3(0, 0, 0), 0))
|
|
{
|
|
xmlNode->setAttr("Pos", GetPos());
|
|
}
|
|
|
|
xmlNode->setAttr("FloorNumber", m_floorNumber);
|
|
|
|
xmlNode->setAttr("Rotate", m_rotate);
|
|
|
|
if (!IsEquivalent(GetScale(), Vec3(1, 1, 1), 0))
|
|
{
|
|
xmlNode->setAttr("Scale", GetScale());
|
|
}
|
|
|
|
xmlNode->setAttr("ColorRGB", GetColor());
|
|
|
|
if (GetArea() != 0)
|
|
{
|
|
xmlNode->setAttr("FlattenArea", GetArea());
|
|
}
|
|
|
|
int flags = m_flags & OBJFLAG_PERSISTMASK;
|
|
if (flags != 0)
|
|
{
|
|
xmlNode->setAttr("Flags", flags);
|
|
}
|
|
|
|
if (m_nMinSpec != 0)
|
|
{
|
|
xmlNode->setAttr("MinSpec", (uint32)m_nMinSpec);
|
|
}
|
|
}
|
|
|
|
// Serialize variables after default entity parameters.
|
|
CVarObject::Serialize(xmlNode, ar.bLoading);
|
|
|
|
m_pTransformDelegate = pTransformDelegate;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
XmlNodeRef CBaseObject::Export([[maybe_unused]] const QString& levelPath, XmlNodeRef& xmlNode)
|
|
{
|
|
XmlNodeRef objNode = xmlNode->newChild("Object");
|
|
|
|
objNode->setAttr("Type", GetTypeName().toUtf8().data());
|
|
objNode->setAttr("Name", GetName().toUtf8().data());
|
|
|
|
Vec3 pos, scale;
|
|
Quat rotate;
|
|
if (m_parent)
|
|
{
|
|
// Export world coordinates.
|
|
AffineParts ap;
|
|
ap.SpectralDecompose(GetWorldTM());
|
|
|
|
pos = ap.pos;
|
|
rotate = ap.rot;
|
|
scale = ap.scale;
|
|
}
|
|
else
|
|
{
|
|
pos = m_pos;
|
|
rotate = m_rotate;
|
|
scale = m_scale;
|
|
}
|
|
|
|
if (!IsEquivalent(pos, Vec3(0, 0, 0), 0))
|
|
{
|
|
objNode->setAttr("Pos", pos);
|
|
}
|
|
|
|
if (!rotate.IsIdentity())
|
|
{
|
|
objNode->setAttr("Rotate", rotate);
|
|
}
|
|
|
|
if (!IsEquivalent(scale, Vec3(0, 0, 0), 0))
|
|
{
|
|
objNode->setAttr("Scale", scale);
|
|
}
|
|
|
|
if (m_nMinSpec != 0)
|
|
{
|
|
objNode->setAttr("MinSpec", (uint32)m_nMinSpec);
|
|
}
|
|
|
|
// Save variables.
|
|
CVarObject::Serialize(objNode, false);
|
|
|
|
return objNode;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject* CBaseObject::FindObject(REFGUID id) const
|
|
{
|
|
return GetObjectManager()->FindObject(id);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::StoreUndo(const char* UndoDescription, bool minimal, int flags)
|
|
{
|
|
if (m_objType == OBJTYPE_DUMMY)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Don't use Sandbox undo for AZ entities, except for the move & scale tools, which rely on it.
|
|
const bool isGizmoTool = 0 != (flags & (eObjectUpdateFlags_MoveTool | eObjectUpdateFlags_ScaleTool | eObjectUpdateFlags_UserInput));
|
|
if (!isGizmoTool && 0 != (m_flags & OBJFLAG_DONT_SAVE))
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (CUndo::IsRecording())
|
|
{
|
|
if (minimal)
|
|
{
|
|
CUndo::Record(new CUndoBaseObjectMinimal(this, UndoDescription, flags));
|
|
}
|
|
else
|
|
{
|
|
CUndo::Record(new CUndoBaseObject(this, UndoDescription));
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsCreateGameObjects() const
|
|
{
|
|
return GetObjectManager()->IsCreateGameObjects();
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
QString CBaseObject::GetTypeName() const
|
|
{
|
|
if (m_objType == OBJTYPE_DUMMY)
|
|
{
|
|
return "";
|
|
}
|
|
QString className = m_classDesc->ClassName();
|
|
QString subClassName = strstr(className.toUtf8().data(), "::");
|
|
if (subClassName.isEmpty())
|
|
{
|
|
return className;
|
|
}
|
|
|
|
QString name;
|
|
name.append(className.mid(0, className.length() - subClassName.length()));
|
|
return name;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IntersectRectBounds(const AABB& bbox)
|
|
{
|
|
AABB aabb;
|
|
GetBoundBox(aabb);
|
|
|
|
return aabb.IsIntersectBox(bbox);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IntersectRayBounds(const Ray& ray)
|
|
{
|
|
Vec3 tmpPnt;
|
|
AABB aabb;
|
|
GetBoundBox(aabb);
|
|
|
|
return Intersect::Ray_AABB(ray, aabb, tmpPnt);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
namespace
|
|
{
|
|
using Edge2D = std::pair<Vec2, Vec2>;
|
|
}
|
|
bool IsIncludePointsInConvexHull(Edge2D* pEdgeArray0, int nEdgeArray0Size, Edge2D* pEdgeArray1, int nEdgeArray1Size)
|
|
{
|
|
if (!pEdgeArray0 || !pEdgeArray1 || nEdgeArray0Size <= 0 || nEdgeArray1Size <= 0)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
static const float kPointEdgeMaxInsideDistance(0.05f);
|
|
|
|
bool bInside(true);
|
|
for (int i = 0; i < nEdgeArray0Size; ++i)
|
|
{
|
|
const Vec2& v(pEdgeArray0[i].first);
|
|
bInside = true;
|
|
for (int k = 0; k < nEdgeArray1Size; ++k)
|
|
{
|
|
Vec2 v0 = pEdgeArray1[k].first;
|
|
Vec2 v1 = pEdgeArray1[k].second;
|
|
Vec3 direction(v1.x - v0.x, v1.y - v0.y, 0);
|
|
Vec3 up(0, 0, 1);
|
|
Vec3 z = up.Cross(direction);
|
|
Vec2 normal;
|
|
normal.x = z.x;
|
|
normal.y = z.y;
|
|
normal.Normalize();
|
|
float distance = -normal.Dot(v0);
|
|
if (normal.Dot(v) + distance > kPointEdgeMaxInsideDistance)
|
|
{
|
|
bInside = false;
|
|
break;
|
|
}
|
|
}
|
|
if (bInside)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
return bInside;
|
|
}
|
|
|
|
void ModifyConvexEdgeDirection(Edge2D* pEdgeArray, int nEdgeArraySize)
|
|
{
|
|
if (!pEdgeArray || nEdgeArraySize < 2)
|
|
{
|
|
return;
|
|
}
|
|
Vec3 v0(pEdgeArray[0].first.x - pEdgeArray[0].second.x, pEdgeArray[0].first.y - pEdgeArray[0].second.y, 0);
|
|
Vec3 v1(pEdgeArray[1].second.x - pEdgeArray[1].first.x, pEdgeArray[1].second.y - pEdgeArray[1].first.y, 0);
|
|
Vec3 vCross = v0.Cross(v1);
|
|
if (vCross.z < 0)
|
|
{
|
|
for (int i = 0; i < nEdgeArraySize; ++i)
|
|
{
|
|
std::swap(pEdgeArray[i].first, pEdgeArray[i].second);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool CBaseObject::HitTestRectBounds(HitContext& hc, const AABB& box)
|
|
{
|
|
if (hc.bUseSelectionHelpers)
|
|
{
|
|
if (IsSkipSelectionHelper())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static const int kNumberOfBoundBoxPt(8);
|
|
|
|
// transform all 8 vertices into view space.
|
|
QPoint p[kNumberOfBoundBoxPt] =
|
|
{
|
|
hc.view->WorldToView(Vec3(box.min.x, box.min.y, box.min.z)),
|
|
hc.view->WorldToView(Vec3(box.min.x, box.max.y, box.min.z)),
|
|
hc.view->WorldToView(Vec3(box.max.x, box.min.y, box.min.z)),
|
|
hc.view->WorldToView(Vec3(box.max.x, box.max.y, box.min.z)),
|
|
hc.view->WorldToView(Vec3(box.min.x, box.min.y, box.max.z)),
|
|
hc.view->WorldToView(Vec3(box.min.x, box.max.y, box.max.z)),
|
|
hc.view->WorldToView(Vec3(box.max.x, box.min.y, box.max.z)),
|
|
hc.view->WorldToView(Vec3(box.max.x, box.max.y, box.max.z))
|
|
};
|
|
|
|
QRect objrc;
|
|
objrc.setLeft(10000);
|
|
objrc.setTop(10000);
|
|
objrc.setRight(-10000);
|
|
objrc.setBottom(-10000);
|
|
|
|
// find new min/max values
|
|
for (int i = 0; i < 8; i++)
|
|
{
|
|
objrc.setLeft(min(objrc.left(), p[i].x()));
|
|
objrc.setRight(max(objrc.right(), p[i].x()));
|
|
objrc.setTop(min(objrc.top(), p[i].y()));
|
|
objrc.setBottom(max(objrc.bottom(), p[i].y()));
|
|
}
|
|
if (objrc.isEmpty())
|
|
{
|
|
// Make objrc at least of size 1.
|
|
objrc.moveBottomRight(objrc.bottomRight() + QPoint(1, 1));
|
|
}
|
|
|
|
if (hc.rect.contains(objrc.topLeft()) &&
|
|
hc.rect.contains(objrc.bottomLeft()) &&
|
|
hc.rect.contains(objrc.topRight()) &&
|
|
hc.rect.contains(objrc.bottomRight()))
|
|
{
|
|
hc.object = this;
|
|
return true;
|
|
}
|
|
|
|
if (objrc.intersects(hc.rect))
|
|
{
|
|
AABB localAABB;
|
|
GetLocalBounds(localAABB);
|
|
CBaseObject* pOldObj = hc.object;
|
|
hc.object = this;
|
|
if (localAABB.IsEmpty())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
const int kMaxSizeOfEdgeList0(4);
|
|
Edge2D edgelist0[kMaxSizeOfEdgeList0] = {
|
|
Edge2D(Vec2(static_cast<f32>(hc.rect.left()), static_cast<f32>(hc.rect.top())), Vec2(static_cast<f32>(hc.rect.right()), static_cast<f32>(hc.rect.top()))),
|
|
Edge2D(Vec2(static_cast<f32>(hc.rect.right()), static_cast<f32>(hc.rect.top())), Vec2(static_cast<f32>(hc.rect.right()), static_cast<f32>(hc.rect.bottom()))),
|
|
Edge2D(Vec2(static_cast<f32>(hc.rect.right()), static_cast<f32>(hc.rect.bottom())), Vec2(static_cast<f32>(hc.rect.left()), static_cast<f32>(hc.rect.bottom()))),
|
|
Edge2D(Vec2(static_cast<f32>(hc.rect.left()), static_cast<f32>(hc.rect.bottom())), Vec2(static_cast<f32>(hc.rect.left()), static_cast<f32>(hc.rect.top())))
|
|
};
|
|
|
|
const int kMaxSizeOfEdgeList1(8);
|
|
Edge2D edgelist1[kMaxSizeOfEdgeList1];
|
|
int nEdgeList1Count(kMaxSizeOfEdgeList1);
|
|
|
|
const Matrix34& worldTM(GetWorldTM());
|
|
OBB obb = OBB::CreateOBBfromAABB(Matrix33(worldTM), localAABB);
|
|
Vec3 ax = obb.m33.GetColumn0() * obb.h.x;
|
|
Vec3 ay = obb.m33.GetColumn1() * obb.h.y;
|
|
Vec3 az = obb.m33.GetColumn2() * obb.h.z;
|
|
QPoint obb_p[kMaxSizeOfEdgeList1] =
|
|
{
|
|
hc.view->WorldToView(-ax - ay - az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(-ax - ay + az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(-ax + ay - az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(-ax + ay + az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(ax - ay - az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(ax - ay + az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(ax + ay - az + worldTM.GetTranslation()),
|
|
hc.view->WorldToView(ax + ay + az + worldTM.GetTranslation())
|
|
};
|
|
|
|
std::vector<Vec3> pointsForRegion1;
|
|
pointsForRegion1.reserve(kMaxSizeOfEdgeList1);
|
|
for (int i = 0; i < kMaxSizeOfEdgeList1; ++i)
|
|
{
|
|
pointsForRegion1.push_back(Vec3(static_cast<f32>(obb_p[i].x()), static_cast<f32>(obb_p[i].y()), 0.0f));
|
|
}
|
|
|
|
std::vector<Vec3> convexHullForRegion1;
|
|
ConvexHull2D(convexHullForRegion1, pointsForRegion1);
|
|
nEdgeList1Count = static_cast<int>(convexHullForRegion1.size());
|
|
if (nEdgeList1Count < 3 || nEdgeList1Count > kMaxSizeOfEdgeList1)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
for (int i = 0; i < nEdgeList1Count; ++i)
|
|
{
|
|
int iNextI = (i + 1) % nEdgeList1Count;
|
|
edgelist1[i] = Edge2D(Vec2(convexHullForRegion1[i].x, convexHullForRegion1[i].y), Vec2(convexHullForRegion1[iNextI].x, convexHullForRegion1[iNextI].y));
|
|
}
|
|
|
|
ModifyConvexEdgeDirection(edgelist0, kMaxSizeOfEdgeList0);
|
|
ModifyConvexEdgeDirection(edgelist1, nEdgeList1Count);
|
|
|
|
bool bInside = IsIncludePointsInConvexHull(edgelist0, kMaxSizeOfEdgeList0, edgelist1, nEdgeList1Count);
|
|
if (!bInside)
|
|
{
|
|
bInside = IsIncludePointsInConvexHull(edgelist1, nEdgeList1Count, edgelist0, kMaxSizeOfEdgeList0);
|
|
}
|
|
if (!bInside)
|
|
{
|
|
hc.object = pOldObj;
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::HitTestRect(HitContext& hc)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Editor);
|
|
|
|
AABB box;
|
|
|
|
if (hc.bUseSelectionHelpers)
|
|
{
|
|
if (IsSkipSelectionHelper())
|
|
{
|
|
return false;
|
|
}
|
|
box.min = GetWorldPos();
|
|
box.max = box.min;
|
|
}
|
|
else
|
|
{
|
|
// Retrieve world space bound box.
|
|
GetBoundBox(box);
|
|
}
|
|
|
|
bool bHit = HitTestRectBounds(hc, box);
|
|
m_bInSelectionBox = bHit;
|
|
if (bHit)
|
|
{
|
|
hc.object = this;
|
|
}
|
|
return bHit;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::HitHelperTest(HitContext& hc)
|
|
{
|
|
return HitHelperAtTest(hc, GetWorldPos());
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::HitHelperAtTest(HitContext& hc, const Vec3& pos)
|
|
{
|
|
AZ_PROFILE_FUNCTION(Editor);
|
|
|
|
bool bResult = false;
|
|
|
|
if (m_nTextureIcon && (gSettings.viewports.bShowIcons || gSettings.viewports.bShowSizeBasedIcons) && !hc.bUseSelectionHelpers)
|
|
{
|
|
int iconSizeX = OBJECT_TEXTURE_ICON_SIZEX;
|
|
int iconSizeY = OBJECT_TEXTURE_ICON_SIZEY;
|
|
|
|
if (gSettings.viewports.bDistanceScaleIcons)
|
|
{
|
|
float fScreenScale = hc.view->GetScreenScaleFactor(pos);
|
|
|
|
iconSizeX = static_cast<int>(static_cast<float>(iconSizeX) * OBJECT_TEXTURE_ICON_SCALE / fScreenScale);
|
|
iconSizeY = static_cast<int>(static_cast<float>(iconSizeY) * OBJECT_TEXTURE_ICON_SCALE / fScreenScale);
|
|
}
|
|
|
|
// Hit Test icon of this object.
|
|
Vec3 testPos = pos;
|
|
int y0 = -(iconSizeY / 2);
|
|
int y1 = +(iconSizeY / 2);
|
|
if (CheckFlags(OBJFLAG_SHOW_ICONONTOP))
|
|
{
|
|
Vec3 objectPos = GetWorldPos();
|
|
|
|
AABB box;
|
|
GetBoundBox(box);
|
|
testPos.z = (pos.z - objectPos.z) + box.max.z;
|
|
y0 = -(iconSizeY);
|
|
y1 = 0;
|
|
}
|
|
QPoint pnt = hc.view->WorldToView(testPos);
|
|
|
|
if (hc.point2d.x() >= pnt.x() - (iconSizeX / 2) && hc.point2d.x() <= pnt.x() + (iconSizeX / 2) &&
|
|
hc.point2d.y() >= pnt.y() + y0 && hc.point2d.y() <= pnt.y() + y1)
|
|
{
|
|
hc.dist = hc.raySrc.GetDistance(testPos) - 0.2f;
|
|
hc.iconHit = true;
|
|
bResult = true;
|
|
}
|
|
}
|
|
else if (hc.bUseSelectionHelpers)
|
|
{
|
|
// Check potentially children first
|
|
bResult = HitHelperTestForChildObjects(hc);
|
|
|
|
// If no hit check this object
|
|
if (!bResult)
|
|
{
|
|
// Hit test helper.
|
|
Vec3 w = pos - hc.raySrc;
|
|
w = hc.rayDir.Cross(w);
|
|
float d = w.GetLengthSquared();
|
|
|
|
static const float screenScaleToRadiusFactor = 0.008f;
|
|
const float radius = hc.view->GetScreenScaleFactor(pos) * screenScaleToRadiusFactor;
|
|
const float pickDistance = hc.raySrc.GetDistance(pos);
|
|
if (d < radius * radius + hc.distanceTolerance && hc.dist >= pickDistance)
|
|
{
|
|
hc.dist = pickDistance;
|
|
hc.object = this;
|
|
bResult = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
return bResult;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
CBaseObject* CBaseObject::GetChild(size_t const i) const
|
|
{
|
|
assert(i < m_childs.size());
|
|
return m_childs[i];
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsChildOf(CBaseObject* node)
|
|
{
|
|
CBaseObject* p = m_parent;
|
|
while (p && p != node)
|
|
{
|
|
p = p->m_parent;
|
|
}
|
|
if (p == node)
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::CloneChildren(CBaseObject* pFromObject)
|
|
{
|
|
if (pFromObject == nullptr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
for (size_t i = 0, nChildCount(pFromObject->GetChildCount()); i < nChildCount; ++i)
|
|
{
|
|
CBaseObject* pFromChildObject = pFromObject->GetChild(i);
|
|
|
|
CBaseObject* pChildClone = GetObjectManager()->CloneObject(pFromChildObject);
|
|
if (pChildClone == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
pChildClone->CloneChildren(pFromChildObject);
|
|
AddMember(pChildClone, false);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::AttachChild(CBaseObject* child, bool bKeepPos)
|
|
{
|
|
Matrix34 childTM;
|
|
ITransformDelegate* pTransformDelegate;
|
|
ITransformDelegate* pChildTransformDelegate;
|
|
|
|
{
|
|
CScopedSuspendUndo suspendUndo;
|
|
|
|
assert(child);
|
|
if (!child || child == GetLookAt())
|
|
{
|
|
return;
|
|
}
|
|
|
|
static_cast<CObjectManager*>(GetObjectManager())->NotifyObjectListeners(child, ON_PREATTACHED);
|
|
child->NotifyListeners(bKeepPos ? ON_PREATTACHEDKEEPXFORM : ON_PREATTACHED);
|
|
|
|
pTransformDelegate = m_pTransformDelegate;
|
|
pChildTransformDelegate = child->m_pTransformDelegate;
|
|
SetTransformDelegate(nullptr);
|
|
child->SetTransformDelegate(nullptr);
|
|
|
|
if (bKeepPos)
|
|
{
|
|
child->InvalidateTM(0);
|
|
childTM = child->GetWorldTM();
|
|
}
|
|
|
|
// If not already attached to this node.
|
|
if (child->m_parent == this)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Add to child list first to make sure node not get deleted while reattaching.
|
|
m_childs.push_back(child);
|
|
if (child->m_parent)
|
|
{
|
|
child->DetachThis(bKeepPos); // Detach node if attached to other parent.
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
child->m_parent = this; // Assign this node as parent to child node.
|
|
}
|
|
|
|
{
|
|
CScopedSuspendUndo suspendUndo;
|
|
|
|
{
|
|
if (bKeepPos)
|
|
{
|
|
child->SetWorldTM(childTM);
|
|
}
|
|
|
|
child->InvalidateTM(0);
|
|
}
|
|
|
|
m_pTransformDelegate = pTransformDelegate;
|
|
child->m_pTransformDelegate = pChildTransformDelegate;
|
|
|
|
static_cast<CObjectManager*>(GetObjectManager())->NotifyObjectListeners(child, ON_ATTACHED);
|
|
child->NotifyListeners(ON_ATTACHED);
|
|
|
|
NotifyListeners(ON_CHILDATTACHED);
|
|
}
|
|
|
|
if (CUndo::IsRecording())
|
|
{
|
|
CUndo::Record(new CUndoAttachBaseObject(child, bKeepPos, true));
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DetachAll(bool bKeepPos)
|
|
{
|
|
while (!m_childs.empty())
|
|
{
|
|
CBaseObject* child = *m_childs.begin();
|
|
child->DetachThis(bKeepPos);
|
|
NotifyListeners(ON_CHILDDETACHED);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::DetachThis(bool bKeepPos)
|
|
{
|
|
if (m_parent)
|
|
{
|
|
if (CUndo::IsRecording())
|
|
{
|
|
CUndo::Record(new CUndoAttachBaseObject(this, bKeepPos, false));
|
|
}
|
|
|
|
Matrix34 worldTM;
|
|
ITransformDelegate* pTransformDelegate;
|
|
|
|
{
|
|
CScopedSuspendUndo suspendUndo;
|
|
static_cast<CObjectManager*>(GetObjectManager())->NotifyObjectListeners(this, ON_PREDETACHED);
|
|
NotifyListeners(bKeepPos ? ON_PREDETACHEDKEEPXFORM : ON_PREDETACHED);
|
|
|
|
pTransformDelegate = m_pTransformDelegate;
|
|
SetTransformDelegate(nullptr);
|
|
|
|
if (bKeepPos)
|
|
{
|
|
ITransformDelegate* pParentTransformDelegate = m_parent->m_pTransformDelegate;
|
|
m_parent->SetTransformDelegate(nullptr);
|
|
worldTM = GetWorldTM();
|
|
m_parent->SetTransformDelegate(pParentTransformDelegate);
|
|
}
|
|
}
|
|
|
|
{
|
|
CScopedSuspendUndo suspendUndo;
|
|
|
|
// Copy parent to temp var, erasing child from parent may delete this node if child referenced only from parent.
|
|
CBaseObject* parent = m_parent;
|
|
m_parent = nullptr;
|
|
parent->RemoveChild(this);
|
|
|
|
if (bKeepPos)
|
|
{
|
|
// Keep old world space transformation.
|
|
SetWorldTM(worldTM);
|
|
}
|
|
|
|
SetTransformDelegate(pTransformDelegate);
|
|
|
|
static_cast<CObjectManager*>(GetObjectManager())->NotifyObjectListeners(this, ON_DETACHED);
|
|
NotifyListeners(ON_DETACHED);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::RemoveChild(CBaseObject* node)
|
|
{
|
|
Childs::iterator it = std::find(m_childs.begin(), m_childs.end(), node);
|
|
if (it != m_childs.end())
|
|
{
|
|
m_childs.erase(it);
|
|
NotifyListeners(ON_CHILDDETACHED);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::ResolveParent(CBaseObject* parent)
|
|
{
|
|
// even though parent is same as m_parent, adding the member to the parent must be done.
|
|
if (parent)
|
|
{
|
|
parent->AddMember(this, false);
|
|
}
|
|
else
|
|
{
|
|
DetachThis(false);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::CalcLocalTM(Matrix34& tm) const
|
|
{
|
|
tm.SetIdentity();
|
|
|
|
if (m_lookat)
|
|
{
|
|
Vec3 pos = GetPos();
|
|
|
|
if (m_parent)
|
|
{
|
|
// Get our world position.
|
|
pos = GetParentAttachPointWorldTM().TransformPoint(pos);
|
|
}
|
|
|
|
// Calculate local TM matrix differently.
|
|
Vec3 lookatPos = m_lookat->GetWorldPos();
|
|
if (lookatPos == pos) // if target at object pos
|
|
{
|
|
tm.SetTranslation(pos);
|
|
}
|
|
else
|
|
{
|
|
tm = Matrix34(Matrix33::CreateRotationVDir((lookatPos - pos).GetNormalized()), pos);
|
|
}
|
|
if (m_parent)
|
|
{
|
|
Matrix34 invParentTM = m_parent->GetWorldTM();
|
|
invParentTM.Invert();
|
|
tm = invParentTM * tm;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
tm = Matrix34::Create(GetScale(), GetRotation(), GetPos());
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
const Matrix34& CBaseObject::GetWorldTM() const
|
|
{
|
|
if (!m_bMatrixValid)
|
|
{
|
|
m_worldTM = GetLocalTM();
|
|
m_bMatrixValid = true;
|
|
m_bMatrixInWorldSpace = false;
|
|
m_bWorldBoxValid = false;
|
|
}
|
|
if (!m_bMatrixInWorldSpace)
|
|
{
|
|
CBaseObject* parent = GetParent();
|
|
if (parent)
|
|
{
|
|
m_worldTM = GetParentAttachPointWorldTM() * m_worldTM;
|
|
}
|
|
m_bMatrixInWorldSpace = true;
|
|
m_bWorldBoxValid = false;
|
|
}
|
|
return m_worldTM;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Matrix34 CBaseObject::GetParentAttachPointWorldTM() const
|
|
{
|
|
CBaseObject* pParent = GetParent();
|
|
if (pParent)
|
|
{
|
|
return pParent->GetWorldTM();
|
|
}
|
|
|
|
return Matrix34(IDENTITY);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsParentAttachmentValid() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::InvalidateTM([[maybe_unused]] int flags)
|
|
{
|
|
bool bMatrixWasValid = m_bMatrixValid;
|
|
|
|
m_bMatrixInWorldSpace = false;
|
|
m_bMatrixValid = false;
|
|
m_bWorldBoxValid = false;
|
|
|
|
// If matrix was valid, invalidate all childs.
|
|
if (bMatrixWasValid)
|
|
{
|
|
if (m_lookatSource)
|
|
{
|
|
m_lookatSource->InvalidateTM(eObjectUpdateFlags_ParentChanged);
|
|
}
|
|
|
|
// Invalidate matrices off all child objects.
|
|
for (int i = 0; i < m_childs.size(); i++)
|
|
{
|
|
if (m_childs[i] != nullptr && m_childs[i]->m_bMatrixValid)
|
|
{
|
|
m_childs[i]->InvalidateTM(eObjectUpdateFlags_ParentChanged);
|
|
}
|
|
}
|
|
NotifyListeners(ON_TRANSFORM);
|
|
|
|
// Notify parent that we were modified.
|
|
if (m_parent)
|
|
{
|
|
m_parent->OnChildModified();
|
|
}
|
|
}
|
|
|
|
if (m_pTransformDelegate)
|
|
{
|
|
m_pTransformDelegate->MatrixInvalidated();
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetLocalTM(const Vec3& pos, const Quat& rotate, const Vec3& scale, int flags)
|
|
{
|
|
const bool b1 = SetPos(pos, flags | eObjectUpdateFlags_DoNotInvalidate);
|
|
const bool b2 = SetRotation(rotate, flags | eObjectUpdateFlags_DoNotInvalidate);
|
|
const bool b3 = SetScale(scale, flags | eObjectUpdateFlags_DoNotInvalidate);
|
|
|
|
if (b1 || b2 || b3 || flags == eObjectUpdateFlags_Animated)
|
|
{
|
|
flags = (b1) ? (flags | eObjectUpdateFlags_PositionChanged) : (flags & (~eObjectUpdateFlags_PositionChanged));
|
|
flags = (b2) ? (flags | eObjectUpdateFlags_RotationChanged) : (flags & (~eObjectUpdateFlags_RotationChanged));
|
|
flags = (b3) ? (flags | eObjectUpdateFlags_ScaleChanged) : (flags & (~eObjectUpdateFlags_ScaleChanged));
|
|
InvalidateTM(flags);
|
|
}
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetLocalTM(const Matrix34& tm, int flags)
|
|
{
|
|
if (m_lookat)
|
|
{
|
|
bool b1 = SetPos(tm.GetTranslation(), eObjectUpdateFlags_DoNotInvalidate);
|
|
flags = (b1) ? (flags | eObjectUpdateFlags_PositionChanged) : (flags & (~eObjectUpdateFlags_PositionChanged));
|
|
InvalidateTM(flags);
|
|
}
|
|
else
|
|
{
|
|
AffineParts affineParts;
|
|
affineParts.SpectralDecompose(tm);
|
|
SetLocalTM(affineParts.pos, affineParts.rot, affineParts.scale, flags);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetWorldPos(const Vec3& pos, int flags)
|
|
{
|
|
if (GetParent())
|
|
{
|
|
Matrix34 invParentTM = GetParentAttachPointWorldTM();
|
|
invParentTM.Invert();
|
|
Vec3 posLocal = invParentTM * pos;
|
|
SetPos(posLocal, flags);
|
|
}
|
|
else
|
|
{
|
|
SetPos(pos, flags);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetWorldTM(const Matrix34& tm, int flags)
|
|
{
|
|
if (GetParent())
|
|
{
|
|
Matrix34 invParentTM = GetParentAttachPointWorldTM();
|
|
invParentTM.Invert();
|
|
Matrix34 localTM = invParentTM * tm;
|
|
SetLocalTM(localTM, flags);
|
|
}
|
|
else
|
|
{
|
|
SetLocalTM(tm, flags);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::UpdateVisibility(bool bVisible)
|
|
{
|
|
if (bVisible == CheckFlags(OBJFLAG_INVISIBLE))
|
|
{
|
|
if (IObjectManager* objectManager = GetObjectManager())
|
|
{
|
|
objectManager->InvalidateVisibleList();
|
|
}
|
|
|
|
if (!bVisible)
|
|
{
|
|
m_flags |= OBJFLAG_INVISIBLE;
|
|
}
|
|
else
|
|
{
|
|
m_flags &= ~OBJFLAG_INVISIBLE;
|
|
}
|
|
|
|
NotifyListeners(ON_VISIBILITY);
|
|
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::AddGizmo(CGizmo* gizmo)
|
|
{
|
|
GetObjectManager()->GetGizmoManager()->AddGizmo(gizmo);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::RemoveGizmo(CGizmo* gizmo)
|
|
{
|
|
GetObjectManager()->GetGizmoManager()->RemoveGizmo(gizmo);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetLookAt(CBaseObject* target)
|
|
{
|
|
if (m_lookat == target)
|
|
{
|
|
return;
|
|
}
|
|
|
|
StoreUndo("Change LookAt");
|
|
|
|
if (m_lookat)
|
|
{
|
|
// Unbind current lookat.
|
|
m_lookat->m_lookatSource = nullptr;
|
|
}
|
|
m_lookat = target;
|
|
if (m_lookat)
|
|
{
|
|
m_lookat->m_lookatSource = this;
|
|
}
|
|
|
|
InvalidateTM(0);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsLookAtTarget() const
|
|
{
|
|
return m_lookatSource != nullptr;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::AddEventListener(EventListener* listener)
|
|
{
|
|
if (find(m_eventListeners.begin(), m_eventListeners.end(), listener) == m_eventListeners.end())
|
|
{
|
|
m_eventListeners.push_back(listener);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::RemoveEventListener(EventListener* listener)
|
|
{
|
|
std::vector<EventListener*>::iterator listenerFound = find(m_eventListeners.begin(), m_eventListeners.end(), listener);
|
|
if (listenerFound != m_eventListeners.end())
|
|
{
|
|
(*listenerFound) = nullptr;
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool IsBaseObjectEventCallbackNULL(CBaseObject::EventListener* listener) { return listener == nullptr; }
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::NotifyListeners(EObjectListenerEvent event)
|
|
{
|
|
for (auto it = m_eventListeners.begin(); it != m_eventListeners.end(); ++it)
|
|
{
|
|
// Call listener callback.
|
|
if (*it)
|
|
{
|
|
(*it)->OnObjectEvent(this, event);
|
|
}
|
|
}
|
|
|
|
m_eventListeners.erase(remove_if(m_eventListeners.begin(), m_eventListeners.end(), IsBaseObjectEventCallbackNULL), std::end(m_eventListeners));
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::ConvertFromObject(CBaseObject* object)
|
|
{
|
|
SetLocalTM(object->GetLocalTM());
|
|
SetName(object->GetName());
|
|
SetColor(object->GetColor());
|
|
m_flattenArea = object->m_flattenArea;
|
|
if (object->GetParent())
|
|
{
|
|
object->GetParent()->AttachChild(this);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsPotentiallyVisible() const
|
|
{
|
|
if (CheckFlags(OBJFLAG_HIDDEN))
|
|
{
|
|
return false;
|
|
}
|
|
if (gSettings.objectHideMask & GetType())
|
|
{
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
//! Analyze errors for this object.
|
|
void CBaseObject::Validate(IErrorReport* report)
|
|
{
|
|
// Checks for invalid values in base object.
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if position is bad.
|
|
const Vec3 pos = GetPos();
|
|
|
|
if (fabs(pos.x) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
fabs(pos.y) > AZ::Constants::MaxFloatBeforePrecisionLoss ||
|
|
fabs(pos.z) > AZ::Constants::MaxFloatBeforePrecisionLoss)
|
|
{
|
|
// File Not found.
|
|
CErrorRecord err;
|
|
err.error = QStringLiteral("Object %1 have invalid position (%2,%3,%4)").arg(GetName()).arg(pos.x).arg(pos.y).arg(pos.z);
|
|
err.pObject = this;
|
|
report->ReportError(err);
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
|
|
float minScale = 0.01f;
|
|
float maxScale = 1000.0f;
|
|
//////////////////////////////////////////////////////////////////////////
|
|
// Check if position is bad.
|
|
if (m_scale.x < minScale || m_scale.x > maxScale ||
|
|
m_scale.y < minScale || m_scale.y > maxScale ||
|
|
m_scale.z < minScale || m_scale.z > maxScale)
|
|
{
|
|
// File Not found.
|
|
CErrorRecord err;
|
|
err.error = QStringLiteral("Object %1 have invalid scale (%2,%3,%4)").arg(GetName()).arg(m_scale.x).arg(m_scale.y).arg(m_scale.z);
|
|
err.pObject = this;
|
|
report->ReportError(err);
|
|
}
|
|
//////////////////////////////////////////////////////////////////////////
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
Ang3 CBaseObject::GetWorldAngles() const
|
|
{
|
|
if (m_scale == Vec3(1, 1, 1))
|
|
{
|
|
Quat q = Quat(GetWorldTM());
|
|
Ang3 angles = RAD2DEG(Ang3::GetAnglesXYZ(Matrix33(q)));
|
|
return angles;
|
|
}
|
|
else
|
|
{
|
|
Matrix34 tm = GetWorldTM();
|
|
tm.OrthonormalizeFast();
|
|
Quat q = Quat(tm);
|
|
Ang3 angles = RAD2DEG(Ang3::GetAnglesXYZ(Matrix33(q)));
|
|
return angles;
|
|
}
|
|
};
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::PostClone(CBaseObject* pFromObject, CObjectCloneContext& ctx)
|
|
{
|
|
CBaseObject* pFromParent = pFromObject->GetParent();
|
|
if (pFromParent)
|
|
{
|
|
SetFloorNumber(pFromObject->GetFloorNumber());
|
|
|
|
CBaseObject* pFromParentInContext = ctx.FindClone(pFromParent);
|
|
if (pFromParentInContext)
|
|
{
|
|
pFromParentInContext->AddMember(this, false);
|
|
}
|
|
else
|
|
{
|
|
pFromParent->AddMember(this, false);
|
|
}
|
|
}
|
|
if (pFromObject->ShouldCloneChildren())
|
|
{
|
|
for (int i = 0; i < pFromObject->GetChildCount(); i++)
|
|
{
|
|
CBaseObject* pChildObject = pFromObject->GetChild(i);
|
|
CBaseObject* pClonedChild = GetObjectManager()->CloneObject(pChildObject);
|
|
ctx.AddClone(pChildObject, pClonedChild);
|
|
}
|
|
for (int i = 0; i < pFromObject->GetChildCount(); i++)
|
|
{
|
|
CBaseObject* pChildObject = pFromObject->GetChild(i);
|
|
CBaseObject* pClonedChild = ctx.FindClone(pChildObject);
|
|
if (pClonedChild)
|
|
{
|
|
pClonedChild->PostClone(pChildObject, ctx);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::GatherUsedResources(CUsedResources& resources)
|
|
{
|
|
if (GetVarBlock())
|
|
{
|
|
GetVarBlock()->GatherUsedResources(resources);
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IsSimilarObject(CBaseObject* pObject)
|
|
{
|
|
if (pObject->GetClassDesc() == GetClassDesc() && pObject->metaObject() == metaObject())
|
|
{
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::SetMinSpec(uint32 nSpec, bool bSetChildren)
|
|
{
|
|
m_nMinSpec = nSpec;
|
|
UpdateVisibility(!IsHidden());
|
|
|
|
// Set min spec for all childs.
|
|
if (bSetChildren)
|
|
{
|
|
for (int i = static_cast<int>(m_childs.size()) - 1; i >= 0; --i)
|
|
{
|
|
m_childs[i]->SetMinSpec(nSpec, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::OnPropertyChanged(IVariable*)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::OnMultiSelPropertyChanged(IVariable*)
|
|
{
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::OnMenuShowInAssetBrowser()
|
|
{
|
|
if (!IsSelected())
|
|
{
|
|
CUndo undo("Select Object");
|
|
GetIEditor()->GetObjectManager()->ClearSelection();
|
|
GetIEditor()->SelectObject(this);
|
|
}
|
|
|
|
GetIEditor()->ExecuteCommand("asset_browser.show_viewport_selection");
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
void CBaseObject::OnContextMenu(QMenu* menu)
|
|
{
|
|
if (!menu->isEmpty())
|
|
{
|
|
menu->addSeparator();
|
|
}
|
|
CUsedResources resources;
|
|
GatherUsedResources(resources);
|
|
|
|
static_cast<CEditorImpl*>(GetIEditor())->OnObjectContextMenuOpened(menu, this);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
bool CBaseObject::IntersectRayMesh(const Vec3& raySrc, const Vec3& rayDir, SRayHitInfo& outHitInfo) const
|
|
{
|
|
const float fRenderMeshTestDistance = 0.2f;
|
|
IRenderNode* pRenderNode = GetEngineNode();
|
|
if (!pRenderNode)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
Matrix34 worldTM;
|
|
IStatObj* pStatObj = pRenderNode->GetEntityStatObj(0, 0, &worldTM);
|
|
if (!pStatObj)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// transform decal into object space
|
|
Matrix34 worldTM_Inverted = worldTM.GetInverted();
|
|
Matrix33 worldRot(worldTM_Inverted);
|
|
worldRot.Transpose();
|
|
// put hit direction into the object space
|
|
Vec3 vRayDir = rayDir.GetNormalized() * worldRot;
|
|
// put hit position into the object space
|
|
Vec3 vHitPos = worldTM_Inverted.TransformPoint(raySrc);
|
|
Vec3 vLineP1 = vHitPos - vRayDir * fRenderMeshTestDistance;
|
|
|
|
memset(&outHitInfo, 0, sizeof(outHitInfo));
|
|
outHitInfo.inReferencePoint = vHitPos;
|
|
outHitInfo.inRay.origin = vLineP1;
|
|
outHitInfo.inRay.direction = vRayDir;
|
|
outHitInfo.bInFirstHit = false;
|
|
outHitInfo.bUseCache = false;
|
|
|
|
return pStatObj->RayIntersection(outHitInfo, nullptr);
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
EScaleWarningLevel CBaseObject::GetScaleWarningLevel() const
|
|
{
|
|
EScaleWarningLevel scaleWarningLevel = eScaleWarningLevel_None;
|
|
|
|
const float kScaleThreshold = 0.001f;
|
|
|
|
if (fabs(m_scale.x - 1.0f) > kScaleThreshold
|
|
|| fabs(m_scale.y - 1.0f) > kScaleThreshold
|
|
|| fabs(m_scale.z - 1.0f) > kScaleThreshold)
|
|
{
|
|
if (fabs(m_scale.x - m_scale.y) < kScaleThreshold
|
|
&& fabs(m_scale.y - m_scale.z) < kScaleThreshold)
|
|
{
|
|
scaleWarningLevel = eScaleWarningLevel_Rescaled;
|
|
}
|
|
else
|
|
{
|
|
scaleWarningLevel = eScaleWarningLevel_RescaledNonUniform;
|
|
}
|
|
}
|
|
|
|
return scaleWarningLevel;
|
|
}
|
|
|
|
//////////////////////////////////////////////////////////////////////////
|
|
ERotationWarningLevel CBaseObject::GetRotationWarningLevel() const
|
|
{
|
|
ERotationWarningLevel rotationWarningLevel = eRotationWarningLevel_None;
|
|
|
|
const float kRotationThreshold = 0.01f;
|
|
|
|
Ang3 eulerRotation = Ang3(GetRotation());
|
|
|
|
if (fabs(eulerRotation.x) > kRotationThreshold
|
|
|| fabs(eulerRotation.y) > kRotationThreshold
|
|
|| fabs(eulerRotation.z) > kRotationThreshold)
|
|
{
|
|
const float xRotationMod = fabs(fmod(eulerRotation.x, gf_PI / 2.0f));
|
|
const float yRotationMod = fabs(fmod(eulerRotation.y, gf_PI / 2.0f));
|
|
const float zRotationMod = fabs(fmod(eulerRotation.z, gf_PI / 2.0f));
|
|
|
|
if ((xRotationMod < kRotationThreshold || xRotationMod > (gf_PI / 2.0f - kRotationThreshold))
|
|
&& (yRotationMod < kRotationThreshold || yRotationMod > (gf_PI / 2.0f - kRotationThreshold))
|
|
&& (zRotationMod < kRotationThreshold || zRotationMod > (gf_PI / 2.0f - kRotationThreshold)))
|
|
{
|
|
rotationWarningLevel = eRotationWarningLevel_Rotated;
|
|
}
|
|
else
|
|
{
|
|
rotationWarningLevel = eRotationWarningLevel_RotatedNonRectangular;
|
|
}
|
|
}
|
|
|
|
return rotationWarningLevel;
|
|
}
|
|
|
|
bool CBaseObject::IsSkipSelectionHelper() const
|
|
{
|
|
return false;
|
|
}
|
|
|
|
bool CBaseObject::CanBeHightlighted() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
Matrix33 CBaseObject::GetWorldRotTM() const
|
|
{
|
|
if (GetParent())
|
|
{
|
|
return GetParent()->GetWorldRotTM() * Matrix33(GetRotation());
|
|
}
|
|
return Matrix33(GetRotation());
|
|
}
|
|
|
|
Matrix33 CBaseObject::GetWorldScaleTM() const
|
|
{
|
|
if (GetParent())
|
|
{
|
|
return GetParent()->GetWorldScaleTM() * Matrix33::CreateScale(GetScale());
|
|
}
|
|
return Matrix33::CreateScale(GetScale());
|
|
}
|
|
|
|
void CBaseObject::SetTransformDelegate(ITransformDelegate* pTransformDelegate)
|
|
{
|
|
m_pTransformDelegate = pTransformDelegate;
|
|
InvalidateTM(0);
|
|
}
|
|
|
|
void CBaseObject::AddMember(CBaseObject* pMember, bool bKeepPos /*=true */)
|
|
{
|
|
AttachChild(pMember, bKeepPos);
|
|
}
|
|
|
|
void CBaseObject::OnBeforeAreaChange()
|
|
{
|
|
AABB aabb;
|
|
GetBoundBox(aabb);
|
|
GetIEditor()->GetGameEngine()->OnAreaModified(aabb);
|
|
}
|
|
|
|
#include <Objects/moc_BaseObject.cpp>
|