/* * Copyright (c) Contributors to the Open 3D Engine Project. * For complete copyright and license terms please see the LICENSE at the root of this distribution. * * SPDX-License-Identifier: Apache-2.0 OR MIT * */ // Description : StaticObject implementation. #include "EditorDefs.h" #include "EntityObject.h" // AzToolsFramework #include // Editor #include "Settings.h" #include "Viewport.h" #include "LineGizmo.h" #include "Include/IObjectManager.h" #include "Objects/ObjectManager.h" #include "ViewManager.h" #include "AnimationContext.h" #include "HitContext.h" #include "Objects/SelectionGroup.h" static constexpr int VIEW_DISTANCE_MULTIPLIER_MAX = 100; ////////////////////////////////////////////////////////////////////////// //! Undo Entity Link class CUndoEntityLink : public IUndoObject { public: CUndoEntityLink(CSelectionGroup* pSelection) { int nObjectSize = pSelection->GetCount(); m_Links.reserve(nObjectSize); for (int i = 0; i < nObjectSize; ++i) { CBaseObject* pObj = pSelection->GetObject(i); if (qobject_cast(pObj)) { SLink link; link.entityID = pObj->GetId(); link.linkXmlNode = XmlHelpers::CreateXmlNode("undo"); ((CEntityObject*)pObj)->SaveLink(link.linkXmlNode); m_Links.push_back(link); } } } protected: void Release() override { delete this; }; int GetSize() override { return sizeof(*this); }; // Return size of xml state. QString GetObjectName() override{ return ""; }; void Undo([[maybe_unused]] bool bUndo) override { for (int i = 0, iLinkSize = static_cast(m_Links.size()); i < iLinkSize; ++i) { SLink& link = m_Links[i]; CBaseObject* pObj = GetIEditor()->GetObjectManager()->FindObject(link.entityID); if (pObj == nullptr) { continue; } if (!qobject_cast(pObj)) { continue; } CEntityObject* pEntity = (CEntityObject*)pObj; if (link.linkXmlNode->getChildCount() == 0) { continue; } pEntity->LoadLink(link.linkXmlNode->getChild(0)); } } void Redo() override{} private: struct SLink { GUID entityID; XmlNodeRef linkXmlNode; }; std::vector m_Links; }; ////////////////////////////////////////////////////////////////////////// //! Undo object for attach/detach changes class CUndoAttachEntity : public IUndoObject { public: CUndoAttachEntity(CEntityObject* pAttachedObject, bool bAttach) : m_attachedEntityGUID(pAttachedObject->GetId()) , m_attachmentTarget(pAttachedObject->GetAttachTarget()) , m_attachmentType(pAttachedObject->GetAttachType()) , m_bAttach(bAttach) {} void Undo([[maybe_unused]] bool bUndo) override { if (!m_bAttach) { SetAttachmentTypeAndTarget(); } } void Redo() override { if (m_bAttach) { SetAttachmentTypeAndTarget(); } } private: void SetAttachmentTypeAndTarget() { CObjectManager* pObjectManager = static_cast(GetIEditor()->GetObjectManager()); CEntityObject* pEntity = static_cast(pObjectManager->FindObject(m_attachedEntityGUID)); if (pEntity) { pEntity->SetAttachType(m_attachmentType); pEntity->SetAttachTarget(m_attachmentTarget.toUtf8().data()); } } int GetSize() override { return sizeof(CUndoAttachEntity); } GUID m_attachedEntityGUID; CEntityObject::EAttachmentType m_attachmentType; QString m_attachmentTarget; bool m_bAttach; }; ////////////////////////////////////////////////////////////////////////// // CBase implementation. ////////////////////////////////////////////////////////////////////////// namespace { CEntityObject* s_pPropertyPanelEntityObject = nullptr; // Prevent OnPropertyChange to be executed when loading many properties at one time. static bool s_ignorePropertiesUpdate = false; }; ////////////////////////////////////////////////////////////////////////// CEntityObject::CEntityObject() { m_bLoadFailed = false; m_box.min.Set(0, 0, 0); m_box.max.Set(0, 0, 0); m_proximityRadius = 0; m_innerRadius = 0; m_outerRadius = 0; m_boxSizeX = 1; m_boxSizeY = 1; m_boxSizeZ = 1; m_bProjectInAllDirs = false; m_bProjectorHasTexture = false; m_bDisplayBBox = true; m_bBBoxSelection = false; m_bDisplaySolidBBox = false; m_bDisplayAbsoluteRadius = false; m_bIconOnTop = false; m_bDisplayArrow = false; m_entityId = 0; m_bVisible = true; m_bCalcPhysics = true; m_bLight = false; m_bAreaLight = false; m_fAreaWidth = 1; m_fAreaHeight = 1; m_fAreaLightSize = 0.05f; m_bBoxProjectedCM = false; m_fBoxWidth = 1; m_fBoxHeight = 1; m_fBoxLength = 1; m_bEnableReload = true; m_lightColor = Vec3(1, 1, 1); SetColor(QColor(255, 255, 0)); // Init Variables. mv_castShadow = true; mv_outdoor = false; mv_recvWind = false; mv_renderNearest = false; mv_noDecals = false; mv_createdThroughPool = false; mv_obstructionMultiplier = 1.f; mv_obstructionMultiplier.SetLimits(0.f, 1.f, 0.01f); mv_hiddenInGame = false; mv_ratioLOD = 100; mv_viewDistanceMultiplier = 1.0f; mv_ratioLOD.SetLimits(0, 255); mv_viewDistanceMultiplier.SetLimits(0.0f, VIEW_DISTANCE_MULTIPLIER_MAX); m_physicsState = nullptr; m_attachmentType = eAT_Pivot; // cache all the variable callbacks, must match order of enum defined in header m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnAreaHeightChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnAreaLightChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnAreaLightSizeChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnAreaWidthChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxHeightChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxLengthChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxProjectionChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxSizeXChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxSizeYChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxSizeZChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnBoxWidthChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnColorChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnInnerRadiusChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnOuterRadiusChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnProjectInAllDirsChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnProjectorFOVChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnProjectorTextureChange(var); }); m_onSetCallbacksCache.emplace_back([this](IVariable* var) { OnRadiusChange(var); }); } CEntityObject::~CEntityObject() { } ////////////////////////////////////////////////////////////////////////// void CEntityObject::InitVariables() { mv_castShadow.SetFlags(mv_castShadow.GetFlags() | IVariable::UI_INVISIBLE); AddVariable(mv_outdoor, "OutdoorOnly", tr("Outdoor Only")); AddVariable(mv_castShadow, "CastShadow", tr("Cast Shadow")); AddVariable(mv_ratioLOD, "LodRatio"); AddVariable(mv_viewDistanceMultiplier, "ViewDistanceMultiplier"); AddVariable(mv_hiddenInGame, "HiddenInGame"); AddVariable(mv_recvWind, "RecvWind", tr("Receive Wind")); // [artemk]: Add RenderNearest entity param because of animator request. // This will cause that slot zero is rendered with ENTITY_SLOT_RENDER_NEAREST flag raised. AddVariable(mv_renderNearest, "RenderNearest"); mv_renderNearest.SetDescription("Used to eliminate z-buffer artifacts when rendering from first person view"); AddVariable(mv_noDecals, "NoStaticDecals"); AddVariable(mv_createdThroughPool, "CreatedThroughPool", tr("Created Through Pool")); AddVariable(mv_obstructionMultiplier, "ObstructionMultiplier", tr("Obstruction Multiplier")); } //////////////////////////////////////////////////////////////////////////& void CEntityObject::Done() { DeleteEntity(); ReleaseEventTargets(); RemoveAllEntityLinks(); CBaseObject::Done(); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::FreeGameData() { DeleteEntity(); } ////////////////////////////////////////////////////////////////////////// bool CEntityObject::Init(IEditor* pEditor, CBaseObject* pPrev, const QString& file) { CBaseObject::Init(pEditor, pPrev, file); if (pPrev) { CEntityObject* pPreviousEntity = ( CEntityObject* )pPrev; // Clone Properties. if (pPreviousEntity->m_pProperties) { m_pProperties = CloneProperties(pPreviousEntity->m_pProperties); } if (pPreviousEntity->m_pProperties2) { m_pProperties2 = CloneProperties(pPreviousEntity->m_pProperties2); } mv_createdThroughPool = pPreviousEntity->mv_createdThroughPool; } else if (!file.isEmpty()) { SetUniqueName(file); m_entityClass = file; } ResetCallbacks(); return true; } ////////////////////////////////////////////////////////////////////////// /*static*/ CEntityObject* CEntityObject::FindFromEntityId(const AZ::EntityId& id) { CEntityObject* retEntity = nullptr; AzToolsFramework::ComponentEntityEditorRequestBus::EventResult(retEntity, id, &AzToolsFramework::ComponentEntityEditorRequestBus::Events::GetSandboxObject); return retEntity; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::SetTransformDelegate(ITransformDelegate* pTransformDelegate) { CBaseObject::SetTransformDelegate(pTransformDelegate); if (this == s_pPropertyPanelEntityObject) { return; } s_ignorePropertiesUpdate = true; ForceVariableUpdate(); s_ignorePropertiesUpdate = false; ResetCallbacks(); } ////////////////////////////////////////////////////////////////////////// bool CEntityObject::ConvertFromObject(CBaseObject* object) { CBaseObject::ConvertFromObject(object); if (qobject_cast(object)) { CEntityObject* pObject = ( CEntityObject* )object; mv_outdoor = pObject->mv_outdoor; mv_ratioLOD = pObject->mv_ratioLOD; mv_viewDistanceMultiplier = pObject->mv_viewDistanceMultiplier; mv_hiddenInGame = pObject->mv_hiddenInGame; mv_recvWind = pObject->mv_recvWind; mv_renderNearest = pObject->mv_renderNearest; mv_noDecals = pObject->mv_noDecals; mv_createdThroughPool = pObject->mv_createdThroughPool; mv_obstructionMultiplier = pObject->mv_obstructionMultiplier; return true; } return false; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::GetLocalBounds(AABB& box) { box = m_box; } ////////////////////////////////////////////////////////////////////////// bool CEntityObject::HitTest(HitContext& hc) { if (!hc.b2DViewport) { // Test 3D viewport. } ////////////////////////////////////////////////////////////////////////// if ((m_bDisplayBBox && gSettings.viewports.bShowTriggerBounds) || hc.b2DViewport || (m_bDisplayBBox && m_bBBoxSelection)) { float hitEpsilon = hc.view->GetScreenScaleFactor(GetWorldPos()) * 0.01f; float hitDist; float fScale = GetScale().x; AABB boxScaled; boxScaled.min = m_box.min * fScale; boxScaled.max = m_box.max * fScale; Matrix34 invertWTM = GetWorldTM(); invertWTM.Invert(); Vec3 xformedRaySrc = invertWTM.TransformPoint(hc.raySrc); Vec3 xformedRayDir = invertWTM.TransformVector(hc.rayDir); xformedRayDir.Normalize(); { Vec3 intPnt; if (m_bBBoxSelection) { // Check intersection with bbox. if (Intersect::Ray_AABB(xformedRaySrc, xformedRayDir, boxScaled, intPnt)) { hc.dist = xformedRaySrc.GetDistance(intPnt); hc.object = this; return true; } } else { // Check intersection with bbox edges. if (Intersect::Ray_AABBEdge(xformedRaySrc, xformedRayDir, boxScaled, hitEpsilon, hitDist, intPnt)) { hc.dist = xformedRaySrc.GetDistance(intPnt); hc.object = this; return true; } } } } return false; } ////////////////////////////////////////////////////////////////////////// bool CEntityObject::HitTestRect(HitContext& hc) { bool bResult = CBaseObject::HitTestRect(hc); if (bResult) { hc.object = this; } return bResult; } ////////////////////////////////////////////////////////////////////////// IVariable* CEntityObject::FindVariableInSubBlock(CVarBlockPtr& properties, IVariable* pSubBlockVar, const char* pVarName) { IVariable* pVar = pSubBlockVar ? pSubBlockVar->FindVariable(pVarName) : properties->FindVariable(pVarName); return pVar; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::AdjustLightProperties(CVarBlockPtr& properties, const char* pSubBlock) { IVariable* pSubBlockVar = pSubBlock ? properties->FindVariable(pSubBlock) : nullptr; if (IVariable* pRadius = FindVariableInSubBlock(properties, pSubBlockVar, "Radius")) { // The value of 0.01 was found through asking Crysis 2 designer // team. pRadius->SetLimits(0.01f, 100.0f, 0.0f, true, false); } if (IVariable* pBoxSizeX = FindVariableInSubBlock(properties, pSubBlockVar, "BoxSizeX")) { pBoxSizeX->SetLimits(0.01f, 100.0f, 0.0f, true, false); } if (IVariable* pBoxSizeY = FindVariableInSubBlock(properties, pSubBlockVar, "BoxSizeY")) { pBoxSizeY->SetLimits(0.01f, 100.0f, 0.0f, true, false); } if (IVariable* pBoxSizeZ = FindVariableInSubBlock(properties, pSubBlockVar, "BoxSizeZ")) { pBoxSizeZ->SetLimits(0.01f, 100.0f, 0.0f, true, false); } if (IVariable* pProjectorFov = FindVariableInSubBlock(properties, pSubBlockVar, "fProjectorFov")) { pProjectorFov->SetLimits(0.01f, 180.0f, 0.0f, true, true); } if (IVariable* pPlaneWidth = FindVariableInSubBlock(properties, pSubBlockVar, "fPlaneWidth")) { pPlaneWidth->SetLimits(0.01f, 10.0f, 0.0f, true, false); pPlaneWidth->SetHumanName("SourceWidth"); } if (IVariable* pPlaneHeight = FindVariableInSubBlock(properties, pSubBlockVar, "fPlaneHeight")) { pPlaneHeight->SetLimits(0.01f, 10.0f, 0.0f, true, false); pPlaneHeight->SetHumanName("SourceDiameter"); } // For backwards compatibility with old lights (avoids changing settings in Lua which will break loading compatibility). // Todo: Change the Lua property names on the next big light refactor. if (IVariable* pAreaLight = FindVariableInSubBlock(properties, pSubBlockVar, "bAreaLight")) { pAreaLight->SetHumanName("PlanarLight"); } if (IVariable* pCastShadowVarLegacy = FindVariableInSubBlock(properties, pSubBlockVar, "bCastShadow")) { pCastShadowVarLegacy->SetFlags(pCastShadowVarLegacy->GetFlags() | IVariable::UI_INVISIBLE); const QString zeroPrefix("0"); if (!pCastShadowVarLegacy->GetDisplayValue().startsWith(zeroPrefix)) { pCastShadowVarLegacy->SetDisplayValue(zeroPrefix); } } if (IVariable* pFade = FindVariableInSubBlock(properties, pSubBlockVar, "vFadeDimensionsLeft")) { pFade->SetFlags(pFade->GetFlags() | IVariable::UI_INVISIBLE); } if (IVariable* pFade = FindVariableInSubBlock(properties, pSubBlockVar, "vFadeDimensionsRight")) { pFade->SetFlags(pFade->GetFlags() | IVariable::UI_INVISIBLE); } if (IVariable* pFade = FindVariableInSubBlock(properties, pSubBlockVar, "vFadeDimensionsNear")) { pFade->SetFlags(pFade->GetFlags() | IVariable::UI_INVISIBLE); } if (IVariable* pFade = FindVariableInSubBlock(properties, pSubBlockVar, "vFadeDimensionsFar")) { pFade->SetFlags(pFade->GetFlags() | IVariable::UI_INVISIBLE); } if (IVariable* pFade = FindVariableInSubBlock(properties, pSubBlockVar, "vFadeDimensionsTop")) { pFade->SetFlags(pFade->GetFlags() | IVariable::UI_INVISIBLE); } if (IVariable* pFade = FindVariableInSubBlock(properties, pSubBlockVar, "vFadeDimensionsBottom")) { pFade->SetFlags(pFade->GetFlags() | IVariable::UI_INVISIBLE); } if (IVariable* pSortPriority = FindVariableInSubBlock(properties, pSubBlockVar, "SortPriority")) { pSortPriority->SetLimits(0, 255, 1, true, true); } if (IVariable* pAttenFalloffMax = FindVariableInSubBlock(properties, pSubBlockVar, "fAttenuationFalloffMax")) { pAttenFalloffMax->SetLimits(0.0f, 1.0f, 1.0f / 255.0f, true, true); } if (IVariable* pVer = FindVariableInSubBlock(properties, pSubBlockVar, "_nVersion")) { int version = -1; pVer->Get(version); if (version == -1) { version++; pVer->Set(version); } } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::SetName(const QString& name) { if (name == GetName()) { return; } const QString oldName = GetName(); CBaseObject::SetName(name); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::SetSelected(bool bSelect) { CBaseObject::SetSelected(bSelect); if (bSelect) { UpdateLightProperty(); } } template struct IVariableType {}; template <> struct IVariableType { enum { value = IVariable::BOOL }; }; template <> struct IVariableType { enum { value = IVariable::INT }; }; template <> struct IVariableType { enum { value = IVariable::FLOAT }; }; template <> struct IVariableType { enum { value = IVariable::STRING }; }; template <> struct IVariableType { enum { value = IVariable::VECTOR }; }; void CEntityObject::DrawExtraLightInfo(DisplayContext& dc) { IObjectManager* objMan = GetIEditor()->GetObjectManager(); if (objMan) { if (objMan->IsLightClass(this) && GetProperties()) { QString csText(""); if (GetEntityPropertyBool("bAmbient")) { csText += "A"; } if (!GetEntityPropertyString("texture_Texture").isEmpty()) { csText += "P"; } int nLightType = GetEntityPropertyInteger("nCastShadows"); if (nLightType > 0) { csText += "S"; } float fScale = GetIEditor()->GetViewManager()->GetView(ET_ViewportUnknown)->GetScreenScaleFactor(GetWorldPos()); Vec3 vDrawPos(GetWorldPos()); vDrawPos.z += fScale / 25; ColorB col(255, 255, 255); dc.SetColor(col); dc.DrawTextLabel(vDrawPos, 1.3f, csText.toUtf8().data()); } } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::DrawProjectorPyramid(DisplayContext& dc, float dist) { const int numPoints = 16; // per one arc const int numArcs = 6; if (m_projectorFOV < FLT_EPSILON) { return; } Vec3 points[numPoints * numArcs]; { // generate 4 arcs on intersection of sphere with pyramid const float fov = DEG2RAD(m_projectorFOV); const Vec3 lightAxis(dist, 0.0f, 0.0f); const float tanA = tan(fov * 0.5f); const float fovProj = asinf(1.0f / sqrtf(2.0f + 1.0f / (tanA * tanA))) * 2.0f; const float halfFov = 0.5f * fov; const float halfFovProj = fovProj * 0.5f; const float anglePerSegmentOfFovProj = 1.0f / (numPoints - 1) * fovProj; const Quat yRot = Quat::CreateRotationY(halfFov); Vec3* arcPoints = points; for (int i = 0; i < numPoints; ++i) { float angle = i * anglePerSegmentOfFovProj - halfFovProj; arcPoints[i] = lightAxis * Quat::CreateRotationZ(angle) * yRot; } const Quat zRot = Quat::CreateRotationZ(halfFov); arcPoints += numPoints; for (int i = 0; i < numPoints; ++i) { float angle = (numPoints - i - 1) * anglePerSegmentOfFovProj - halfFovProj; arcPoints[i] = lightAxis * Quat::CreateRotationY(angle) * zRot; } const Quat nyRot = Quat::CreateRotationY(-halfFov); arcPoints += numPoints; for (int i = 0; i < numPoints; ++i) { float angle = (numPoints - i - 1) * anglePerSegmentOfFovProj - halfFovProj; arcPoints[i] = lightAxis * Quat::CreateRotationZ(angle) * nyRot; } const Quat nzRot = Quat::CreateRotationZ(-halfFov); arcPoints += numPoints; for (int i = 0; i < numPoints; ++i) { float angle = i * anglePerSegmentOfFovProj - halfFovProj; arcPoints[i] = lightAxis * Quat::CreateRotationY(angle) * nzRot; } // generate cross arcPoints += numPoints; const float anglePerSegmentOfFov = 1.0f / (numPoints - 1) * fov; for (int i = 0; i < numPoints; ++i) { float angle = i * anglePerSegmentOfFov - halfFov; arcPoints[i] = lightAxis * Quat::CreateRotationY(angle); } arcPoints += numPoints; for (int i = 0; i < numPoints; ++i) { float angle = i * anglePerSegmentOfFov - halfFov; arcPoints[i] = lightAxis * Quat::CreateRotationZ(angle); } } // draw pyramid and sphere intersection dc.DrawPolyLine(points, numPoints * 4, false); // draw cross dc.DrawPolyLine(points + numPoints * 4, numPoints, false); dc.DrawPolyLine(points + numPoints * 5, numPoints, false); Vec3 org(0.0f, 0.0f, 0.0f); dc.DrawLine(org, points[numPoints * 0]); dc.DrawLine(org, points[numPoints * 1]); dc.DrawLine(org, points[numPoints * 2]); dc.DrawLine(org, points[numPoints * 3]); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::DrawProjectorFrustum(DisplayContext& dc, Vec2 size, float dist) { static const Vec3 org(0.0f, 0.0f, 0.0f); const Vec3 corners[4] = { Vec3(dist, -size.x, -size.y), Vec3(dist, size.x, -size.y), Vec3(dist, -size.x, size.y), Vec3(dist, size.x, size.y) }; dc.DrawLine(org, corners[0]); dc.DrawLine(org, corners[1]); dc.DrawLine(org, corners[2]); dc.DrawLine(org, corners[3]); dc.DrawWireBox(Vec3(dist, -size.x, -size.y), Vec3(dist, size.x, size.y)); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::Serialize(CObjectArchive& ar) { CBaseObject::Serialize(ar); XmlNodeRef xmlNode = ar.node; if (ar.bLoading) { // Load QString entityClass = m_entityClass; m_bLoadFailed = false; xmlNode->getAttr("EntityClass", entityClass); m_physicsState = xmlNode->findChild("PhysicsState"); Vec3 angles; // Backward compatibility, with FarCry levels. if (xmlNode->getAttr("Angles", angles)) { angles = DEG2RAD(angles); angles.z += gf_PI / 2; Quat quat; quat.SetRotationXYZ(Ang3(angles)); SetRotation(quat); } // Load Event Targets. ReleaseEventTargets(); XmlNodeRef eventTargets = xmlNode->findChild("EventTargets"); if (eventTargets) { for (int i = 0; i < eventTargets->getChildCount(); i++) { XmlNodeRef eventTarget = eventTargets->getChild(i); CEntityEventTarget et; et.target = nullptr; GUID targetId = GUID_NULL; eventTarget->getAttr("TargetId", targetId); eventTarget->getAttr("Event", et.event); eventTarget->getAttr("SourceEvent", et.sourceEvent); m_eventTargets.emplace_back(AZStd::move(et)); if (targetId != GUID_NULL) { ar.SetResolveCallback( this, targetId, [this,i](CBaseObject* object) { ResolveEventTarget(object, i); }); } } } XmlNodeRef propsNode = xmlNode->findChild("Properties"); XmlNodeRef props2Node = xmlNode->findChild("Properties2"); QString attachmentType; xmlNode->getAttr("AttachmentType", attachmentType); if (attachmentType == "CharacterBone") { m_attachmentType = eAT_CharacterBone; } else { m_attachmentType = eAT_Pivot; } xmlNode->getAttr("AttachmentTarget", m_attachmentTarget); if (ar.bUndo) { RemoveAllEntityLinks(); PostLoad(ar); } } else { if (m_attachmentType != eAT_Pivot) { if (m_attachmentType == eAT_CharacterBone) { xmlNode->setAttr("AttachmentType", "CharacterBone"); } xmlNode->setAttr("AttachmentTarget", m_attachmentTarget.toUtf8().data()); } // Saving. if (!m_entityClass.isEmpty()) { xmlNode->setAttr("EntityClass", m_entityClass.toUtf8().data()); } if (m_physicsState) { xmlNode->addChild(m_physicsState); } //! Save properties. if (m_pProperties) { XmlNodeRef propsNode = xmlNode->newChild("Properties"); m_pProperties->Serialize(propsNode, ar.bLoading); } //! Save properties. if (m_pProperties2) { XmlNodeRef propsNode = xmlNode->newChild("Properties2"); m_pProperties2->Serialize(propsNode, ar.bLoading); } // Save Event Targets. if (!m_eventTargets.empty()) { XmlNodeRef eventTargets = xmlNode->newChild("EventTargets"); for (int i = 0; i < m_eventTargets.size(); i++) { CEntityEventTarget& et = m_eventTargets[i]; GUID targetId = GUID_NULL; if (et.target != nullptr) { targetId = et.target->GetId(); } XmlNodeRef eventTarget = eventTargets->newChild("EventTarget"); eventTarget->setAttr("TargetId", targetId); eventTarget->setAttr("Event", et.event.toUtf8().data()); eventTarget->setAttr("SourceEvent", et.sourceEvent.toUtf8().data()); } } // Save Entity Links. SaveLink(xmlNode); } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::PostLoad(CObjectArchive& ar) { ////////////////////////////////////////////////////////////////////////// // Load Links. XmlNodeRef linksNode = ar.node->findChild("EntityLinks"); LoadLink(linksNode, &ar); } ////////////////////////////////////////////////////////////////////////// XmlNodeRef CEntityObject::Export([[maybe_unused]] const QString& levelPath, XmlNodeRef& xmlExportNode) { if (m_bLoadFailed) { return nullptr; } // Do not export entity with bad id. if (!m_entityId) { return XmlHelpers::CreateXmlNode("Temp"); } // Export entities to entities.ini XmlNodeRef objNode = xmlExportNode->newChild("Entity"); objNode->setAttr("Name", GetName().toUtf8().data()); Vec3 pos = GetPos(), scale = GetScale(); Quat rotate = GetRotation(); if (GetParent()) { if (qobject_cast(GetParent())) { // Store parent entity id. CEntityObject* parentEntity = ( CEntityObject* )GetParent(); if (parentEntity) { objNode->setAttr("ParentId", parentEntity->GetEntityId()); if (m_attachmentType != eAT_Pivot) { if (m_attachmentType == eAT_CharacterBone) { objNode->setAttr("AttachmentType", "CharacterBone"); } objNode->setAttr("AttachmentTarget", m_attachmentTarget.toUtf8().data()); } } } else { // Export world coordinates. AffineParts ap; ap.SpectralDecompose(GetWorldTM()); pos = ap.pos; rotate = ap.rot; scale = ap.scale; } } if (!IsEquivalent(pos, Vec3(0, 0, 0), 0)) { objNode->setAttr("Pos", pos); } if (!rotate.IsIdentity()) { objNode->setAttr("Rotate", rotate); } if (!IsEquivalent(scale, Vec3(1, 1, 1), 0)) { objNode->setAttr("Scale", scale); } objNode->setTag("Entity"); objNode->setAttr("EntityClass", m_entityClass.toUtf8().data()); objNode->setAttr("EntityId", m_entityId); if (mv_ratioLOD != 100) { objNode->setAttr("LodRatio", ( int )mv_ratioLOD); } if (fabs(mv_viewDistanceMultiplier - 1.f) > FLT_EPSILON) { objNode->setAttr("ViewDistanceMultiplier", mv_viewDistanceMultiplier); } if (mv_recvWind) { objNode->setAttr("RecvWind", true); } if (mv_noDecals) { objNode->setAttr("NoDecals", true); } if (mv_outdoor) { objNode->setAttr("OutdoorOnly", true); } if (mv_hiddenInGame) { objNode->setAttr("HiddenInGame", true); } if (mv_createdThroughPool) { objNode->setAttr("CreatedThroughPool", true); } if (mv_obstructionMultiplier != 1.f) { objNode->setAttr("ObstructionMultiplier", (float)mv_obstructionMultiplier); } if (m_physicsState) { objNode->addChild(m_physicsState); } // Export Event Targets. if (!m_eventTargets.empty()) { XmlNodeRef eventTargets = objNode->newChild("EventTargets"); for (int i = 0; i < m_eventTargets.size(); i++) { CEntityEventTarget& et = m_eventTargets[i]; int entityId = 0; if (et.target) { if (qobject_cast(et.target)) { entityId = (( CEntityObject* )et.target)->GetEntityId(); } } XmlNodeRef eventTarget = eventTargets->newChild("EventTarget"); eventTarget->setAttr("Target", entityId); eventTarget->setAttr("Event", et.event.toUtf8().data()); eventTarget->setAttr("SourceEvent", et.sourceEvent.toUtf8().data()); } } // Save Entity Links. if (!m_links.empty()) { XmlNodeRef linksNode = objNode->newChild("EntityLinks"); for (size_t i = 0, num = m_links.size(); i < num; i++) { if (m_links[i].target) { XmlNodeRef linkNode = linksNode->newChild("Link"); linkNode->setAttr("TargetId", m_links[i].target->GetEntityId()); linkNode->setAttr("Name", m_links[i].name.toUtf8().data()); } } } //! Export properties. if (m_pProperties) { XmlNodeRef propsNode = objNode->newChild("Properties"); m_pProperties->Serialize(propsNode, false); } //! Export properties. if (m_pProperties2) { XmlNodeRef propsNode = objNode->newChild("Properties2"); m_pProperties2->Serialize(propsNode, false); } return objNode; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnEvent(ObjectEvent event) { CBaseObject::OnEvent(event); switch (event) { case EVENT_RELOAD_ENTITY: GetIEditor()->GetErrorReport()->SetCurrentValidatorObject(this); break; case EVENT_RELOAD_GEOM: GetIEditor()->GetErrorReport()->SetCurrentValidatorObject(this); break; case EVENT_FREE_GAME_DATA: FreeGameData(); break; case EVENT_CONFIG_SPEC_CHANGE: { break; } default: break; } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::UpdateVisibility(bool bVisible) { CBaseObject::UpdateVisibility(bVisible); bool bVisibleWithSpec = bVisible; if (bVisibleWithSpec != static_cast(m_bVisible)) { m_bVisible = bVisibleWithSpec; } size_t const numChildren = GetChildCount(); for (size_t i = 0; i < numChildren; ++i) { CBaseObject* const pChildObject = GetChild(i); pChildObject->SetHidden(!m_bVisible); if (qobject_cast(pChildObject)) { pChildObject->UpdateVisibility(m_bVisible); } } }; ////////////////////////////////////////////////////////////////////////// IVariable* CEntityObject::GetLightVariable(const char* name0) const { if (m_pProperties2) { IVariable* pLightProperties = m_pProperties2->FindVariable("LightProperties_Base"); if (pLightProperties) { for (int i = 0; i < pLightProperties->GetNumVariables(); ++i) { IVariable* pChild = pLightProperties->GetVariable(i); if (pChild == nullptr) { continue; } QString name(pChild->GetName()); if (name == name0) { return pChild; } } } } return m_pProperties ? m_pProperties->FindVariable(name0) : nullptr; } ////////////////////////////////////////////////////////////////////////// QString CEntityObject::GetLightAnimation() const { IVariable* pStyleGroup = GetLightVariable("Style"); if (pStyleGroup) { for (int i = 0; i < pStyleGroup->GetNumVariables(); ++i) { IVariable* pChild = pStyleGroup->GetVariable(i); if (pChild == nullptr) { continue; } QString name(pChild->GetName()); if (name == "lightanimation_LightAnimation") { QString lightAnimationName; pChild->Get(lightAnimationName); return lightAnimationName; } } } return ""; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::ResolveEventTarget(CBaseObject* object, unsigned int index) { // Find target id. assert(index < m_eventTargets.size()); if (object) { object->AddEventListener(this); } m_eventTargets[index].target = object; // Make line gizmo. if (!m_eventTargets[index].pLineGizmo && object) { CLineGizmo* pLineGizmo = new CLineGizmo; pLineGizmo->SetObjects(this, object); pLineGizmo->SetColor(Vec3(0.8f, 0.4f, 0.4f), Vec3(0.8f, 0.4f, 0.4f)); pLineGizmo->SetName(m_eventTargets[index].event.toUtf8().data()); AddGizmo(pLineGizmo); m_eventTargets[index].pLineGizmo = pLineGizmo; } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::RemoveAllEntityLinks() { while (!m_links.empty()) { RemoveEntityLink(static_cast(m_links.size() - 1)); } m_links.clear(); SetModified(false); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::ReleaseEventTargets() { while (!m_eventTargets.empty()) { RemoveEventTarget(static_cast(m_eventTargets.size() - 1), false); } m_eventTargets.clear(); SetModified(false); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::LoadLink(XmlNodeRef xmlNode, CObjectArchive* pArchive) { RemoveAllEntityLinks(); if (!xmlNode) { return; } QString name; GUID targetId; for (int i = 0; i < xmlNode->getChildCount(); i++) { XmlNodeRef linkNode = xmlNode->getChild(i); linkNode->getAttr("Name", name); if (linkNode->getAttr("TargetId", targetId)) { int version = 0; linkNode->getAttr("Version", version); GUID newTargetId = pArchive ? pArchive->ResolveID(targetId) : targetId; // Backwards compatibility with old bone attachment system const char kOldBoneLinkPrefix = '@'; if (version == 0 && !name.isEmpty() && name[0] == kOldBoneLinkPrefix) { CBaseObject* pObject = FindObject(newTargetId); if (qobject_cast(pObject)) { CEntityObject* pTargetEntity = static_cast(pObject); Quat relRot(IDENTITY); linkNode->getAttr("RelRot", relRot); Vec3 relPos(IDENTITY); linkNode->getAttr("RelPos", relPos); SetAttachType(eAT_CharacterBone); SetAttachTarget(name.mid(1).toUtf8().data()); pTargetEntity->AttachChild(this); SetPos(relPos); SetRotation(relRot); } } else { AddEntityLink(name, newTargetId); } } } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::SaveLink(XmlNodeRef xmlNode) { if (m_links.empty()) { return; } XmlNodeRef linksNode = xmlNode->newChild("EntityLinks"); for (size_t i = 0, num = m_links.size(); i < num; i++) { XmlNodeRef linkNode = linksNode->newChild("Link"); linkNode->setAttr("TargetId", m_links[i].targetId); linkNode->setAttr("Name", m_links[i].name.toUtf8().data()); linkNode->setAttr("Version", 1); } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnObjectEvent(CBaseObject* target, int event) { // When event target is deleted. if (event == CBaseObject::ON_DELETE) { // Find this target in events list and remove. int numTargets = static_cast(m_eventTargets.size()); for (int i = 0; i < numTargets; i++) { if (m_eventTargets[i].target == target) { RemoveEventTarget(i); numTargets = static_cast(m_eventTargets.size()); i--; } } } else if (event == CBaseObject::ON_PREDELETE) { int numTargets = static_cast(m_links.size()); for (int i = 0; i < numTargets; i++) { if (m_links[i].target == target) { RemoveEntityLink(i); numTargets = static_cast(m_eventTargets.size()); i--; } } } } ////////////////////////////////////////////////////////////////////////// int CEntityObject::AddEventTarget(CBaseObject* target, const QString& event, const QString& sourceEvent, [[maybe_unused]] bool bUpdateScript) { StoreUndo(); CEntityEventTarget et; et.target = target; et.event = event; et.sourceEvent = sourceEvent; // Assign event target. if (et.target) { et.target->AddEventListener(this); } if (target) { // Make line gizmo. CLineGizmo* pLineGizmo = new CLineGizmo; pLineGizmo->SetObjects(this, target); pLineGizmo->SetColor(Vec3(0.8f, 0.4f, 0.4f), Vec3(0.8f, 0.4f, 0.4f)); pLineGizmo->SetName(event.toUtf8().data()); AddGizmo(pLineGizmo); et.pLineGizmo = pLineGizmo; } m_eventTargets.push_back(et); SetModified(false); return static_cast(m_eventTargets.size() - 1); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::RemoveEventTarget(int index, [[maybe_unused]] bool bUpdateScript) { if (index >= 0 && index < m_eventTargets.size()) { StoreUndo(); if (m_eventTargets[index].pLineGizmo) { RemoveGizmo(m_eventTargets[index].pLineGizmo); } if (m_eventTargets[index].target) { m_eventTargets[index].target->RemoveEventListener(this); } m_eventTargets.erase(m_eventTargets.begin() + index); SetModified(false); } } ////////////////////////////////////////////////////////////////////////// int CEntityObject::AddEntityLink(const QString& name, GUID targetEntityId) { CEntityObject* target = nullptr; if (targetEntityId != GUID_NULL) { CBaseObject* pObject = FindObject(targetEntityId); if (qobject_cast(pObject)) { target = ( CEntityObject* )pObject; // Legacy entities and AZ entities shouldn't be linked. if (target->GetType() == OBJTYPE_AZENTITY || GetType() == OBJTYPE_AZENTITY) { return -1; } } } StoreUndo(); CLineGizmo* pLineGizmo = nullptr; // Assign event target. if (target) { target->AddEventListener(this); // Make line gizmo. pLineGizmo = new CLineGizmo; pLineGizmo->SetObjects(this, target); pLineGizmo->SetColor(Vec3(0.4f, 1.0f, 0.0f), Vec3(0.0f, 1.0f, 0.0f)); pLineGizmo->SetName(name.toUtf8().data()); AddGizmo(pLineGizmo); } CEntityLink lnk; lnk.targetId = targetEntityId; lnk.target = target; lnk.name = name; lnk.pLineGizmo = pLineGizmo; m_links.push_back(lnk); SetModified(false); return static_cast(m_links.size() - 1); } ////////////////////////////////////////////////////////////////////////// bool CEntityObject::EntityLinkExists(const QString& name, GUID targetEntityId) { for (size_t i = 0, num = m_links.size(); i < num; ++i) { if (m_links[i].targetId == targetEntityId && name.compare(m_links[i].name, Qt::CaseInsensitive) == 0) { return true; } } return false; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::RemoveEntityLink(int index) { if (index >= 0 && index < m_links.size()) { CEntityLink& link = m_links[index]; StoreUndo(); if (link.pLineGizmo) { RemoveGizmo(link.pLineGizmo); } if (link.target) { link.target->RemoveEventListener(this); link.target->EntityUnlinked(link.name, GetId()); } m_links.erase(m_links.begin() + index); SetModified(false); } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::RenameEntityLink(int index, const QString& newName) { if (index >= 0 && index < m_links.size()) { StoreUndo(); if (m_links[index].pLineGizmo) { m_links[index].pLineGizmo->SetName(newName.toUtf8().data()); } m_links[index].name = newName; SetModified(false); } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnRadiusChange(IVariable* var) { var->Get(m_proximityRadius); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnInnerRadiusChange(IVariable* var) { var->Get(m_innerRadius); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnOuterRadiusChange(IVariable* var) { var->Get(m_outerRadius); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxSizeXChange(IVariable* var) { var->Get(m_boxSizeX); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxSizeYChange(IVariable* var) { var->Get(m_boxSizeY); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxSizeZChange(IVariable* var) { var->Get(m_boxSizeZ); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnProjectorFOVChange(IVariable* var) { var->Get(m_projectorFOV); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnProjectInAllDirsChange(IVariable* var) { int value; var->Get(value); m_bProjectInAllDirs = value; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnProjectorTextureChange(IVariable* var) { QString texture; var->Get(texture); m_bProjectorHasTexture = !texture.isEmpty(); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnAreaLightChange(IVariable* var) { int value; var->Get(value); m_bAreaLight = value; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnAreaWidthChange(IVariable* var) { var->Get(m_fAreaWidth); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnAreaHeightChange(IVariable* var) { var->Get(m_fAreaHeight); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnAreaLightSizeChange(IVariable* var) { var->Get(m_fAreaLightSize); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnColorChange(IVariable* var) { var->Get(m_lightColor); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxProjectionChange(IVariable* var) { int value; var->Get(value); m_bBoxProjectedCM = value; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxWidthChange(IVariable* var) { var->Get(m_fBoxWidth); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxHeightChange(IVariable* var) { var->Get(m_fBoxHeight); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnBoxLengthChange(IVariable* var) { var->Get(m_fBoxLength); } ////////////////////////////////////////////////////////////////////////// CVarBlock* CEntityObject::CloneProperties(CVarBlock* srcProperties) { assert(srcProperties); return srcProperties->Clone(true); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::OnLoadFailed() { m_bLoadFailed = true; CErrorRecord err; err.error = tr("Entity %1 Failed to Spawn (Script: %2)").arg(GetName(), m_entityClass); err.pObject = this; GetIEditor()->GetErrorReport()->ReportError(err); } ////////////////////////////////////////////////////////////////////////// //! Analyze errors for this object. void CEntityObject::Validate(IErrorReport* report) { CBaseObject::Validate(report); if (!m_entityClass.isEmpty()) { CErrorRecord err; err.error = tr("Entity %1 Failed to Spawn (Script: %2)").arg(GetName(), m_entityClass); err.pObject = this; report->ReportError(err); return; } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::GatherUsedResources(CUsedResources& resources) { CBaseObject::GatherUsedResources(resources); if (m_pProperties) { m_pProperties->GatherUsedResources(resources); } if (m_pProperties2) { m_pProperties2->GatherUsedResources(resources); } } ////////////////////////////////////////////////////////////////////////// bool CEntityObject::IsSimilarObject(CBaseObject* pObject) { if (pObject->GetClassDesc() == GetClassDesc() && pObject->metaObject() == metaObject()) { CEntityObject* pEntity = ( CEntityObject* )pObject; if (m_entityClass == pEntity->m_entityClass && m_proximityRadius == pEntity->m_proximityRadius && m_innerRadius == pEntity->m_innerRadius && m_outerRadius == pEntity->m_outerRadius) { return true; } } return false; } ////////////////////////////////////////////////////////////////////////// void CEntityObject::PreInitLightProperty() { if (!IsLight() || !m_pProperties) { return; } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::UpdateLightProperty() { if (!IsLight() || !m_pProperties) { return; } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::ForceVariableUpdate() { if (m_pProperties) { m_pProperties->OnSetValues(); } if (m_pProperties2) { m_pProperties2->OnSetValues(); } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::ResetCallbacks() { ClearCallbacks(); CVarBlock* pProperties = m_pProperties; CVarBlock* pProperties2 = m_pProperties2; if (pProperties) { m_callbacks.reserve(6); //@FIXME Hack to display radii of properties. // wires properties from param block, to this entity internal variables. IVariable* var = nullptr; var = pProperties->FindVariable("Radius", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_proximityRadius); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnRadiusChange]); } else { var = pProperties->FindVariable("radius", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_proximityRadius); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnRadiusChange]); } } var = pProperties->FindVariable("InnerRadius", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_innerRadius); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnInnerRadiusChange]); } var = pProperties->FindVariable("OuterRadius", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_outerRadius); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnOuterRadiusChange]); } var = pProperties->FindVariable("BoxSizeX", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_boxSizeX); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxSizeXChange]); } var = pProperties->FindVariable("BoxSizeY", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_boxSizeY); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxSizeYChange]); } var = pProperties->FindVariable("BoxSizeZ", false); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_boxSizeZ); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxSizeZChange]); } var = pProperties->FindVariable("fAttenuationBulbSize"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_fAreaLightSize); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnAreaLightSizeChange]); } IVariable* pProjector = pProperties->FindVariable("Projector"); if (pProjector) { var = pProjector->FindVariable("fProjectorFov"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_projectorFOV); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnProjectorFOVChange]); } var = pProjector->FindVariable("bProjectInAllDirs"); if (var && var->GetType() == IVariable::BOOL) { int value; var->Get(value); m_bProjectInAllDirs = value; SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnProjectInAllDirsChange]); } var = pProjector->FindVariable("texture_Texture"); if (var && var->GetType() == IVariable::STRING) { QString projectorTexture; var->Get(projectorTexture); m_bProjectorHasTexture = !projectorTexture.isEmpty(); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnProjectorTextureChange]); } } IVariable* pColorGroup = pProperties->FindVariable("Color", false); if (pColorGroup) { const int nChildCount = pColorGroup->GetNumVariables(); for (int i = 0; i < nChildCount; ++i) { IVariable* pChild = pColorGroup->GetVariable(i); if (!pChild) { continue; } QString name(pChild->GetName()); if (name == "clrDiffuse") { pChild->Get(m_lightColor); SetVariableCallback(pChild, &m_onSetCallbacksCache[VariableCallbackIndex::OnColorChange]); break; } } } IVariable* pType = pProperties->FindVariable("Shape"); if (pType) { var = pType->FindVariable("bAreaLight"); if (var && var->GetType() == IVariable::BOOL) { int value; var->Get(value); m_bAreaLight = value; SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnAreaLightChange]); } var = pType->FindVariable("fPlaneWidth"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_fAreaWidth); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnAreaWidthChange]); } var = pType->FindVariable("fPlaneHeight"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_fAreaHeight); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnAreaHeightChange]); } } IVariable* pProjection = pProperties->FindVariable("Projection"); if (pProjection) { var = pProjection->FindVariable("bBoxProject"); if (var && var->GetType() == IVariable::BOOL) { int value; var->Get(value); m_bBoxProjectedCM = value; SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxProjectionChange]); } var = pProjection->FindVariable("fBoxWidth"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_fBoxWidth); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxWidthChange]); } var = pProjection->FindVariable("fBoxHeight"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_fBoxHeight); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxHeightChange]); } var = pProjection->FindVariable("fBoxLength"); if (var && (var->GetType() == IVariable::FLOAT || var->GetType() == IVariable::INT)) { var->Get(m_fBoxLength); SetVariableCallback(var, &m_onSetCallbacksCache[VariableCallbackIndex::OnBoxLengthChange]); } } // Each property must have callback to our OnPropertyChange. pProperties->AddOnSetCallback(&m_onSetCallbacksCache[VariableCallbackIndex::OnPropertyChange]); } if (pProperties2) { pProperties2->AddOnSetCallback(&m_onSetCallbacksCache[VariableCallbackIndex::OnPropertyChange]); } } ////////////////////////////////////////////////////////////////////////// void CEntityObject::SetVariableCallback(IVariable* pVar, IVariable::OnSetCallback* func) { pVar->AddOnSetCallback(func); m_callbacks.push_back(std::make_pair(pVar, func)); } ////////////////////////////////////////////////////////////////////////// void CEntityObject::ClearCallbacks() { if (m_pProperties) { m_pProperties->RemoveOnSetCallback(&m_onSetCallbacksCache[VariableCallbackIndex::OnPropertyChange]); } if (m_pProperties2) { m_pProperties2->RemoveOnSetCallback(&m_onSetCallbacksCache[VariableCallbackIndex::OnPropertyChange]); } for (auto iter = m_callbacks.begin(); iter != m_callbacks.end(); ++iter) { iter->first->RemoveOnSetCallback(iter->second); } m_callbacks.clear(); } void CEntityObject::StoreUndoEntityLink(CSelectionGroup* pGroup) { if (!pGroup) { return; } if (CUndo::IsRecording()) { CUndo::Record(new CUndoEntityLink(pGroup)); } } template T CEntityObject::GetEntityProperty(const char* pName, T defaultvalue) const { CVarBlock* pProperties = GetProperties2(); IVariable* pVariable = nullptr; if (pProperties) { pVariable = pProperties->FindVariable(pName); } if (!pVariable) { pProperties = GetProperties(); if (pProperties) { pVariable = pProperties->FindVariable(pName); } if (!pVariable) { return defaultvalue; } } if (pVariable->GetType() != IVariableType::value) { return defaultvalue; } T value; pVariable->Get(value); return value; } template void CEntityObject::SetEntityProperty(const char* pName, T value) { CVarBlock* pProperties = GetProperties2(); IVariable* pVariable = nullptr; if (pProperties) { pVariable = pProperties->FindVariable(pName); } if (!pVariable) { pProperties = GetProperties(); if (pProperties) { pVariable = pProperties->FindVariable(pName); } if (!pVariable) { throw std::runtime_error((QString("\"") + pName + "\" is an invalid property.").toUtf8().data()); } } if (pVariable->GetType() != IVariableType::value) { throw std::logic_error("Data type is invalid."); } pVariable->Set(value); } bool CEntityObject::GetEntityPropertyBool(const char* name) const { return GetEntityProperty(name, false); } int CEntityObject::GetEntityPropertyInteger(const char* name) const { return GetEntityProperty(name, 0); } float CEntityObject::GetEntityPropertyFloat(const char* name) const { return GetEntityProperty(name, 0.0f); } QString CEntityObject::GetEntityPropertyString(const char* name) const { return GetEntityProperty(name, ""); } void CEntityObject::SetEntityPropertyBool(const char* name, bool value) { SetEntityProperty(name, value); } void CEntityObject::SetEntityPropertyInteger(const char* name, int value) { SetEntityProperty(name, value); } void CEntityObject::SetEntityPropertyFloat(const char* name, float value) { SetEntityProperty(name, value); } void CEntityObject::SetEntityPropertyString(const char* name, const QString& value) { SetEntityProperty(name, value); } #include