/* * All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or * its licensors. * * For complete copyright and license terms please see the LICENSE at the root of this * distribution (the "License"). All use of this software is governed by the License, * or, if provided, by the license below or the license accompanying this file. Do not * remove or modify any license notices. This file is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * */ // Original file Copyright Crytek GMBH or its affiliates, used under license. // Description : CBaseObject implementation. #include "StdAfx.h" #include "BaseObject.h" #include "CameraObject.h" #include "Group.h" #include "../Viewport.h" #include "../ObjectPanel.h" #include "../DisplaySettings.h" #include "QtViewPaneManager.h" #include "Undo/Undo.h" #include "Material/Material.h" #include "Material/MaterialManager.h" #include "GizmoManager.h" #include #include #include "Controls/RollupBar.h" #include "Objects/ObjectLayer.h" #include "Objects/ObjectLayerManager.h" #include "Include/IIconManager.h" // To use the Andrew's algorithm in order to make convex hull from the points, this header is needed. #include "Util/GeometryUtil.h" #include "SurfaceInfoPicker.h" #include "BrushObject.h" #include "PrefabObject.h" #include "ViewManager.h" #include "AssetResolver/AssetResolver.h" #include "IEditorImpl.h" #include "GameEngine.h" #include "AzCore/Math/MathUtils.h" #include #include #include #include #include namespace { QColor kLinkColorParent = QColor(0, 255, 255); QColor kLinkColorChild = QColor(0, 0, 255); QColor kLinkColorGray = QColor(128, 128, 128); } class CMaterialLayersMaskPanelUI; namespace { int s_rollupIndex = 0; CObjectPanel* s_objectPanel = 0; int s_varsIndex = 0; ReflectedPropertiesPanel* s_varsPanel = 0; CMaterialLayersMaskPanelUI* s_pMaterialLayersMaskPanel = NULL; int s_MaterialLayersMaskPanelId = 0; }; extern CObjectManager* g_pObjectManager; ////////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////// //! Undo object for CBaseObject. class CUndoBaseObject : public IUndoObject { public: CUndoBaseObject(CBaseObject* pObj, const char* undoDescription); protected: virtual int GetSize() { return sizeof(*this); } virtual QString GetDescription() { return m_undoDescription; }; virtual QString GetObjectName(); virtual void Undo(bool bUndo); virtual void Redo(); 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: virtual int GetSize() { return sizeof(*this); } virtual QString GetDescription() { return m_undoDescription; }; virtual QString GetObjectName(); virtual void Undo(bool bUndo); virtual void Redo(); 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) {} virtual void Undo(bool bUndo) override { if (m_bAttach) { Detach(); } else { Attach(); } } virtual void Redo() override { if (m_bAttach) { Attach(); } else { Detach(); } } private: void Attach() { CObjectManager* pObjectManager = static_cast(GetIEditor()->GetObjectManager()); CBaseObject* pObject = pObjectManager->FindObject(m_attachedObjectGUID); CBaseObject* pParentObject = pObjectManager->FindObject(m_parentObjectGUID); if (pObject && pParentObject) { if (qobject_cast(pParentObject)) { pParentObject->AddMember(pObject, m_bKeepPos); } else { pParentObject->AttachChild(pObject, m_bKeepPos); } pObject->UpdatePrefab(); } } void Detach() { CObjectManager* pObjectManager = static_cast(GetIEditor()->GetObjectManager()); CBaseObject* pObject = pObjectManager->FindObject(m_attachedObjectGUID); CBaseObject* pParentObject = pObjectManager->FindObject(m_parentObjectGUID); if (pObject) { CPrefabObject* pPrefab = pObject->GetPrefab(); pObject->DetachThis(m_bKeepPos); if (pPrefab) { pPrefab->AttachChild(pObject, m_bKeepPos); pObject->UpdatePrefab(); } } } virtual int GetSize() { return sizeof(CUndoAttachBaseObject); } virtual QString GetDescription() { 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 = 0; m_undo = XmlHelpers::CreateXmlNode("Undo"); CObjectArchive ar(GetIEditor()->GetObjectManager(), m_undo, false); ar.bUndo = true; obj->Serialize(ar); obj->SetLayerModified(); } ////////////////////////////////////////////////////////////////////////// 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; CObjectLayer* pLayer = pObject->GetLayer(); pObject->Serialize(ar); pObject->SetLayerModified(); if (pLayer && pLayer != pObject->GetLayer()) { pLayer->SetModified(); } if (bUndo) { pObject->UpdateGroup(); pObject->UpdatePrefab(); } 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; CObjectLayer* pLayer = pObject->GetLayer(); pObject->Serialize(ar); pObject->SetLayerModified(); if (pLayer && pLayer != pObject->GetLayer()) { pLayer->SetModified(); } pObject->UpdateGroup(); pObject->UpdatePrefab(); GetIEditor()->ResumeUndo(); using namespace AzToolsFramework; ComponentEntityObjectRequestBus::Event(pObject, &ComponentEntityObjectRequestBus::Events::UpdatePreemptiveUndoCache); } ////////////////////////////////////////////////////////////////////////// CUndoBaseObjectMinimal::CUndoBaseObjectMinimal(CBaseObject* pObj, const char* undoDescription, 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(); pObj->SetLayerModified(); } ////////////////////////////////////////////////////////////////////////// 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); pObject->SetLayerModified(); if (s_objectPanel && s_objectPanel->GetObject() == pObject) { CObjectPanel::SParams uip(pObject); s_objectPanel->SetParams(pObject, uip); } if (bUndo) { pObject->UpdateGroup(); } 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); pObject->SetLayerModified(); pObject->UpdateGroup(); if (s_objectPanel && s_objectPanel->GetObject() == pObject) { CObjectPanel::SParams uip(pObject); s_objectPanel->SetParams(pObject, uip); } 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); } ////////////////////////////////////////////////////////////////////////// // CBase implementation. ////////////////////////////////////////////////////////////////////////// void CBaseObject::DeleteUIPanels() { delete s_objectPanel; delete s_varsPanel; } ////////////////////////////////////////////////////////////////////////// class CMaterialLayersMaskPanelUI : public ReflectedPropertiesPanel { private: _smart_ptr m_pVarBlock; CSmartVariable mv_frozen; CSmartVariable mv_dynamic_frozen; CSmartVariable mv_wet; //CSmartVariable mv_burned; public: CMaterialLayersMaskPanelUI() { m_pVarBlock = new CVarBlock; mv_frozen = false; mv_dynamic_frozen = false; mv_wet = false; m_pVarBlock->AddVariable(mv_frozen, "Frozen"); m_pVarBlock->AddVariable(mv_dynamic_frozen, "Dynamic Frozen"); m_pVarBlock->AddVariable(mv_wet, "Wet"); } void SetObject(CBaseObject* pObject) { DeleteVars(); if (pObject) { uint32 nMtlLayersMask = pObject->GetMaterialLayersMask(); mv_frozen = nMtlLayersMask & MTL_LAYER_FROZEN; mv_dynamic_frozen = nMtlLayersMask & MTL_LAYER_DYNAMICFROZEN; mv_wet = nMtlLayersMask & MTL_LAYER_WET; } SetVarBlock(m_pVarBlock, functor(*this, &CMaterialLayersMaskPanelUI::OnVarChange)); } void ModifyFlag(uint32& nFlags, uint32 flag, CSmartVariable& var, IVariable* pVar) { if (var.GetVar() == pVar) { nFlags = (var) ? (nFlags | flag) : (nFlags & (~flag)); } } void OnVarChange(IVariable* pVar) { CSelectionGroup* selection = GetIEditor()->GetSelection(); for (int i = 0; i < selection->GetCount(); i++) { CBaseObject* pObj = selection->GetObject(i); uint32 nFlags = pObj->GetMaterialLayersMask(); ModifyFlag(nFlags, MTL_LAYER_FROZEN, mv_frozen, pVar); ModifyFlag(nFlags, MTL_LAYER_DYNAMICFROZEN, mv_dynamic_frozen, pVar); ModifyFlag(nFlags, MTL_LAYER_WET, mv_wet, pVar); pObj->SetMaterialLayersMask(nFlags); pObj->SetMaterial(pObj->GetMaterial()); // Force change of material. pObj->UpdateGroup(); pObj->UpdatePrefab(); } } }; ////////////////////////////////////////////////////////////////////////// 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*) NULL); 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_guidInPrefab(GUID_NULL) , m_floorNumber(-1) , m_layer(nullptr) , 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_numRefs(0) , m_parent(nullptr) , m_pMaterial(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(IEditor* ie, CBaseObject* prev, const QString& file) { SetFlags(m_flags & (~OBJFLAG_DELETED)); if (prev != 0) { // Same layer. SetLayer(prev->GetLayer()); SetUniqueName(prev->GetName()); SetLocalTM(prev->GetPos(), prev->GetRotation(), prev->GetScale()); SetArea(prev->GetArea()); SetColor(prev->GetColor()); m_nMaterialLayersMask = prev->m_nMaterialLayersMask; SetMaterial(prev->GetMaterial()); 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 = 0; } m_childs.clear(); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::Done() { CGroup* pGroup = GetGroup(); // From parent if (pGroup) { bool bSuspended(pGroup->SuspendUpdate()); pGroup->RemoveMember(this); if (bSuspended) { pGroup->ResumeUpdate(); } } else { DetachThis(); } // From children DetachAll(); SetLookAt(0); if (m_lookatSource) { m_lookatSource->SetLookAt(0); } SetFlags(m_flags | OBJFLAG_DELETED); NotifyListeners(CBaseObject::ON_DELETE); m_eventListeners.clear(); if (m_pMaterial) { m_pMaterial->Release(); m_pMaterial = NULL; } } ////////////////////////////////////////////////////////////////////////// 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(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); } float terrainElevation = AzFramework::Terrain::TerrainDataRequests::GetDefaultTerrainHeight(); AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainElevation , &AzFramework::Terrain::TerrainDataRequests::GetHeightFromFloats , pos.x, pos.y, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR, nullptr); m_height = pos.z - terrainElevation; 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) { if (!IsRotatable()) { return false; } 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 (!IsScalable()) { return false; } 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::UpdateUIVars() { if (s_varsPanel) { s_varsPanel->DeleteVars(); s_varsPanel->AddVars(GetVarBlock()); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::RefreshUIPanel() { if (s_varsPanel) { s_varsPanel->UpdateVarBlock(GetVarBlock()); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::BeginEditParams(IEditor* ie, int flags) { if (!s_objectPanel) { s_objectPanel = new CObjectPanel(); bool bCollapse = flags & OBJECT_COLLAPSE_OBJECTPANEL ? true : false; s_rollupIndex = GetIEditor()->AddRollUpPage(ROLLUP_OBJECTS, GetTypeName(), s_objectPanel, -1, !bCollapse); if (s_rollupIndex == 0) { delete s_objectPanel; s_objectPanel = 0; return; } } if (GetVarBlock()) { if (!s_varsPanel) { s_varsPanel = new ReflectedPropertiesPanel; s_varsPanel->Setup(true, 180); } else { s_varsPanel->DeleteVars(); } s_varsPanel->AddVars(GetVarBlock()); if (!s_varsIndex) { s_varsIndex = AddUIPage((GetTypeName() + " Params").toUtf8().data(), s_varsPanel); } if (s_varsPanel) { s_varsPanel->SetUpdateCallback(functor(*this, &CBaseObject::OnPropertyChanged)); } } assert(s_objectPanel != 0); if (s_objectPanel) { CObjectPanel::SParams uip(this); s_objectPanel->SetParams(this, uip); } SetFlags(OBJFLAG_EDITING); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::EndEditParams(IEditor* ie) { OnUIUpdate(); ClearFlags(OBJFLAG_EDITING); if (s_pMaterialLayersMaskPanel) { s_pMaterialLayersMaskPanel->ClearUpdateCallback(); } if (s_varsPanel) { s_varsPanel->ClearUpdateCallback(); } if (s_rollupIndex != 0) { GetIEditor()->RemoveRollUpPage(ROLLUP_OBJECTS, s_rollupIndex); } s_rollupIndex = 0; s_objectPanel = 0; if (s_varsIndex != 0) { s_varsPanel->DeleteVars(); RemoveUIPage(s_varsIndex); } if (s_pMaterialLayersMaskPanel) { s_pMaterialLayersMaskPanel->DeleteVars(); GetIEditor()->RemoveRollUpPage(ROLLUP_OBJECTS, s_MaterialLayersMaskPanelId); s_pMaterialLayersMaskPanel = 0; s_MaterialLayersMaskPanelId = 0; } s_varsIndex = 0; s_varsPanel = 0; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::BeginEditMultiSelParams(bool bAllOfSameType) { if (!s_objectPanel) { s_objectPanel = new CObjectPanel(); s_rollupIndex = AddUIPage(GetTypeName().toUtf8().data(), s_objectPanel); s_objectPanel->SetMultiSelect(true); } if (bAllOfSameType) { if (GetVarBlock()) { if (!s_varsPanel) { s_varsPanel = new ReflectedPropertiesPanel; s_varsPanel->Setup(true, 180); } else { s_varsPanel->DeleteVars(); } // Add all selected objects. CSelectionGroup* grp = GetIEditor()->GetSelection(); for (int i = 0; i < grp->GetCount(); i++) { CVarBlock* vb = grp->GetObject(i)->GetVarBlock(); if (vb) { s_varsPanel->AddVars(vb); } } if (!s_varsIndex) { s_varsIndex = AddUIPage((GetTypeName() + " Params").toUtf8().data(), s_varsPanel); } if (s_varsPanel) { s_varsPanel->SetUpdateCallback(functor(*this, &CBaseObject::OnMultiSelPropertyChanged)); } } } else { // Different typed objects. if (s_pMaterialLayersMaskPanel) { RemoveUIPage(s_MaterialLayersMaskPanelId); s_pMaterialLayersMaskPanel = 0; s_MaterialLayersMaskPanelId = 0; } } SetFlags(OBJFLAG_EDITING); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::EndEditMultiSelParams() { ClearFlags(OBJFLAG_EDITING); if (s_pMaterialLayersMaskPanel) { s_pMaterialLayersMaskPanel->ClearUpdateCallback(); } if (s_varsPanel) { s_varsPanel->ClearUpdateCallback(); } if (s_rollupIndex != 0) { RemoveUIPage(s_rollupIndex); } s_rollupIndex = 0; s_objectPanel = 0; if (s_varsIndex != 0) { s_varsPanel->DeleteVars(); RemoveUIPage(s_varsIndex); } s_varsIndex = 0; s_varsPanel = 0; if (s_pMaterialLayersMaskPanel) { s_pMaterialLayersMaskPanel->DeleteVars(); RemoveUIPage(s_MaterialLayersMaskPanelId); s_pMaterialLayersMaskPanel = 0; s_MaterialLayersMaskPanelId = 0; } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::OnUIUpdate() { if (s_objectPanel && !s_objectPanel->IsMultiSelect()) { CObjectPanel::SParams uip; s_objectPanel->GetParams(uip); CObjectLayer* pLayer = GetObjectManager()->GetLayersManager()->FindLayerByName(uip.layer); const bool objectChanged = (uip.area != m_flattenArea) || (uip.name != m_name) || (uip.color != m_color) || (pLayer != m_layer) || (uip.minSpec != GetMinSpec()); if (uip.area != m_flattenArea) { SetArea(uip.area); } if (uip.name != m_name) { // This may also change object id. GetObjectManager()->ChangeObjectName(this, uip.name); } if (uip.color != m_color) { ChangeColor(uip.color); } if (pLayer && pLayer != m_layer) { if (SupportsLayers()) { SetLayer(pLayer); } } if (uip.minSpec != GetMinSpec()) { StoreUndo("Change MinSpec", true); SetMinSpec(uip.minSpec); } if (objectChanged) { UpdatePrefab(); } } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::UpdateEditParams() { if (s_objectPanel && s_objectPanel->GetObject() == this) { // If its current object update its panel. CObjectPanel::SParams uip(this); s_objectPanel->SetParams(this, uip); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetModified(bool boModifiedTransformOnly) { ((CObjectManager*)GetObjectManager())->OnObjectModified(this, false, boModifiedTransformOnly); // Send signal to prefab if this object is part of any if (boModifiedTransformOnly) { UpdatePrefab(eOCOT_ModifyTransform); } else { UpdatePrefab(); } } 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) { bool prefabOpenedCheck = true; // If we are part of a prefab draw links only if the prefab is in opened state if (CheckFlags(OBJFLAG_PREFAB)) { prefabOpenedCheck = GetPrefab() && GetPrefab()->IsOpen(); } if (GetParent() && GetParent() != GetGroup() && prefabOpenedCheck) { dc.DrawLine(GetParentAttachPointWorldTM().GetTranslation(), wp, IsFrozen() ? kLinkColorGray : kLinkColorParent, IsFrozen() ? kLinkColorGray : kLinkColorChild); } int nChildCount = GetChildCount(); if (nChildCount && !qobject_cast(this)) { for (int i = 0; i < nChildCount; ++i) { const CBaseObject* pChild = GetChild(i); if (prefabOpenedCheck) { 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& dc, AABB* pMergedBoundBox) { if (HasMeasurementAxis() && GetIEditor()->GetDisplaySettings()->IsDisplayDimensionFigures()) { AABB localBoundBox; GetLocalBounds(localBoundBox); DrawDimensionsImpl(dc, localBoundBox, pMergedBoundBox); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::DrawDimensionsImpl(DisplayContext& dc, const AABB& localBoundBox, AABB* pMergedBoundBox) { AABB boundBox; Matrix34 rotatedTM; bool bHave2Axis(false); float xLength(0); float yLength(0); float zLength(0); if (pMergedBoundBox) { rotatedTM = Matrix34::CreateIdentity(); boundBox = *pMergedBoundBox; xLength = boundBox.max.x - boundBox.min.x; zLength = boundBox.max.z - boundBox.min.z; yLength = boundBox.max.y - boundBox.min.y; } else { rotatedTM = GetWorldRotTM(); Matrix34 scaledTranslatedTM = GetWorldScaleTM(); scaledTranslatedTM.SetTranslation(GetWorldPos()); boundBox.SetTransformedAABB(scaledTranslatedTM, localBoundBox); IVariable* pVarXLength(NULL); IVariable* pVarYLength(NULL); IVariable* pVarZLength(NULL); IVariable* pVarDimX(NULL); IVariable* pVarDimY(NULL); IVariable* pVarDimZ(NULL); CVarBlock* pVarBlock(GetVarBlock()); if (pVarBlock) { pVarXLength = pVarBlock->FindVariable("Width"); pVarYLength = pVarBlock->FindVariable("Length"); pVarZLength = pVarBlock->FindVariable("Height"); pVarDimX = pVarBlock->FindVariable("DimX"); pVarDimY = pVarBlock->FindVariable("DimY"); pVarDimZ = pVarBlock->FindVariable("DimZ"); } xLength = boundBox.max.x - boundBox.min.x; zLength = boundBox.max.z - boundBox.min.z; yLength = boundBox.max.y - boundBox.min.y; if (pVarDimX && pVarDimY && pVarDimZ) { pVarDimX->Get(xLength); pVarDimZ->Get(zLength); pVarDimY->Get(yLength); xLength *= m_scale.x; zLength *= m_scale.z; yLength *= m_scale.y; } else if (pVarXLength && pVarYLength && pVarZLength) { // A case of an area box. pVarXLength->Get(xLength); pVarZLength->Get(zLength); pVarYLength->Get(yLength); xLength *= m_scale.x; zLength *= m_scale.z; yLength *= m_scale.y; } else if (!pVarXLength && !pVarYLength && pVarZLength) { // A case of an area shape. pVarZLength->Get(zLength); zLength *= m_scale.z; } } const float kMinimumLimitation(0.4f); if (xLength < kMinimumLimitation && yLength < kMinimumLimitation && zLength < kMinimumLimitation) { return; } const float kEpsilon(0.001f); bHave2Axis = GetType() == OBJTYPE_DECAL || fabs(zLength) < kEpsilon; Vec3 basePoints[] = { Vec3(boundBox.min.x, boundBox.min.y, boundBox.min.z), Vec3(boundBox.min.x, boundBox.max.y, boundBox.min.z), Vec3(boundBox.max.x, boundBox.max.y, boundBox.min.z), Vec3(boundBox.max.x, boundBox.min.y, boundBox.min.z), Vec3(boundBox.min.x, boundBox.min.y, boundBox.max.z), Vec3(boundBox.min.x, boundBox.max.y, boundBox.max.z), Vec3(boundBox.max.x, boundBox.max.y, boundBox.max.z), Vec3(boundBox.max.x, boundBox.min.y, boundBox.max.z) }; const int kElementSize(sizeof(basePoints) / sizeof(*basePoints)); Vec3 axisDirections[kElementSize] = { Vec3(1, 1, 1), Vec3(1, -1, 1), Vec3(-1, -1, 1), Vec3(-1, 1, 1), Vec3(1, 1, -1), Vec3(1, -1, -1), Vec3(-1, -1, -1), Vec3(-1, 1, -1) }; int nLoopCount = bHave2Axis ? (kElementSize / 2) : kElementSize; if (bHave2Axis) { for (int i = 0; i < nLoopCount; ++i) { basePoints[i].z = 0.5f * (boundBox.min.z + boundBox.max.z); } } // Find out the nearest base point of a bounding box from a camera position and use it as a pivot. const CCamera& camera = gEnv->pRenderer->GetCamera(); Vec3 cameraPos(camera.GetPosition()); Vec3 pivot(rotatedTM.TransformVector(basePoints[0] - GetWorldPos()) + GetWorldPos()); float fNearestDist = (cameraPos - pivot).GetLength(); int nNearestAxisIndex(0); bool bPrevVisible(camera.IsPointVisible(pivot)); for (int i = 1; i < nLoopCount; ++i) { Vec3 candidatePivot(rotatedTM.TransformVector(basePoints[i] - GetWorldPos()) + GetWorldPos()); float candidateLength = (candidatePivot - cameraPos).GetLength(); bool bVisible = camera.IsPointVisible(candidatePivot); if (bVisible) { if (!bPrevVisible || candidateLength < fNearestDist) { fNearestDist = candidateLength; pivot = candidatePivot; nNearestAxisIndex = i; } bPrevVisible = bVisible; } } float fScale = dc.view->GetScreenScaleFactor(pivot); float fArrowScale = fScale * 0.04f; Vec3 vX(xLength, 0, 0); Vec3 vY(0, yLength, 0); Vec3 vZ(0, 0, zLength); vX = vX * axisDirections[nNearestAxisIndex].x; vY = vY * axisDirections[nNearestAxisIndex].y; vZ = vZ * axisDirections[nNearestAxisIndex].z; vX = rotatedTM.TransformVector(vX); vY = rotatedTM.TransformVector(vY); vZ = rotatedTM.TransformVector(vZ); const float kArrowPivotOffset = 0.1f; pivot = pivot + (-(vX + vY + vZ)).GetNormalized() * kArrowPivotOffset; Vec3 centerPt(boundBox.GetCenter()); // Display texts of width, height and depth float fTextScale(1.3f); dc.SetColor(QColor(200, 200, 200)); QString str; const float kBrightness(0.35f); const ColorF kXColor(1.0f, kBrightness, kBrightness, 0.9f); const ColorF kYColor(kBrightness, 1.0f, kBrightness, 0.9f); const ColorF kZColor(kBrightness, kBrightness, 1.0f, 0.9f); const ColorF TextBoxColor(0, 0, 0, 0.75f); ColorB backupcolor = dc.GetColor(); uint32 backupstate = dc.GetState(); int backupThickness = dc.GetLineWidth(); dc.SetState(backupstate | e_DepthTestOff); Vec3 vNX = vX.GetNormalized(); Vec3 vNY = vY.GetNormalized(); Vec3 vNZ = vZ.GetNormalized(); const float kMinimumOffset(0.20f); const float kMaximumOffset(30.0f); float fMaximumOffset[3] = { kMaximumOffset, kMaximumOffset, kMaximumOffset }; if (xLength > kMaximumOffset * 0.5f) { fMaximumOffset[0] = xLength * 3.0f; } if (yLength > kMaximumOffset * 0.5f) { fMaximumOffset[1] = yLength * 3.0f; } if (zLength > kMaximumOffset * 0.5f) { fMaximumOffset[2] = zLength * 3.0f; } Vec3 textPos[3] = {pivot, pivot, pivot}; Vec3 textMinPos[3] = { pivot + vNX * kMinimumOffset, pivot + vNY * kMinimumOffset, pivot + vNZ * kMinimumOffset }; Vec3 textCenterPos[3] = { pivot + vX * 0.5f, pivot + vY * 0.5f, pivot + vZ * 0.5f }; Vec3 textMaxPos[3] = { pivot + vNX * fMaximumOffset[0], pivot + vNY * fMaximumOffset[1], pivot + vNZ * fMaximumOffset[2] }; const Vec3& cameraDir(camera.GetViewdir()); for (int i = 0; i < 3; ++i) { Vec3 d = (textMaxPos[i] - cameraPos).GetNormalized(); float fCameraDir = d.Dot(cameraDir); if (fCameraDir < 0) { fCameraDir = 0; } textPos[i] = textMinPos[i] + (textCenterPos[i] - textPos[i]) * fCameraDir; } SRayHitInfo hitInfo; CSurfaceInfoPicker picker; bool bVisiableText[3] = { true, true, true }; for (int i = 0; i < 3; ++i) { if (picker.PickObject(cameraPos, textPos[i] - cameraPos, hitInfo, this)) { if (hitInfo.fDistance < cameraPos.GetDistance(textPos[i])) { bVisiableText[i] = false; } } } if (bVisiableText[0]) { str = QString::number(xLength, 'f', 3); DrawTextOn2DBox(dc, textPos[0], str.toUtf8().data(), fTextScale, kXColor, TextBoxColor); } if (!bHave2Axis) { if (bVisiableText[2]) { str = QString::number(zLength, 'f', 3); DrawTextOn2DBox(dc, textPos[2], str.toUtf8().data(), fTextScale, kZColor, TextBoxColor); } } if (bVisiableText[1]) { str = QString::number(yLength, 'f', 3); DrawTextOn2DBox(dc, textPos[1], str.toUtf8().data(), fTextScale, kYColor, TextBoxColor); } dc.SetState(backupstate | e_DepthTestOn); dc.SetLineWidth(4); // Draw arrows of each axis. dc.SetColor(kXColor); dc.DrawArrow(pivot, pivot + vX, fArrowScale, true); if (!bHave2Axis) { dc.SetColor(kZColor); dc.DrawArrow(pivot, pivot + vZ, fArrowScale, true); } dc.SetColor(kYColor); dc.DrawArrow(pivot, pivot + vY, fArrowScale, true); dc.SetState(backupstate); dc.SetColor(backupcolor); dc.SetLineWidth(backupThickness); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::DrawTextOn2DBox(DisplayContext& dc, const Vec3& pos, const char* text, float textScale, const ColorF& TextColor, const ColorF& TextBackColor) { Vec3 worldPos = dc.ToWorldSpacePosition(pos); int vx, vy, vw, vh; gEnv->pRenderer->GetViewport(&vx, &vy, &vw, &vh); const CCamera& camera = gEnv->pRenderer->GetCamera(); Vec3 screenPos; camera.Project(worldPos, screenPos, Vec2i(0, 0), Vec2i(0, 0)); //! Font size information doesn't seem to exist so the proper size is used int textlen = strlen(text); float fontsize = 7.5f; float textwidth = fontsize * textlen; float textheight = 16.0f; screenPos.x = screenPos.x - textwidth * 0.5f; Vec3 textregion[4] = { Vec3(screenPos.x, screenPos.y, screenPos.z), Vec3(screenPos.x + textwidth, screenPos.y, screenPos.z), Vec3(screenPos.x + textwidth, screenPos.y + textheight, screenPos.z), Vec3(screenPos.x, screenPos.y + textheight, screenPos.z) }; Vec3 textworldreign[4]; Matrix34 dcInvTm = dc.GetMatrix().GetInverted(); Matrix44A mProj, mView; mathMatrixPerspectiveFov(&mProj, camera.GetFov(), camera.GetProjRatio(), camera.GetNearPlane(), camera.GetFarPlane()); mathMatrixLookAt(&mView, camera.GetPosition(), camera.GetPosition() + camera.GetViewdir(), Vec3(0, 0, 1)); Matrix44A mInvViewProj = (mView * mProj).GetInverted(); for (int i = 0; i < 4; ++i) { Vec4 projectedpos = Vec4((textregion[i].x - vx) / vw * 2.0f - 1.0f, -((textregion[i].y - vy) / vh) * 2.0f + 1.0f, textregion[i].z, 1.0f); Vec4 wp = projectedpos * mInvViewProj; wp.x /= wp.w; wp.y /= wp.w; wp.z /= wp.w; textworldreign[i] = dcInvTm.TransformPoint(Vec3(wp.x, wp.y, wp.z)); } ColorB backupcolor = dc.GetColor(); uint32 backupstate = dc.GetState(); dc.SetColor(TextBackColor); dc.SetDrawInFrontMode(true); dc.DrawQuad(textworldreign[3], textworldreign[2], textworldreign[1], textworldreign[0]); dc.SetColor(TextColor); dc.DrawTextLabel(pos, textScale, text); dc.SetDrawInFrontMode(false); dc.SetColor(backupcolor); dc.SetState(backupstate); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::DrawSelectionHelper(DisplayContext& dc, const Vec3& pos, const QColor& labelColor, 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, const Vec3& pos, 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; // Check if our group is not closed. if (!GetGroup() || GetGroup()->IsOpen()) { 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(labelColor.redF(), labelColor.greenF(), labelColor.redF()); if (IsSelected()) { c = Vec3(dc.GetSelectedColor().redF(), dc.GetSelectedColor().greenF(), dc.GetSelectedColor().blueF()); } float col[4] = { c.x, c.y, c.z, 1 }; if (dc.flags & DISPLAY_SELECTION_HELPERS) { if (IsHighlighted()) { c = Vec3(dc.GetSelectedColor().redF(), dc.GetSelectedColor().greenF(), 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(DisplayContext& dc, const Vec3& pos, 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(AZ::Debug::ProfileCategory::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) { double angle = 1; if (view->GetViewManager()->GetGrid()->IsAngleSnapEnabled()) { angle = view->GetViewManager()->GetGrid()->GetAngleSnap(); } Quat rot = GetRotation(); rot.SetRotationXYZ(Ang3(0, 0, rot.GetRotZ() + DEG2RAD(flags > 0 ? angle * (-1) : angle))); SetRotation(rot); } return MOUSECREATE_CONTINUE; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::OnEvent(ObjectEvent event) { switch (event) { case EVENT_KEEP_HEIGHT: { float h = m_height; float newz = GetIEditor()->GetTerrainElevation(m_pos.x, m_pos.y) + m_height; SetPos(Vec3(m_pos.x, m_pos.y, newz)); m_height = h; } break; case EVENT_CONFIG_SPEC_CHANGE: UpdateVisibility(!IsHidden()); break; } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetShared(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 > gSettings.editorConfigSpec); } ////////////////////////////////////////////////////////////////////////// //! Returns true if object hidden. bool CBaseObject::IsHidden() const { if (m_objType == OBJTYPE_DUMMY) { return false; } #ifndef AZ_TESTS_ENABLED return (CheckFlags(OBJFLAG_HIDDEN)) || ((m_layer && !m_layer->IsVisible())) || (gSettings.objectHideMask & GetType()); #else return CheckFlags(OBJFLAG_HIDDEN); #endif } ////////////////////////////////////////////////////////////////////////// //! Returns true if object frozen. bool CBaseObject::IsFrozen() const { return CheckFlags(OBJFLAG_FROZEN) || (m_layer && m_layer->IsFrozen()); } ////////////////////////////////////////////////////////////////////////// bool CBaseObject::IsSelectable() const { if (m_objType == OBJTYPE_DUMMY) { return true; } // Not selectable if frozen. if (IsFrozen()) { return false; } // Not selectable if in closed group. CGroup* group = GetGroup(); if (group) { if (!group->IsOpen()) { return false; } } return 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; CObjectLayer* pLayer = 0; // Find layer name. GUID layerGUID; QString layerName; if (xmlNode->getAttr("LayerGUID", layerGUID)) { pLayer = GetObjectManager()->GetLayersManager()->FindLayer(layerGUID); } else if (xmlNode->getAttr("Layer", layerName)) { pLayer = GetObjectManager()->GetLayersManager()->FindLayerByName(layerName); } 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 idInPrefab = 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("IdInPrefab", idInPrefab); 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); } if (idInPrefab != GUID_NULL) { SetIdInPrefab(idInPrefab); } SetColor(color); SetArea(flattenArea); SetFrozen(bFrozen); SetHidden(bHidden); if (pLayer) { SetLayer(pLayer); } ////////////////////////////////////////////////////////////////////////// // Load material. ////////////////////////////////////////////////////////////////////////// SetMaterial(mtlName); ar.SetResolveCallback(this, parentId, functor(*this, &CBaseObject::ResolveParent)); ar.SetResolveCallback(this, lookatId, functor(*this, &CBaseObject::SetLookAt)); InvalidateTM(0); SetModified(false); ////////////////////////////////////////////////////////////////////////// if (ar.bUndo) { // If we are selected update UI Panel. UpdateEditParams(); 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. const bool isPartOfPrefab = IsPartOfPrefab(); // This attributed only readed by ObjectManager. xmlNode->setAttr("Type", GetTypeName().toUtf8().data()); if (m_layer) { xmlNode->setAttr("Layer", m_layer->GetName().toUtf8().data()); xmlNode->setAttr("LayerGUID", m_layer->GetGUID()); } xmlNode->setAttr("Id", m_guid); if (isPartOfPrefab && !ar.IsSavingInPrefab()) { xmlNode->setAttr("IdInPrefab", m_guidInPrefab); } 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 (isPartOfPrefab || !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_pMaterial) { xmlNode->setAttr("Material", GetMaterialName().toUtf8().data()); } 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(const QString& levelPath, XmlNodeRef& xmlNode) { XmlNodeRef objNode = xmlNode->newChild("Object"); objNode->setAttr("Type", GetTypeName().toUtf8().data()); objNode->setAttr("Name", GetName().toUtf8().data()); if (m_pMaterial) { objNode->setAttr("Material", m_pMaterial->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; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetLayer(CObjectLayer* layer) { if (layer == m_layer && !m_childs.size()) { return; } assert(layer != 0); StoreUndo("Set Layer"); m_layer = layer; UpdateVisibility(layer->IsVisible()); // Set layer for all childs. for (int i = 0; i < m_childs.size(); i++) { m_childs[i]->SetLayer(layer); } // If object have target, target must also go into this layer. if (m_lookat) { m_lookat->SetLayer(layer); } OnLayerChanged(layer); } ////////////////////////////////////////////////////////////////////////// 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 { typedef std::pair Edge2D; } 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(hc.rect.left(), hc.rect.top()), Vec2(hc.rect.right(), hc.rect.top())), Edge2D(Vec2(hc.rect.right(), hc.rect.top()), Vec2(hc.rect.right(), hc.rect.bottom())), Edge2D(Vec2(hc.rect.right(), hc.rect.bottom()), Vec2(hc.rect.left(), hc.rect.bottom())), Edge2D(Vec2(hc.rect.left(), hc.rect.bottom()), Vec2(hc.rect.left(), 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 pointsForRegion1; pointsForRegion1.reserve(kMaxSizeOfEdgeList1); for (int i = 0; i < kMaxSizeOfEdgeList1; ++i) { pointsForRegion1.push_back(Vec3(obb_p[i].x(), obb_p[i].y(), 0)); } std::vector convexHullForRegion1; ConvexHull2D(convexHullForRegion1, pointsForRegion1); nEdgeList1Count = 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(AZ::Debug::ProfileCategory::Entity); 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(AZ::Debug::ProfileCategory::Entity); 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 *= OBJECT_TEXTURE_ICON_SCALE / fScreenScale; 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 >= 0 && 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::GetAllChildren(TBaseObjects& outAllChildren, CBaseObject* pObj) const { const CBaseObject* pBaseObj = pObj ? pObj : this; for (int i = 0, iChildCount(pBaseObj->GetChildCount()); i < iChildCount; ++i) { CBaseObject* pChild = pBaseObj->GetChild(i); if (pChild == NULL) { continue; } outAllChildren.push_back(pChild); GetAllChildren(outAllChildren, pChild); } } void CBaseObject::GetAllChildren(DynArray< _smart_ptr >& outAllChildren, CBaseObject* pObj) const { const CBaseObject* pBaseObj = pObj ? pObj : this; for (int i = 0, iChildCount(pBaseObj->GetChildCount()); i < iChildCount; ++i) { CBaseObject* pChild = pBaseObj->GetChild(i); if (pChild == NULL) { continue; } outAllChildren.push_back(pChild); GetAllChildren(outAllChildren, pChild); } } void CBaseObject::GetAllChildren(CSelectionGroup& outAllChildren, CBaseObject* pObj) const { const CBaseObject* pBaseObj = pObj ? pObj : this; for (int i = 0, iChildCount(pBaseObj->GetChildCount()); i < iChildCount; ++i) { CBaseObject* pChild = pBaseObj->GetChild(i); if (pChild == NULL) { continue; } outAllChildren.AddObject(pChild); GetAllChildren(outAllChildren, pChild); } } void CBaseObject::GetAllPrefabFlagedChildren(TBaseObjects& outAllChildren, CBaseObject* pObj) const { const CBaseObject* pBaseObj = pObj ? pObj : this; for (int i = 0, iChildCount(pBaseObj->GetChildCount()); i < iChildCount; ++i) { CBaseObject* pChild = pBaseObj->GetChild(i); if (pChild == NULL || !pChild->CheckFlags(OBJFLAG_PREFAB)) { continue; } outAllChildren.push_back(pChild); if (qobject_cast(pChild)) { continue; } GetAllPrefabFlagedChildren(outAllChildren, pChild); } } void CBaseObject::GetAllPrefabFlagedChildren(CSelectionGroup& outAllChildren, CBaseObject* pObj) const { const CBaseObject* pBaseObj = pObj ? pObj : this; for (int i = 0, iChildCount(pBaseObj->GetChildCount()); i < iChildCount; ++i) { CBaseObject* pChild = pBaseObj->GetChild(i); if (pChild == NULL || !pChild->CheckFlags(OBJFLAG_PREFAB)) { continue; } outAllChildren.AddObject(pChild); if (qobject_cast(pChild)) { continue; } GetAllPrefabFlagedChildren(outAllChildren, pChild); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::CloneChildren(CBaseObject* pFromObject) { if (pFromObject == NULL) { return; } for (int i = 0, nChildCount(pFromObject->GetChildCount()); i < nChildCount; ++i) { CBaseObject* pFromChildObject = pFromObject->GetChild(i); CBaseObject* pChildClone = GetObjectManager()->CloneObject(pFromChildObject); if (pChildClone == NULL) { 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(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. } OnAttachChild(child); { CScopedSuspendUndo suspendUndo; { if (bKeepPos) { child->SetWorldTM(childTM); } child->InvalidateTM(0); } m_pTransformDelegate = pTransformDelegate; child->m_pTransformDelegate = pChildTransformDelegate; static_cast(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(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); } } OnDetachThis(); { 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 = 0; parent->RemoveChild(this); if (bKeepPos) { // Keep old world space transformation. SetWorldTM(worldTM); } SetTransformDelegate(pTransformDelegate); static_cast(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) { bool bDerivedFromGroup = qobject_cast(parent); bool bSuspended(false); if (bDerivedFromGroup) { bSuspended = ((CGroup*)parent)->SuspendUpdate(); } parent->AddMember(this, false); if (bSuspended) { ((CGroup*)parent)->ResumeUpdate(); } } else { if (GetGroup()) { GetGroup()->RemoveMember(this); } 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(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] != 0 && 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) { bool bVisibleWithSpec = bVisible && !IsHiddenBySpec(); 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); #if ENABLE_CRY_PHYSICS IPhysicalEntity* pPhEn = GetCollisionEntity(); if (pPhEn && gEnv->pPhysicalWorld) { gEnv->pPhysicalWorld->DestroyPhysicalEntity(pPhEn, bVisibleWithSpec ? 2 : 1); } #endif // ENABLE_CRY_PHYSICS } } ////////////////////////////////////////////////////////////////////////// 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 != 0; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::AddEventListener(const EventCallback& cb) { if (find(m_eventListeners.begin(), m_eventListeners.end(), cb) == m_eventListeners.end()) { m_eventListeners.push_back(cb); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::RemoveEventListener(const EventCallback& cb) { std::vector::iterator cbFound = find(m_eventListeners.begin(), m_eventListeners.end(), cb); if (cbFound != m_eventListeners.end()) { (*cbFound) = EventCallback(); } } ////////////////////////////////////////////////////////////////////////// bool IsBaseObjectEventCallbackNULL(const CBaseObject::EventCallback& cb) { return cb.getFunc() == NULL; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::NotifyListeners(EObjectListenerEvent event) { for (auto it = m_eventListeners.begin(); it != m_eventListeners.end(); ++it) { // Call listener callback. if ((*it).getFunc() != NULL) { (*it)(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()); SetLayer(object->GetLayer()); SetColor(object->GetColor()); m_flattenArea = object->m_flattenArea; if (object->GetParent()) { object->GetParent()->AttachChild(this); } SetMaterial(object->GetMaterial()); return true; } ////////////////////////////////////////////////////////////////////////// bool CBaseObject::IsPotentiallyVisible() const { if (m_layer && !m_layer->IsVisible()) { return false; } if (CheckFlags(OBJFLAG_HIDDEN)) { return false; } if (gSettings.objectHideMask & GetType()) { return false; } CGroup* pGroup = GetGroup(); if (pGroup) { if (!pGroup->IsPotentiallyVisible()) { 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); } ////////////////////////////////////////////////////////////////////////// if (GetMaterial() != NULL && GetMaterial()->IsDummy()) { CErrorRecord err; err.error = QStringLiteral("Material: %1 for object: %2 not found,").arg(GetMaterial()->GetName(), GetName()); 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); } if (m_pMaterial) { m_pMaterial->GatherUsedResources(resources); } } ////////////////////////////////////////////////////////////////////////// bool CBaseObject::IsSimilarObject(CBaseObject* pObject) { if (pObject->GetClassDesc() == GetClassDesc() && pObject->metaObject() == metaObject()) { return true; } return false; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetMaterial(CMaterial* mtl) { if (m_pMaterial == mtl) { return; } StoreUndo("Assign Material"); if (m_pMaterial) { m_pMaterial->Release(); } m_pMaterial = mtl; if (m_pMaterial) { m_pMaterial->AddRef(); } UpdateEditParams(); OnMaterialChanged(MATERIALCHANGE_ALL); } ////////////////////////////////////////////////////////////////////////// QString CBaseObject::GetMaterialName() const { if (m_pMaterial) { return m_pMaterial->GetName(); } return ""; } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetMaterial(const QString& materialName) { GetIEditor()->GetMissingAssetResolver()->CancelRequest(functor(*this, &CBaseObject::OnMtlResolved)); GetIEditor()->GetMissingAssetResolver()->AddResolveRequest(materialName.toUtf8().data(), functor(*this, &CBaseObject::OnMtlResolved), IVariable::DT_MATERIAL); CMaterial* pMaterial = NULL; CMaterialManager* pManager = GetIEditor()->GetMaterialManager(); if (!materialName.isEmpty() && pManager != NULL) { pMaterial = pManager->LoadMaterial(materialName); } SetMaterial(pMaterial); } void CBaseObject::OnMtlResolved(uint32 id, bool success, const char* orgName, const char* newName) { if (success) { QString materialName = newName; CMaterial* pMaterial = NULL; if (!materialName.isEmpty()) { pMaterial = GetIEditor()->GetMaterialManager()->LoadMaterial(materialName); } SetMaterial(pMaterial); } } int CBaseObject::AddUIPage(const char* sCaption, QWidget* pDialog, int iIndex, bool bAutoExpand) { return GetIEditor()->AddRollUpPage(ROLLUP_OBJECTS, sCaption, pDialog, iIndex, bAutoExpand); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::RemoveUIPage(int iIndex) { GetIEditor()->RemoveRollUpPage(ROLLUP_OBJECTS, iIndex); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::RenameUIPage(int iIndex, const char* pNewName) { GetIEditor()->RenameRollUpPage(ROLLUP_OBJECTS, iIndex, pNewName); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetLayerModified() { if (m_layer) { m_layer->SetModified(); } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::SetMinSpec(uint32 nSpec, bool bSetChildren) { m_nMinSpec = nSpec; UpdateVisibility(!IsHidden()); // Set min spec for all childs. if (bSetChildren) { for (int i = m_childs.size() - 1; i >= 0; --i) { m_childs[i]->SetMinSpec(nSpec, true); } } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::OnPropertyChanged(IVariable*) { SetLayerModified(); UpdateGroup(); UpdatePrefab(); } ////////////////////////////////////////////////////////////////////////// void CBaseObject::OnMultiSelPropertyChanged(IVariable*) { CSelectionGroup* grp = GetIEditor()->GetSelection(); for (int i = 0; i < grp->GetCount(); i++) { grp->GetObject(i)->SetLayerModified(); ; } } ////////////////////////////////////////////////////////////////////////// void CBaseObject::OnMenuProperties() { if (!IsSelected()) { CUndo undo("Select Object"); GetIEditor()->GetObjectManager()->ClearSelection(); GetIEditor()->SelectObject(this); } GetIEditor()->GetEditTool()->EndEditParams(); GetIEditor()->SetEditTool("EditTool.ObjectMode"); BeginEditParams(GetIEditor(), OBJECT_EDIT); QtViewPaneManager::instance()->OpenPane(LyViewPane::LegacyRollupBar); GetIEditor()->SelectRollUpBar(ROLLUP_OBJECTS); } ////////////////////////////////////////////////////////////////////////// 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); QAction* action = menu->addAction(QObject::tr("Properties")); QObject::connect(action, &QAction::triggered, this, &CBaseObject::OnMenuProperties); static_cast(GetIEditor())->OnObjectContextMenuOpened(menu, this); //TODO: to be re-added when AssetBrowser is loading fast //menu->Add("Show in Asset Browser", functor(*this, &CBaseObject::OnMenuShowInAssetBrowser)).Enable(!resources.files.empty()); } ////////////////////////////////////////////////////////////////////////// bool CBaseObject::IntersectRayMesh(const Vec3& raySrc, const Vec3& rayDir, SRayHitInfo& outHitInfo) const { const float fRenderMeshTestDistance = 0.2f; IRenderNode* pRenderNode = GetEngineNode(); if (!pRenderNode) { return false; } int cntSlots = pRenderNode->GetSlotCount(); Matrix34A worldTM; IStatObj* pStatObj = pRenderNode->GetEntityStatObj(0, 0, &worldTM); if (!pStatObj) { return false; } // transform decal into object space Matrix34A 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, 0); } ////////////////////////////////////////////////////////////////////////// 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 { CEditTool* pEditTool(GetIEditor()->GetEditTool()); if (pEditTool && pEditTool->IsNeedToSkipPivotBoxForObjects()) { return true; } return false; } CGroup* CBaseObject::GetGroup() const { CBaseObject* pParentObject = GetParent(); while (pParentObject) { if (qobject_cast(pParentObject)) { return static_cast(pParentObject); } pParentObject = pParentObject->GetParent(); } return NULL; } CPrefabObject* CBaseObject::GetPrefab() const { CBaseObject* pParentObject = GetParent(); while (pParentObject) { if (qobject_cast(pParentObject)) { return static_cast(pParentObject); } pParentObject = pParentObject->GetParent(); } return nullptr; } bool CBaseObject::IsPartOfPrefab() const { return CheckFlags(OBJFLAG_PREFAB); } void CBaseObject::UpdateGroup() { CBaseObject* pParentObject = GetParent(); while (pParentObject) { if (qobject_cast(pParentObject)) { ((CGroup*)pParentObject)->Sync(); } pParentObject = pParentObject->GetParent(); } } void CBaseObject::UpdatePrefab(EObjectChangedOpType updateReason) { CPrefabObject* pPrefab = GetPrefab(); if (!pPrefab || pPrefab->IsModifyInProgress()) { return; } SObjectChangedContext context; context.m_modifiedObjectGlobalId = GetId(); context.m_modifiedObjectGuidInPrefab = GetIdInPrefab(); context.m_operation = updateReason; pPrefab->SyncPrefab(context); } bool CBaseObject::CanBeHightlighted() const { return !GetGroup() || GetGroup()->IsOpen(); } 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); if (CPrefabObject* pPrefab = GetPrefab()) { pPrefab->AddMember(pMember, bKeepPos); } } void CBaseObject::OnBeforeAreaChange() { AABB aabb; GetBoundBox(aabb); GetIEditor()->GetGameEngine()->OnAreaModified(aabb); } #include