Fix merge conflicts.

Signed-off-by: John <jonawals@amazon.com>
This commit is contained in:
John
2021-10-18 09:24:09 +01:00
468 changed files with 11158 additions and 5459 deletions
+1 -1
View File
@@ -102,7 +102,7 @@ ly_add_target(
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
3rdParty::Qt::Concurrent
3rdParty::tiff
3rdParty::TIFF
3rdParty::squish-ccr
3rdParty::AWSNativeSDK::STS
Legacy::CryCommon
@@ -19,10 +19,16 @@ namespace Editor
if (GetIEditor()->IsInGameMode())
{
#ifdef PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
// We need to handle RAW Input events in a separate loop. This is a workaround to enable XInput2 RAW Inputs using Editor mode.
// TODO To have this call here might be not be perfect.
AzFramework::XcbEventHandlerBus::Broadcast(&AzFramework::XcbEventHandler::PollSpecialEvents);
// Now handle the rest of the events.
AzFramework::XcbEventHandlerBus::Broadcast(
&AzFramework::XcbEventHandler::HandleXcbEvent, static_cast<xcb_generic_event_t*>(message));
#endif
return true;
}
return false;
}
}
} // namespace Editor
+10
View File
@@ -57,6 +57,7 @@ AZ_POP_DISABLE_WARNING
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <AzFramework/ProjectManager/ProjectManager.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
// AzToolsFramework
#include <AzToolsFramework/Component/EditorComponentAPIBus.h>
@@ -3021,6 +3022,15 @@ CCryEditApp::ECreateLevelResult CCryEditApp::CreateLevel(const QString& levelNam
bool bIsDocModified = GetIEditor()->GetDocument()->IsModified();
OnSwitchPhysics();
GetIEditor()->GetDocument()->SetModifiedFlag(bIsDocModified);
if (usePrefabSystemForLevels)
{
auto* rootSpawnableInterface = AzFramework::RootSpawnableInterface::Get();
if (rootSpawnableInterface)
{
rootSpawnableInterface->ProcessSpawnableQueue();
}
}
}
const QScopedValueRollback<bool> rollback(m_creatingNewLevel);
+3
View File
@@ -264,6 +264,9 @@ void EditorPreferencesDialog::SetFilter(const QString& filter)
else if (m_currentPageItem)
{
m_currentPageItem->UpdateEditorFilter(ui->propertyEditor, m_filter);
// Refresh the Stylesheet - when using search functionality.
AzQtComponents::StyleManager::repolishStyleSheet(this);
}
}
+12 -10
View File
@@ -14,6 +14,7 @@
// Editor
#include "Settings.h"
#include "EditorViewportSettings.h"
@@ -43,17 +44,16 @@ void CEditorPreferencesPage_Files::Reflect(AZ::SerializeContext& serialize)
->Field("MaxCount", &AutoBackup::m_maxCount)
->Field("RemindTime", &AutoBackup::m_remindTime);
serialize.Class<AssetBrowserSearch>()
serialize.Class<AssetBrowserSettings>()
->Version(1)
->Field("Max number of items displayed", &AssetBrowserSearch::m_maxNumberOfItemsShownInSearch);
->Field("MaxEntriesShownCount", &AssetBrowserSettings::m_maxNumberOfItemsShownInSearch);
serialize.Class<CEditorPreferencesPage_Files>()
->Version(1)
->Field("Files", &CEditorPreferencesPage_Files::m_files)
->Field("Editors", &CEditorPreferencesPage_Files::m_editors)
->Field("AutoBackup", &CEditorPreferencesPage_Files::m_autoBackup)
->Field("AssetBrowserSearch", &CEditorPreferencesPage_Files::m_assetBrowserSearch);
->Field("AssetBrowserSettings", &CEditorPreferencesPage_Files::m_assetBrowserSettings);
AZ::EditContext* editContext = serialize.GetEditContext();
if (editContext)
@@ -85,9 +85,10 @@ void CEditorPreferencesPage_Files::Reflect(AZ::SerializeContext& serialize)
->Attribute(AZ::Edit::Attributes::Max, 100)
->DataElement(AZ::Edit::UIHandlers::SpinBox, &AutoBackup::m_remindTime, "Remind Time", "Auto Remind Every (Minutes)");
editContext->Class<AssetBrowserSearch>("Asset Browser Search View", "Asset Browser Search View")
->DataElement(AZ::Edit::UIHandlers::SpinBox, &AssetBrowserSearch::m_maxNumberOfItemsShownInSearch, "Maximum number of displayed items",
"Maximum number of displayed items displayed in the Search View")
editContext->Class<AssetBrowserSettings>("Asset Browser Settings", "Asset Browser Settings")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &AssetBrowserSettings::m_maxNumberOfItemsShownInSearch, "Maximum number of displayed items",
"Maximum number of items to display in the Search View.")
->Attribute(AZ::Edit::Attributes::Min, 50)
->Attribute(AZ::Edit::Attributes::Max, 5000);
@@ -97,7 +98,7 @@ void CEditorPreferencesPage_Files::Reflect(AZ::SerializeContext& serialize)
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_Files::m_files, "Files", "File Preferences")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_Files::m_editors, "External Editors", "External Editors")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_Files::m_autoBackup, "Auto Backup", "Auto Backup")
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_Files::m_assetBrowserSearch, "Asset Browser Search", "Asset Browser Search");
->DataElement(AZ::Edit::UIHandlers::Default, &CEditorPreferencesPage_Files::m_assetBrowserSettings, "Asset Browser Settings","Asset Browser Settings");
}
}
@@ -117,6 +118,7 @@ QIcon& CEditorPreferencesPage_Files::GetIcon()
void CEditorPreferencesPage_Files::OnApply()
{
using namespace AzToolsFramework::SliceUtilities;
auto sliceSettings = AZ::UserSettings::CreateFind<SliceUserSettings>(AZ_CRC("SliceUserSettings", 0x055b32eb), AZ::UserSettings::CT_LOCAL);
sliceSettings->m_autoNumber = m_files.m_autoNumberSlices;
sliceSettings->m_saveLocation = m_files.m_saveLocation;
@@ -137,7 +139,7 @@ void CEditorPreferencesPage_Files::OnApply()
gSettings.autoBackupMaxCount = m_autoBackup.m_maxCount;
gSettings.autoRemindTime = m_autoBackup.m_remindTime;
gSettings.maxNumberOfItemsShownInSearch = m_assetBrowserSearch.m_maxNumberOfItemsShownInSearch;
SandboxEditor::SetMaxItemsShownInAssetBrowserSearch(m_assetBrowserSettings.m_maxNumberOfItemsShownInSearch);
}
void CEditorPreferencesPage_Files::InitializeSettings()
@@ -163,5 +165,5 @@ void CEditorPreferencesPage_Files::InitializeSettings()
m_autoBackup.m_maxCount = gSettings.autoBackupMaxCount;
m_autoBackup.m_remindTime = gSettings.autoRemindTime;
m_assetBrowserSearch.m_maxNumberOfItemsShownInSearch = gSettings.maxNumberOfItemsShownInSearch;
m_assetBrowserSettings.m_maxNumberOfItemsShownInSearch = SandboxEditor::MaxItemsShownInAssetBrowserSearch();
}
+4 -7
View File
@@ -69,18 +69,15 @@ private:
int m_remindTime;
};
struct AssetBrowserSearch
struct AssetBrowserSettings
{
AZ_TYPE_INFO(AssetBrowserSearch, "{9FBFCD24-9452-49DF-99F4-2711443CEAAE}")
int m_maxNumberOfItemsShownInSearch;
AZ_TYPE_INFO(AssetBrowserSettings, "{5F407EC4-BBD1-4A87-92DB-D938D7127BB0}")
AZ::u64 m_maxNumberOfItemsShownInSearch;
};
Files m_files;
ExternalEditors m_editors;
AutoBackup m_autoBackup;
AssetBrowserSearch m_assetBrowserSearch;
AssetBrowserSettings m_assetBrowserSettings;
QIcon m_icon;
};
+11
View File
@@ -15,6 +15,7 @@
namespace SandboxEditor
{
constexpr AZStd::string_view AssetBrowserMaxItemsShownInSearchSetting = "/Amazon/Preferences/Editor/AssetBrowser/MaxItemsShowInSearch";
constexpr AZStd::string_view GridSnappingSetting = "/Amazon/Preferences/Editor/GridSnapping";
constexpr AZStd::string_view GridSizeSetting = "/Amazon/Preferences/Editor/GridSize";
constexpr AZStd::string_view AngleSnappingSetting = "/Amazon/Preferences/Editor/AngleSnapping";
@@ -110,6 +111,16 @@ namespace SandboxEditor
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
}
AZ::u64 MaxItemsShownInAssetBrowserSearch()
{
return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50));
}
void SetMaxItemsShownInAssetBrowserSearch(const AZ::u64 numberOfItemsShown)
{
SetRegistry(AssetBrowserMaxItemsShownInSearchSetting, numberOfItemsShown);
}
bool GridSnappingEnabled()
{
return GetRegistry(GridSnappingSetting, false);
+3
View File
@@ -32,6 +32,9 @@ namespace SandboxEditor
//! event will fire when a value in the settings registry (editorpreferences.setreg) is modified.
SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks();
SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch();
SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown);
SANDBOX_API bool GridSnappingEnabled();
SANDBOX_API void SetGridSnapping(bool enabled);
-35
View File
@@ -108,15 +108,11 @@ CObjectManager::CObjectManager()
m_objectsByName.reserve(1024);
LoadRegistry();
AzToolsFramework::ViewportEditorModeNotificationsBus::Handler::BusConnect(AzToolsFramework::GetEntityContextId());
}
//////////////////////////////////////////////////////////////////////////
CObjectManager::~CObjectManager()
{
AzToolsFramework::ViewportEditorModeNotificationsBus::Handler::BusDisconnect();
m_bExiting = true;
SaveRegistry();
DeleteAllObjects();
@@ -2307,37 +2303,6 @@ void CObjectManager::SelectObjectInRect(CBaseObject* pObj, CViewport* view, HitC
}
}
void CObjectManager::OnEditorModeActivated(
[[maybe_unused]] const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode)
{
if (mode == AzToolsFramework::ViewportEditorMode::Component)
{
// hide current gizmo for entity (translate/rotate/scale)
IGizmoManager* gizmoManager = GetGizmoManager();
const size_t gizmoCount = static_cast<size_t>(gizmoManager->GetGizmoCount());
for (size_t i = 0; i < gizmoCount; ++i)
{
gizmoManager->RemoveGizmo(gizmoManager->GetGizmoByIndex(static_cast<int>(i)));
}
}
}
void CObjectManager::OnEditorModeDeactivated(
[[maybe_unused]] const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode)
{
if (mode == AzToolsFramework::ViewportEditorMode::Component)
{
// show translate/rotate/scale gizmo again
if (IGizmoManager* gizmoManager = GetGizmoManager())
{
if (CBaseObject* selectedObject = GetIEditor()->GetSelectedObject())
{
gizmoManager->AddGizmo(new CAxisGizmo(selectedObject));
}
}
}
}
//////////////////////////////////////////////////////////////////////////
namespace
{
-8
View File
@@ -20,7 +20,6 @@
#include "ObjectManagerEventBus.h"
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzToolsFramework/API/ViewportEditorModeTrackerNotificationBus.h>
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/Component.h>
#include <Include/SandboxAPI.h>
@@ -59,7 +58,6 @@ public:
*/
class CObjectManager
: public IObjectManager
, private AzToolsFramework::ViewportEditorModeNotificationsBus::Handler
{
public:
//! Selection functor callback.
@@ -330,12 +328,6 @@ private:
void FindDisplayableObjects(DisplayContext& dc, bool bDisplay);
// ViewportEditorModeNotificationsBus overrides ...
void OnEditorModeActivated(
const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode) override;
void OnEditorModeDeactivated(
const AzToolsFramework::ViewportEditorModesInterface& editorModeState, AzToolsFramework::ViewportEditorMode mode) override;
private:
typedef std::map<GUID, CBaseObjectPtr, guid_less_predicate> Objects;
Objects m_objects;
+3 -4
View File
@@ -10,6 +10,7 @@
#include "EditorDefs.h"
#include "Settings.h"
#include "EditorViewportSettings.h"
// Qt
#include <QGuiApplication>
@@ -487,7 +488,6 @@ void SEditorSettings::Save()
SaveValue("Settings", "AutoBackupTime", autoBackupTime);
SaveValue("Settings", "AutoBackupMaxCount", autoBackupMaxCount);
SaveValue("Settings", "AutoRemindTime", autoRemindTime);
SaveValue("Settings", "MaxDisplayedItemsNumInSearch", maxNumberOfItemsShownInSearch);
SaveValue("Settings", "CameraMoveSpeed", cameraMoveSpeed);
SaveValue("Settings", "CameraRotateSpeed", cameraRotateSpeed);
SaveValue("Settings", "StylusMode", stylusMode);
@@ -682,7 +682,6 @@ void SEditorSettings::Load()
LoadValue("Settings", "AutoBackupTime", autoBackupTime);
LoadValue("Settings", "AutoBackupMaxCount", autoBackupMaxCount);
LoadValue("Settings", "AutoRemindTime", autoRemindTime);
LoadValue("Settings", "MaxDisplayedItemsNumInSearch", maxNumberOfItemsShownInSearch);
LoadValue("Settings", "CameraMoveSpeed", cameraMoveSpeed);
LoadValue("Settings", "CameraRotateSpeed", cameraRotateSpeed);
LoadValue("Settings", "StylusMode", stylusMode);
@@ -1174,7 +1173,7 @@ AzToolsFramework::ConsoleColorTheme SEditorSettings::GetConsoleColorTheme() cons
return consoleBackgroundColorTheme;
}
int SEditorSettings::GetMaxNumberOfItemsShownInSearchView() const
AZ::u64 SEditorSettings::GetMaxNumberOfItemsShownInSearchView() const
{
return SEditorSettings::maxNumberOfItemsShownInSearch;
return SandboxEditor::MaxItemsShownInAssetBrowserSearch();
}
+1 -9
View File
@@ -279,7 +279,7 @@ AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
SettingOutcome GetValue(const AZStd::string_view path) override;
SettingOutcome SetValue(const AZStd::string_view path, const AZStd::any& value) override;
AzToolsFramework::ConsoleColorTheme GetConsoleColorTheme() const override;
int GetMaxNumberOfItemsShownInSearchView() const override;
AZ::u64 GetMaxNumberOfItemsShownInSearchView() const override;
void ConvertPath(const AZStd::string_view sourcePath, AZStd::string& category, AZStd::string& attribute);
@@ -353,14 +353,6 @@ AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
int autoRemindTime;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Asset Browser Search View.
//////////////////////////////////////////////////////////////////////////
//! Current maximum number of items that can be displayed in the AssetBrowser Search View.
int maxNumberOfItemsShownInSearch;
//////////////////////////////////////////////////////////////////////////
//! If true preview windows is displayed when browsing geometries.
bool bPreviewGeometryWindow;
+2 -2
View File
@@ -1195,7 +1195,7 @@ bool CFileUtil::IsFileExclusivelyAccessable(const QString& strFilePath)
//////////////////////////////////////////////////////////////////////////
bool CFileUtil::CreatePath(const QString& strPath)
{
#if defined(AZ_PLATFORM_MAC)
#if !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
bool pathCreated = true;
QString cleanPath = QDir::cleanPath(strPath);
@@ -1252,7 +1252,7 @@ bool CFileUtil::CreatePath(const QString& strPath)
}
return true;
#endif
#endif // !AZ_TRAIT_OS_USE_WINDOWS_FILE_PATHS
}
//////////////////////////////////////////////////////////////////////////
@@ -225,8 +225,16 @@ namespace AZ
ConsoleCommandContainer commandSubset;
for (ConsoleFunctorBase* curr = m_head; curr != nullptr; curr = curr->m_next)
for (const auto& functor : m_commands)
{
if (functor.second.empty())
{
continue;
}
// Filter functors registered with the same name
const ConsoleFunctorBase* curr = functor.second.front();
if ((curr->GetFlags() & ConsoleFunctorFlags::IsInvisible) == ConsoleFunctorFlags::IsInvisible)
{
// Filter functors marked as invisible
@@ -236,7 +244,12 @@ namespace AZ
if (StringFunc::StartsWith(curr->m_name, command, false))
{
AZLOG_INFO("- %s : %s\n", curr->m_name, curr->m_desc);
commandSubset.push_back(curr->m_name);
if (commandSubset.size() < MaxConsoleCommandPlusArgsLength)
{
commandSubset.push_back(curr->m_name);
}
if (matches)
{
matches->push_back(curr->m_name);
@@ -271,7 +284,10 @@ namespace AZ
{
for (auto& curr : m_commands)
{
visitor(curr.second.front());
if (!curr.second.empty())
{
visitor(curr.second.front());
}
}
}
@@ -336,6 +352,11 @@ namespace AZ
{
iter->second.erase(iter2);
}
if (iter->second.empty())
{
m_commands.erase(iter);
}
}
functor->Unlink(m_head);
functor->m_console = nullptr;
@@ -40,6 +40,12 @@ namespace AZ
static unsigned int Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount = 0, void* nativeThread = 0);
};
class StackConverter
{
public:
static unsigned int FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext);
};
class SymbolStorage
{
public:
+10 -6
View File
@@ -31,6 +31,8 @@ namespace AZ
{
namespace Debug
{
struct StackFrame;
namespace Platform
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
@@ -551,17 +553,19 @@ namespace AZ
{
StackFrame frames[25];
// Without StackFrame explicit alignment frames array is aligned to 4 bytes
// which causes the stack tracing to fail.
//size_t bla = AZStd::alignment_of<StackFrame>::value;
//printf("Alignment value %d address 0x%08x : 0x%08x\n",bla,frames);
SymbolStorage::StackLine lines[AZ_ARRAY_SIZE(frames)];
unsigned int numFrames = 0;
if (!nativeContext)
{
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrinCallstack).
suppressCount += 1; /// If we don't provide a context we will capture in the RecordFunction, so skip us (Trace::PrintCallstack).
numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount);
}
unsigned int numFrames = StackRecorder::Record(frames, AZ_ARRAY_SIZE(frames), suppressCount, nativeContext);
else
{
numFrames = StackConverter::FromNative(frames, AZ_ARRAY_SIZE(frames), nativeContext);
}
if (numFrames)
{
SymbolStorage::DecodeFrames(frames, numFrames, lines);
@@ -15,7 +15,7 @@ using namespace Intersect;
// IntersectSegmentTriangleCCW
// [10/21/2009]
//=========================================================================
int Intersect::IntersectSegmentTriangleCCW(
bool Intersect::IntersectSegmentTriangleCCW(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t)
{
@@ -34,7 +34,7 @@ int Intersect::IntersectSegmentTriangleCCW(
float d = qp.Dot(normal);
if (d <= 0.0f)
{
return 0;
return false;
}
// Compute intersection t value of pq with plane of triangle. A ray
@@ -46,7 +46,7 @@ int Intersect::IntersectSegmentTriangleCCW(
// range segment check t[0,1] (it this case [0,d])
if (t < 0.0f || t > d)
{
return 0;
return false;
}
// Compute barycentric coordinate components and test if within bounds
@@ -54,12 +54,12 @@ int Intersect::IntersectSegmentTriangleCCW(
v = ac.Dot(e);
if (v < 0.0f || v > d)
{
return 0;
return false;
}
w = -ab.Dot(e);
if (w < 0.0f || v + w > d)
{
return 0;
return false;
}
// Segment/ray intersects triangle. Perform delayed division and
@@ -72,14 +72,14 @@ int Intersect::IntersectSegmentTriangleCCW(
normal.Normalize();
return 1;
return true;
}
//=========================================================================
// IntersectSegmentTriangle
// [10/21/2009]
//=========================================================================
int
bool
Intersect::IntersectSegmentTriangle(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t)
@@ -111,7 +111,7 @@ Intersect::IntersectSegmentTriangle(
// so either have a parallel ray or our normal is flipped
if (d >= -Constants::FloatEpsilon)
{
return 0; // parallel
return false; // parallel
}
d = -d;
e = ap.Cross(qp);
@@ -125,19 +125,19 @@ Intersect::IntersectSegmentTriangle(
// range segment check t[0,1] (it this case [0,d])
if (t < 0.0f || t > d)
{
return 0;
return false;
}
// Compute barycentric coordinate components and test if within bounds
v = ac.Dot(e);
if (v < 0.0f || v > d)
{
return 0;
return false;
}
w = -ab.Dot(e);
if (w < 0.0f || v + w > d)
{
return 0;
return false;
}
// Segment/ray intersects the triangle. Perform delayed division and
@@ -150,14 +150,14 @@ Intersect::IntersectSegmentTriangle(
normal.Normalize();
return 1;
return true;
}
//=========================================================================
// TestSegmentAABBOrigin
// [10/21/2009]
//=========================================================================
int
bool
AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& halfVector, const Vector3& aabbExtends)
{
const Vector3 EPSILON(0.001f); // \todo this is slow load move to a const
@@ -168,7 +168,7 @@ AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& hal
// Try world coordinate axes as separating axes
if (!absMidpoint.IsLessEqualThan(absHalfMidpoint))
{
return 0;
return false;
}
// Add in an epsilon term to counteract arithmetic errors when segment is
@@ -188,11 +188,11 @@ AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& hal
Vector3 ead(ey * adz + ez * ady, ex * adz + ez * adx, ex * ady + ey * adx);
if (!absMDCross.IsLessEqualThan(ead))
{
return 0;
return false;
}
// No separating axis found; segment must be overlapping AABB
return 1;
return true;
}
@@ -200,7 +200,7 @@ AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& hal
// IntersectRayAABB
// [10/21/2009]
//=========================================================================
int
RayAABBIsectTypes
AZ::Intersect::IntersectRayAABB(
const Vector3& rayStart, const Vector3& dir, const Vector3& dirRCP, const Aabb& aabb,
float& tStart, float& tEnd, Vector3& startNormal /*, Vector3& inter*/)
@@ -356,7 +356,7 @@ AZ::Intersect::IntersectRayAABB(
// IntersectRayAABB2
// [2/18/2011]
//=========================================================================
int
RayAABBIsectTypes
AZ::Intersect::IntersectRayAABB2(const Vector3& rayStart, const Vector3& dirRCP, const Aabb& aabb, float& start, float& end)
{
float tmin, tmax, tymin, tymax, tzmin, tzmax;
@@ -408,7 +408,7 @@ AZ::Intersect::IntersectRayAABB2(const Vector3& rayStart, const Vector3& dirRCP,
return ISECT_RAY_AABB_ISECT;
}
int AZ::Intersect::IntersectRayDisk(
bool AZ::Intersect::IntersectRayDisk(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& diskCenter, const float diskRadius, const Vector3& diskNormal, float& t)
{
// First intersect with the plane of the disk
@@ -421,10 +421,10 @@ int AZ::Intersect::IntersectRayDisk(
if (pointOnPlane.GetDistance(diskCenter) < diskRadius)
{
t = planeIntersectionDistance;
return 1;
return true;
}
}
return 0;
return false;
}
// Reference: Real-Time Collision Detection - 5.3.7 Intersecting Ray or Segment Against Cylinder, and the book's errata.
@@ -1012,7 +1012,7 @@ int AZ::Intersect::IntersectRayQuad(
}
// reference: Real-Time Collision Detection, 5.3.3 Intersecting Ray or Segment Against Box
int AZ::Intersect::IntersectRayBox(
bool AZ::Intersect::IntersectRayBox(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& boxCenter, const Vector3& boxAxis1,
const Vector3& boxAxis2, const Vector3& boxAxis3, float boxHalfExtent1, float boxHalfExtent2, float boxHalfExtent3, float& t)
{
@@ -1044,7 +1044,7 @@ int AZ::Intersect::IntersectRayBox(
// If the ray is parallel to the slab and the ray origin is outside, return no intersection.
if (tp < 0.0f || tn < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1065,7 +1065,7 @@ int AZ::Intersect::IntersectRayBox(
tmax = AZ::GetMin(tmax, t2);
if (tmin > tmax)
{
return 0;
return false;
}
}
@@ -1085,7 +1085,7 @@ int AZ::Intersect::IntersectRayBox(
// If the ray is parallel to the slab and the ray origin is outside, return no intersection.
if (tp < 0.0f || tn < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1106,7 +1106,7 @@ int AZ::Intersect::IntersectRayBox(
tmax = AZ::GetMin(tmax, t2);
if (tmin > tmax)
{
return 0;
return false;
}
}
@@ -1126,7 +1126,7 @@ int AZ::Intersect::IntersectRayBox(
// If the ray is parallel to the slab and the ray origin is outside, return no intersection.
if (tp < 0.0f || tn < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1147,15 +1147,15 @@ int AZ::Intersect::IntersectRayBox(
tmax = AZ::GetMin(tmax, t2);
if (tmin > tmax)
{
return 0;
return false;
}
}
t = (isRayOriginInsideBox ? tmax : tmin);
return 1;
return true;
}
int AZ::Intersect::IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t)
bool AZ::Intersect::IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t)
{
return AZ::Intersect::IntersectRayBox(rayOrigin, rayDir, obb.GetPosition(),
obb.GetAxisX(), obb.GetAxisY(), obb.GetAxisZ(),
@@ -1166,7 +1166,7 @@ int AZ::Intersect::IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayD
// IntersectSegmentCylinder
// [10/21/2009]
//=========================================================================
int
CylinderIsectTypes
AZ::Intersect::IntersectSegmentCylinder(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t)
{
@@ -1225,7 +1225,7 @@ AZ::Intersect::IntersectSegmentCylinder(
return RR_ISECT_RAY_CYL_NONE; // No real roots; no intersection
}
t = (-b - Sqrt(discr)) / a;
int result = RR_ISECT_RAY_CYL_PQ; // default along the PQ segment
CylinderIsectTypes result = RR_ISECT_RAY_CYL_PQ; // default along the PQ segment
if (md + t * nd < 0.0f)
{
@@ -1294,7 +1294,7 @@ AZ::Intersect::IntersectSegmentCylinder(
// IntersectSegmentCapsule
// [10/21/2009]
//=========================================================================
int
CapsuleIsectTypes
AZ::Intersect::IntersectSegmentCapsule(const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t)
{
int result = IntersectSegmentCylinder(sa, dir, p, q, r, t);
@@ -1361,13 +1361,13 @@ AZ::Intersect::IntersectSegmentCapsule(const Vector3& sa, const Vector3& dir, co
// IntersectSegmentPolyhedron
// [10/21/2009]
//=========================================================================
int
bool
AZ::Intersect::IntersectSegmentPolyhedron(
const Vector3& sa, const Vector3& sBA, const Plane p[], int numPlanes,
const Vector3& sa, const Vector3& dir, const Plane p[], int numPlanes,
float& tfirst, float& tlast, int& iFirstPlane, int& iLastPlane)
{
// Compute direction vector for the segment
Vector3 d = /*b - a*/ sBA;
Vector3 d = /*b - a*/ dir;
// Set initial interval to being the whole segment. For a ray, tlast should be
// set to +RR_FLT_MAX. For a line, additionally tfirst should be set to -RR_FLT_MAX
tfirst = 0.0f;
@@ -1388,7 +1388,7 @@ AZ::Intersect::IntersectSegmentPolyhedron(
// If so, return "no intersection" if segment lies outside plane
if (dist < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1417,7 +1417,7 @@ AZ::Intersect::IntersectSegmentPolyhedron(
// Exit with "no intersection" if intersection becomes empty
if (tfirst > tlast)
{
return 0;
return false;
}
}
}
@@ -1425,11 +1425,11 @@ AZ::Intersect::IntersectSegmentPolyhedron(
//DBG_Assert(iFirstPlane!=-1&&iLastPlane!=-1,("We have some bad border case to have only one plane, fix this function!"));
if (iFirstPlane == -1 && iLastPlane == -1)
{
return 0;
return false;
}
// A nonzero logical intersection, so the segment intersects the polyhedron
return 1;
return true;
}
//=========================================================================
@@ -1442,7 +1442,7 @@ AZ::Intersect::ClosestSegmentSegment(
const Vector3& segment2Start, const Vector3& segment2End,
float& segment1Proportion, float& segment2Proportion,
Vector3& closestPointSegment1, Vector3& closestPointSegment2,
float epsilon /*= 1e-4f*/ )
float epsilon)
{
const Vector3 segment1 = segment1End - segment1Start;
const Vector3 segment2 = segment2End - segment2Start;
@@ -5,363 +5,398 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_MATH_SEGMENT_INTERSECTION_H
#define AZCORE_MATH_SEGMENT_INTERSECTION_H
#pragma once
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Obb.h>
#include <AzCore/Math/Plane.h>
/// \file isect_segment.h
#include <AzCore/Math/Vector3.h>
namespace AZ
{
namespace Intersect
{
//! LineToPointDistanceTime computes the time of the shortest distance from point 'p' to segment (s1,s2).
//! To calculate the point of intersection:
//! P = s1 + u (s2 - s1)
//! @param s1 segment start point
//! @param s2 segment end point
//! @param p point to find the closest time to.
//! @return time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
inline float LineToPointDistanceTime(const Vector3& s1, const Vector3& s21, const Vector3& p)
{
// so u = (p.x - s1.x)*(s2.x - s1.x) + (p.y - s1.y)*(s2.y - s1.y) + (p.z-s1.z)*(s2.z-s1.z) / |s2-s1|^2
return s21.Dot(p - s1) / s21.Dot(s21);
}
//! To calculate the point of intersection: P = s1 + u (s2 - s1)
//! @param s1 Segment start point.
//! @param s2 Segment end point.
//! @param p Point to find the closest time to.
//! @return Time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
float LineToPointDistanceTime(const Vector3& s1, const Vector3& s21, const Vector3& p);
//! LineToPointDistance computes the closest point to 'p' from a segment (s1,s2).
//! @param s1 segment start point
//! @param s2 segment end point
//! @param p point to find the closest time to.
//! @param u time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
//! @return the closest point
inline Vector3 LineToPointDistance(const Vector3& s1, const Vector3& s2, const Vector3& p, float& u)
{
const Vector3 s21 = s2 - s1;
// we assume seg1 and seg2 are NOT coincident
AZ_MATH_ASSERT(!s21.IsClose(Vector3(0.0f), 1e-4f), "OK we agreed that we will pass valid segments! (s1 != s2)");
u = LineToPointDistanceTime(s1, s21, p);
return s1 + u * s21;
}
//! @param s1 Segment start point
//! @param s2 Segment end point
//! @param p Point to find the closest time to.
//! @param u Time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
//! @return The closest point
Vector3 LineToPointDistance(const Vector3& s1, const Vector3& s2, const Vector3& p, float& u);
//! Given segment pq and triangle abc (CCW), returns whether segment intersects
//! triangle and if so, also returns the barycentric coordinates (u,v,w)
//! of the intersection point.
//! @param p segment start point
//! @param q segment end point
//! @param a triangle point 1
//! @param b triangle point 2
//! @param c triangle point 3
//! @param normal at the intersection point.
//! @param t time of intersection along the segment [0.0 (p), 1.0 (q)]
//! @return 1 if the segment intersects the triangle otherwise 0
int IntersectSegmentTriangleCCW(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t);
//! @param p Segment start point.
//! @param q Segment end point.
//! @param a Triangle point 1.
//! @param b Triangle point 2.
//! @param c Triangle point 3.
//! @param normal At the intersection point.
//! @param t Time of intersection along the segment [0.0 (p), 1.0 (q)].
//! @return true if the segments intersects the triangle otherwise false.
bool IntersectSegmentTriangleCCW(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c, Vector3& normal, float& t);
//! Same as \ref IntersectSegmentTriangleCCW without respecting the triangle (a,b,c) vertex order (double sided).
int IntersectSegmentTriangle(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t);
//! @param p Segment start point.
//! @param q Segment end point.
//! @param a Triangle point 1.
//! @param b Triangle point 2.
//! @param c Triangle point 3.
//! @param normal At the intersection point.
//! @param t Time of intersection along the segment [0.0 (p), 1.0 (q)].
//! @return True if the segments intersects the triangle otherwise false.
bool IntersectSegmentTriangle(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c, Vector3& normal, float& t);
//! Ray aabb intersection result types.
enum RayAABBIsectTypes
enum RayAABBIsectTypes : AZ::s32
{
ISECT_RAY_AABB_NONE = 0, ///< no intersection
ISECT_RAY_AABB_SA_INSIDE, ///< the ray starts inside the aabb
ISECT_RAY_AABB_ISECT, ///< intersects along the PQ segment
ISECT_RAY_AABB_NONE = 0, ///< no intersection
ISECT_RAY_AABB_SA_INSIDE, ///< the ray starts inside the aabb
ISECT_RAY_AABB_ISECT, ///< intersects along the PQ segment
};
//! Intersect ray R(t) = rayStart + t*d against AABB a. When intersecting,
//! return intersection distance tmin and point q of intersection.
//! @param rayStart ray starting point
//! @param dir ray direction and length (dir = rayEnd - rayStart)
//! @param dirRCP 1/dir (reciprocal direction - we cache this result very often so we don't need to compute it multiple times, otherwise just use dir.GetReciprocal())
//! @param rayStart Ray starting point
//! @param dir Ray direction and length (dir = rayEnd - rayStart)
//! @param dirRCP 1/dir (reciprocal direction - we cache this result very often so we don't need to compute it multiple times,
//! otherwise just use dir.GetReciprocal())
//! @param aabb Axis aligned bounding box to intersect against
//! @param tStart time on ray of the first intersection [0,1] or 0 if the ray starts inside the aabb - check the return value
//! @param tEnd time of the of the second intersection [0,1] (it can be > 1 if intersects after the rayEnd)
//! @param startNormal normal at the start point.
//! @param tStart Time on ray of the first intersection [0,1] or 0 if the ray starts inside the aabb - check the return value
//! @param tEnd Time of the of the second intersection [0,1] (it can be > 1 if intersects after the rayEnd)
//! @param startNormal Normal at the start point.
//! @return \ref RayAABBIsectTypes
int IntersectRayAABB(
const Vector3& rayStart, const Vector3& dir, const Vector3& dirRCP, const Aabb& aabb,
float& tStart, float& tEnd, Vector3& startNormal /*, Vector3& inter*/);
RayAABBIsectTypes IntersectRayAABB(
const Vector3& rayStart,
const Vector3& dir,
const Vector3& dirRCP,
const Aabb& aabb,
float& tStart,
float& tEnd,
Vector3& startNormal);
//! Intersect ray against AABB.
//! @param rayStart ray starting point.
//! @param dir ray reciprocal direction.
//! @param rayStart Ray starting point.
//! @param dir Ray reciprocal direction.
//! @param aabb Axis aligned bounding box to intersect against.
//! @param start length on ray of the first intersection.
//! @param end length of the of the second intersection.
//! @return \ref RayAABBIsectTypes In this faster version than IntersectRayAABB we return only ISECT_RAY_AABB_NONE and ISECT_RAY_AABB_ISECT.
//! You can check yourself for that case.
int IntersectRayAABB2(
const Vector3& rayStart, const Vector3& dirRCP, const Aabb& aabb,
float& start, float& end);
//! @param start Length on ray of the first intersection.
//! @param end Length of the of the second intersection.
//! @return \ref RayAABBIsectTypes In this faster version than IntersectRayAABB we return only ISECT_RAY_AABB_NONE and
//! ISECT_RAY_AABB_ISECT. You can check yourself for that case.
RayAABBIsectTypes IntersectRayAABB2(const Vector3& rayStart, const Vector3& dirRCP, const Aabb& aabb, float& start, float& end);
//! Clip a ray to an aabb. return true if ray was clipped. The ray
//! can be inside so don't use the result if the ray intersect the box.
inline int ClipRayWithAabb(
const Aabb& aabb, Vector3& rayStart, Vector3& rayEnd, float& tClipStart, float& tClipEnd)
{
Vector3 startNormal;
float tStart, tEnd;
Vector3 dirLen = rayEnd - rayStart;
if (IntersectRayAABB(rayStart, dirLen, dirLen.GetReciprocal(), aabb, tStart, tEnd, startNormal) != ISECT_RAY_AABB_NONE)
{
// clip the ray with the box
if (tStart > 0.0f)
{
rayStart = rayStart + tStart * dirLen;
tClipStart = tStart;
}
if (tEnd < 1.0f)
{
rayEnd = rayStart + tEnd * dirLen;
tClipEnd = tEnd;
}
return 1;
}
return 0;
}
//! @param aabb Bounds to test against.
//! @param rayStart The start of the ray.
//! @param rayEnd The end of the ray.
//! @param[out] tClipStart The proportion where the ray enters the \ref Aabb.
//! @param[out] tClipEnd The proportion where the ray exits the \ref Aabb.
//! @return True if the ray was clipped, otherwise false.
bool ClipRayWithAabb(const Aabb& aabb, Vector3& rayStart, Vector3& rayEnd, float& tClipStart, float& tClipEnd);
//! Test segment and aabb where the segment is defined by midpoint
//! midPoint = (p1-p0) * 0.5f and half vector halfVector = p1 - midPoint.
//! the aabb is at the origin and defined by half extents only.
//! @return 1 if the intersect, otherwise 0.
int TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& halfVector, const Vector3& aabbExtends);
//! @param midPoint Midpoint of a line segment.
//! @param halfVector Half vector of an aabb.
//! @param aabbExtends The extends of a bounded box.
//! @return True if the segment and AABB intersect, otherwise false
bool TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& halfVector, const Vector3& aabbExtends);
//! Test if segment specified by points p0 and p1 intersects AABB. \ref TestSegmentAABBOrigin
//! @return 1 if the segment and AABB intersect, otherwise 0.
inline int TestSegmentAABB(const Vector3& p0, const Vector3& p1, const Aabb& aabb)
{
Vector3 e = aabb.GetExtents();
Vector3 d = p1 - p0;
Vector3 m = p0 + p1 - aabb.GetMin() - aabb.GetMax();
return TestSegmentAABBOrigin(m, d, e);
}
//! Test if segment specified by points p0 and p1 intersects AABB. \ref TestSegmentAABBOrigin.
//! @param p0 Segment start point.
//! @param p1 Segment end point.
//! @param aabb Bounded box to test against.
//! @return True if the segment and AABB intersect, otherwise false.
bool TestSegmentAABB(const Vector3& p0, const Vector3& p1, const Aabb& aabb);
//! Ray sphere intersection result types.
enum SphereIsectTypes
enum SphereIsectTypes : AZ::s32
{
ISECT_RAY_SPHERE_SA_INSIDE = -1, // the ray starts inside the cylinder
ISECT_RAY_SPHERE_NONE, // no intersection
ISECT_RAY_SPHERE_ISECT, // along the PQ segment
ISECT_RAY_SPHERE_SA_INSIDE = -1, //!< The ray starts inside the cylinder
ISECT_RAY_SPHERE_NONE, //!< No intersection
ISECT_RAY_SPHERE_ISECT, //!< Along the PQ segment
};
//! IntersectRaySphereOrigin
//! return time t>=0 but not limited, so if you check a segment make sure
//! t <= segmentLen
//! @param rayStart ray start point
//! t <= segmentLen.
//! @param rayStart ray start point.
//! @param rayDirNormalized ray direction normalized.
//! @param shereRadius sphere radius
//! @param shereRadius Radius of sphere at origin.
//! @param time of closest intersection [0,+INF] in relation to the normalized direction.
//! @return \ref SphereIsectTypes
AZ_INLINE int IntersectRaySphereOrigin(
const Vector3& rayStart, const Vector3& rayDirNormalized,
const float sphereRadius, float& t)
{
Vector3 m = rayStart;
float b = m.Dot(rayDirNormalized);
float c = m.Dot(m) - sphereRadius * sphereRadius;
// Exit if r's origin outside s (c > 0)and r pointing away from s (b > 0)
if (c > 0.0f && b > 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
float discr = b * b - c;
// A negative discriminant corresponds to ray missing sphere
if (discr < 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
// Ray now found to intersect sphere, compute smallest t value of intersection
t = -b - Sqrt(discr);
// If t is negative, ray started inside sphere so clamp t to zero
if (t < 0.0f)
{
// t = 0.0f;
return ISECT_RAY_SPHERE_SA_INSIDE; // no hit if inside
}
//q = p + t * d;
return ISECT_RAY_SPHERE_ISECT;
}
//! @return \ref SphereIsectTypes.
SphereIsectTypes IntersectRaySphereOrigin(
const Vector3& rayStart, const Vector3& rayDirNormalized, const float sphereRadius, float& t);
//! Intersect ray (rayStart,rayDirNormalized) and sphere (sphereCenter,sphereRadius) \ref IntersectRaySphereOrigin
inline int IntersectRaySphere(
const Vector3& rayStart, const Vector3& rayDirNormalized, const Vector3& sphereCenter, const float sphereRadius, float& t)
{
return IntersectRaySphereOrigin(rayStart - sphereCenter, rayDirNormalized, sphereRadius, t);
}
//! @param rayStart The start of the ray.
//! @param rayDirNormalized The direction of the ray normalized.
//! @param sphereCenter The center of the sphere.
//! @param sphereRadius Radius of the sphere.
//! @param[out] t Coefficient in the ray's explicit equation from which an
//! intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @return SphereIsectTypes
SphereIsectTypes IntersectRaySphere(
const Vector3& rayStart, const Vector3& rayDirNormalized, const Vector3& sphereCenter, const float sphereRadius, float& t);
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param diskCenter Center point of the disk
//! @param diskRadius Radius of the disk
//! @param diskNormal A normal perpendicular to the disk
//! @param[out] t If returning 1 (indicating a hit), this contains distance from rayOrigin along the normalized rayDir that the hit occured at.
//! @return The number of intersecting points.
int IntersectRayDisk(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& diskCenter, const float diskRadius, const AZ::Vector3& diskNormal, float& t);
//! Intersect ray (rayStarty, rayDirNormalized) and disk (center, radius, normal)
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param diskCenter Center point of the disk.
//! @param diskRadius Radius of the disk.
//! @param diskNormal A normal perpendicular to the disk.
//! @param[out] t If returning 1 (indicating a hit), this contains distance from rayOrigin along the normalized rayDir
//! that the hit occured at.
//! @return False if not interesecting and true if intersecting
bool IntersectRayDisk(
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& diskCenter,
const float diskRadius,
const AZ::Vector3& diskNormal,
float& t);
//! If there is only one intersecting point, the coefficient is stored in \ref t1.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param cylinderEnd1 The center of the circle on one end of the cylinder.
//! @param cylinderDir The direction pointing from \ref cylinderEnd1 to the other end of the cylinder. It has to be unit length.
//! @param cylinderHeight The distance between two centers of the circles on two ends of the cylinder respectively.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param cylinderEnd1 The center of the circle on one end of the cylinder.
//! @param cylinderDir The direction pointing from \ref cylinderEnd1 to the other end of the cylinder. It has to be unit length.
//! @param cylinderHeight The distance between two centers of the circles on two ends of the cylinder respectively.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
int IntersectRayCappedCylinder(
const Vector3& rayOrigin, const Vector3& rayDir,
const Vector3& cylinderEnd1, const Vector3& cylinderDir, float cylinderHeight, float cylinderRadius,
float& t1, float& t2);
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& cylinderEnd1,
const Vector3& cylinderDir,
float cylinderHeight,
float cylinderRadius,
float& t1,
float& t2);
//! If there is only one intersecting point, the coefficient is stored in \ref t1.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param coneApex The apex of the cone.
//! @param coneDir The unit-length direction from the apex to the base.
//! @param coneHeight The height of the cone, from the apex to the base.
//! @param coneBaseRadius The radius of the cone base circle.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param coneApex The apex of the cone.
//! @param coneDir The unit-length direction from the apex to the base.
//! @param coneHeight The height of the cone, from the apex to the base.
//! @param coneBaseRadius The radius of the cone base circle.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
int IntersectRayCone(
const Vector3& rayOrigin, const Vector3& rayDir,
const Vector3& coneApex, const Vector3& coneDir, float coneHeight, float coneBaseRadius,
float& t1, float& t2);
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& coneApex,
const Vector3& coneDir,
float coneHeight,
float coneBaseRadius,
float& t1,
float& t2);
//! Test intersection between a ray and a plane in 3D.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param planePos A point on the plane to test intersection with.
//! @param planeNormal The normal of the plane to test intersection with.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param planePos A point on the plane to test intersection with.
//! @param planeNormal The normal of the plane to test intersection with.
//! @param[out] t The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
int IntersectRayPlane(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& planePos,
const Vector3& planeNormal, float& t);
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& planePos, const Vector3& planeNormal, float& t);
//! Test intersection between a ray and a two-sided quadrilateral defined by four points in 3D.
//! The four points that define the quadrilateral could be passed in with either counter clock-wise
//! The four points that define the quadrilateral could be passed in with either counter clock-wise
//! winding or clock-wise winding.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param vertexA One of the four points that define the quadrilateral.
//! @param vertexB One of the four points that define the quadrilateral.
//! @param vertexC One of the four points that define the quadrilateral.
//! @param vertexD One of the four points that define the quadrilateral.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param vertexA One of the four points that define the quadrilateral.
//! @param vertexB One of the four points that define the quadrilateral.
//! @param vertexC One of the four points that define the quadrilateral.
//! @param vertexD One of the four points that define the quadrilateral.
//! @param[out] t The coefficient in the ray's explicit equation from which the
//! intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
int IntersectRayQuad(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& vertexA,
const Vector3& vertexB, const Vector3& vertexC, const Vector3& vertexD, float& t);
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& vertexA,
const Vector3& vertexB,
const Vector3& vertexC,
const Vector3& vertexD,
float& t);
//! Test intersection between a ray and an oriented box in 3D.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param boxCenter The position of the center of the box.
//! @param boxAxis1 An axis along one dimension of the oriented box.
//! @param boxAxis2 An axis along one dimension of the oriented box.
//! @param boxAxis3 An axis along one dimension of the oriented box.
//! @param boxHalfExtent1 The half extent of the box on the dimension of \ref boxAxis1.
//! @param boxHalfExtent2 The half extent of the box on the dimension of \ref boxAxis2.
//! @param boxHalfExtent3 The half extent of the box on the dimension of \ref boxAxis3.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return 1 if there is an intersection, 0 otherwise.
int IntersectRayBox(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& boxCenter, const Vector3& boxAxis1,
const Vector3& boxAxis2, const Vector3& boxAxis3, float boxHalfExtent1, float boxHalfExtent2, float boxHalfExtent3,
//! Test intersection between a ray and an oriented box in 3D.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param boxCenter The position of the center of the box.
//! @param boxAxis1 An axis along one dimension of the oriented box.
//! @param boxAxis2 An axis along one dimension of the oriented box.
//! @param boxAxis3 An axis along one dimension of the oriented box.
//! @param boxHalfExtent1 The half extent of the box on the dimension of \ref boxAxis1.
//! @param boxHalfExtent2 The half extent of the box on the dimension of \ref boxAxis2.
//! @param boxHalfExtent3 The half extent of the box on the dimension of \ref boxAxis3.
//! @param[out] t The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return true if there is an intersection, false otherwise.
bool IntersectRayBox(
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& boxCenter,
const Vector3& boxAxis1,
const Vector3& boxAxis2,
const Vector3& boxAxis3,
float boxHalfExtent1,
float boxHalfExtent2,
float boxHalfExtent3,
float& t);
//! Test intersection between a ray and an OBB.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param obb The OBB to test for intersection with the ray.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return 1 if there is an intersection, 0 otherwise.
int IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t);
//! @param[out] t The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return True if there is an intersection, false otherwise.
bool IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t);
//! Ray cylinder intersection types.
enum CylinderIsectTypes
enum CylinderIsectTypes : AZ::s32
{
RR_ISECT_RAY_CYL_SA_INSIDE = -1, // the ray starts inside the cylinder
RR_ISECT_RAY_CYL_NONE, // no intersection
RR_ISECT_RAY_CYL_PQ, // along the PQ segment
RR_ISECT_RAY_CYL_P_SIDE, // on the P side
RR_ISECT_RAY_CYL_Q_SIDE, // on the Q side
RR_ISECT_RAY_CYL_SA_INSIDE = -1, //!< the ray starts inside the cylinder
RR_ISECT_RAY_CYL_NONE, //!< no intersection
RR_ISECT_RAY_CYL_PQ, //!< along the PQ segment
RR_ISECT_RAY_CYL_P_SIDE, //!< on the P side
RR_ISECT_RAY_CYL_Q_SIDE, //!< on the Q side
};
//! Reference: Real-Time Collision Detection - 5.3.7 Intersecting Ray or Segment Against Cylinder
//! Intersect segment S(t)=sa+t(dir), 0<=t<=1 against cylinder specified by p, q and r.
int IntersectSegmentCylinder(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q,
const float r, float& t);
//! @param sa The initial point.
//! @param dir Magnitude and direction for sa.
//! @param p Center point of side 1 cylinder.
//! @param q Center point of side 2 cylinder.
//! @param r Radius of cylinder.
//! @param[out] t Proporition along line segment.
//! @return CylinderIsectTypes
CylinderIsectTypes IntersectSegmentCylinder(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t);
//! Capsule ray intersect types.
enum CapsuleIsectTypes
{
ISECT_RAY_CAPSULE_SA_INSIDE = -1, // the ray starts inside the cylinder
ISECT_RAY_CAPSULE_NONE, // no intersection
ISECT_RAY_CAPSULE_PQ, // along the PQ segment
ISECT_RAY_CAPSULE_P_SIDE, // on the P side
ISECT_RAY_CAPSULE_Q_SIDE, // on the Q side
ISECT_RAY_CAPSULE_SA_INSIDE = -1, //!< The ray starts inside the cylinder
ISECT_RAY_CAPSULE_NONE, //!< No intersection
ISECT_RAY_CAPSULE_PQ, //!< Along the PQ segment
ISECT_RAY_CAPSULE_P_SIDE, //!< On the P side
ISECT_RAY_CAPSULE_Q_SIDE, //!< On the Q side
};
//! This is a quick implementation of segment capsule based on segment cylinder \ref IntersectSegmentCylinder
//! segment sphere intersection. We can optimize it a lot once we fix the ray
//! cylinder intersection.
int IntersectSegmentCapsule(
const Vector3& sa, const Vector3& dir, const Vector3& p,
const Vector3& q, const float r, float& t);
//! @param sa The beginning of the line segment.
//! @param dir The direction and length of the segment.
//! @param p Center point of side 1 capsule.
//! @param q Center point of side 1 capsule.
//! @param r The radius of the capsule.
//! @param[out] t Proporition along line segment.
//! @return CapsuleIsectTypes
CapsuleIsectTypes IntersectSegmentCapsule(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t);
//! Intersect segment S(t)=A+t(B-A), 0<=t<=1 against convex polyhedron specified
//! by the n halfspaces defined by the planes p[]. On exit tfirst and tlast
//! define the intersection, if any.
int IntersectSegmentPolyhedron(
const Vector3& sa, const Vector3& sBA, const Plane p[], int numPlanes,
float& tfirst, float& tlast, int& iFirstPlane, int& iLastPlane);
//! @param sa The beggining of the line segment.
//! @param dir The direction and length of the segment.
//! @param p Planes that compose a convex ponvex polyhedron.
//! @param numPlanes number of planes.
//! @param[out] tfirst Proportion along the line segment where the line enters.
//! @param[out] tlast Proportion along the line segment where the line exits.
//! @param[out] iFirstPlane The plane where the line enters.
//! @param[out] iLastPlane The plane where the line exits.
//! @return True if intersects else false.
bool IntersectSegmentPolyhedron(
const Vector3& sa,
const Vector3& dir,
const Plane p[],
int numPlanes,
float& tfirst,
float& tlast,
int& iFirstPlane,
int& iLastPlane);
//! Calculate the line segment closestPointSegment1<->closestPointSegment2 that is the shortest route between
//! two segments segment1Start<->segment1End and segment2Start<->segment2End. Also calculate the values of segment1Proportion and segment2Proportion where
//! closestPointSegment1 = segment1Start + (segment1Proportion * (segment1End - segment1Start))
//! two segments segment1Start<->segment1End and segment2Start<->segment2End. Also calculate the values of segment1Proportion and
//! segment2Proportion where closestPointSegment1 = segment1Start + (segment1Proportion * (segment1End - segment1Start))
//! closestPointSegment2 = segment2Start + (segment2Proportion * (segment2End - segment2Start))
//! If segments are parallel returns a solution.
//! @param segment1Start Start of segment 1.
//! @param segment1End End of segment 1.
//! @param segment2Start Start of segment 2.
//! @param segment2End End of segment 2.
//! @param[out] segment1Proportion The proporition along segment 1 [0..1]
//! @param[out] segment2Proportion The proporition along segment 2 [0..1]
//! @param[out] closestPointSegment1 Closest point on segment 1.
//! @param[out] closestPointSegment2 Closest point on segment 2.
//! @param epsilon The minimum square distance where a line segment can be treated as a single point.
void ClosestSegmentSegment(
const Vector3& segment1Start, const Vector3& segment1End,
const Vector3& segment2Start, const Vector3& segment2End,
float& segment1Proportion, float& segment2Proportion,
Vector3& closestPointSegment1, Vector3& closestPointSegment2,
const Vector3& segment1Start,
const Vector3& segment1End,
const Vector3& segment2Start,
const Vector3& segment2End,
float& segment1Proportion,
float& segment2Proportion,
Vector3& closestPointSegment1,
Vector3& closestPointSegment2,
float epsilon = 1e-4f);
//! Calculate the line segment closestPointSegment1<->closestPointSegment2 that is the shortest route between
//! two segments segment1Start<->segment1End and segment2Start<->segment2End.
//! If segments are parallel returns a solution.
//! @param segment1Start Start of segment 1.
//! @param segment1End End of segment 1.
//! @param segment2Start Start of segment 2.
//! @param segment2End End of segment 2.
//! @param[out] closestPointSegment1 Closest point on segment 1.
//! @param[out] closestPointSegment2 Closest point on segment 2.
//! @param epsilon The minimum square distance where a line segment can be treated as a single point.
void ClosestSegmentSegment(
const Vector3& segment1Start, const Vector3& segment1End,
const Vector3& segment2Start, const Vector3& segment2End,
Vector3& closestPointSegment1, Vector3& closestPointSegment2,
const Vector3& segment1Start,
const Vector3& segment1End,
const Vector3& segment2Start,
const Vector3& segment2End,
Vector3& closestPointSegment1,
Vector3& closestPointSegment2,
float epsilon = 1e-4f);
//! Calculate the point (closestPointOnSegment) that is the closest point on
//! segment segmentStart/segmentEnd to point. Also calculate the value of proportion where
//! closestPointOnSegment = segmentStart + (proportion * (segmentEnd - segmentStart))
//! @param point The point to test
//! @param segmentStart The start of the segment
//! @param segmentEnd The end of the segment
//! @param[out] proportion The proportion of the segment L(t) = (end - start) * t
//! @param[out] closestPointOnSegment The point along the line segment
void ClosestPointSegment(
const Vector3& point, const Vector3& segmentStart, const Vector3& segmentEnd,
float& proportion, Vector3& closestPointOnSegment);
}
}
const Vector3& point,
const Vector3& segmentStart,
const Vector3& segmentEnd,
float& proportion,
Vector3& closestPointOnSegment);
} // namespace Intersect
} // namespace AZ
#endif // AZCORE_MATH_SEGMENT_INTERSECTION_H
#pragma once
#include <AzCore/Math/IntersectSegment.inl>
@@ -0,0 +1,101 @@
/*
* 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
*
*/
namespace AZ
{
namespace Intersect
{
AZ_MATH_INLINE bool ClipRayWithAabb(const Aabb& aabb, Vector3& rayStart, Vector3& rayEnd, float& tClipStart, float& tClipEnd)
{
Vector3 startNormal;
float tStart, tEnd;
Vector3 dirLen = rayEnd - rayStart;
if (IntersectRayAABB(rayStart, dirLen, dirLen.GetReciprocal(), aabb, tStart, tEnd, startNormal) != ISECT_RAY_AABB_NONE)
{
// clip the ray with the box
if (tStart > 0.0f)
{
rayStart = rayStart + tStart * dirLen;
tClipStart = tStart;
}
if (tEnd < 1.0f)
{
rayEnd = rayStart + tEnd * dirLen;
tClipEnd = tEnd;
}
return true;
}
return false;
}
AZ_MATH_INLINE SphereIsectTypes
IntersectRaySphereOrigin(const Vector3& rayStart, const Vector3& rayDirNormalized, const float sphereRadius, float& t)
{
Vector3 m = rayStart;
float b = m.Dot(rayDirNormalized);
float c = m.Dot(m) - sphereRadius * sphereRadius;
// Exit if r's origin outside s (c > 0)and r pointing away from s (b > 0)
if (c > 0.0f && b > 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
float discr = b * b - c;
// A negative discriminant corresponds to ray missing sphere
if (discr < 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
// Ray now found to intersect sphere, compute smallest t value of intersection
t = -b - Sqrt(discr);
// If t is negative, ray started inside sphere so clamp t to zero
if (t < 0.0f)
{
// t = 0.0f;
return ISECT_RAY_SPHERE_SA_INSIDE; // no hit if inside
}
// q = p + t * d;
return ISECT_RAY_SPHERE_ISECT;
}
AZ_MATH_INLINE SphereIsectTypes IntersectRaySphere(const Vector3& rayStart, const Vector3& rayDirNormalized, const Vector3& sphereCenter, const float sphereRadius, float& t)
{
return IntersectRaySphereOrigin(rayStart - sphereCenter, rayDirNormalized, sphereRadius, t);
}
AZ_MATH_INLINE Vector3 LineToPointDistance(const Vector3& s1, const Vector3& s2, const Vector3& p, float& u)
{
const Vector3 s21 = s2 - s1;
// we assume seg1 and seg2 are NOT coincident
AZ_MATH_ASSERT(!s21.IsClose(Vector3(0.0f), 1e-4f), "OK we agreed that we will pass valid segments! (s1 != s2)");
u = LineToPointDistanceTime(s1, s21, p);
return s1 + u * s21;
}
AZ_MATH_INLINE float LineToPointDistanceTime(const Vector3& s1, const Vector3& s21, const Vector3& p)
{
// so u = (p.x - s1.x)*(s2.x - s1.x) + (p.y - s1.y)*(s2.y - s1.y) + (p.z-s1.z)*(s2.z-s1.z) / |s2-s1|^2
return s21.Dot(p - s1) / s21.Dot(s21);
}
AZ_MATH_INLINE bool TestSegmentAABB(const Vector3& p0, const Vector3& p1, const Aabb& aabb)
{
Vector3 e = aabb.GetExtents();
Vector3 d = p1 - p0;
Vector3 m = p0 + p1 - aabb.GetMin() - aabb.GetMax();
return TestSegmentAABBOrigin(m, d, e);
}
} // namespace Intersect
} // namespace AZ
@@ -282,6 +282,7 @@ set(FILES
Math/Internal/VertexContainer.inl
Math/InterpolationSample.h
Math/IntersectPoint.h
Math/IntersectSegment.inl
Math/IntersectSegment.cpp
Math/IntersectSegment.h
Math/MathIntrinsics.h
@@ -17,6 +17,11 @@ namespace AZ
return false;
}
unsigned int StackConverter::FromNative(StackFrame*, unsigned int, void*)
{
return 0;
}
void SymbolStorage::LoadModuleData(const void*, unsigned int)
{}
@@ -78,6 +78,12 @@ StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned
return count;
}
unsigned int StackConverter::FromNative([[maybe_unused]] StackFrame* frames, [[maybe_unused]] unsigned int maxNumOfFrames, [[maybe_unused]] void* nativeContext)
{
AZ_Assert(false, "StackConverter::FromNative() is not supported for UnixLike platform yet");
return 0;
}
void
SymbolStorage::DecodeFrames(const StackFrame* frames, unsigned int numFrames, StackLine* textLines)
{
@@ -1048,9 +1048,9 @@ cleanup:
unsigned int
StackRecorder::Record(StackFrame* frames, unsigned int maxNumOfFrames, unsigned int suppressCount, void* nativeThread)
{
#if defined(AZ_ENABLE_DEBUG_TOOLS)
unsigned int numFrames = 0;
#if defined(AZ_ENABLE_DEBUG_TOOLS)
if (nativeThread == NULL)
{
++suppressCount; // Skip current call
@@ -1079,9 +1079,8 @@ cleanup:
STACKFRAME64 sf;
memset(&sf, 0, sizeof(STACKFRAME64));
DWORD imageType;
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
imageType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = context.Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = context.Rsp;
@@ -1090,8 +1089,7 @@ cleanup:
sf.AddrStack.Mode = AddrModeFlat;
EnterCriticalSection(&g_csDbgHelpDll);
s32 frame = -(s32)suppressCount;
for (; frame < (s32)maxNumOfFrames; ++frame)
for (s32 frame = -static_cast<s32>(suppressCount); frame < static_cast<s32>(maxNumOfFrames); ++frame)
{
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, &context, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
{
@@ -1111,15 +1109,68 @@ cleanup:
}
LeaveCriticalSection(&g_csDbgHelpDll);
}
return numFrames;
}
#else
(void)frames;
(void)maxNumOfFrames;
(void)suppressCount;
(void)nativeThread;
return 0;
AZ_UNUSED(frames);
AZ_UNUSED(maxNumOfFrames);
AZ_UNUSED(suppressCount);
AZ_UNUSED(nativeThread);
#endif // AZ_ENABLE_DEBUG_TOOLS
return numFrames;
}
unsigned int StackConverter::FromNative(StackFrame* frames, unsigned int maxNumOfFrames, void* nativeContext)
{
unsigned int numFrames = 0;
#if defined(AZ_ENABLE_DEBUG_TOOLS)
if (!g_dbgHelpLoaded)
{
LoadDbgHelp();
}
HANDLE hThread;
DuplicateHandle(GetCurrentProcess(), GetCurrentThread(), GetCurrentProcess(), &hThread, 0, false, DUPLICATE_SAME_ACCESS);
PCONTEXT nativeContextType = reinterpret_cast<PCONTEXT>(nativeContext);
STACKFRAME64 sf;
memset(&sf, 0, sizeof(STACKFRAME64));
DWORD imageType = IMAGE_FILE_MACHINE_AMD64;
sf.AddrPC.Offset = nativeContextType->Rip;
sf.AddrPC.Mode = AddrModeFlat;
sf.AddrFrame.Offset = nativeContextType->Rsp;
sf.AddrFrame.Mode = AddrModeFlat;
sf.AddrStack.Offset = nativeContextType->Rsp;
sf.AddrStack.Mode = AddrModeFlat;
EnterCriticalSection(&g_csDbgHelpDll);
for (unsigned int frame = 0; frame < maxNumOfFrames; ++frame)
{
if (!g_StackWalk64(imageType, g_currentProcess, hThread, &sf, nativeContext, 0, g_SymFunctionTableAccess64, g_SymGetModuleBase64, 0))
{
break;
}
if (sf.AddrPC.Offset == sf.AddrReturn.Offset)
{
// "StackWalk64-Endless-Callstack!"
break;
}
frames[numFrames++].m_programCounter = sf.AddrPC.Offset;
}
LeaveCriticalSection(&g_csDbgHelpDll);
#else
AZ_UNUSED(frames);
AZ_UNUSED(maxNumOfFrames);
AZ_UNUSED(nativeContext);
#endif
return numFrames;
}
//////////////////////////////////////////////////////////////////////////
+4 -4
View File
@@ -1413,7 +1413,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(HashedSetDifferentAllocatorFixture, SetTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(HashedSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1435,7 +1435,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
template<typename ContainerType>
class HashedMapContainers
@@ -1811,7 +1811,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(HashedMapDifferentAllocatorFixture, MapTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(HashedMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1833,7 +1833,7 @@ namespace UnitTest
}
} , ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
namespace HashedContainerTransparentTestInternal
{
@@ -1095,7 +1095,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(TreeSetDifferentAllocatorFixture, SetTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeSetDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1117,7 +1117,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeSetDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
{
@@ -1516,7 +1516,7 @@ namespace UnitTest
>;
TYPED_TEST_CASE(TreeMapDifferentAllocatorFixture, MapTemplateConfigs);
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeMapDifferentAllocatorFixture, InsertNodeHandleWithDifferentAllocatorsLogsTraceMessages)
{
using ContainerType = typename TypeParam::ContainerType;
@@ -1538,7 +1538,7 @@ namespace UnitTest
}
}, ".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
TYPED_TEST(TreeMapDifferentAllocatorFixture, SwapMovesElementsWhenAllocatorsDiffer)
{
@@ -1595,7 +1595,7 @@ namespace UnitTest
}
};
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
TEST_F(ThreadEventsDeathTest, UsingClientBus_AvoidsDeadlock)
{
EXPECT_EXIT(
@@ -1608,5 +1608,5 @@ namespace UnitTest
, ::testing::ExitedWithCode(0),".*");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
}
@@ -736,7 +736,10 @@ namespace UnitTest
auto& assetManager = AssetManager::Instance();
AssetBusCallbacks callbacks{};
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
callbacks.SetOnAssetReadyCallback([&, AssetNoRefB](const Asset<AssetData>&, AssetBusCallbacks&)
AZ_POP_DISABLE_WARNING
{
// This callback should run inside the "main thread" dispatch events loop
auto loadAsset = assetManager.GetAsset<AssetWithSerializedData>(AZ::Uuid(AssetNoRefB), AssetLoadBehavior::Default);
@@ -288,6 +288,21 @@ namespace AZ
AZStd::string completeCommand = console->AutoCompleteCommand("testVec3");
AZ_TEST_ASSERT(completeCommand == "testVec3");
}
// Duplicate names
{
// Register two cvars with the same name
auto id = AZ::TypeId();
auto flag = AZ::ConsoleFunctorFlags::Null;
auto signature = AZ::ConsoleFunctor<void, false>::FunctorSignature();
AZ::ConsoleFunctor<void, false> cvarOne(*console, "testAutoCompleteDuplication", "", flag, id, signature);
AZ::ConsoleFunctor<void, false> cvarTwo(*console, "testAutoCompleteDuplication", "", flag, id, signature);
// Autocomplete given name expecting one match (not two)
AZStd::vector<AZStd::string> matches;
AZStd::string completeCommand = console->AutoCompleteCommand("testAutoCompleteD", &matches);
AZ_TEST_ASSERT(matches.size() == 1 && completeCommand == "testAutoCompleteDuplication");
}
}
TEST_F(ConsoleTests, ConsoleFunctor_FreeFunctorExecutionTest)
@@ -109,7 +109,10 @@ namespace AZ::Debug
AZStd::thread threads[totalThreads];
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
threads[threadIndex] = AZStd::thread([&startLogging, &messages]()
AZ_POP_DISABLE_WARNING
{
while (!startLogging)
{
@@ -226,7 +229,10 @@ namespace AZ::Debug
AZStd::thread threads[totalThreads];
for (size_t threadIndex = 0; threadIndex < totalThreads; ++threadIndex)
{
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
threads[threadIndex] = AZStd::thread([&startLogging, &message, &totalRecordsWritten]()
AZ_POP_DISABLE_WARNING
{
AZ_UNUSED(message);
@@ -0,0 +1,31 @@
/*
* 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
*
*/
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
class UnhandledExceptions
: public ScopedAllocatorSetupFixture
{
public:
void causeAccessViolation()
{
int* someVariable = reinterpret_cast<int*>(0);
*someVariable = 0;
}
};
#if GTEST_HAS_DEATH_TEST
TEST_F(UnhandledExceptions, Handle)
{
EXPECT_DEATH(causeAccessViolation(), "");
}
#endif
}
@@ -144,14 +144,13 @@ namespace UnitTest
}
};
#if GTEST_OS_SUPPORTS_DEATH_TEST
// SPEC-2669: Disabled since it is causing hangs on Linux
#if GTEST_HAS_DEATH_TEST
TEST_F(AllocatorsTestFixtureLeakDetectionDeathTest_SKIPCODECOVERAGE, AllocatorLeak)
{
// testing that the TraceBusHook will fail on cause the test to die
EXPECT_DEATH(TestAllocatorLeak(), "");
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Testing ScopedAllocatorSetupFixture. Testing that detects leaks
@@ -597,7 +597,10 @@ namespace AZ::IO
path.InitFromAbsolutePath(m_dummyFilepath);
request->CreateRead(nullptr, buffer.get(), fileSize, path, 0, fileSize);
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback = [&fileSize, this](const FileRequest& request)
AZ_POP_DISABLE_WARNING
{
EXPECT_EQ(request.GetStatus(), AZ::IO::IStreamerTypes::RequestStatus::Completed);
auto& readRequest = AZStd::get<AZ::IO::FileRequest::ReadData>(request.GetCommand());
@@ -639,7 +642,10 @@ namespace AZ::IO
path.InitFromAbsolutePath(m_dummyFilepath);
request->CreateRead(nullptr, buffer, unalignedSize + 4, path, unalignedOffset, unalignedSize);
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback = [unalignedOffset, unalignedSize, this](const FileRequest& request)
AZ_POP_DISABLE_WARNING
{
EXPECT_EQ(request.GetStatus(), AZ::IO::IStreamerTypes::RequestStatus::Completed);
auto& readRequest = AZStd::get<AZ::IO::FileRequest::ReadData>(request.GetCommand());
@@ -784,7 +790,10 @@ namespace AZ::IO
requests[i] = m_context->GetNewInternalRequest();
requests[i]->CreateRead(nullptr, buffers[i].get(), chunkSize, path, i * chunkSize, chunkSize);
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback = [chunkSize, i](const FileRequest& request)
AZ_POP_DISABLE_WARNING
{
EXPECT_EQ(request.GetStatus(), AZ::IO::IStreamerTypes::RequestStatus::Completed);
auto& readRequest = AZStd::get<AZ::IO::FileRequest::ReadData>(request.GetCommand());
@@ -970,7 +979,10 @@ namespace AZ::IO
i * chunkSize
));
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback = [numChunks, &numCallbacks, &waitForReads](FileRequestHandle request)
AZ_POP_DISABLE_WARNING
{
IStreamer* streamer = Interface<IStreamer>::Get();
if (streamer)
@@ -1038,7 +1050,10 @@ namespace AZ::IO
i * chunkSize
));
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback = [numChunks, &waitForReads, &waitForSingleRead, &numReadCallbacks]([[maybe_unused]] FileRequestHandle request)
AZ_POP_DISABLE_WARNING
{
numReadCallbacks++;
if (numReadCallbacks == 1)
@@ -1059,7 +1074,10 @@ namespace AZ::IO
for (size_t i = 0; i < numChunks; ++i)
{
cancels.push_back(m_streamer->Cancel(requests[numChunks - i - 1]));
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback = [&numCancelCallbacks, &waitForCancels, numChunks](FileRequestHandle request)
AZ_POP_DISABLE_WARNING
{
auto result = Interface<IStreamer>::Get()->GetRequestStatus(request);
EXPECT_EQ(result, IStreamerTypes::RequestStatus::Completed);
@@ -327,7 +327,7 @@ namespace JsonSerializationTests
SerializerWithOneType::Unreflect(m_jsonRegistrationContext.get());
}
#if GTEST_OS_SUPPORTS_DEATH_TEST
#if GTEST_HAS_DEATH_TEST
using JsonSerializationDeathTests = JsonRegistrationContextTests;
TEST_F(JsonSerializationDeathTests, DoubleUnregisterSerializer_Asserts)
{
@@ -338,5 +338,6 @@ namespace JsonSerializationTests
}, ".*"
);
}
#endif // GTEST_OS_SUPPORTS_DEATH_TEST
#endif // GTEST_HAS_DEATH_TEST
} //namespace JsonSerializationTests
@@ -363,7 +363,10 @@ namespace AZ
{
constexpr AZStd::array visitTokens = { "Hello", "World", "", "More", "", "", "Tokens" };
size_t visitIndex{};
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto visitor = [&visitIndex, &visitTokens](AZStd::string_view token)
AZ_POP_DISABLE_WARNING
{
if (visitIndex > visitTokens.size())
{
@@ -389,7 +392,10 @@ namespace AZ
{
constexpr AZStd::array visitTokens = { "Hello", "World", "", "More", "", "", "Tokens" };
size_t visitIndex = visitTokens.size() - 1;
AZ_PUSH_DISABLE_WARNING(5233, "-Wunknown-warning-option") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto visitor = [&visitIndex, &visitTokens](AZStd::string_view token)
AZ_POP_DISABLE_WARNING
{
if (visitIndex > visitTokens.size())
{
@@ -72,6 +72,7 @@ set(FILES
Debug/AssetTracking.cpp
Debug/LocalFileEventLoggerTests.cpp
Debug/Trace.cpp
Debug/UnhandledExceptions.cpp
Name/NameJsonSerializerTests.cpp
Name/NameTests.cpp
RTTI/TypeSafeIntegralTests.cpp
@@ -29,6 +29,10 @@ namespace AzFramework
AZStd::vector<AZ::IO::Path> m_absoluteSourcePaths; //!< Where the gem's source path folder are located(as an absolute path)
static constexpr const char* GetGemAssetFolder() { return "Assets"; }
static constexpr const char* GetGemRegistryFolder()
{
return "Registry";
}
};
//! Returns a list of GemInfo of all the gems that are active for the for the specified game project.
@@ -20,6 +20,7 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
@@ -190,6 +191,25 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////////
void InputSystemComponent::Activate()
{
const auto* settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
AZ::u64 value = 0;
if (settingsRegistry->Get(value, "/O3DE/InputSystem/MouseMovementSampleRateHertz"))
{
m_mouseMovementSampleRateHertz = aznumeric_caster(value);
}
if (settingsRegistry->Get(value, "/O3DE/InputSystem/GamepadsEnabled"))
{
m_gamepadsEnabled = aznumeric_caster(value);
}
settingsRegistry->Get(m_keyboardEnabled, "/O3DE/InputSystem/KeyboardEnabled");
settingsRegistry->Get(m_motionEnabled, "/O3DE/InputSystem/MotionEnabled");
settingsRegistry->Get(m_mouseEnabled, "/O3DE/InputSystem/MouseEnabled");
settingsRegistry->Get(m_touchEnabled, "/O3DE/InputSystem/TouchEnabled");
settingsRegistry->Get(m_virtualKeyboardEnabled, "/O3DE/InputSystem/VirtualKeyboardEnabled");
}
// Create all enabled input devices
CreateEnabledInputDevices();
@@ -1,234 +0,0 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzFramework/Physics/WorldBody.h>
#include <AzFramework/Physics/ShapeConfiguration.h>
namespace
{
class ReflectContext;
}
namespace Physics
{
class ShapeConfiguration;
class World;
class Shape;
/// Default values used for initializing RigidBodySettings.
/// These can be modified by Physics Implementation gems. // O3DE_DEPRECATED(LY-114472) - DefaultRigidBodyConfiguration values are not shared across modules.
// Use RigidBodyConfiguration default values.
struct DefaultRigidBodyConfiguration
{
static float m_mass;
static bool m_computeInertiaTensor;
static float m_linearDamping;
static float m_angularDamping;
static float m_sleepMinEnergy;
static float m_maxAngularVelocity;
};
enum class MassComputeFlags : AZ::u8
{
NONE = 0,
//! Flags indicating whether a certain mass property should be auto-computed or not.
COMPUTE_MASS = 1,
COMPUTE_INERTIA = 1 << 1,
COMPUTE_COM = 1 << 2,
//! If set, non-simulated shapes will also be included in the mass properties calculation.
INCLUDE_ALL_SHAPES = 1 << 3,
DEFAULT = COMPUTE_COM | COMPUTE_INERTIA | COMPUTE_MASS
};
class RigidBodyConfiguration
: public WorldBodyConfiguration
{
public:
AZ_CLASS_ALLOCATOR(RigidBodyConfiguration, AZ::SystemAllocator, 0);
AZ_RTTI(RigidBodyConfiguration, "{ACFA8900-8530-4744-AF00-AA533C868A8E}", WorldBodyConfiguration);
static void Reflect(AZ::ReflectContext* context);
enum PropertyVisibility : AZ::u16
{
InitialVelocities = 1 << 0, ///< Whether the initial linear and angular velocities are visible.
InertiaProperties = 1 << 1, ///< Whether the whole category of inertia properties (mass, compute inertia,
///< inertia tensor etc) is visible.
Damping = 1 << 2, ///< Whether linear and angular damping are visible.
SleepOptions = 1 << 3, ///< Whether the sleep threshold and start asleep options are visible.
Interpolation = 1 << 4, ///< Whether the interpolation option is visible.
Gravity = 1 << 5, ///< Whether the effected by gravity option is visible.
Kinematic = 1 << 6, ///< Whether the option to make the body kinematic is visible.
ContinuousCollisionDetection = 1 << 7, ///< Whether the option to enable continuous collision detection is visible.
MaxVelocities = 1 << 8 ///< Whether upper limits on velocities are visible.
};
RigidBodyConfiguration() = default;
RigidBodyConfiguration(const RigidBodyConfiguration& settings) = default;
// Visibility functions.
AZ::Crc32 GetPropertyVisibility(PropertyVisibility property) const;
void SetPropertyVisibility(PropertyVisibility property, bool isVisible);
AZ::Crc32 GetInitialVelocitiesVisibility() const;
/// Returns whether the whole category of inertia settings (mass, inertia, center of mass offset etc) is visible.
AZ::Crc32 GetInertiaSettingsVisibility() const;
/// Returns whether the individual inertia tensor field is visible or is hidden because the compute inertia option is selected.
AZ::Crc32 GetInertiaVisibility() const;
/// Returns whether the mass field is visible or is hidden because compute mass option is selected.
AZ::Crc32 GetMassVisibility() const;
/// Returns whether the individual centre of mass offset field is visible or is hidden because compute CoM option is selected.
AZ::Crc32 GetCoMVisibility() const;
AZ::Crc32 GetDampingVisibility() const;
AZ::Crc32 GetSleepOptionsVisibility() const;
AZ::Crc32 GetInterpolationVisibility() const;
AZ::Crc32 GetGravityVisibility() const;
AZ::Crc32 GetKinematicVisibility() const;
AZ::Crc32 GetCCDVisibility() const;
AZ::Crc32 GetMaxVelocitiesVisibility() const;
MassComputeFlags GetMassComputeFlags() const;
void SetMassComputeFlags(MassComputeFlags flags);
bool IsCCDEnabled() const;
// Basic initial settings.
AZ::Vector3 m_initialLinearVelocity = AZ::Vector3::CreateZero();
AZ::Vector3 m_initialAngularVelocity = AZ::Vector3::CreateZero();
AZ::Vector3 m_centerOfMassOffset = AZ::Vector3::CreateZero();
// Simulation parameters.
float m_mass = DefaultRigidBodyConfiguration::m_mass;
AZ::Matrix3x3 m_inertiaTensor = AZ::Matrix3x3::CreateIdentity();
float m_linearDamping = DefaultRigidBodyConfiguration::m_linearDamping;
float m_angularDamping = DefaultRigidBodyConfiguration::m_angularDamping;
float m_sleepMinEnergy = DefaultRigidBodyConfiguration::m_sleepMinEnergy;
float m_maxAngularVelocity = DefaultRigidBodyConfiguration::m_maxAngularVelocity;
// Visibility settings.
AZ::u16 m_propertyVisibilityFlags = (std::numeric_limits<AZ::u16>::max)();
bool m_startAsleep = false;
bool m_interpolateMotion = false;
bool m_gravityEnabled = true;
bool m_simulated = true;
bool m_kinematic = false;
bool m_ccdEnabled = false; ///< Whether continuous collision detection is enabled.
float m_ccdMinAdvanceCoefficient = 0.15f; ///< Coefficient affecting how granularly time is subdivided in CCD.
bool m_ccdFrictionEnabled = false; ///< Whether friction is applied when resolving CCD collisions.
bool m_computeCenterOfMass = true;
bool m_computeInertiaTensor = true;
bool m_computeMass = true;
//! If set, non-simulated shapes will also be included in the mass properties calculation.
bool m_includeAllShapesInMassCalculation = false;
};
/// Dynamic rigid body.
class RigidBody
: public WorldBody
{
public:
AZ_CLASS_ALLOCATOR(RigidBody, AZ::SystemAllocator, 0);
AZ_RTTI(RigidBody, "{156E459F-7BB7-4B4E-ADA0-2130D96B7E80}", WorldBody);
public:
RigidBody() = default;
explicit RigidBody(const RigidBodyConfiguration& settings);
virtual void AddShape(AZStd::shared_ptr<Shape> shape) = 0;
virtual void RemoveShape(AZStd::shared_ptr<Shape> shape) = 0;
virtual AZ::u32 GetShapeCount() { return 0; }
virtual AZStd::shared_ptr<Shape> GetShape(AZ::u32 /*index*/) { return nullptr; }
virtual AZ::Vector3 GetCenterOfMassWorld() const = 0;
virtual AZ::Vector3 GetCenterOfMassLocal() const = 0;
virtual AZ::Matrix3x3 GetInverseInertiaWorld() const = 0;
virtual AZ::Matrix3x3 GetInverseInertiaLocal() const = 0;
virtual float GetMass() const = 0;
virtual float GetInverseMass() const = 0;
virtual void SetMass(float mass) = 0;
virtual void SetCenterOfMassOffset(const AZ::Vector3& comOffset) = 0;
/// Retrieves the velocity at center of mass; only linear velocity, no rotational velocity contribution.
virtual AZ::Vector3 GetLinearVelocity() const = 0;
virtual void SetLinearVelocity(const AZ::Vector3& velocity) = 0;
virtual AZ::Vector3 GetAngularVelocity() const = 0;
virtual void SetAngularVelocity(const AZ::Vector3& angularVelocity) = 0;
virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) = 0;
virtual void ApplyLinearImpulse(const AZ::Vector3& impulse) = 0;
virtual void ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) = 0;
virtual void ApplyAngularImpulse(const AZ::Vector3& angularImpulse) = 0;
virtual float GetLinearDamping() const = 0;
virtual void SetLinearDamping(float damping) = 0;
virtual float GetAngularDamping() const = 0;
virtual void SetAngularDamping(float damping) = 0;
virtual bool IsAwake() const = 0;
virtual void ForceAsleep() = 0;
virtual void ForceAwake() = 0;
virtual float GetSleepThreshold() const = 0;
virtual void SetSleepThreshold(float threshold) = 0;
virtual bool IsKinematic() const = 0;
virtual void SetKinematic(bool kinematic) = 0;
virtual void SetKinematicTarget(const AZ::Transform& targetPosition) = 0;
virtual bool IsGravityEnabled() const = 0;
virtual void SetGravityEnabled(bool enabled) = 0;
virtual void SetSimulationEnabled(bool enabled) = 0;
virtual void SetCCDEnabled(bool enabled) = 0;
//! Recalculates mass, inertia and center of mass based on the flags passed.
//! @param flags MassComputeFlags specifying which properties should be recomputed.
//! @param centerOfMassOffsetOverride Optional override of the center of mass. Note: This parameter will be ignored if COMPUTE_COM is passed in flags.
//! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags.
//! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags.
virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT,
const AZ::Vector3* centerOfMassOffsetOverride = nullptr,
const AZ::Matrix3x3* inertiaTensorOverride = nullptr,
const float* massOverride = nullptr) = 0;
};
/// Bitwise operators for MassComputeFlags
inline MassComputeFlags operator|(MassComputeFlags lhs, MassComputeFlags rhs)
{
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) | aznumeric_cast<AZ::u8>(rhs));
}
inline MassComputeFlags operator&(MassComputeFlags lhs, MassComputeFlags rhs)
{
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) & aznumeric_cast<AZ::u8>(rhs));
}
/// Static rigid body.
class RigidBodyStatic
: public WorldBody
{
public:
AZ_CLASS_ALLOCATOR(RigidBodyStatic, AZ::SystemAllocator, 0);
AZ_RTTI(RigidBodyStatic, "{13A677BB-7085-4EDB-BCC8-306548238692}", WorldBody);
virtual void AddShape(const AZStd::shared_ptr<Shape>& shape) = 0;
virtual AZ::u32 GetShapeCount() { return 0; }
virtual AZStd::shared_ptr<Shape> GetShape(AZ::u32 /*index*/) { return nullptr; }
};
} // namespace Physics
@@ -89,9 +89,9 @@ namespace AzPhysics
//! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags.
//! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags.
virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT,
const AZ::Vector3* centerOfMassOffsetOverride = nullptr,
const AZ::Matrix3x3* inertiaTensorOverride = nullptr,
const float* massOverride = nullptr) = 0;
const AZ::Vector3& centerOfMassOffsetOverride = AZ::Vector3::CreateZero(),
const AZ::Matrix3x3& inertiaTensorOverride = AZ::Matrix3x3::CreateIdentity(),
const float massOverride = 1.0f) = 0;
};
} // namespace AzPhysics
@@ -22,6 +22,7 @@ namespace AzFramework
->Field("terminationTime", &SessionConfig::m_terminationTime)
->Field("creatorId", &SessionConfig::m_creatorId)
->Field("sessionProperties", &SessionConfig::m_sessionProperties)
->Field("matchmakingData", &SessionConfig::m_matchmakingData)
->Field("sessionId", &SessionConfig::m_sessionId)
->Field("sessionName", &SessionConfig::m_sessionName)
->Field("dnsName", &SessionConfig::m_dnsName)
@@ -46,6 +47,8 @@ namespace AzFramework
"CreatorId", "A unique identifier for a player or entity creating the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_matchmakingData,
"MatchmakingData", "The matchmaking process information that was used to create the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId,
"SessionId", "A unique identifier for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName,
@@ -35,6 +35,9 @@ namespace AzFramework
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// The matchmaking process information that was used to create the session.
AZStd::string m_matchmakingData;
// A unique identifier for the session.
AZStd::string m_sessionId;
@@ -41,6 +41,11 @@ namespace AzFramework
// OnDestroySessionBegin is fired at the beginning of session termination
// @return The result of all OnDestroySessionBegin notifications
virtual bool OnDestroySessionBegin() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update
// @param sessionConfig The properties to describe a session
// @param updateReason The reason for session update
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
};
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework
@@ -61,6 +61,10 @@ namespace AzFramework
//! be deleted and the spawnable asset to be released. This call is automatically done when
//! AssignRootSpawnable is called while a root spawnable is assigned.
virtual void ReleaseRootSpawnable() = 0;
//! Force processing all SpawnableEntitiesManager requests immediately
//! This is useful when loading a different level while SpawnableEntitiesManager still has
//! pending requests
virtual void ProcessSpawnableQueue() = 0;
};
using RootSpawnableInterface = AZ::Interface<RootSpawnableDefinition>;
@@ -45,8 +45,7 @@ namespace AzFramework
void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
{
m_entitiesManager.ProcessQueue(
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
ProcessSpawnableQueue();
RootSpawnableNotificationBus::ExecuteQueuedEvents();
}
@@ -121,6 +120,12 @@ namespace AzFramework
m_rootSpawnableId = AZ::Data::AssetId();
}
void SpawnableSystemComponent::ProcessSpawnableQueue()
{
m_entitiesManager.ProcessQueue(
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
}
void SpawnableSystemComponent::OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
[[maybe_unused]] uint32_t generation)
{
@@ -161,6 +166,8 @@ namespace AzFramework
void SpawnableSystemComponent::Deactivate()
{
ProcessSpawnableQueue();
m_registryChangeHandler.Disconnect();
AZ::TickBus::Handler::BusDisconnect();
@@ -75,6 +75,7 @@ namespace AzFramework
uint64_t AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable) override;
void ReleaseRootSpawnable() override;
void ProcessSpawnableQueue() override;
//
// RootSpawnbleNotificationBus
@@ -23,10 +23,12 @@ namespace AzFramework
virtual ~XcbEventHandler() = default;
virtual void HandleXcbEvent(xcb_generic_event_t* event) = 0;
// ATTN This is used as a workaround for RAW Input events when using the Editor.
virtual void PollSpecialEvents(){};
};
class XcbEventHandlerBusTraits
: public AZ::EBusTraits
class XcbEventHandlerBusTraits : public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
@@ -0,0 +1,652 @@
/*
* 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
*
*/
#include <AzCore/std/typetraits/integral_constant.h>
#include <AzFramework/API/ApplicationAPI_Linux.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInputDeviceMouse.h>
namespace AzFramework
{
xcb_window_t GetSystemCursorFocusWindow()
{
void* systemCursorFocusWindow = nullptr;
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
if (!systemCursorFocusWindow)
{
return XCB_NONE;
}
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
}
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
xcb_screen_t* XcbInputDeviceMouse::s_xcbScreen = nullptr;
bool XcbInputDeviceMouse::m_xfixesInitialized = false;
bool XcbInputDeviceMouse::m_xInputInitialized = false;
XcbInputDeviceMouse::XcbInputDeviceMouse(InputDeviceMouse& inputDevice)
: InputDeviceMouse::Implementation(inputDevice)
, m_systemCursorState(SystemCursorState::Unknown)
, m_systemCursorPositionNormalized(0.5f, 0.5f)
, m_prevConstraintWindow(XCB_NONE)
, m_focusWindow(XCB_NONE)
, m_cursorShown(true)
{
XcbEventHandlerBus::Handler::BusConnect();
SetSystemCursorState(SystemCursorState::Unknown);
}
XcbInputDeviceMouse::~XcbInputDeviceMouse()
{
XcbEventHandlerBus::Handler::BusDisconnect();
SetSystemCursorState(SystemCursorState::Unknown);
}
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
{
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
if (!interface)
{
AZ_Warning("XcbInput", false, "XCB interface not available");
return nullptr;
}
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
if (!s_xcbConnection)
{
AZ_Warning("XcbInput", false, "XCB connection not available");
return nullptr;
}
const xcb_setup_t* xcbSetup = xcb_get_setup(s_xcbConnection);
s_xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
if (!s_xcbScreen)
{
AZ_Warning("XcbInput", false, "XCB screen not available");
return nullptr;
}
// Initialize XFixes extension which we use to create pointer barriers.
if (!InitializeXFixes())
{
AZ_Warning("XcbInput", false, "XCB XFixes initialization failed");
return nullptr;
}
// Initialize XInput extension which is used to get RAW Input events.
if (!InitializeXInput())
{
AZ_Warning("XcbInput", false, "XCB XInput initialization failed");
return nullptr;
}
return aznew XcbInputDeviceMouse(inputDevice);
}
bool XcbInputDeviceMouse::IsConnected() const
{
return true;
}
void XcbInputDeviceMouse::CreateBarriers(xcb_window_t window, bool create)
{
// Don't create any barriers if we are debugging. This will cause artifacts but better then
// a confined cursor during debugging.
if (AZ::Debug::Trace::IsDebuggerPresent())
{
AZ_Warning("XcbInput", false, "Debugger running. Barriers will not be created.");
return;
}
if (create)
{
// Destroy barriers if they are active already.
if (!m_activeBarriers.empty())
{
for (const auto& barrier : m_activeBarriers)
{
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
}
m_activeBarriers.clear();
}
// Get window information.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xcbGeometryReply)
{
return;
}
const xcb_translate_coordinates_cookie_t translate_coord =
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
s_xcbConnection, translate_coord, NULL) };
if (!xkbTranslateCoordReply)
{
return;
}
const int16_t x0 = xkbTranslateCoordReply->dst_x < 0 ? 0 : xkbTranslateCoordReply->dst_x;
const int16_t y0 = xkbTranslateCoordReply->dst_y < 0 ? 0 : xkbTranslateCoordReply->dst_y;
const int16_t x1 = xkbTranslateCoordReply->dst_x + xcbGeometryReply->width;
const int16_t y1 = xkbTranslateCoordReply->dst_y + xcbGeometryReply->height;
// ATTN For whatever reason, when making an exact rectangle the pointer will escape the top right corner in some cases. Adding
// an offset to the lines so that they cross each other prevents that.
const int16_t offset = 30;
// Create the left barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_X, x0, Clamp(y0 - offset),
x0, Clamp(y1 + offset) });
// Create the right barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_X, x1, Clamp(y0 - offset),
x1, Clamp(y1 + offset) });
// Create the top barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_Y, Clamp(x0 - offset), y0,
Clamp(x1 + offset), y0 });
// Create the bottom barrier info.
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_Y, Clamp(x0 - offset), y1,
Clamp(x1 + offset), y1 });
// Create the xfixes barriers.
for (const auto& barrier : m_activeBarriers)
{
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
AZ_Warning(
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
barrier.x1, barrier.y1);
}
}
else
{
for (const auto& barrier : m_activeBarriers)
{
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
}
m_activeBarriers.clear();
}
xcb_flush(s_xcbConnection);
}
bool XcbInputDeviceMouse::InitializeXFixes()
{
m_xfixesInitialized = false;
// We don't have to free query_extension_reply according to xcb documentation.
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_xfixes_id);
if (!query_extension_reply || !query_extension_reply->present)
{
return m_xfixesInitialized;
}
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
xcb_generic_error_t* error = NULL;
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
s_xcbConnection, query_cookie, &error) };
if (!xkbQueryRequestReply || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving XFixes version failed : Error code %d", error->error_code);
free(error);
}
return m_xfixesInitialized;
}
else if (xkbQueryRequestReply->major_version < 5)
{
AZ_Warning("XcbInput", false, "XFixes version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
return m_xfixesInitialized;
}
m_xfixesInitialized = true;
return m_xfixesInitialized;
}
bool XcbInputDeviceMouse::InitializeXInput()
{
m_xInputInitialized = false;
// We don't have to free query_extension_reply according to xcb documentation.
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_input_id);
if (!query_extension_reply || !query_extension_reply->present)
{
return m_xInputInitialized;
}
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
xcb_generic_error_t* error = NULL;
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
s_xcbConnection, query_version_cookie, &error) };
if (!xkbQueryRequestReply || error)
{
if (error)
{
AZ_Warning("XcbInput", false, "Retrieving XInput version failed : Error code %d", error->error_code);
free(error);
}
return m_xInputInitialized;
}
else if (xkbQueryRequestReply->major_version < 2)
{
AZ_Warning("XcbInput", false, "XInput version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
return m_xInputInitialized;
}
m_xInputInitialized = true;
return m_xInputInitialized;
}
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
{
struct
{
xcb_input_event_mask_t head;
int mask;
} mask;
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
mask.head.mask_len = 1;
if (enable)
{
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
}
else
{
mask.mask = XCB_NONE;
}
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
{
if (systemCursorState != m_systemCursorState)
{
m_systemCursorState = systemCursorState;
m_focusWindow = GetSystemCursorFocusWindow();
HandleCursorState(m_focusWindow, systemCursorState);
}
}
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
{
bool confined = false, cursorShown = true;
switch (systemCursorState)
{
case SystemCursorState::ConstrainedAndHidden:
{
//!< Constrained to the application's main window and hidden
confined = true;
cursorShown = false;
}
break;
case SystemCursorState::ConstrainedAndVisible:
{
//!< Constrained to the application's main window and visible
confined = true;
}
break;
case SystemCursorState::UnconstrainedAndHidden:
{
//!< Free to move outside the main window but hidden while inside
cursorShown = false;
}
break;
case SystemCursorState::UnconstrainedAndVisible:
{
//!< Free to move outside the application's main window and visible
}
case SystemCursorState::Unknown:
default:
break;
}
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
// We need however the window id to reset the cursor.
if (XCB_NONE == window && (confined || cursorShown))
{
// Reuse the previous window to reset states.
window = m_prevConstraintWindow;
m_prevConstraintWindow = XCB_NONE;
}
else
{
// Remember the window we used to modify cursor and barrier states.
m_prevConstraintWindow = window;
}
SetEnableXInput(!cursorShown);
CreateBarriers(window, confined);
ShowCursor(window, cursorShown);
}
SystemCursorState XcbInputDeviceMouse::GetSystemCursorState() const
{
return m_systemCursorState;
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
{
// TODO Basically not done at all. Added only the basic functions needed.
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xkbGeometryReply)
{
return;
}
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
{
return;
}
SetSystemCursorPositionNormalizedInternal(window, positionNormalized);
}
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const
{
AZ::Vector2 position = AZ::Vector2::CreateZero();
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
if (!xkbQueryPointerReply)
{
return position;
}
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
if (!xkbGeometryReply)
{
return position;
}
AZ_Assert(xkbGeometryReply->width != 0, "xkbGeometry response width must be non-zero. (%d)", xkbGeometryReply->width);
const float normalizedCursorPostionX = static_cast<float>(xkbQueryPointerReply->win_x) / xkbGeometryReply->width;
AZ_Assert(xkbGeometryReply->height != 0, "xkbGeometry response height must be non-zero. (%d)", xkbGeometryReply->height);
const float normalizedCursorPostionY = static_cast<float>(xkbQueryPointerReply->win_y) / xkbGeometryReply->height;
position = AZ::Vector2(normalizedCursorPostionX, normalizedCursorPostionY);
return position;
}
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
{
const xcb_window_t window = GetSystemCursorFocusWindow();
if (XCB_NONE == window)
{
return AZ::Vector2::CreateZero();
}
return GetSystemCursorPositionNormalizedInternal(window);
}
void XcbInputDeviceMouse::TickInputDevice()
{
ProcessRawEventQueues();
}
void XcbInputDeviceMouse::ShowCursor(xcb_window_t window, bool show)
{
xcb_void_cookie_t cookie;
if (show)
{
cookie = xcb_xfixes_show_cursor_checked(s_xcbConnection, window);
}
else
{
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
}
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
if (xkbError)
{
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
return;
}
// ATTN In the following part we will when cursor gets hidden store the position of the cursor in screen space
// not window space. We use that to re-position when showing the cursor again. Is this the correct
// behavior?
const bool cursorWasHidden = !m_cursorShown;
m_cursorShown = show;
if (!m_cursorShown)
{
m_cursorHiddenPosition = GetSystemCursorPositionNormalizedInternal(s_xcbScreen->root);
SetSystemCursorPositionNormalized(AZ::Vector2(0.5f, 0.5f));
}
else if (cursorWasHidden)
{
SetSystemCursorPositionNormalizedInternal(s_xcbScreen->root, m_cursorHiddenPosition);
}
xcb_flush(s_xcbConnection);
}
void XcbInputDeviceMouse::HandleButtonPressEvents(uint32_t detail, bool pressed)
{
bool isWheel;
float wheelDirection;
const auto* button = InputChannelFromMouseEvent(detail, isWheel, wheelDirection);
if (button)
{
QueueRawButtonEvent(*button, pressed);
}
if (isWheel)
{
float axisValue = MAX_XI_WHEEL_SENSITIVITY * wheelDirection;
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, axisValue);
}
}
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
{
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
}
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
switch (genericEvent->event_type)
{
case XCB_INPUT_RAW_BUTTON_PRESS:
{
const xcb_input_raw_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_raw_button_press_event_t*>(event);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_RAW_BUTTON_RELEASE:
{
const xcb_input_raw_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_raw_button_release_event_t*>(event);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_RAW_MOTION:
{
const xcb_input_raw_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_raw_motion_event_t*>(event);
int axisLen = xcb_input_raw_button_press_axisvalues_length(mouseMotionEvent);
const xcb_input_fp3232_t* axisvalues = xcb_input_raw_button_press_axisvalues_raw(mouseMotionEvent);
for (int i = 0; i < axisLen; ++i)
{
const float axisValue = fp3232ToFloat(axisvalues[i]);
switch (i)
{
case 0:
QueueRawMovementEvent(InputDeviceMouse::Movement::X, axisValue);
break;
case 1:
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, axisValue);
break;
}
}
}
break;
}
}
void XcbInputDeviceMouse::PollSpecialEvents()
{
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
{
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
switch (genericEvent->response_type & ~0x80)
{
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
// Only handle raw inputs if we have focus.
// Handle Raw Input events first.
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(geGenericEvent);
free(genericEvent);
}
}
break;
}
}
}
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
{
switch (event->response_type & ~0x80)
{
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
case XCB_GE_GENERIC:
{
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
// not handling RAW input events and passing to.
if (!m_cursorShown)
{
// Handle Raw Input events first.
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
{
HandleRawInputEvents(genericEvent);
}
}
else
{
switch (genericEvent->event_type)
{
case XCB_INPUT_BUTTON_PRESS:
{
const xcb_input_button_press_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, true);
}
break;
case XCB_INPUT_BUTTON_RELEASE:
{
const xcb_input_button_release_event_t* mouseButtonEvent =
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
HandleButtonPressEvents(mouseButtonEvent->detail, false);
}
break;
case XCB_INPUT_MOTION:
{
HandlePointerMotionEvents(event);
}
break;
}
}
}
break;
case XCB_FOCUS_IN:
{
const xcb_focus_in_event_t* focusInEvent = reinterpret_cast<const xcb_focus_in_event_t*>(event);
if (m_focusWindow != focusInEvent->event)
{
m_focusWindow = focusInEvent->event;
HandleCursorState(m_focusWindow, m_systemCursorState);
}
}
break;
case XCB_FOCUS_OUT:
{
const xcb_focus_out_event_t* focusOutEvent = reinterpret_cast<const xcb_focus_out_event_t*>(event);
HandleCursorState(focusOutEvent->event, SystemCursorState::UnconstrainedAndVisible);
ProcessRawEventQueues();
ResetInputChannelStates();
m_focusWindow = XCB_NONE;
}
break;
}
}
} // namespace AzFramework
@@ -0,0 +1,193 @@
/*
* 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
*
*/
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbEventHandler.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xfixes.h>
#include <xcb/xinput.h>
// The maximum number of raw input axis this mouse device supports.
constexpr uint32_t MAX_XI_RAW_AXIS = 2;
// The sensitivity of the wheel.
constexpr float MAX_XI_WHEEL_SENSITIVITY = 140.0f;
namespace AzFramework
{
class XcbInputDeviceMouse
: public InputDeviceMouse::Implementation
, public XcbEventHandlerBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(XcbInputDeviceMouse, AZ::SystemAllocator, 0);
////////////////////////////////////////////////////////////////////////////////////////////
//! Constructor
//! \param[in] inputDevice Reference to the input device being implemented
XcbInputDeviceMouse(InputDeviceMouse& inputDevice);
////////////////////////////////////////////////////////////////////////////////////////////
//! Destructor
virtual ~XcbInputDeviceMouse();
static XcbInputDeviceMouse::Implementation* Create(InputDeviceMouse& inputDevice);
protected:
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
bool IsConnected() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
void SetSystemCursorState(SystemCursorState systemCursorState) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
SystemCursorState GetSystemCursorState() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
////////////////////////////////////////////////////////////////////////////////////////////
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
void TickInputDevice() override;
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
void PollSpecialEvents() override;
//! Handle X11 events.
void HandleXcbEvent(xcb_generic_event_t* event) override;
//! Initialize XFixes extension. Used for barriers.
static bool InitializeXFixes();
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
static bool InitializeXInput();
//! Enables/Disables XInput Raw Input events.
void SetEnableXInput(bool enable);
//! Create barriers.
void CreateBarriers(xcb_window_t window, bool create);
//! Helper function.
void SystemCursorStateToLogic(SystemCursorState systemCursorState, bool& confined, bool& cursorShown);
//! Shows/Hides the cursor.
void ShowCursor(xcb_window_t window, bool show);
//! Get the normalized cursor position. The coordinates returned are relative to the specified window.
AZ::Vector2 GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const;
//! Set the normalized cursor position. The normalized position will be relative to the specified window.
void SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized);
//! Handle button press/release events.
void HandleButtonPressEvents(uint32_t detail, bool pressed);
//! Handle motion notify events.
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
//! Will set cursor states and confinement modes.
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
//! Will handle all raw input events.
void HandleRawInputEvents(const xcb_ge_generic_event_t* event);
//! Convert XInput fp1616 to float.
inline float fp1616ToFloat(xcb_input_fp1616_t value) const
{
return static_cast<float>((value >> 16) + (value & 0xffff) / 0xffff);
}
//! Convert XInput fp3232 to float.
inline float fp3232ToFloat(xcb_input_fp3232_t value) const
{
return static_cast<float>(value.integral) + static_cast<float>(value.frac / (float)(1ull << 32));
}
const InputChannelId* InputChannelFromMouseEvent(xcb_button_t button, bool& isWheel, float& direction) const
{
isWheel = false;
direction = 1.0f;
switch (button)
{
case XCB_BUTTON_INDEX_1:
return &InputDeviceMouse::Button::Left;
case XCB_BUTTON_INDEX_2:
return &InputDeviceMouse::Button::Right;
case XCB_BUTTON_INDEX_3:
return &InputDeviceMouse::Button::Middle;
case XCB_BUTTON_INDEX_4:
isWheel = true;
direction = 1.0f;
break;
case XCB_BUTTON_INDEX_5:
isWheel = true;
direction = -1.0f;
break;
default:
break;
}
return nullptr;
}
// Barriers work only with positive values. We clamp here to zero.
inline int16_t Clamp(int16_t value) const
{
return value < 0 ? 0 : value;
}
private:
//! The current system cursor state
SystemCursorState m_systemCursorState;
//! The cursor position before it got hidden.
AZ::Vector2 m_cursorHiddenPosition;
AZ::Vector2 m_systemCursorPositionNormalized;
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
static xcb_connection_t* s_xcbConnection;
static xcb_screen_t* s_xcbScreen;
//! Will be true if the xfixes extension could be initialized.
static bool m_xfixesInitialized;
//! Will be true if the xinput2 extension could be initialized.
static bool m_xInputInitialized;
//! The window that had focus
xcb_window_t m_prevConstraintWindow;
//! The current window that has focus
xcb_window_t m_focusWindow;
//! Will be true if the cursor is shown else false.
bool m_cursorShown;
struct XFixesBarrierProperty
{
xcb_xfixes_barrier_t id;
uint32_t direction;
int16_t x0, y0, x1, y1;
};
//! Array that holds barrier information used to confine the cursor.
std::vector<XFixesBarrierProperty> m_activeBarriers;
};
} // namespace AzFramework
@@ -8,24 +8,31 @@
#include <AzFramework/Application/Application.h>
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/XcbNativeWindow.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInterface.h>
#include <AzFramework/XcbNativeWindow.h>
#include <xcb/xcb.h>
namespace AzFramework
{
[[maybe_unused]] const char XcbErrorWindow[] = "XcbNativeWindow";
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
static constexpr uint8_t s_XcbFormatDataSize = 32; // Format indicator for xcb for client messages
static constexpr uint16_t s_DefaultXcbWindowBorderWidth = 4; // The default border with in pixels if a border was specified
static constexpr uint8_t s_XcbResponseTypeMask = 0x7f; // Mask to extract the specific event type from an xcb event
#define _NET_WM_STATE_REMOVE 0l
#define _NET_WM_STATE_ADD 1l
#define _NET_WM_STATE_TOGGLE 2l
////////////////////////////////////////////////////////////////////////////////////////////////
XcbNativeWindow::XcbNativeWindow()
XcbNativeWindow::XcbNativeWindow()
: NativeWindow::Implementation()
, m_xcbConnection(nullptr)
, m_xcbRootScreen(nullptr)
, m_xcbWindow(XCB_NONE)
{
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get();
xcbConnectionManager != nullptr)
if (auto xcbConnectionManager = AzFramework::XcbConnectionManagerInterface::Get(); xcbConnectionManager != nullptr)
{
m_xcbConnection = xcbConnectionManager->GetXcbConnection();
}
@@ -33,89 +40,184 @@ namespace AzFramework
}
////////////////////////////////////////////////////////////////////////////////////////////////
XcbNativeWindow::~XcbNativeWindow() = default;
XcbNativeWindow::~XcbNativeWindow()
{
if (XCB_NONE != m_xcbWindow)
{
xcb_destroy_window(m_xcbConnection, m_xcbWindow);
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks)
void XcbNativeWindow::InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks)
{
// Get the parent window
// Get the parent window
const xcb_setup_t* xcbSetup = xcb_get_setup(m_xcbConnection);
xcb_screen_t* xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_window_t xcbParentWindow = xcbRootScreen->root;
m_xcbRootScreen = xcb_setup_roots_iterator(xcbSetup).data;
xcb_window_t xcbParentWindow = m_xcbRootScreen->root;
// Create an XCB window from the connection
m_xcbWindow = xcb_generate_id(m_xcbConnection);
uint16_t borderWidth = 0;
const uint32_t mask = styleMasks.m_platformAgnosticStyleMask;
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) ||
(mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
if ((mask & WindowStyleMasks::WINDOW_STYLE_BORDERED) || (mask & WindowStyleMasks::WINDOW_STYLE_RESIZEABLE))
{
borderWidth = s_DefaultXcbWindowBorderWidth;
}
uint32_t eventMask = XCB_CW_BACK_PIXEL | XCB_CW_EVENT_MASK;
const uint32_t interestedEvents =
XCB_EVENT_MASK_STRUCTURE_NOTIFY
| XCB_EVENT_MASK_BUTTON_PRESS
| XCB_EVENT_MASK_BUTTON_RELEASE
| XCB_EVENT_MASK_KEY_PRESS
| XCB_EVENT_MASK_KEY_RELEASE
| XCB_EVENT_MASK_POINTER_MOTION
;
uint32_t valueList[] = { xcbRootScreen->black_pixel,
interestedEvents };
const uint32_t interestedEvents = XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_KEY_PRESS | XCB_EVENT_MASK_KEY_RELEASE |
XCB_EVENT_MASK_FOCUS_CHANGE | XCB_EVENT_MASK_PROPERTY_CHANGE;
uint32_t valueList[] = { m_xcbRootScreen->black_pixel, interestedEvents };
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_create_window_checked(m_xcbConnection,
XCB_COPY_FROM_PARENT,
m_xcbWindow,
xcbParentWindow,
aznumeric_cast<int16_t>(geometry.m_posX),
aznumeric_cast<int16_t>(geometry.m_posY),
aznumeric_cast<int16_t>(geometry.m_width),
aznumeric_cast<int16_t>(geometry.m_height),
borderWidth,
XCB_WINDOW_CLASS_INPUT_OUTPUT,
xcbRootScreen->root_visual,
eventMask,
valueList);
xcbCheckResult = xcb_create_window_checked(
m_xcbConnection, XCB_COPY_FROM_PARENT, m_xcbWindow, xcbParentWindow, aznumeric_cast<int16_t>(geometry.m_posX),
aznumeric_cast<int16_t>(geometry.m_posY), aznumeric_cast<int16_t>(geometry.m_width), aznumeric_cast<int16_t>(geometry.m_height),
borderWidth, XCB_WINDOW_CLASS_INPUT_OUTPUT, m_xcbRootScreen->root_visual, eventMask, valueList);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to create xcb window.");
SetWindowTitle(title);
// Setup the window close event
const static char* wmProtocolString = "WM_PROTOCOLS";
xcb_intern_atom_cookie_t cookieProtocol = xcb_intern_atom(m_xcbConnection, 1, strlen(wmProtocolString), wmProtocolString);
xcb_intern_atom_reply_t* replyProtocol = xcb_intern_atom_reply(m_xcbConnection, cookieProtocol, nullptr);
AZ_Error(XcbErrorWindow, replyProtocol != nullptr, "Unable to query xcb '%s' atom", wmProtocolString);
m_xcbAtomProtocols = replyProtocol->atom;
const static char* wmDeleteWindowString = "WM_DELETE_WINDOW";
xcb_intern_atom_cookie_t cookieDeleteWindow = xcb_intern_atom(m_xcbConnection, 0, strlen(wmDeleteWindowString), wmDeleteWindowString);
xcb_intern_atom_reply_t* replyDeleteWindow = xcb_intern_atom_reply(m_xcbConnection, cookieDeleteWindow, nullptr);
AZ_Error(XcbErrorWindow, replyDeleteWindow != nullptr, "Unable to query xcb '%s' atom", wmDeleteWindowString);
m_xcbAtomDeleteWindow = replyDeleteWindow->atom;
xcbCheckResult = xcb_change_property_checked(m_xcbConnection,
XCB_PROP_MODE_REPLACE,
m_xcbWindow,
m_xcbAtomProtocols,
XCB_ATOM_ATOM,
s_XcbFormatDataSize,
1,
&m_xcbAtomDeleteWindow);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to change the xcb atom property for WM_CLOSE event");
m_posX = geometry.m_posX;
m_posY = geometry.m_posY;
m_width = geometry.m_width;
m_height = geometry.m_height;
InitializeAtoms();
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_REQUEST_FRAME_EXTENTS;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = 0l;
event.data.data32[1] = 0l;
event.data.data32[2] = 0l;
event.data.data32[3] = 0l;
event.data.data32[4] = 0l;
xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_REQUEST_FRAME_EXTENTS");
// The WM will be able to kill the application if it gets unresponsive.
int32_t pid = getpid();
xcb_change_property(m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, _NET_WM_PID, XCB_ATOM_CARDINAL, 32, 1, &pid);
xcb_flush(m_xcbConnection);
}
xcb_atom_t XcbNativeWindow::GetAtom(const char* atomName)
{
xcb_intern_atom_cookie_t intern_atom_cookie = xcb_intern_atom(m_xcbConnection, 0, strlen(atomName), atomName);
XcbStdFreePtr<xcb_intern_atom_reply_t> xkbinternAtom{ xcb_intern_atom_reply(m_xcbConnection, intern_atom_cookie, NULL) };
if (!xkbinternAtom)
{
AZ_Error(XcbErrorWindow, xkbinternAtom != nullptr, "Unable to query xcb '%s' atom", atomName);
return XCB_NONE;
}
return xkbinternAtom->atom;
}
int XcbNativeWindow::SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data)
{
xcb_void_cookie_t cookie = xcb_change_property_checked(m_xcbConnection, XCB_PROP_MODE_REPLACE, window, atom, type, 32, len, data);
XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(m_xcbConnection, cookie) };
if (!xkbError)
{
return 0;
}
return xkbError->error_code;
}
////////////////////////////////////////////////////////////////////////////////////////////////
void XcbNativeWindow::InitializeAtoms()
{
AZStd::vector<xcb_atom_t> Atoms;
_NET_ACTIVE_WINDOW = GetAtom("_NET_ACTIVE_WINDOW");
_NET_WM_BYPASS_COMPOSITOR = GetAtom("_NET_WM_BYPASS_COMPOSITOR");
// ---------------------------------------------------------------------
// Handle all WM Protocols atoms.
//
WM_PROTOCOLS = GetAtom("WM_PROTOCOLS");
// This atom is used to close a window. Emitted when user clicks the close button.
WM_DELETE_WINDOW = GetAtom("WM_DELETE_WINDOW");
Atoms.push_back(WM_DELETE_WINDOW);
xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, WM_PROTOCOLS, XCB_ATOM_ATOM, 32, Atoms.size(), Atoms.data());
xcb_flush(m_xcbConnection);
// ---------------------------------------------------------------------
// Handle all WM State atoms.
//
_NET_WM_STATE = GetAtom("_NET_WM_STATE");
_NET_WM_STATE_FULLSCREEN = GetAtom("_NET_WM_STATE_FULLSCREEN");
_NET_WM_STATE_MAXIMIZED_VERT = GetAtom("_NET_WM_STATE_MAXIMIZED_VERT");
_NET_WM_STATE_MAXIMIZED_HORZ = GetAtom("_NET_WM_STATE_MAXIMIZED_HORZ");
_NET_MOVERESIZE_WINDOW = GetAtom("_NET_MOVERESIZE_WINDOW");
_NET_REQUEST_FRAME_EXTENTS = GetAtom("_NET_REQUEST_FRAME_EXTENTS");
_NET_FRAME_EXTENTS = GetAtom("_NET_FRAME_EXTENTS");
_NET_WM_PID = GetAtom("_NET_WM_PID");
}
void XcbNativeWindow::GetWMStates()
{
xcb_get_property_cookie_t cookie = xcb_get_property(m_xcbConnection, 0, m_xcbWindow, _NET_WM_STATE, XCB_ATOM_ATOM, 0, 1024);
xcb_generic_error_t* error = nullptr;
XcbStdFreePtr<xcb_get_property_reply_t> xkbGetPropertyReply{ xcb_get_property_reply(m_xcbConnection, cookie, &error) };
if (!xkbGetPropertyReply || error || !((xkbGetPropertyReply->format == 32) && (xkbGetPropertyReply->type == XCB_ATOM_ATOM)))
{
AZ_Warning("ApplicationLinux", false, "Acquiring _NET_WM_STATE information from the WM failed.");
if (error)
{
AZ_TracePrintf("Error", "Error code %d", error->error_code);
free(error);
}
return;
}
m_fullscreenState = false;
m_horizontalyMaximized = false;
m_verticallyMaximized = false;
const xcb_atom_t* states = static_cast<const xcb_atom_t*>(xcb_get_property_value(xkbGetPropertyReply.get()));
for (int i = 0; i < xkbGetPropertyReply->length; i++)
{
if (states[i] == _NET_WM_STATE_FULLSCREEN)
{
m_fullscreenState = true;
}
else if (states[i] == _NET_WM_STATE_MAXIMIZED_HORZ)
{
m_horizontalyMaximized = true;
}
else if (states[i] == _NET_WM_STATE_MAXIMIZED_VERT)
{
m_verticallyMaximized = true;
}
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -145,7 +247,7 @@ namespace AzFramework
xcb_flush(m_xcbConnection);
}
XcbEventHandlerBus::Handler::BusDisconnect();
}
}
////////////////////////////////////////////////////////////////////////////////////////////////
NativeWindowHandle XcbNativeWindow::GetWindowHandle() const
@@ -157,14 +259,9 @@ namespace AzFramework
void XcbNativeWindow::SetWindowTitle(const AZStd::string& title)
{
xcb_void_cookie_t xcbCheckResult;
xcbCheckResult = xcb_change_property(m_xcbConnection,
XCB_PROP_MODE_REPLACE,
m_xcbWindow,
XCB_ATOM_WM_NAME,
XCB_ATOM_STRING,
8,
static_cast<uint32_t>(title.size()),
title.c_str());
xcbCheckResult = xcb_change_property(
m_xcbConnection, XCB_PROP_MODE_REPLACE, m_xcbWindow, XCB_ATOM_WM_NAME, XCB_ATOM_STRING, 8, static_cast<uint32_t>(title.size()),
title.c_str());
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set window title.");
}
@@ -174,7 +271,7 @@ namespace AzFramework
const uint32_t values[] = { clientAreaSize.m_width, clientAreaSize.m_height };
xcb_configure_window(m_xcbConnection, m_xcbWindow, XCB_CONFIG_WINDOW_WIDTH | XCB_CONFIG_WINDOW_HEIGHT, values);
m_width = clientAreaSize.m_width;
m_height = clientAreaSize.m_height;
}
@@ -184,16 +281,77 @@ namespace AzFramework
{
// [GFX TODO][GHI - 2678]
// Using 60 for now until proper support is added
return 60;
}
bool XcbNativeWindow::GetFullScreenState() const
{
return m_fullscreenState;
}
void XcbNativeWindow::SetFullScreenState(bool fullScreenState)
{
// TODO This is a pretty basic full-screen implementation using WM's _NET_WM_STATE_FULLSCREEN state.
// Do we have to provide also the old way?
GetWMStates();
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_WM_STATE;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = fullScreenState ? _NET_WM_STATE_ADD : _NET_WM_STATE_REMOVE;
event.data.data32[1] = _NET_WM_STATE_FULLSCREEN;
event.data.data32[2] = 0;
event.data.data32[3] = 1;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
// Also try to disable/enable the compositor if possible. Might help in some cases.
const long _NET_WM_BYPASS_COMPOSITOR_HINT_ON = m_fullscreenState ? 1 : 0;
SetAtom(m_xcbWindow, _NET_WM_BYPASS_COMPOSITOR, XCB_ATOM_CARDINAL, 32, (char*)&_NET_WM_BYPASS_COMPOSITOR_HINT_ON);
if (!fullScreenState)
{
if (m_horizontalyMaximized || m_verticallyMaximized)
{
printf("Remove maximized state.\n");
xcb_client_message_event_t event;
event.response_type = XCB_CLIENT_MESSAGE;
event.type = _NET_WM_STATE;
event.window = m_xcbWindow;
event.format = 32;
event.sequence = 0;
event.data.data32[0] = _NET_WM_STATE_MAXIMIZED_VERT;
event.data.data32[1] = _NET_WM_STATE_MAXIMIZED_HORZ;
event.data.data32[2] = 0;
event.data.data32[3] = 0;
event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event);
AZ_Assert(
ValidateXcbResult(xcbCheckResult), "Failed to remove _NET_WM_STATE_MAXIMIZED_VERT | _NET_WM_STATE_MAXIMIZED_HORZ");
}
}
xcb_flush(m_xcbConnection);
m_fullscreenState = fullScreenState;
}
////////////////////////////////////////////////////////////////////////////////////////////////
bool XcbNativeWindow::ValidateXcbResult(xcb_void_cookie_t cookie)
{
bool result = true;
if (xcb_generic_error_t* error = xcb_request_check(m_xcbConnection, cookie))
{
AZ_TracePrintf("Error","Error code %d", error->error_code);
AZ_TracePrintf("Error", "Error code %d", error->error_code);
result = false;
}
return result;
@@ -204,20 +362,20 @@ namespace AzFramework
{
switch (event->response_type & s_XcbResponseTypeMask)
{
case XCB_CONFIGURE_NOTIFY:
case XCB_CONFIGURE_NOTIFY:
{
xcb_configure_notify_event_t* cne = reinterpret_cast<xcb_configure_notify_event_t*>(event);
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width),
aznumeric_cast<uint32_t>(cne->height));
if ((cne->width != m_width) || (cne->height != m_height))
{
WindowSizeChanged(aznumeric_cast<uint32_t>(cne->width), aznumeric_cast<uint32_t>(cne->height));
}
break;
}
case XCB_CLIENT_MESSAGE:
case XCB_CLIENT_MESSAGE:
{
xcb_client_message_event_t* cme = reinterpret_cast<xcb_client_message_event_t*>(event);
if ((cme->type == m_xcbAtomProtocols) &&
(cme->format == s_XcbFormatDataSize) &&
(cme->data.data32[0] == m_xcbAtomDeleteWindow))
if ((cme->type == WM_PROTOCOLS) && (cme->format == s_XcbFormatDataSize) && (cme->data.data32[0] == WM_DELETE_WINDOW))
{
Deactivate();
@@ -238,7 +396,8 @@ namespace AzFramework
if (m_activated)
{
WindowNotificationBus::Event(reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
WindowNotificationBus::Event(
reinterpret_cast<NativeWindowHandle>(m_xcbWindow), &WindowNotificationBus::Events::OnWindowResized, width, height);
}
}
}
@@ -27,15 +27,16 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////
// NativeWindow::Implementation
void InitWindow(const AZStd::string& title,
const WindowGeometry& geometry,
const WindowStyleMasks& styleMasks) override;
void InitWindow(const AZStd::string& title, const WindowGeometry& geometry, const WindowStyleMasks& styleMasks) override;
void Activate() override;
void Deactivate() override;
NativeWindowHandle GetWindowHandle() const override;
void SetWindowTitle(const AZStd::string& title) override;
void ResizeClientArea(WindowSize clientAreaSize) override;
uint32_t GetDisplayRefreshRate() const override;
uint32_t GetDisplayRefreshRate() const override;
bool GetFullScreenState() const override;
void SetFullScreenState(bool fullScreenState) override;
////////////////////////////////////////////////////////////////////////////////////////////
// XcbEventHandlerBus::Handler
@@ -44,10 +45,46 @@ namespace AzFramework
private:
bool ValidateXcbResult(xcb_void_cookie_t cookie);
void WindowSizeChanged(const uint32_t width, const uint32_t height);
int SetAtom(xcb_window_t window, xcb_atom_t atom, xcb_atom_t type, size_t len, void* data);
xcb_connection_t* m_xcbConnection = nullptr;
xcb_window_t m_xcbWindow = 0;
xcb_atom_t m_xcbAtomProtocols;
xcb_atom_t m_xcbAtomDeleteWindow;
// Initialize one atom.
xcb_atom_t GetAtom(const char* atomName);
// Initialize all used atoms.
void InitializeAtoms();
void GetWMStates();
xcb_connection_t* m_xcbConnection = nullptr;
xcb_screen_t* m_xcbRootScreen = nullptr;
xcb_window_t m_xcbWindow = 0;
int32_t m_posX;
int32_t m_posY;
bool m_fullscreenState = false;
bool m_horizontalyMaximized = false;
bool m_verticallyMaximized = false;
// Use exact atom names for easy readability and usage.
xcb_atom_t WM_PROTOCOLS;
xcb_atom_t WM_DELETE_WINDOW;
// This atom is used to activate a window.
xcb_atom_t _NET_ACTIVE_WINDOW;
// This atom is use to bypass a compositor. Used during fullscreen mode.
xcb_atom_t _NET_WM_BYPASS_COMPOSITOR;
// This atom is used to change the state of a window using the WM.
xcb_atom_t _NET_WM_STATE;
// This atom is used to enable/disable fullscreen mode of a window.
xcb_atom_t _NET_WM_STATE_FULLSCREEN;
// This atom is used to extend the window to max vertically.
xcb_atom_t _NET_WM_STATE_MAXIMIZED_VERT;
// This atom is used to extend the window to max horizontally.
xcb_atom_t _NET_WM_STATE_MAXIMIZED_HORZ;
// This atom is used to position and resize a window.
xcb_atom_t _NET_MOVERESIZE_WINDOW;
// This atom is used to request the extent of the window.
xcb_atom_t _NET_REQUEST_FRAME_EXTENTS;
// This atom is used to identify the reply event for _NET_REQUEST_FRAME_EXTENTS
xcb_atom_t _NET_FRAME_EXTENTS;
// This atom is used to allow WM to kill app if not responsive anymore
xcb_atom_t _NET_WM_PID;
};
} // namespace AzFramework
@@ -12,6 +12,8 @@ set(FILES
AzFramework/XcbConnectionManager.h
AzFramework/XcbInputDeviceKeyboard.cpp
AzFramework/XcbInputDeviceKeyboard.h
AzFramework/XcbInputDeviceMouse.cpp
AzFramework/XcbInputDeviceMouse.h
AzFramework/XcbInterface.h
AzFramework/XcbNativeWindow.cpp
AzFramework/XcbNativeWindow.h
@@ -0,0 +1,27 @@
/*
* 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
*
*/
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
#include <AzFramework/XcbInputDeviceMouse.h>
#endif
namespace AzFramework
{
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
{
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
return XcbInputDeviceMouse::Create(inputDevice);
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
#error "Linux Window Manager Wayland not supported."
return nullptr;
#else
#error "Linux Window Manager not recognized."
return nullptr;
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
}
} // namespace AzFramework
@@ -22,8 +22,10 @@ if (${PAL_TRAIT_LINUX_WINDOW_MANAGER} STREQUAL "xcb")
PRIVATE
3rdParty::X11::xcb
3rdParty::X11::xcb_xkb
3rdParty::X11::xcb_xfixes
3rdParty::X11::xkbcommon
3rdParty::X11::xkbcommon_X11
xcb-xinput
)
elseif(PAL_TRAIT_LINUX_WINDOW_MANAGER STREQUAL "wayland")
@@ -22,8 +22,8 @@ set(FILES
AzFramework/Windowing/NativeWindow_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Gamepad/InputDeviceGamepad_Unimplemented.cpp
AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard_Linux.cpp
AzFramework/Input/Devices/Mouse/InputDeviceMouse_Linux.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Motion/InputDeviceMotion_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Mouse/InputDeviceMouse_Unimplemented.cpp
../Common/Unimplemented/AzFramework/Input/Devices/Touch/InputDeviceTouch_Unimplemented.cpp
AzFramework/Input/User/LocalUserId_Platform.h
../Common/Default/AzFramework/Input/User/LocalUserId_Default.h
@@ -12,7 +12,6 @@
#include <xcb/xcb.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbInputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/InputTextNotificationBus.h>
@@ -20,6 +19,7 @@
#include "Matchers.h"
#include "Actions.h"
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
@@ -33,6 +33,7 @@ namespace AzFramework
class XcbInputDeviceKeyboardTests
: public XcbBaseTestFixture
{
public:
void SetUp() override
{
using testing::Return;
@@ -123,6 +124,15 @@ namespace AzFramework
static constexpr xcb_keycode_t s_keycodeForAKey{38};
static constexpr xcb_keycode_t s_keycodeForShiftLKey{50};
XcbTestApplication m_application{
/*enabledGamepadsCount=*/0,
/*keyboardEnabled=*/true,
/*motionEnabled=*/false,
/*mouseEnabled=*/false,
/*touchEnabled=*/false,
/*virtualKeyboardEnabled=*/false
};
};
class InputTextNotificationListener
@@ -195,27 +205,23 @@ namespace AzFramework
EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
.Times(2);
Application application;
application.Start({}, {});
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_application.Start();
const InputChannel* inputChannel = InputChannelRequests::FindInputChannel(InputDeviceKeyboard::Key::AlphanumericA);
ASSERT_TRUE(inputChannel);
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Idle));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Began));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Ended));
application.Stop();
}
TEST_F(XcbInputDeviceKeyboardTests, TextEnteredFromXcbKeyPressEvents)
@@ -420,17 +426,13 @@ namespace AzFramework
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("a"), _)).Times(1);
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("A"), _)).Times(1);
Application application;
application.Start({}, {});
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_application.Start();
for (int i = 0; i < 4; ++i)
{
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
}
application.Stop();
}
} // namespace AzFramework
@@ -0,0 +1,38 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Application/Application.h>
namespace AzFramework
{
class XcbTestApplication
: public Application
{
public:
XcbTestApplication(AZ::u64 enabledGamepadsCount, bool keyboardEnabled, bool motionEnabled, bool mouseEnabled, bool touchEnabled, bool virtualKeyboardEnabled)
{
auto* settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Set("/O3DE/InputSystem/GamepadsEnabled", enabledGamepadsCount);
settingsRegistry->Set("/O3DE/InputSystem/KeyboardEnabled", keyboardEnabled);
settingsRegistry->Set("/O3DE/InputSystem/MotionEnabled", motionEnabled);
settingsRegistry->Set("/O3DE/InputSystem/MouseEnabled", mouseEnabled);
settingsRegistry->Set("/O3DE/InputSystem/TouchEnabled", touchEnabled);
settingsRegistry->Set("/O3DE/InputSystem/VirtualKeyboardEnabled", virtualKeyboardEnabled);
}
void Start(const Descriptor& descriptor = {}, const StartupParameters& startupParameters = {}) override
{
Application::Start(descriptor, startupParameters);
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
};
} // namespace AzFramework
@@ -17,4 +17,5 @@ set(FILES
XcbBaseTestFixture.cpp
XcbBaseTestFixture.h
XcbInputDeviceKeyboardTests.cpp
XcbTestApplication.h
)
@@ -569,11 +569,12 @@ namespace UnitTest
FillSpawnable(NumEntities);
CreateEntityReferences(refScheme);
AZ_PUSH_DISABLE_WARNING(5233, "-Wunused-lambda-capture") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback =
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
AZ_POP_DISABLE_WARNING
{
AZ_UNUSED(refScheme);
AZ_UNUSED(NumEntities);
ValidateEntityReferences(refScheme, NumEntities, entities);
};
@@ -591,11 +592,12 @@ namespace UnitTest
FillSpawnable(NumEntities);
CreateEntityReferences(refScheme);
AZ_PUSH_DISABLE_WARNING(5233, "-Wunused-lambda-capture") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback =
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
AZ_POP_DISABLE_WARNING
{
AZ_UNUSED(refScheme);
AZ_UNUSED(NumEntities);
ValidateEntityReferences(refScheme, NumEntities, entities);
};
@@ -720,11 +722,12 @@ namespace UnitTest
FillSpawnable(NumEntities);
CreateEntityReferences(refScheme);
AZ_PUSH_DISABLE_WARNING(5233, "-Wunused-lambda-capture") // Older versions of MSVC toolchain require to pass constexpr in the
// capture. Newer versions issue unused warning
auto callback =
[this, refScheme, NumEntities](AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableConstEntityContainerView entities)
AZ_POP_DISABLE_WARNING
{
AZ_UNUSED(refScheme);
AZ_UNUSED(NumEntities);
ValidateEntityReferences(refScheme, NumEntities, entities);
};
@@ -96,6 +96,8 @@ namespace AzGameFramework
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#else
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
#endif
// Update the Runtime file paths in case the "{BootstrapSettingsRootKey}/assets" key was overriden by a setting registry
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
@@ -12,6 +12,7 @@ set(FILES
../../Utilities/QtWindowUtilities_linux.cpp
../../Utilities/ScreenGrabber_linux.cpp
../../../Platform/Linux/AzQtComponents/Components/StyledDockWidget_Linux.cpp
../../../Platform/Linux/AzQtComponents/Utilities/DesktopUtilities_Linux.cpp
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h
)
@@ -12,6 +12,7 @@ set(FILES
../../Utilities/QtWindowUtilities_mac.mm
../../Utilities/ScreenGrabber_mac.mm
../../../Platform/Mac/AzQtComponents/Components/StyledDockWidget_Mac.cpp
../../../Platform/Mac/AzQtComponents/Utilities/DesktopUtilities_Mac.cpp
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h
)
@@ -9,6 +9,7 @@
set(FILES
../../natvis/qt.natvis
../../../Platform/Windows/AzQtComponents/Utilities/HandleDpiAwareness_Windows.cpp
../../../Platform/Windows/AzQtComponents/Utilities/DesktopUtilities_Windows.cpp
../../Utilities/MouseHider_win.cpp
../../Utilities/QtWindowUtilities_win.cpp
../../Utilities/ScreenGrabber_win.cpp
@@ -271,7 +271,6 @@ set(FILES
Utilities/ColorUtilities.h
Utilities/Conversions.h
Utilities/Conversions.cpp
Utilities/DesktopUtilities.cpp
Utilities/DesktopUtilities.h
Utilities/HandleDpiAwareness.cpp
Utilities/HandleDpiAwareness.h
@@ -0,0 +1,49 @@
/*
* 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
*
*/
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QDir>
#include <QProcess>
namespace AzQtComponents
{
void ShowFileOnDesktop(const QString& path)
{
const char* defaultNautilusPath = "/usr/bin/nautilus";
const char* defaultXdgOpenPath = "/usr/bin/xdg-open";
// Determine if Nautilus (for Gnome Desktops) is available because it supports opening the file manager
// and selecting a specific file
bool nautilusAvailable = QFileInfo(defaultNautilusPath).exists();
QFileInfo pathInfo(path);
if (pathInfo.isDir())
{
QProcess::startDetached(defaultXdgOpenPath, { path });
}
else
{
if (nautilusAvailable)
{
QProcess::startDetached(defaultNautilusPath, { "--select", path });
}
else
{
QDir parentDir { pathInfo.dir() };
QProcess::startDetached(defaultXdgOpenPath, { parentDir.path() });
}
}
}
QString fileBrowserActionName()
{
const char* exploreActionName = "Open in file browser";
return QObject::tr(exploreActionName);
}
}
@@ -15,21 +15,6 @@ namespace AzQtComponents
{
void ShowFileOnDesktop(const QString& path)
{
#if defined(AZ_PLATFORM_WINDOWS)
// Launch explorer at the path provided
QStringList args;
if (!QFileInfo(path).isDir())
{
// Folders are just opened, files are selected
args << "/select,";
}
args << QDir::toNativeSeparators(path);
QProcess::startDetached("explorer", args);
#else
if (QFileInfo(path).isDir())
{
QProcess::startDetached("/usr/bin/osascript", { "-e",
@@ -43,19 +28,11 @@ namespace AzQtComponents
QProcess::startDetached("/usr/bin/osascript", { "-e",
QStringLiteral("tell application \"Finder\" to activate") });
#endif
}
QString fileBrowserActionName()
{
#ifdef AZ_PLATFORM_WINDOWS
const char* exploreActionName = "Open in Explorer";
#elif defined(AZ_PLATFORM_MAC)
const char* exploreActionName = "Open in Finder";
#else
const char* exploreActionName = "Open in file browser";
#endif
return QObject::tr(exploreActionName);
}
}
@@ -0,0 +1,35 @@
/*
* 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
*
*/
#include <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QDir>
#include <QProcess>
namespace AzQtComponents
{
void ShowFileOnDesktop(const QString& path)
{
// Launch explorer at the path provided
QStringList args;
if (!QFileInfo(path).isDir())
{
// Folders are just opened, files are selected
args << "/select,";
}
args << QDir::toNativeSeparators(path);
QProcess::startDetached("explorer", args);
}
QString fileBrowserActionName()
{
const char* exploreActionName = "Open in Explorer";
return QObject::tr(exploreActionName);
}
}
+6 -11
View File
@@ -90,19 +90,16 @@ namespace AZ
}
}
//! Filter out integration tests from the test run
void excludeIntegTests()
{
AddExcludeFilter("INTEG_*");
AddExcludeFilter("Integ_*");
}
void ApplyGlobalParameters(int* argc, char** argv)
{
// this is a hook that can be used to apply any other global non-google parameters
// that we use.
// this is a hook that can be used to apply any other global parameters that we use.
AZ_UNUSED(argc);
AZ_UNUSED(argv);
// Disable gtest catching unhandled exceptions, instead, AzTestRunner will do it through:
// AZ::Debug::Trace::HandleExceptions(true). This gives us a stack trace when the exception
// is thrown (googletest does not).
testing::FLAGS_gtest_catch_exceptions = false;
}
//! Print out parameters that are not used by the framework
@@ -160,7 +157,6 @@ namespace AZ
}
::testing::InitGoogleMock(&argc, argv);
AZ::Test::excludeIntegTests();
AZ::Test::ApplyGlobalParameters(&argc, argv);
AZ::Test::printUnusedParametersWarning(argc, argv);
AZ::Test::addTestEnvironments(m_envs);
@@ -281,7 +277,6 @@ namespace AZ
}
}
AZ::Test::excludeIntegTests();
AZ::Test::printUnusedParametersWarning(argc, argv);
return RUN_ALL_TESTS();
-2
View File
@@ -104,7 +104,6 @@ namespace AZ
void addTestEnvironment(ITestEnvironment* env);
void addTestEnvironments(std::vector<ITestEnvironment*> envs);
void excludeIntegTests();
//! A hook that can be used to read any other misc parameters and remove them before google sees them.
//! Note that this modifies argc and argv to delete the parameters it consumes.
@@ -266,7 +265,6 @@ namespace AZ
::testing::TestEventListeners& listeners = testing::UnitTest::GetInstance()->listeners(); \
listeners.Append(new AZ::Test::OutputEventListener); \
} \
AZ::Test::excludeIntegTests(); \
AZ::Test::ApplyGlobalParameters(&argc, argv); \
AZ::Test::printUnusedParametersWarning(argc, argv); \
AZ::Test::addTestEnvironments({TEST_ENV}); \
@@ -43,8 +43,7 @@ namespace AzToolsFramework
};
//! Provides a bus to notify when the different editor modes are entered/exit.
class ViewportEditorModeNotifications
: public AZ::EBusTraits
class ViewportEditorModeNotifications : public AZ::EBusTraits
{
public:
//////////////////////////////////////////////////////////////////////////
@@ -58,14 +57,17 @@ namespace AzToolsFramework
static void Reflect(AZ::ReflectContext* context);
//! Notifies subscribers of the a given viewport to the activation of the specified editor mode.
virtual void OnEditorModeActivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode)
virtual void OnEditorModeActivated(
[[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode)
{
}
//! Notifies subscribers of the a given viewport to the deactivation of the specified editor mode.
virtual void OnEditorModeDeactivated([[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode)
virtual void OnEditorModeDeactivated(
[[maybe_unused]] const ViewportEditorModesInterface& editorModeState, [[maybe_unused]] ViewportEditorMode mode)
{
}
};
using ViewportEditorModeNotificationsBus = AZ::EBus<ViewportEditorModeNotifications>;
} // namespace AzToolsFramework
@@ -53,7 +53,7 @@ namespace AzToolsFramework
private slots:
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
int m_numberOfItemsDisplayed = 50;
AZ::u64 m_numberOfItemsDisplayed = 0;
int m_displayedItemsCounter = 0;
QPointer<AssetBrowserFilterModel> m_filterModel;
QMap<int, QModelIndex> m_indexMap;
@@ -137,6 +137,7 @@ namespace AzToolsFramework
if (componentTypeIt == m_activeComponentTypes.end())
{
m_activeComponentTypes.push_back(componentType);
m_viewportUiHandlers.emplace_back(componentType);
}
// see if we already have a ComponentModeBuilder for the specific component on this entity
@@ -225,6 +226,7 @@ namespace AzToolsFramework
if (!m_entitiesAndComponentModes.empty())
{
RefreshActions();
PopulateViewportUi();
}
// if entering ComponentMode not as an undo/redo step (an action was
@@ -285,6 +287,10 @@ namespace AzToolsFramework
componentModeCommand.release();
}
// remove the component mode viewport border
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::RemoveViewportBorder);
// notify listeners the editor has left ComponentMode - listeners may
// wish to modify state to indicate this (e.g. appearance, functionality etc.)
m_viewportEditorModeTracker->DeactivateMode({ GetEntityContextId() }, ViewportEditorMode::Component);
@@ -301,6 +307,7 @@ namespace AzToolsFramework
}
m_entitiesAndComponentModeBuilders.clear();
m_activeComponentTypes.clear();
m_viewportUiHandlers.clear();
m_componentMode = false;
m_selectedComponentModeIndex = 0;
@@ -385,6 +392,24 @@ namespace AzToolsFramework
return m_activeComponentTypes.size() > 1;
}
static ComponentModeViewportUi* FindViewportUiHandlerForType(
AZStd::vector<ComponentModeViewportUi>& viewportUiHandlers, const AZ::Uuid& componentType)
{
auto handler = AZStd::find_if(
viewportUiHandlers.begin(), viewportUiHandlers.end(),
[componentType](const ComponentModeViewportUi& handler)
{
return handler.GetComponentType() == componentType;
});
if (handler == viewportUiHandlers.end())
{
return nullptr;
}
return handler;
}
bool ComponentModeCollection::ActiveComponentModeChanged(const AZ::Uuid& previousComponentType)
{
if (m_activeComponentTypes[m_selectedComponentModeIndex] != previousComponentType)
@@ -410,6 +435,20 @@ namespace AzToolsFramework
// replace the current component mode by invoking the builder
// for the new 'active' component mode
componentMode.m_componentMode = componentModeBuilder->m_componentModeBuilder();
// populate the viewport UI with the new component mode
PopulateViewportUi();
// set the appropriate viewportUiHandler to active
if (auto viewportUiHandler =
FindViewportUiHandlerForType(m_viewportUiHandlers, m_activeComponentTypes[m_selectedComponentModeIndex]))
{
viewportUiHandler->SetComponentModeViewportUiActive(true);
}
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder,
componentMode.m_componentMode->GetComponentModeName().c_str());
}
RefreshActions();
@@ -519,5 +558,18 @@ namespace AzToolsFramework
}
}
void ComponentModeCollection::PopulateViewportUi()
{
// update viewport UI for new component type
if (m_selectedComponentModeIndex < m_activeComponentTypes.size())
{
// iterate over all entities and their active Component Mode, populate viewport UI for the new mode
for (auto& entityAndComponentMode : m_entitiesAndComponentModes)
{
// build viewport UI based on current state
entityAndComponentMode.m_componentMode->PopulateViewportUi();
}
}
}
} // namespace ComponentModeFramework
} // namespace AzToolsFramework
@@ -55,7 +55,7 @@ namespace AzToolsFramework
GetEntityComponentIdPair(), elementIdsToDisplay);
// create the component mode border with the specific name for this component mode
ViewportUi::ViewportUiRequestBus::Event(
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateComponentModeBorder,
ViewportUi::DefaultViewportId, &ViewportUi::ViewportUiRequestBus::Events::CreateViewportBorder,
GetComponentModeName());
// set the EntityComponentId for this ComponentMode to active in the ComponentModeViewportUi system
ComponentModeViewportUiRequestBus::Event(
@@ -38,7 +38,7 @@ namespace AzToolsFramework
virtual SettingOutcome GetValue(const AZStd::string_view path) = 0;
virtual SettingOutcome SetValue(const AZStd::string_view path, const AZStd::any& value) = 0;
virtual ConsoleColorTheme GetConsoleColorTheme() const = 0;
virtual int GetMaxNumberOfItemsShownInSearchView() const = 0;
virtual AZ::u64 GetMaxNumberOfItemsShownInSearchView() const = 0;
};
using EditorSettingsAPIBus = AZ::EBus<EditorSettingsAPIRequests>;
@@ -28,8 +28,10 @@ namespace AzToolsFramework
//////////////////////////////////////////////////////////////////////////
//! Triggered when the editor focus is changed to a different entity.
//! @param entityId The entity the focus has been moved to.
virtual void OnEditorFocusChanged(AZ::EntityId entityId) = 0;
//! @param previousFocusEntityId The entity the focus has been moved from.
//! @param newFocusEntityId The entity the focus has been moved to.
virtual void OnEditorFocusChanged(
[[maybe_unused]] AZ::EntityId previousFocusEntityId, [[maybe_unused]] AZ::EntityId newFocusEntityId) {}
protected:
~FocusModeNotifications() = default;
@@ -83,8 +83,9 @@ namespace AzToolsFramework
}
}
AZ::EntityId previousFocusEntityId = m_focusRoot;
m_focusRoot = entityId;
FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, m_focusRoot);
FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, previousFocusEntityId, m_focusRoot);
}
void FocusModeSystemComponent::ClearFocusRoot([[maybe_unused]] AzFramework::EntityContextId entityContextId)
@@ -116,7 +116,7 @@ namespace AzToolsFramework
{
return AZ::Intersect::IntersectRayBox(
rayOrigin, rayDirection, m_center, m_axis1, m_axis2, m_axis3, m_halfExtents.GetX(), m_halfExtents.GetY(),
m_halfExtents.GetZ(), rayIntersectionDistance) > 0;
m_halfExtents.GetZ(), rayIntersectionDistance);
}
void ManipulatorBoundBox::SetShapeData(const BoundRequestShapeBase& shapeData)
@@ -262,7 +262,7 @@ namespace AzToolsFramework
if (assetId.IsValid())
{
asset.Create(assetId, true);
asset.Create(assetId, false);
}
}
};
@@ -8,12 +8,14 @@
#include <AzToolsFramework/Prefab/PrefabFocusHandler.h>
#include <AzToolsFramework/Commands/SelectionCommand.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusNotificationBus.h>
#include <AzToolsFramework/Prefab/PrefabFocusUndo.h>
namespace AzToolsFramework::Prefab
{
@@ -28,10 +30,12 @@ namespace AzToolsFramework::Prefab
EditorEntityContextNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabFocusInterface>::Register(this);
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
}
PrefabFocusHandler::~PrefabFocusHandler()
{
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
AZ::Interface<PrefabFocusInterface>::Unregister(this);
EditorEntityContextNotificationBus::Handler::BusDisconnect();
}
@@ -61,6 +65,44 @@ namespace AzToolsFramework::Prefab
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
{
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Edit Prefab");
// Clear selection
{
const EntityIdList selectedEntities = EntityIdList{};
auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities);
}
// Edit Prefab
{
auto editUndo = aznew PrefabFocusUndo("Edit Prefab");
editUndo->Capture(entityId);
editUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, editUndo);
}
return AZ::Success();
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{
if (index < 0 || index >= m_instanceFocusVector.size())
{
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
}
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
return AZ::Success();
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId)
{
InstanceOptionalReference focusedInstance;
@@ -85,18 +127,6 @@ namespace AzToolsFramework::Prefab
return FocusOnPrefabInstance(focusedInstance);
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{
if (index < 0 || index >= m_instanceFocusVector.size())
{
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
}
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
return FocusOnPrefabInstance(focusedInstance);
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPrefabInstance(InstanceOptionalReference focusedInstance)
{
if (!focusedInstance.has_value())
@@ -122,17 +152,10 @@ namespace AzToolsFramework::Prefab
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{
containerEntityId = focusedInstance->get().GetContainerEntityId();
// Select the container entity
AzToolsFramework::SelectEntity(containerEntityId);
}
else
{
containerEntityId = AZ::EntityId();
// Clear the selection
AzToolsFramework::SelectEntities({});
}
// Focus on the descendants of the container entity
@@ -161,6 +184,17 @@ namespace AzToolsFramework::Prefab
return m_focusedInstance;
}
AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{
if (!m_focusedInstance.has_value())
{
// PrefabFocusHandler has not been initialized yet.
return AZ::EntityId();
}
return m_focusedInstance->get().GetContainerEntityId();
}
bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const
{
if (!m_focusedInstance.has_value())
@@ -200,7 +234,7 @@ namespace AzToolsFramework::Prefab
m_instanceFocusVector.clear();
// Focus on the root prefab (AZ::EntityId() will default to it)
FocusOnOwningPrefab(AZ::EntityId());
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
}
void PrefabFocusHandler::RefreshInstanceFocusList()
@@ -13,6 +13,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework
@@ -28,6 +29,7 @@ namespace AzToolsFramework::Prefab
//! Handles Prefab Focus mode, determining which prefab file entity changes will target.
class PrefabFocusHandler final
: private PrefabFocusInterface
, private PrefabFocusPublicInterface
, private EditorEntityContextNotificationBus::Handler
{
public:
@@ -39,10 +41,14 @@ namespace AzToolsFramework::Prefab
void Initialize();
// PrefabFocusInterface overrides ...
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
// PrefabFocusPublicInterface overrides ...
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override;
bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override;
const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const override;
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
@@ -20,7 +20,7 @@ namespace AzToolsFramework::Prefab
{
using PrefabFocusOperationResult = AZ::Outcome<void, AZStd::string>;
//! Interface to handle operations related to the Prefab Focus system.
//! Interface to handle internal operations related to the Prefab Focus system.
class PrefabFocusInterface
{
public:
@@ -28,29 +28,13 @@ namespace AzToolsFramework::Prefab
//! Set the focused prefab instance to the owning instance of the entityId provided.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
//! Set the focused prefab instance to the instance at position index of the current path.
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;
//! Returns the template id of the instance the prefab system is focusing on.
virtual TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns a reference to the instance the prefab system is focusing on.
virtual InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants.
//! @param entityId The entityId of the queried entity.
//! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise.
virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0;
//! Returns the path from the root instance to the currently focused instance.
//! @return A path composed from the names of the container entities for the instance path.
virtual const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns the size of the path to the currently focused instance.
virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0;
};
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,53 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework::Prefab
{
using PrefabFocusOperationResult = AZ::Outcome<void, AZStd::string>;
//! Public Interface for external systems to utilize the Prefab Focus system.
class PrefabFocusPublicInterface
{
public:
AZ_RTTI(PrefabFocusPublicInterface, "{53EE1D18-A41F-4DB1-9B73-9448F425722E}");
//! Set the focused prefab instance to the owning instance of the entityId provided. Supports undo/redo.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
//! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo.
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
//! Returns the entity id of the container entity for the instance the prefab system is focusing on.
virtual AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants.
//! @param entityId The entityId of the queried entity.
//! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise.
virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0;
//! Returns the path from the root instance to the currently focused instance.
//! @return A path composed from the names of the container entities for the instance path.
virtual const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns the size of the path to the currently focused instance.
virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0;
};
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,52 @@
/*
* 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
*
*/
#include <AzToolsFramework/Prefab/PrefabFocusUndo.h>
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
namespace AzToolsFramework::Prefab
{
PrefabFocusUndo::PrefabFocusUndo(const AZStd::string& undoOperationName)
: UndoSystem::URSequencePoint(undoOperationName)
{
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
AZ_Assert(m_prefabFocusInterface, "PrefabFocusUndo - Failed to grab prefab focus interface");
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
AZ_Assert(m_prefabFocusPublicInterface, "PrefabFocusUndo - Failed to grab prefab focus public interface");
}
bool PrefabFocusUndo::Changed() const
{
return true;
}
void PrefabFocusUndo::Capture(AZ::EntityId entityId)
{
auto entityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(entityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
m_beforeEntityId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(entityContextId);
m_afterEntityId = entityId;
}
void PrefabFocusUndo::Undo()
{
m_prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(m_beforeEntityId);
}
void PrefabFocusUndo::Redo()
{
m_prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(m_afterEntityId);
}
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,39 @@
/*
* 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
*
*/
#pragma once
#include <AzCore/Component/EntityId.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
namespace AzToolsFramework::Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
//! Undo node for prefab focus change operations.
class PrefabFocusUndo
: public UndoSystem::URSequencePoint
{
public:
explicit PrefabFocusUndo(const AZStd::string& undoOperationName);
bool Changed() const override;
void Capture(AZ::EntityId entityId);
void Undo() override;
void Redo() override;
protected:
PrefabFocusInterface* m_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
AZ::EntityId m_beforeEntityId;
AZ::EntityId m_afterEntityId;
};
} // namespace AzToolsFramework::Prefab
@@ -43,6 +43,12 @@ namespace AzToolsFramework
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "PrefabPublicHandler - Could not retrieve instance of InstanceToTemplateInterface");
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
AZ_Assert(m_prefabFocusInterface, "Could not get PrefabFocusInterface on PrefabPublicHandler construction.");
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
AZ_Assert(m_prefabFocusPublicInterface, "Could not get PrefabFocusPublicInterface on PrefabPublicHandler construction.");
m_prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
AZ_Assert(m_prefabLoaderInterface, "Could not get PrefabLoaderInterface on PrefabPublicHandler construction.");
@@ -552,6 +558,13 @@ namespace AzToolsFramework
PrefabEntityResult PrefabPublicHandler::CreateEntity(AZ::EntityId parentId, const AZ::Vector3& position)
{
// If the parent is invalid, parent to the container of the currently focused prefab.
if (!parentId.IsValid())
{
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
parentId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
}
InstanceOptionalReference owningInstanceOfParentEntity = GetOwnerInstanceByEntityId(parentId);
if (!owningInstanceOfParentEntity)
{
@@ -968,13 +981,13 @@ namespace AzToolsFramework
return AZ::Failure(AZStd::string("No entities to duplicate."));
}
const EntityIdList entityIdsNoLevelInstance = GenerateEntityIdListWithoutLevelInstance(entityIds);
if (entityIdsNoLevelInstance.empty())
const EntityIdList entityIdsNoFocusContainer = GenerateEntityIdListWithoutFocusedInstanceContainer(entityIds);
if (entityIdsNoFocusContainer.empty())
{
return AZ::Failure(AZStd::string("No entities to duplicate because only instance selected is the level instance."));
return AZ::Failure(AZStd::string("No entities to duplicate because only instance selected is the container entity of the focused instance."));
}
if (!EntitiesBelongToSameInstance(entityIdsNoLevelInstance))
if (!EntitiesBelongToSameInstance(entityIdsNoFocusContainer))
{
return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation."
"Change your selection to contain entities in the same instance."));
@@ -982,7 +995,7 @@ namespace AzToolsFramework
// We've already verified the entities are all owned by the same instance,
// so we can just retrieve our instance from the first entity in the list.
AZ::EntityId firstEntityIdToDuplicate = entityIdsNoLevelInstance[0];
AZ::EntityId firstEntityIdToDuplicate = entityIdsNoFocusContainer[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate);
if (!commonOwningInstance.has_value())
{
@@ -1002,7 +1015,7 @@ namespace AzToolsFramework
// This will cull out any entities that have ancestors in the list, since we will end up duplicating
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIdsNoLevelInstance);
AzToolsFramework::EntityIdSet duplicationSet = AzToolsFramework::GetCulledEntityHierarchy(entityIdsNoFocusContainer);
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -1039,10 +1052,10 @@ namespace AzToolsFramework
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
entities, instanceDomAfter, duplicatedEntityAndInstanceIds, duplicateEntityAliasMap);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication", false);
command->SetParent(undoBatch.GetUndoBatch());
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
command->RedoBatched();
command->Redo();
DuplicateNestedInstancesInInstance(commonOwningInstance->get(),
instances, instanceDomAfter, duplicatedEntityAndInstanceIds, newInstanceAliasToOldInstanceMap);
@@ -1106,19 +1119,21 @@ namespace AzToolsFramework
PrefabOperationResult PrefabPublicHandler::DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants)
{
const EntityIdList entityIdsNoLevelInstance = GenerateEntityIdListWithoutLevelInstance(entityIds);
// Remove the container entity of the focused prefab from the list, if it is included.
const EntityIdList entityIdsNoFocusContainer = GenerateEntityIdListWithoutFocusedInstanceContainer(entityIds);
if (entityIdsNoLevelInstance.empty())
if (entityIdsNoFocusContainer.empty())
{
return AZ::Success();
}
if (!EntitiesBelongToSameInstance(entityIdsNoLevelInstance))
// All entities in this list need to belong to the same prefab instance for the operation to be valid.
if (!EntitiesBelongToSameInstance(entityIdsNoFocusContainer))
{
return AZ::Failure(AZStd::string("Cannot delete multiple entities belonging to different instances with one operation."));
}
AZ::EntityId firstEntityIdToDelete = entityIdsNoLevelInstance[0];
AZ::EntityId firstEntityIdToDelete = entityIdsNoFocusContainer[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDelete);
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
@@ -1128,8 +1143,15 @@ namespace AzToolsFramework
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
}
// We only allow explicit deletions for entities inside the currently focused prefab.
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
if (&m_prefabFocusInterface->GetFocusedPrefabInstance(editorEntityContextId)->get() != &commonOwningInstance->get())
{
return AZ::Failure(AZStd::string("Cannot delete entities belonging to an instance that is not being edited."));
}
// Retrieve entityList from entityIds
EntityList inputEntityList = EntityIdListToEntityList(entityIdsNoLevelInstance);
EntityList inputEntityList = EntityIdListToEntityList(entityIdsNoFocusContainer);
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -1186,7 +1208,7 @@ namespace AzToolsFramework
}
else
{
for (AZ::EntityId entityId : entityIdsNoLevelInstance)
for (AZ::EntityId entityId : entityIdsNoFocusContainer)
{
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
// If this is the container entity, it actually represents the instance so get its owner
@@ -1227,9 +1249,12 @@ namespace AzToolsFramework
return AZ::Failure(AZStd::string("Cannot detach Prefab Instance with invalid container entity."));
}
if (IsLevelInstanceContainerEntity(containerEntityId))
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
if (containerEntityId == m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
{
return AZ::Failure(AZStd::string("Cannot detach level Prefab Instance."));
return AZ::Failure(AZStd::string("Cannot detach focused Prefab Instance."));
}
InstanceOptionalReference owningInstance = GetOwnerInstanceByEntityId(containerEntityId);
@@ -1298,7 +1323,7 @@ namespace AzToolsFramework
Prefab::PrefabDom instanceDomAfter;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomAfter, parentInstance);
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment");
PrefabUndoInstance* command = aznew PrefabUndoInstance("Instance detachment", false);
command->Capture(instanceDomBefore, instanceDomAfter, parentTemplateId);
command->SetParent(undoBatch.GetUndoBatch());
{
@@ -1452,9 +1477,14 @@ namespace AzToolsFramework
AZStd::queue<AZ::Entity*> entityQueue;
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
AZ::EntityId focusedPrefabContainerEntityId =
m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
for (auto inputEntity : inputEntities)
{
if (inputEntity && !IsLevelInstanceContainerEntity(inputEntity->GetId()))
if (inputEntity && inputEntity->GetId() != focusedPrefabContainerEntityId)
{
entityQueue.push(inputEntity);
}
@@ -1548,19 +1578,19 @@ namespace AzToolsFramework
return AZ::Success();
}
EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutLevelInstance(
EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutFocusedInstanceContainer(
const EntityIdList& entityIds) const
{
EntityIdList outEntityIds;
outEntityIds.reserve(entityIds.size()); // Actual size could be smaller.
EntityIdList outEntityIds(entityIds);
for (const AZ::EntityId& entityId : entityIds)
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
AZ::EntityId focusedInstanceContainerEntityId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId);
if (auto iter = AZStd::find(outEntityIds.begin(), outEntityIds.end(), focusedInstanceContainerEntityId); iter != outEntityIds.end())
{
if (!IsLevelInstanceContainerEntity(entityId))
{
outEntityIds.emplace_back(entityId);
}
outEntityIds.erase(iter);
}
return outEntityIds;
}
@@ -74,7 +74,7 @@ namespace AzToolsFramework
Instance& commonRootEntityOwningInstance,
EntityList& outEntities,
AZStd::vector<Instance*>& outInstances) const;
EntityIdList GenerateEntityIdListWithoutLevelInstance(const EntityIdList& entityIds) const;
EntityIdList GenerateEntityIdListWithoutFocusedInstanceContainer(const EntityIdList& entityIds) const;
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
@@ -187,6 +187,8 @@ namespace AzToolsFramework
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
PrefabFocusInterface* m_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
PrefabLoaderInterface* m_prefabLoaderInterface = nullptr;
PrefabSystemComponentInterface* m_prefabSystemComponentInterface = nullptr;
@@ -6,11 +6,14 @@
*
*/
#include <API/ToolsApplicationAPI.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/RTTI/BehaviorContext.h>
#include <Prefab/PrefabSystemComponentInterface.h>
#include <Prefab/PrefabSystemScriptingHandler.h>
#include <AzCore/Component/Entity.h>
#include <Prefab/EditorPrefabComponent.h>
#include <ToolsComponents/TransformComponent.h>
namespace AzToolsFramework::Prefab
{
@@ -61,9 +64,30 @@ namespace AzToolsFramework::Prefab
entities.push_back(entity);
}
}
auto prefab = m_prefabSystemComponentInterface->CreatePrefab(entities, {}, AZ::IO::PathView(AZStd::string_view(filePath)));
bool result = false;
[[maybe_unused]] AZ::EntityId commonRoot;
EntityList topLevelEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(result, &AzToolsFramework::ToolsApplicationRequestBus::Events::FindCommonRootInactive,
entities, commonRoot, &topLevelEntities);
auto containerEntity = AZStd::make_unique<AZ::Entity>();
containerEntity->CreateComponent<Prefab::EditorPrefabComponent>();
for (AZ::Entity* entity : topLevelEntities)
{
AzToolsFramework::Components::TransformComponent* transformComponent =
entity->FindComponent<AzToolsFramework::Components::TransformComponent>();
if (transformComponent)
{
transformComponent->SetParent(containerEntity->GetId());
}
}
auto prefab = m_prefabSystemComponentInterface->CreatePrefab(
entities, {}, AZ::IO::PathView(AZStd::string_view(filePath)), AZStd::move(containerEntity));
if (!prefab)
{
AZ_Error("PrefabSystemComponenent", false, "Failed to create prefab %s", filePath.c_str());
@@ -17,17 +17,16 @@ namespace AzToolsFramework
{
PrefabUndoBase::PrefabUndoBase(const AZStd::string& undoOperationName)
: UndoSystem::URSequencePoint(undoOperationName)
, m_changed(true)
, m_templateId(InvalidTemplateId)
{
m_instanceToTemplateInterface = AZ::Interface<InstanceToTemplateInterface>::Get();
AZ_Assert(m_instanceToTemplateInterface, "Failed to grab instance to template interface");
}
//PrefabInstanceUndo
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName)
PrefabUndoInstance::PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation)
: PrefabUndoBase(undoOperationName)
{
m_useImmediatePropagation = useImmediatePropagation;
}
void PrefabUndoInstance::Capture(
@@ -43,17 +42,12 @@ namespace AzToolsFramework
void PrefabUndoInstance::Undo()
{
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, true);
m_instanceToTemplateInterface->PatchTemplate(m_undoPatch, m_templateId, m_useImmediatePropagation);
}
void PrefabUndoInstance::Redo()
{
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, true);
}
void PrefabUndoInstance::RedoBatched()
{
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId);
m_instanceToTemplateInterface->PatchTemplate(m_redoPatch, m_templateId, m_useImmediatePropagation);
}
@@ -29,14 +29,15 @@ namespace AzToolsFramework
bool Changed() const override { return m_changed; }
protected:
TemplateId m_templateId;
TemplateId m_templateId = InvalidTemplateId;
PrefabDom m_redoPatch;
PrefabDom m_undoPatch;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
bool m_changed;
bool m_changed = true;
bool m_useImmediatePropagation = true;
};
//! handles the addition and removal of entities from instances
@@ -44,7 +45,7 @@ namespace AzToolsFramework
: public PrefabUndoBase
{
public:
explicit PrefabUndoInstance(const AZStd::string& undoOperationName);
explicit PrefabUndoInstance(const AZStd::string& undoOperationName, const bool useImmediatePropagation = true);
void Capture(
const PrefabDom& initialState,
@@ -53,7 +54,6 @@ namespace AzToolsFramework
void Undo() override;
void Redo() override;
void RedoBatched();
};
//! handles entity updates, such as when the values on an entity change
@@ -23,10 +23,10 @@ namespace AzToolsFramework
PrefabDom instanceDomAfterUpdate;
PrefabDomUtils::StoreInstanceInPrefabDom(instance, instanceDomAfterUpdate);
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage);
PrefabUndoInstance* state = aznew Prefab::PrefabUndoInstance(undoMessage, false);
state->Capture(instanceDomBeforeUpdate, instanceDomAfterUpdate, instance.GetTemplateId());
state->SetParent(undoBatch);
state->RedoBatched();
state->Redo();
}
LinkId CreateLink(
@@ -313,7 +313,8 @@ namespace AzToolsFramework
StyledTreeView::StartCustomDrag(indexListSorted, supportedActions);
}
void EntityOutlinerTreeView::OnEditorFocusChanged([[maybe_unused]] AZ::EntityId entityId)
void EntityOutlinerTreeView::OnEditorFocusChanged(
[[maybe_unused]] AZ::EntityId previousFocusEntityId, [[maybe_unused]] AZ::EntityId newFocusEntityId)
{
viewport()->repaint();
}
@@ -64,7 +64,7 @@ namespace AzToolsFramework
void leaveEvent(QEvent* event) override;
// FocusModeNotificationBus overrides ...
void OnEditorFocusChanged(AZ::EntityId entityId) override;
void OnEditorFocusChanged(AZ::EntityId previousFocusEntityId, AZ::EntityId newFocusEntityId) override;
//! Renders the left side of the item: appropriate background, branch lines, icons.
void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
@@ -324,7 +324,8 @@ namespace AzToolsFramework
// Currently, the first behavior is implemented.
void EntityOutlinerWidget::OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{
if (m_selectionChangeInProgress || !m_enableSelectionUpdates)
if (m_selectionChangeInProgress || !m_enableSelectionUpdates
|| (selected.empty() && deselected.empty()))
{
return;
}
@@ -552,6 +553,13 @@ namespace AzToolsFramework
return;
}
// Do not display the context menu if the item under the mouse cursor is not selectable.
if (const QModelIndex& index = m_gui->m_objectTree->indexAt(pos); index.isValid()
&& (index.flags() & Qt::ItemIsSelectable) == 0)
{
return;
}
QMenu* contextMenu = new QMenu(this);
// Populate global context menu.
@@ -24,11 +24,12 @@
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
@@ -39,7 +40,6 @@
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/Widgets/CardHeader.h>
#include <QApplication>
#include <QCheckBox>
#include <QDialog>
@@ -56,14 +56,13 @@
#include <QVBoxLayout>
#include <QWidget>
namespace AzToolsFramework
{
namespace Prefab
{
ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr;
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
PrefabFocusInterface* PrefabIntegrationManager::s_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr;
PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr;
PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
PrefabSystemComponentInterface* PrefabIntegrationManager::s_prefabSystemComponentInterface = nullptr;
@@ -129,10 +128,10 @@ namespace AzToolsFramework
return;
}
s_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
if (s_prefabFocusInterface == nullptr)
s_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
if (s_prefabFocusPublicInterface == nullptr)
{
AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabIntegrationManager construction.");
AZ_Assert(false, "Prefab - could not get PrefabFocusPublicInterface on PrefabIntegrationManager construction.");
return;
}
@@ -177,12 +176,16 @@ namespace AzToolsFramework
AzFramework::ApplicationRequests::Bus::BroadcastResult(
prefabWipFeaturesEnabled, &AzFramework::ApplicationRequests::ArePrefabWipFeaturesEnabled);
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
// Create Prefab
{
if (!selectedEntities.empty())
{
// Hide if the only selected entity is the Level Container
if (selectedEntities.size() > 1 || !s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0]))
// Hide if the only selected entity is the Focused Instance Container
if (selectedEntities.size() > 1 ||
selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
{
bool layerInSelection = false;
@@ -247,21 +250,16 @@ namespace AzToolsFramework
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
{
// Edit Prefab
if (prefabWipFeaturesEnabled)
if (prefabWipFeaturesEnabled && !s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
bool beingEdited = s_prefabFocusInterface->IsOwningPrefabBeingFocused(selectedEntity);
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
if (!beingEdited)
{
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] {
ContextMenu_EditPrefab(selectedEntity);
});
QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] {
ContextMenu_EditPrefab(selectedEntity);
});
itemWasShown = true;
}
itemWasShown = true;
}
// Save Prefab
@@ -290,8 +288,9 @@ namespace AzToolsFramework
QAction* deleteAction = menu->addAction(QObject::tr("Delete"));
QObject::connect(deleteAction, &QAction::triggered, deleteAction, [] { ContextMenu_DeleteSelected(); });
if (selectedEntities.size() == 0 ||
(selectedEntities.size() == 1 && s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntities[0])))
if (selectedEntities.empty() ||
(selectedEntities.size() == 1 && selectedEntities[0] == s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId)))
{
deleteAction->setDisabled(true);
}
@@ -299,17 +298,17 @@ namespace AzToolsFramework
// Detach Prefab
if (selectedEntities.size() == 1)
{
AZ::EntityId selectedEntity = selectedEntities[0];
AZ::EntityId selectedEntityId = selectedEntities[0];
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity) &&
!s_prefabPublicInterface->IsLevelInstanceContainerEntity(selectedEntity))
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntityId) &&
selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId))
{
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
QObject::connect(
detachPrefabAction, &QAction::triggered, detachPrefabAction,
[selectedEntity]
[selectedEntityId]
{
ContextMenu_DetachPrefab(selectedEntity);
ContextMenu_DetachPrefab(selectedEntityId);
});
}
}
@@ -317,7 +316,7 @@ namespace AzToolsFramework
void PrefabIntegrationManager::OnEscape()
{
s_prefabFocusInterface->FocusOnOwningPrefab(AZ::EntityId());
s_prefabFocusPublicInterface->FocusOnOwningPrefab(AZ::EntityId());
}
void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
@@ -338,13 +337,21 @@ namespace AzToolsFramework
QWidget* activeWindow = QApplication::activeWindow();
const AZStd::string prefabFilesPath = "@projectroot@/Prefabs";
// Remove Level entity if it's part of the list
auto levelContainerIter =
AZStd::find(selectedEntities.begin(), selectedEntities.end(), s_prefabPublicInterface->GetLevelInstanceContainerEntityId());
if (levelContainerIter != selectedEntities.end())
// Remove focused instance container entity if it's part of the list
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
auto focusedContainerIter = AZStd::find(
selectedEntities.begin(), selectedEntities.end(),
s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(editorEntityContextId));
if (focusedContainerIter != selectedEntities.end())
{
selectedEntities.erase(levelContainerIter);
selectedEntities.erase(focusedContainerIter);
}
if (selectedEntities.empty())
{
return;
}
// Set default folder for prefabs
@@ -490,7 +497,7 @@ namespace AzToolsFramework
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
{
s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
s_prefabFocusPublicInterface->FocusOnOwningPrefab(containerEntity);
}
void PrefabIntegrationManager::ContextMenu_SavePrefab(AZ::EntityId containerEntity)
@@ -30,7 +30,7 @@ namespace AzToolsFramework
namespace Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
class PrefabLoaderInterface;
//! Structure for saving/retrieving user settings related to prefab workflows.
@@ -144,7 +144,7 @@ namespace AzToolsFramework
static ContainerEntityInterface* s_containerEntityInterface;
static EditorEntityUiInterface* s_editorEntityUiInterface;
static PrefabFocusInterface* s_prefabFocusInterface;
static PrefabFocusPublicInterface* s_prefabFocusPublicInterface;
static PrefabLoaderInterface* s_prefabLoaderInterface;
static PrefabPublicInterface* s_prefabPublicInterface;
static PrefabSystemComponentInterface* s_prefabSystemComponentInterface;
@@ -10,7 +10,7 @@
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
@@ -35,10 +35,10 @@ namespace AzToolsFramework
return;
}
m_prefabFocusInterface = AZ::Interface<Prefab::PrefabFocusInterface>::Get();
if (m_prefabFocusInterface == nullptr)
m_prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get();
if (m_prefabFocusPublicInterface == nullptr)
{
AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusInterface on PrefabUiHandler construction.");
AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusPublicInterface on PrefabUiHandler construction.");
return;
}
}
@@ -83,7 +83,7 @@ namespace AzToolsFramework
QIcon PrefabUiHandler::GenerateItemIcon(AZ::EntityId entityId) const
{
if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
return QIcon(m_prefabEditIconPath);
}
@@ -105,7 +105,7 @@ namespace AzToolsFramework
const bool hasVisibleChildren = index.data(EntityOutlinerListModel::ExpandedRole).value<bool>() && index.model()->hasChildren(index);
QColor backgroundColor = m_prefabCapsuleColor;
if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
backgroundColor = m_prefabCapsuleEditColor;
}
@@ -178,12 +178,6 @@ namespace AzToolsFramework
AZ::EntityId entityId(index.data(EntityOutlinerListModel::EntityIdRole).value<AZ::u64>());
// We hide the root instance container entity from the Outliner, so avoid drawing its full container on children
if (m_prefabPublicInterface->IsLevelInstanceContainerEntity(entityId))
{
return;
}
const QTreeView* outlinerTreeView(qobject_cast<const QTreeView*>(option.widget));
const int ancestorLeft = outlinerTreeView->visualRect(index).left() + (m_prefabBorderThickness / 2) - 1;
const int curveRectSize = m_prefabCapsuleRadius * 2;
@@ -191,7 +185,7 @@ namespace AzToolsFramework
const bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle;
QColor borderColor = m_prefabCapsuleColor;
if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
borderColor = m_prefabCapsuleEditColor;
}
@@ -329,7 +323,7 @@ namespace AzToolsFramework
if (prefabWipFeaturesEnabled)
{
// Focus on this prefab
m_prefabFocusInterface->FocusOnOwningPrefab(entityId);
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
}
}
}
@@ -15,7 +15,7 @@ namespace AzToolsFramework
namespace Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
class PrefabPublicInterface;
};
@@ -39,7 +39,7 @@ namespace AzToolsFramework
void OnDoubleClick(AZ::EntityId entityId) const override;
private:
Prefab::PrefabFocusInterface* m_prefabFocusInterface = nullptr;
Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
static bool IsLastVisibleChild(const QModelIndex& parent, const QModelIndex& child);
@@ -8,7 +8,7 @@
#include <AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
namespace AzToolsFramework::Prefab
{
@@ -31,8 +31,8 @@ namespace AzToolsFramework::Prefab
void PrefabViewportFocusPathHandler::Initialize(AzQtComponents::BreadCrumbs* breadcrumbsWidget, QToolButton* backButton)
{
// Get reference to the PrefabFocusInterface handler
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
if (m_prefabFocusInterface == nullptr)
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
if (m_prefabFocusPublicInterface == nullptr)
{
AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabViewportFocusPathHandler construction.");
return;
@@ -46,7 +46,7 @@ namespace AzToolsFramework::Prefab
connect(m_breadcrumbsWidget, &AzQtComponents::BreadCrumbs::linkClicked, this,
[&](const QString&, int linkIndex)
{
m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, linkIndex);
m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, linkIndex);
}
);
@@ -54,9 +54,9 @@ namespace AzToolsFramework::Prefab
connect(m_backButton, &QToolButton::clicked, this,
[&]()
{
if (int length = m_prefabFocusInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1)
if (int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1)
{
m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2);
m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2);
}
}
);
@@ -65,7 +65,7 @@ namespace AzToolsFramework::Prefab
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
{
// Push new Path
m_breadcrumbsWidget->pushPath(m_prefabFocusInterface->GetPrefabFocusPath(m_editorEntityContextId).c_str());
m_breadcrumbsWidget->pushPath(m_prefabFocusPublicInterface->GetPrefabFocusPath(m_editorEntityContextId).c_str());
}
} // namespace AzToolsFramework::Prefab
@@ -19,7 +19,7 @@
namespace AzToolsFramework::Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
class PrefabViewportFocusPathHandler
: public PrefabFocusNotificationBus::Handler
@@ -40,6 +40,6 @@ namespace AzToolsFramework::Prefab
AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
PrefabFocusInterface* m_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
};
} // namespace AzToolsFramework::Prefab
@@ -9,6 +9,7 @@
#include "EditorHelpers.h"
#include <AzCore/Console/Console.h>
#include <AzCore/Math/VectorConversions.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
@@ -150,6 +151,11 @@ namespace AzToolsFramework
"EditorHelpers - "
"Focus Mode Interface could not be found. "
"Check that it is being correctly initialized.");
AZStd::vector<AZStd::unique_ptr<InvalidClick>> invalidClicks;
invalidClicks.push_back(AZStd::make_unique<FadingText>("Not in focus"));
invalidClicks.push_back(AZStd::make_unique<ExpandingFadingCircles>());
m_invalidClicks = AZStd::make_unique<InvalidClicks>(AZStd::move(invalidClicks));
}
CursorEntityIdQuery EditorHelpers::FindEntityIdUnderCursor(
@@ -213,13 +219,20 @@ namespace AzToolsFramework
}
}
// Verify if the entity Id corresponds to an entity that is focused; if not, halt selection.
if (!IsSelectableAccordingToFocusMode(entityIdUnderCursor))
// verify if the entity Id corresponds to an entity that is focused; if not, halt selection.
if (entityIdUnderCursor.IsValid() && !IsSelectableAccordingToFocusMode(entityIdUnderCursor))
{
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down ||
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::DoubleClick)
{
m_invalidClicks->AddInvalidClick(mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
}
return CursorEntityIdQuery(AZ::EntityId(), AZ::EntityId());
}
// Container Entity support - if the entity that is being selected is part of a closed container,
// container entity support - if the entity that is being selected is part of a closed container,
// change the selection to the container instead.
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
{
@@ -230,6 +243,12 @@ namespace AzToolsFramework
return CursorEntityIdQuery(entityIdUnderCursor, AZ::EntityId());
}
void EditorHelpers::Display2d(
[[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay)
{
m_invalidClicks->Display2d(viewportInfo, debugDisplay);
}
void EditorHelpers::DisplayHelpers(
const AzFramework::ViewportInfo& viewportInfo,
const AzFramework::CameraState& cameraState,
@@ -291,19 +310,19 @@ namespace AzToolsFramework
}
}
bool EditorHelpers::IsSelectableInViewport(AZ::EntityId entityId)
bool EditorHelpers::IsSelectableInViewport(const AZ::EntityId entityId) const
{
return IsSelectableAccordingToFocusMode(entityId) && IsSelectableAccordingToContainerEntities(entityId);
}
bool EditorHelpers::IsSelectableAccordingToFocusMode(AZ::EntityId entityId)
bool EditorHelpers::IsSelectableAccordingToFocusMode(const AZ::EntityId entityId) const
{
return m_focusModeInterface->IsInFocusSubTree(entityId);
}
bool EditorHelpers::IsSelectableAccordingToContainerEntities(AZ::EntityId entityId)
bool EditorHelpers::IsSelectableAccordingToContainerEntities(const AZ::EntityId entityId) const
{
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
if (const auto* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
{
return !containerEntityInterface->IsUnderClosedContainerEntity(entityId);
}
@@ -11,6 +11,9 @@
#include <AzCore/Component/EntityId.h>
#include <AzCore/Memory/Memory.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/containers/vector.h>
#include <AzFramework/Viewport/ScreenGeometry.h>
#include <AzToolsFramework/ViewportSelection/InvalidClicks.h>
namespace AzFramework
{
@@ -80,20 +83,27 @@ namespace AzToolsFramework
AzFramework::DebugDisplayRequests& debugDisplay,
const AZStd::function<bool(AZ::EntityId)>& showIconCheck);
//! Handle 2d drawing for EditorHelper functionality.
void Display2d(
const AzFramework::ViewportInfo& viewportInfo,
AzFramework::DebugDisplayRequests& debugDisplay);
//! Returns whether the entityId can be selected in the viewport according
//! to the current Editor Focus Mode and Container Entity setup.
bool IsSelectableInViewport(AZ::EntityId entityId);
bool IsSelectableInViewport(AZ::EntityId entityId) const;
private:
//! Returns whether the entityId can be selected in the viewport according
//! to the current Editor Focus Mode setup.
bool IsSelectableAccordingToFocusMode(AZ::EntityId entityId);
bool IsSelectableAccordingToFocusMode(AZ::EntityId entityId) const;
//! Returns whether the entityId can be selected in the viewport according
//! to the current Container Entityu setup.
bool IsSelectableAccordingToContainerEntities(AZ::EntityId entityId);
//! to the current Container Entity setup.
bool IsSelectableAccordingToContainerEntities(AZ::EntityId entityId) const;
AZStd::unique_ptr<InvalidClicks> m_invalidClicks; //!< Display for invalid click behavior.
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
const FocusModeInterface* m_focusModeInterface = nullptr;
const FocusModeInterface* m_focusModeInterface = nullptr; //!< API to interact with focus mode functionality.
};
} // namespace AzToolsFramework

Some files were not shown because too many files have changed in this diff Show More