Apply reviewer suggestions

Signed-off-by: Nemerle <nemerle5+git@gmail.com>
This commit is contained in:
Nemerle
2021-12-17 16:02:33 +01:00
449 changed files with 11205 additions and 5685 deletions
@@ -18,7 +18,7 @@
namespace AZ::Data
{
namespace Internal
namespace DataStreamInternal
{
struct AssetDataStreamPrivate
{
@@ -39,7 +39,7 @@ namespace AZ::Data
}
void BlockUntilReadComplete()
{
AZStd::unique_lock<AZStd::mutex> lock(m_readRequestMutex);
AZStd::unique_lock lock(m_readRequestMutex);
m_readRequestActive.wait(
lock,
[this]
@@ -59,9 +59,10 @@ namespace AZ::Data
}
};
} // namespace Internal
AssetDataStream::AssetDataStream(AZ::IO::IStreamerTypes::RequestMemoryAllocator* bufferAllocator)
: m_bufferAllocator(bufferAllocator ? bufferAllocator : &m_defaultAllocator)
, m_privateData(new Internal::AssetDataStreamPrivate)
: m_privateData(AZStd::make_unique<DataStreamInternal::AssetDataStreamPrivate>())
, m_bufferAllocator(bufferAllocator ? bufferAllocator : &m_defaultAllocator)
{
ClearInternalStateData();
}
@@ -72,7 +73,6 @@ namespace AZ::Data
{
Close();
}
delete m_privateData;
}
@@ -9,8 +9,8 @@
#include <AzCore/IO/GenericStreams.h>
#include <AzCore/IO/IStreamerTypes.h>
#include <AzCore/std/function/function_template.h>
#include <AzCore/std/functional.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZStd
{
@@ -20,7 +20,7 @@ namespace AZStd
namespace AZ::Data
{
namespace Internal
namespace DataStreamInternal
{
struct AssetDataStreamPrivate;
}
@@ -100,7 +100,7 @@ namespace AZ::Data
void ClearInternalStateData();
Internal::AssetDataStreamPrivate* m_privateData;
AZStd::unique_ptr<DataStreamInternal::AssetDataStreamPrivate> m_privateData;
//! The allocator to use for allocating / deallocating asset buffers
AZ::IO::IStreamerTypes::RequestMemoryAllocator* m_bufferAllocator{ nullptr };
@@ -74,7 +74,7 @@ namespace AZ::IO
void CreateDedicatedCache(FileRequest* request, Requests::CreateDedicatedCacheData& data);
void DestroyDedicatedCache(FileRequest* request, Requests::DestroyDedicatedCacheData& data);
void ReadFile(FileRequest* request, AZ::IO::Requests::ReadData& data);
void ReadFile(FileRequest* request, Requests::ReadData& data);
size_t FindCache(const RequestPath& filename, FileRange range);
size_t FindCache(const RequestPath& filename, u64 offset);
@@ -153,7 +153,6 @@ namespace AZ::IO::Requests
namespace AZ::IO
{
using namespace Requests;
//
// FileRequest
//
@@ -176,14 +175,14 @@ namespace AZ::IO
m_parent = request->m_request.m_parent;
request->m_request.m_parent = this;
m_dependencies++;
m_command.emplace<ExternalRequestData>(AZStd::move(request));
m_command.emplace<Requests::ExternalRequestData>(AZStd::move(request));
}
void FileRequest::CreateRequestPathStore(FileRequest* parent, RequestPath path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CreateRequestPathStore', but another task was already assigned.");
m_command.emplace<RequestPathStoreData>(AZStd::move(path));
m_command.emplace<Requests::RequestPathStoreData>(AZStd::move(path));
SetOptionalParent(parent);
}
@@ -192,7 +191,7 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'ReadRequest', but another task was already assigned.");
m_command.emplace<ReadRequestData>(AZStd::move(path), output, outputSize, offset, size, deadline, priority);
m_command.emplace<Requests::ReadRequestData>(AZStd::move(path), output, outputSize, offset, size, deadline, priority);
}
void FileRequest::CreateReadRequest(RequestPath path, IStreamerTypes::RequestMemoryAllocator* allocator, u64 offset, u64 size,
@@ -200,7 +199,7 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'ReadRequest', but another task was already assigned.");
m_command.emplace<ReadRequestData>(AZStd::move(path), allocator, offset, size, deadline, priority);
m_command.emplace<Requests::ReadRequestData>(AZStd::move(path), allocator, offset, size, deadline, priority);
}
void FileRequest::CreateRead(FileRequest* parent, void* output, u64 outputSize, const RequestPath& path,
@@ -208,7 +207,7 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Read', but another task was already assigned.");
m_command.emplace<ReadData>(output, outputSize, AZStd::move(path), offset, size, sharedRead);
m_command.emplace<Requests::ReadData>(output, outputSize, AZStd::move(path), offset, size, sharedRead);
SetOptionalParent(parent);
}
@@ -223,7 +222,7 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CompressedRead', but another task was already assigned.");
m_command.emplace<CompressedReadData>(AZStd::move(compressionInfo), output, readOffset, readSize);
m_command.emplace<Requests::CompressedReadData>(AZStd::move(compressionInfo), output, readOffset, readSize);
SetOptionalParent(parent);
}
@@ -231,7 +230,7 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Wait', but another task was already assigned.");
m_command.emplace<WaitData>();
m_command.emplace<Requests::WaitData>();
SetOptionalParent(parent);
}
@@ -239,21 +238,21 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FileExistsCheck', but another task was already assigned.");
m_command.emplace<FileExistsCheckData>(path);
m_command.emplace<Requests::FileExistsCheckData>(path);
}
void FileRequest::CreateFileMetaDataRetrieval(const RequestPath& path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FileMetaDataRetrieval', but another task was already assigned.");
m_command.emplace<FileMetaDataRetrievalData>(path);
m_command.emplace<Requests::FileMetaDataRetrievalData>(path);
}
void FileRequest::CreateCancel(FileRequestPtr target)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Cancel', but another task was already assigned.");
m_command.emplace<CancelData>(AZStd::move(target));
m_command.emplace<Requests::CancelData>(AZStd::move(target));
}
void FileRequest::CreateReschedule(FileRequestPtr target, AZStd::chrono::system_clock::time_point newDeadline,
@@ -261,28 +260,28 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Reschedule', but another task was already assigned.");
m_command.emplace<RescheduleData>(AZStd::move(target), newDeadline, newPriority);
m_command.emplace<Requests::RescheduleData>(AZStd::move(target), newDeadline, newPriority);
}
void FileRequest::CreateFlush(RequestPath path)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Flush', but another task was already assigned.");
m_command.emplace<FlushData>(AZStd::move(path));
m_command.emplace<Requests::FlushData>(AZStd::move(path));
}
void FileRequest::CreateFlushAll()
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'FlushAll', but another task was already assigned.");
m_command.emplace<FlushAllData>();
m_command.emplace<Requests::FlushAllData>();
}
void FileRequest::CreateDedicatedCacheCreation(RequestPath path, const FileRange& range, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'CreateDedicateCache', but another task was already assigned.");
m_command.emplace<CreateDedicatedCacheData>(AZStd::move(path), range);
m_command.emplace<Requests::CreateDedicatedCacheData>(AZStd::move(path), range);
SetOptionalParent(parent);
}
@@ -290,7 +289,7 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'DestroyDedicateCache', but another task was already assigned.");
m_command.emplace<DestroyDedicatedCacheData>(AZStd::move(path), range);
m_command.emplace<Requests::DestroyDedicatedCacheData>(AZStd::move(path), range);
SetOptionalParent(parent);
}
@@ -298,14 +297,14 @@ namespace AZ::IO
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Report', but another task was already assigned.");
m_command.emplace<ReportData>(reportType);
m_command.emplace<Requests::ReportData>(reportType);
}
void FileRequest::CreateCustom(AZStd::any data, bool failWhenUnhandled, FileRequest* parent)
{
AZ_Assert(AZStd::holds_alternative<AZStd::monostate>(m_command),
"Attempting to set FileRequest to 'Custom', but another task was already assigned.");
m_command.emplace<CustomData>(AZStd::move(data), failWhenUnhandled);
m_command.emplace<Requests::CustomData>(AZStd::move(data), failWhenUnhandled);
SetOptionalParent(parent);
}
@@ -429,7 +428,7 @@ namespace AZ::IO
const FileRequest* current = this;
while (current)
{
auto* link = AZStd::get_if<ExternalRequestData>(&current->m_command);
auto* link = AZStd::get_if<Requests::ExternalRequestData>(&current->m_command);
if (!link)
{
current = current->m_parent;
@@ -343,7 +343,7 @@ namespace AZ::IO
m_numRunningJobs == 0;
}
void FullFileDecompressor::PrepareReadRequest(FileRequest* request, Requests::ReadRequestData &data)
void FullFileDecompressor::PrepareReadRequest(FileRequest* request, Requests::ReadRequestData& data)
{
CompressionInfo info;
if (CompressionUtils::FindCompressionInfo(info, data.m_path.GetRelativePath()))
@@ -8,9 +8,9 @@
#include <AzCore/IO/Streamer/Scheduler.h>
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/IO/Streamer/FileRequest.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/sort.h>
@@ -37,9 +37,7 @@ namespace AZ::IO
m_threadData.m_streamStack = AZStd::move(streamStack);
}
Scheduler::~Scheduler()
{
}
Scheduler::~Scheduler() = default;
void Scheduler::Start(const AZStd::thread_desc& threadDesc)
{
@@ -60,13 +60,13 @@ namespace AZ::IO
AZ_PROFILE_FUNCTION(AzCore);
AZ_Assert(request, "PrepareRequest was provided a null request.");
if (AZStd::holds_alternative<Requests::ReadRequestData>(request->GetCommand()))
if (AZStd::holds_alternative<Requests::ReadRequestData>(request->GetCommand()))
{
auto& readRequest = AZStd::get<Requests::ReadRequestData>(request->GetCommand());
auto& readRequest = AZStd::get<Requests::ReadRequestData>(request->GetCommand());
FileRequest* read = m_context->GetNewInternalRequest();
read->CreateRead(request, readRequest.m_output, readRequest.m_outputSize, readRequest.m_path,
readRequest.m_offset, readRequest.m_size);
read->CreateRead(
request, readRequest.m_output, readRequest.m_outputSize, readRequest.m_path, readRequest.m_offset, readRequest.m_size);
m_context->PushPreparedRequest(read);
return;
}
@@ -23,9 +23,8 @@ namespace AZ
static constexpr char MissedDeadlinesName[] = "Missed deadlines";
#endif // AZ_STREAMER_ADD_EXTRA_PROFILING_INFO
StreamerContext::StreamerContext()
{
}
StreamerContext::StreamerContext() = default;
StreamerContext::~StreamerContext()
{
for (FileRequest* entry : m_internalRecycleBin)
@@ -228,7 +227,7 @@ namespace AZ
top->m_onCompletion(*top);
AZ_PROFILE_INTERVAL_END(AzCore, top);
}
if (parent)
{
AZ_Assert(parent->m_dependencies > 0,
@@ -7,11 +7,11 @@
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/IO/Streamer/Statistics.h>
#include <AzCore/IO/Streamer/StreamerConfiguration.h>
#include <AzCore/IO/Streamer/StreamerContext_Platform.h>
#include <AzCore/Statistics/RunningStatistic.h>
#include <AzCore/base.h>
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/containers/vector.h>
@@ -254,13 +254,13 @@ namespace AZ
Method("CreateFromMatrix3x3", &Quaternion::CreateFromMatrix3x3)->
Method("CreateFromMatrix4x4", &Quaternion::CreateFromMatrix4x4)->
Method("CreateFromAxisAngle", &Quaternion::CreateFromAxisAngle)->
Method("CreateFromScaledAxisAngle", &Quaternion::CreateFromScaledAxisAngle)->
Method("CreateShortestArc", &Quaternion::CreateShortestArc)->
Method("CreateFromEulerAnglesDegrees", &Quaternion::CreateFromEulerAnglesDegrees)
;
}
}
Quaternion Quaternion::CreateFromMatrix3x3(const Matrix3x3& m)
{
return CreateFromBasis(m.GetBasisX(), m.GetBasisY(), m.GetBasisZ());
@@ -430,4 +430,24 @@ namespace AZ
outAngle = 0.0f;
}
}
Vector3 Quaternion::ConvertToScaledAxisAngle() const
{
// Take the log of the quaternion to convert it to the exponential map
// and multiply it by 2.0 to bring it into the scaled axis-angle representation.
const AZ::Vector3 imaginary = GetImaginary();
const float length = imaginary.GetLength();
if (length < AZ::Constants::FloatEpsilon)
{
return imaginary * 2.0f;
}
else
{
const float halfAngle = acosf(AZ::GetClamp(GetW(), -1.0f, 1.0f));
// Multiply by 2.0 to convert the half angle into the full one.
return halfAngle * 2.0f * (imaginary / length);
}
}
}
@@ -77,6 +77,9 @@ namespace AZ
static Quaternion CreateFromAxisAngle(const Vector3& axis, float angle);
//! Create a quaternion from a scaled axis-angle representation.
static Quaternion CreateFromScaledAxisAngle(const Vector3& scaledAxisAngle);
static Quaternion CreateShortestArc(const Vector3& v1, const Vector3& v2);
//! Creates a quaternion using rotation in degrees about the axes. First rotated about the X axis, followed by the Y axis, then the Z axis.
@@ -231,6 +234,9 @@ namespace AZ
//! @param[out] outAngle A float rotation angle around the axis in radians.
void ConvertToAxisAngle(Vector3& outAxis, float& outAngle) const;
//! Convert the quaternion into scaled axis-angle representation.
Vector3 ConvertToScaledAxisAngle() const;
//! Returns the imaginary (X/Y/Z) portion of the quaternion.
Vector3 GetImaginary() const;
@@ -109,6 +109,24 @@ namespace AZ
}
AZ_MATH_INLINE Quaternion Quaternion::CreateFromScaledAxisAngle(const Vector3& scaledAxisAngle)
{
const AZ::Vector3 exponentialMap = scaledAxisAngle / 2.0f;
const float halfAngle = exponentialMap.GetLength();
if (halfAngle < AZ::Constants::FloatEpsilon)
{
return AZ::Quaternion::CreateFromVector3AndValue(exponentialMap, 1.0f).GetNormalized();
}
else
{
float sin, cos;
SinCos(halfAngle, sin, cos);
return AZ::Quaternion::CreateFromVector3AndValue((sin / halfAngle) * exponentialMap, cos);
}
}
AZ_MATH_INLINE void Quaternion::StoreToFloat4(float* values) const
{
Simd::Vec4::StoreUnaligned(values, m_value);
@@ -105,6 +105,8 @@ void ScriptContextDebug::ConnectHook()
void ScriptContextDebug::DisconnectHook()
{
lua_sethook(m_context.NativeContext(), nullptr, 0, 0);
m_currentStackLevel = -1;
m_stepStackLevel = -1;
}
//=========================================================================
@@ -651,6 +653,11 @@ void AZ::LuaHook(lua_State* l, lua_Debug* ar)
context->PopCallstack();
}
context->m_currentStackLevel--;
if (context->m_currentStackLevel == -1)
{
context->m_stepStackLevel = -1;
}
}
else if (ar->event == LUA_HOOKLINE)
{
@@ -731,7 +738,7 @@ void AZ::LuaHook(lua_State* l, lua_Debug* ar)
//}
}
if (doBreak)
if (doBreak && bp->m_lineNumber > 0)
{
context->m_luaDebug = ar;
context->m_breakCallback(context, bp);
@@ -62,6 +62,7 @@ namespace AZ
TimeSystem::TimeSystem()
{
m_lastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
m_realLastInvokedTimeUs = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
AZ::Interface<ITime>::Register(this);
ITimeRequestBus::Handler::BusConnect();
}
@@ -101,7 +102,11 @@ namespace AZ
TimeUs TimeSystem::GetRealElapsedTimeUs() const
{
return static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
const TimeUs currentTime = static_cast<TimeUs>(AZStd::GetTimeNowMicroSecond());
m_realAccumulatedTimeUs += currentTime - m_realLastInvokedTimeUs;
m_realLastInvokedTimeUs = currentTime;
return m_realAccumulatedTimeUs;
}
TimeUs TimeSystem::GetSimulationTickDeltaTimeUs() const
@@ -64,6 +64,14 @@ namespace AZ
//! Mutable to allow GetElapsedTimeMs/TimeUs() to be a const functions.
mutable TimeUs m_accumulatedTimeUs = AZ::Time::ZeroTimeUs;
//! Used to calculate the delta time between calls to GetRealElapsedTimeMs/TimeUs().
//! Mutable to allow GetRealElapsedTimeMs/TimeUs() to be a const functions.
mutable TimeUs m_realLastInvokedTimeUs = AZ::Time::ZeroTimeUs;
//! Accumulates the delta time of GetRealElapsedTimeMs/TimeUs() calls.
//! Mutable to allow GetRealElapsedTimeMs/TimeUs() to be a const functions.
mutable TimeUs m_realAccumulatedTimeUs = AZ::Time::ZeroTimeUs;
//! The current game tick delta time.
//! Can be affected by time system cvars.
//! Updated in AdvanceTickDeltaTimes().
@@ -69,6 +69,15 @@ namespace UnitTest
return numAssertsFailed;
}
void ResetSuppressionSettingsToDefault()
{
m_suppressErrors = true;
m_suppressWarnings = true;
m_suppressAsserts = true;
m_suppressOutput = true;
m_suppressPrintf = true;
}
bool m_isAssertTest;
bool m_suppressErrors = true;
bool m_suppressWarnings = true;
@@ -254,27 +254,29 @@ namespace AZ::IO
else if (!m_pendingRequests.empty())
{
FileRequest* request = m_pendingRequests.front();
hasWorked = AZStd::visit([this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
hasWorked = AZStd::visit(
[this, request](auto&& args)
{
using Command = AZStd::decay_t<decltype(args)>;
if constexpr (AZStd::is_same_v<Command, Requests::FileExistsCheckData>)
{
FileExistsRequest(request);
m_pendingRequests.pop_front();
return true;
}
else if constexpr (AZStd::is_same_v<Command, Requests::FileMetaDataRetrievalData>)
{
FileMetaDataRetrievalRequest(request);
m_pendingRequests.pop_front();
return true;
}
else
{
AZ_Assert(false, "A request was added to StorageDriveWin's pending queue that isn't supported.");
return false;
}
}, request->GetCommand());
{
FileExistsRequest(request);
m_pendingRequests.pop_front();
return true;
}
else if constexpr (AZStd::is_same_v<Command, Requests::FileMetaDataRetrievalData>)
{
FileMetaDataRetrievalRequest(request);
m_pendingRequests.pop_front();
return true;
}
else
{
AZ_Assert(false, "A request was added to StorageDriveWin's pending queue that isn't supported.");
return false;
}
},
request->GetCommand());
}
return StreamStackEntry::ExecuteRequests() || hasFinalizedReads || hasWorked;
@@ -408,4 +408,106 @@ namespace UnitTest
Matrix4x4 m = Matrix4x4::CreateFromQuaternion(rotQuat);
AZ_TEST_ASSERT(m.IsClose(rotMatrix));
}
class QuaternionScaledAxisAngleConversionFixture
: public ::testing::TestWithParam<AZ::Quaternion>
{
public:
AZ::Quaternion GetAbs(const AZ::Quaternion& in)
{
// Take the shortest path for quaternions containing rotations bigger than 180.0°.
if (in.GetW() < 0.0f)
{
return -in;
}
return in;
}
};
static const AZ::Quaternion RotationRepresentationConversionTestQuats[] =
{
AZ::Quaternion::CreateIdentity(),
-AZ::Quaternion::CreateIdentity(),
AZ::Quaternion::CreateRotationX(AZ::Constants::TwoPi),
AZ::Quaternion::CreateRotationY(AZ::Constants::Pi),
AZ::Quaternion::CreateRotationZ(AZ::Constants::HalfPi),
AZ::Quaternion::CreateRotationX(AZ::Constants::QuarterPi),
AZ::Quaternion(0.64f, 0.36f, 0.48f, 0.48f),
AZ::Quaternion(0.70f, -0.34f, 0.10f, 0.62f),
AZ::Quaternion(-0.38f, 0.34f, 0.70f, -0.50f),
AZ::Quaternion(0.70f, -0.34f, -0.38f, 0.50f),
AZ::Quaternion(0.00f, 0.00f, -0.28f, 0.96f),
AZ::Quaternion(0.24f, -0.64f, 0.72f, 0.12f),
AZ::Quaternion(-0.66f, 0.62f, 0.42f, 0.06f)
};
TEST_P(QuaternionScaledAxisAngleConversionFixture, ScaledAxisAngleQuatRoundtripTests)
{
const AZ::Quaternion testQuat = GetAbs(GetParam());
// Convert test quaternion to scaled axis-angle representation.
const AZ::Vector3 scaledAxisAngle = testQuat.ConvertToScaledAxisAngle();
// Convert the scaled axis-angle back into a quaternion.
AZ::Quaternion backFromScaledAxisAngle = AZ::Quaternion::CreateFromScaledAxisAngle(scaledAxisAngle);
// Compare the original quaternion with the one after the conversion.
EXPECT_TRUE(testQuat.IsClose(backFromScaledAxisAngle, 1e-6f));
}
TEST_P(QuaternionScaledAxisAngleConversionFixture, AxisAngleQuatRoundtripTests)
{
const AZ::Quaternion testQuat = GetAbs(GetParam());
// Convert test quaternion to axis-angle representation.
AZ::Vector3 axis;
float angle;
testQuat.ConvertToAxisAngle(axis, angle);
// Convert the axis-angle back into a quaternion and compare the original quaternion with the one after the conversion.
const AZ::Quaternion backFromAxisAngle = AZ::Quaternion::CreateFromAxisAngle(axis, angle);
EXPECT_TRUE(testQuat.IsClose(backFromAxisAngle, 1e-6f));
}
TEST_P(QuaternionScaledAxisAngleConversionFixture, CompareAxisAngleConversionTests)
{
const AZ::Quaternion testQuat = GetAbs(GetParam());
// Convert test quaternion to scaled axis-angle representation.
const AZ::Vector3 scaledAxisAngle = testQuat.ConvertToScaledAxisAngle();
// Convert test quaternion to axis-angle representation and scale it manually.
AZ::Vector3 axis;
float angle;
testQuat.ConvertToAxisAngle(axis, angle);
// Compare the scaled result to the version from the helper that directly converts it to scaled axis-angle.
AZ::Vector3 scaledResult = axis*angle;
EXPECT_TRUE(scaledResult.IsClose(scaledAxisAngle, 1e-5f));
}
TEST_P(QuaternionScaledAxisAngleConversionFixture, CompareScaledAxisAngleConversionTests)
{
const AZ::Quaternion testQuat = GetAbs(GetParam());
// Convert test quaternion to axis-angle representation and scale it manually.
AZ::Vector3 axis;
float angle;
testQuat.ConvertToAxisAngle(axis, angle);
AZ::Vector3 scaledResult = axis*angle;
// Special case handling for identity rotation.
AZ::Vector3 axisFromScaledResult = scaledResult.GetNormalized();
float angleFromScaledResult = scaledResult.GetLength();
if (AZ::IsClose(angleFromScaledResult, 0.0f))
{
axisFromScaledResult = AZ::Vector3::CreateAxisY();
}
const AZ::Quaternion backFromAxisAngle = AZ::Quaternion::CreateFromAxisAngle(axisFromScaledResult, angleFromScaledResult);
EXPECT_TRUE(testQuat.IsClose(backFromAxisAngle, 1e-6f));
}
INSTANTIATE_TEST_CASE_P(MATH_Quaternion, QuaternionScaledAxisAngleConversionFixture, ::testing::ValuesIn(RotationRepresentationConversionTestQuats));
}
@@ -213,27 +213,33 @@ namespace AzFramework
Camera Cameras::StepCamera(const Camera& targetCamera, const ScreenVector& cursorDelta, const float scrollDelta, const float deltaTime)
{
for (int i = 0; i < m_idleCameraInputs.size();)
for (int idleIndex = 0; idleIndex < m_idleCameraInputs.size();)
{
auto& cameraInput = m_idleCameraInputs[i];
auto& cameraInput = m_idleCameraInputs[idleIndex];
const bool canBegin = cameraInput->Beginning() &&
AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
[](const auto& input)
{
return !input->Exclusive();
}) &&
(!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty()));
(!cameraInput->Exclusive() || m_activeCameraInputs.empty());
if (canBegin)
{
m_activeCameraInputs.push_back(cameraInput);
using AZStd::swap;
swap(m_idleCameraInputs[i], m_idleCameraInputs[m_idleCameraInputs.size() - 1]);
swap(m_idleCameraInputs[idleIndex], m_idleCameraInputs[m_idleCameraInputs.size() - 1]);
m_idleCameraInputs.pop_back();
}
else
{
i++;
// if a camera attempted to start but was not allowed to, ensure activation is cancelled
if (!cameraInput->Idle())
{
cameraInput->CancelActivation();
}
idleIndex++;
}
}
@@ -245,21 +251,21 @@ namespace AzFramework
return acc;
});
for (int i = 0; i < m_activeCameraInputs.size();)
for (int activeIndex = 0; activeIndex < m_activeCameraInputs.size();)
{
auto& cameraInput = m_activeCameraInputs[i];
auto& cameraInput = m_activeCameraInputs[activeIndex];
if (cameraInput->Ending())
{
cameraInput->ClearActivation();
m_idleCameraInputs.push_back(cameraInput);
using AZStd::swap;
swap(m_activeCameraInputs[i], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
swap(m_activeCameraInputs[activeIndex], m_activeCameraInputs[m_activeCameraInputs.size() - 1]);
m_activeCameraInputs.pop_back();
}
else
{
cameraInput->ContinueActivation();
i++;
activeIndex++;
}
}
@@ -470,9 +476,10 @@ namespace AzFramework
{
if (input->m_state == InputChannel::State::Began)
{
m_translation |= TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds);
if (m_translation != TranslationType::Nil)
if (auto translation = TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds);
translation != TranslationType::Nil)
{
m_translation |= translation;
BeginActivation();
}
@@ -484,11 +491,16 @@ namespace AzFramework
// ensure we don't process end events in the idle state
else if (input->m_state == InputChannel::State::Ended && !Idle())
{
m_translation &= ~(TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds));
if (m_translation == TranslationType::Nil)
if (auto translation = TranslationFromKey(input->m_channelId, m_translateCameraInputChannelIds);
translation != TranslationType::Nil)
{
EndActivation();
m_translation &= ~translation;
if (m_translation == TranslationType::Nil)
{
EndActivation();
}
}
if (input->m_channelId == m_translateCameraInputChannelIds.m_boostChannelId)
{
m_boost = false;
@@ -185,6 +185,11 @@ namespace AzFramework
m_activation = Activation::Ending;
}
void CancelActivation()
{
m_activation = Activation::Idle;
}
void ContinueActivation()
{
// continue activation is called after the first step of the camera input,
@@ -10,6 +10,7 @@
#include <AzCore/Casting/numeric_cast.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/RTTI/TypeInfoSimple.h>
#include <AzCore/base.h>
@@ -203,6 +204,12 @@ namespace AzFramework
return AZ::Vector2(aznumeric_cast<float>(screenPoint.m_x), aznumeric_cast<float>(screenPoint.m_y));
}
//! Return an AZ::Vector3 from a ScreenPoint (including z/depth value, defaulting to 0.0f).
inline AZ::Vector3 Vector3FromScreenPoint(const ScreenPoint& screenPoint, const float z = 0.0f)
{
return AZ::Vector3(aznumeric_cast<float>(screenPoint.m_x), aznumeric_cast<float>(screenPoint.m_y), z);
}
//! Return an AZ::Vector2 from a ScreenVector.
inline AZ::Vector2 Vector2FromScreenVector(const ScreenVector& screenVector)
{
@@ -14,7 +14,7 @@
#include <Psapi.h>
AZ_CVAR(bool, ap_tether_lifetime, false, nullptr, AZ::ConsoleFunctorFlags::Null,
AZ_CVAR(bool, ap_tether_lifetime, true, nullptr, AZ::ConsoleFunctorFlags::Null,
"If enabled, a parent process that launches the AP will terminate the AP on exit");
namespace AzFramework::AssetSystem::Platform
@@ -96,7 +96,7 @@ namespace AzFramework
AZStd::string operator()(const AZStd::vector<AZStd::string>& commandLineArray) const
{
AZStd::string commandLineResult;
Az::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " ");
AZ::StringFunc::Join(commandLineResult, commandLineArray.begin(), commandLineArray.end(), " ");
return commandLineResult;
}
};
@@ -416,4 +416,78 @@ namespace UnitTest
using ::testing::FloatNear;
EXPECT_THAT(m_camera.m_pitch, FloatNear(expectedPitch, 0.001f));
}
TEST_F(CameraInputFixture, InvalidTranslationInputKeyCannotBeginTranslateCameraInputAgain)
{
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
AzFramework::InputChannel::State::Began });
const bool consumed =
m_cameraSystem->HandleEvents(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(consumed, IsTrue());
EXPECT_THAT(m_firstPersonTranslateCamera->Beginning(), IsFalse());
EXPECT_THAT(m_firstPersonTranslateCamera->Active(), IsTrue());
}
TEST_F(CameraInputFixture, InvalidTranslationInputKeyDownCannotBeginTranslateCameraInputAgain)
{
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
AzFramework::InputChannel::State::Began });
const bool consumed =
m_cameraSystem->HandleEvents(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(consumed, IsTrue());
EXPECT_THAT(m_firstPersonTranslateCamera->Beginning(), IsFalse());
EXPECT_THAT(m_firstPersonTranslateCamera->Active(), IsTrue());
}
TEST_F(CameraInputFixture, InvalidTranslationInputKeyUpDoesNotAffectTranslateCameraInputEnd)
{
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
AzFramework::InputChannel::State::Began });
const bool consumed =
m_cameraSystem->HandleEvents(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
AzFramework::InputChannel::State::Ended });
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(consumed, IsTrue());
EXPECT_THAT(m_firstPersonTranslateCamera->Idle(), IsTrue());
}
TEST_F(CameraInputFixture, OrbitCameraInputCannotBeLeftInInvalidStateIfItCannotFullyBeginAfterInputChannelBegin)
{
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(m_orbitCamera->Beginning(), IsFalse());
EXPECT_THAT(m_orbitCamera->Idle(), IsTrue());
}
TEST_F(CameraInputFixture, OrbitCameraInputCannotBeLeftInInvalidStateIfItCannotFullyBeginAfterInputChannelBeginAndEnd)
{
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_translateCameraInputChannelIds.m_forwardChannelId,
AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Began });
HandleEventAndUpdate(AzFramework::DiscreteInputEvent{ m_orbitChannelId, AzFramework::InputChannel::State::Ended });
using ::testing::IsFalse;
using ::testing::IsTrue;
EXPECT_THAT(m_orbitCamera->Ending(), IsFalse());
EXPECT_THAT(m_orbitCamera->Idle(), IsTrue());
}
} // namespace UnitTest
@@ -26,29 +26,33 @@ namespace AzManipulatorTestFramework
{
public:
virtual ~ViewportInteractionInterface() = default;
//! Return the camera state.
//! Returns the camera state.
virtual AzFramework::CameraState GetCameraState() = 0;
//! Set the camera state.
//! Sets the camera state.
virtual void SetCameraState(const AzFramework::CameraState& cameraState) = 0;
//! Retrieve the debug display.
//! Retrieves the debug display.
virtual AzFramework::DebugDisplayRequests& GetDebugDisplay() = 0;
//! Set if grid snapping is enabled or not.
//! Sets if grid snapping is enabled or not.
virtual void SetGridSnapping(bool enabled) = 0;
//! Set if angular snapping is enabled or not.
//! Sets if angular snapping is enabled or not.
virtual void SetAngularSnapping(bool enabled) = 0;
//! Set the grid size.
//! Sets the grid size.
virtual void SetGridSize(float size) = 0;
//! Set the angular step.
//! Sets the angular step.
virtual void SetAngularStep(float step) = 0;
//! Get the viewport id.
//! Gets the viewport id.
virtual AzFramework::ViewportId GetViewportId() const = 0;
//! Updates the visibility state.
//! Updates which entities are currently visible given the current camera state.
virtual void UpdateVisibility() = 0;
//! Set if sticky select is enabled or not.
//! Sets if sticky select is enabled or not.
virtual void SetStickySelect(bool enabled) = 0;
//! Get default Editor Camera Position.
//! Gets default Editor Camera Position.
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
//! Sets if icons are visible in the viewport.
virtual void SetIconsVisible(bool visible) = 0;
//! Sets if helpers are visible in the viewport.
virtual void SetHelpersVisible(bool visible) = 0;
};
//! This interface is used to simulate the manipulator manager while the manipulators are under test.
@@ -26,6 +26,7 @@ namespace UnitTest
using IndirectCallManipulatorViewportInteraction = AzManipulatorTestFramework::IndirectCallManipulatorViewportInteraction;
using ImmediateModeActionDispatcher = AzManipulatorTestFramework::ImmediateModeActionDispatcher;
public:
void SetUpEditorFixtureImpl() override
{
ToolsApplicationFixtureT::SetUpEditorFixtureImpl();
@@ -43,7 +44,6 @@ namespace UnitTest
ToolsApplicationFixtureT::TearDownEditorFixtureImpl();
}
public:
AzFramework::CameraState m_cameraState;
AZStd::unique_ptr<ImmediateModeActionDispatcher> m_actionDispatcher;
AZStd::unique_ptr<IndirectCallManipulatorViewportInteraction> m_viewportManipulatorInteraction;
@@ -27,29 +27,6 @@ namespace AzManipulatorTestFramework
const AZ::Vector3& position = AZ::Vector3::CreateZero(),
float radius = 1.0f);
//! Create a mouse pick from the specified ray and screen point.
AzToolsFramework::ViewportInteraction::MousePick CreateMousePick(
const AZ::Vector3& origin, const AZ::Vector3& direction, const AzFramework::ScreenPoint& screenPoint);
//! Build a mouse pick from the specified mouse position and camera state.
AzToolsFramework::ViewportInteraction::MousePick BuildMousePick(
const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState);
//! Create a mouse interaction from the specified pick, buttons, interaction id and keyboard modifiers.
AzToolsFramework::ViewportInteraction::MouseInteraction CreateMouseInteraction(
const AzToolsFramework::ViewportInteraction::MousePick& mousePick,
AzToolsFramework::ViewportInteraction::MouseButtons buttons,
AzToolsFramework::ViewportInteraction::InteractionId interactionId,
AzToolsFramework::ViewportInteraction::KeyboardModifiers modifiers);
//! Create a mouse buttons from the specified mouse button.
AzToolsFramework::ViewportInteraction::MouseButtons CreateMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton button);
//! Create a mouse interaction event from the specified interaction and event.
AzToolsFramework::ViewportInteraction::MouseInteractionEvent CreateMouseInteractionEvent(
const AzToolsFramework::ViewportInteraction::MouseInteraction& mouseInteraction,
AzToolsFramework::ViewportInteraction::MouseEvent event);
//! Dispatch a mouse event to the main manipulator manager via a bus call.
void DispatchMouseInteractionEvent(const AzToolsFramework::ViewportInteraction::MouseInteractionEvent& event);
@@ -24,9 +24,12 @@ namespace AzManipulatorTestFramework
explicit IndirectCallManipulatorViewportInteraction(AZStd::shared_ptr<AzFramework::DebugDisplayRequests> debugDisplayRequests);
~IndirectCallManipulatorViewportInteraction();
// ManipulatorViewportInteractionInterface ...
// ManipulatorViewportInteraction overrides ...
const ViewportInteractionInterface& GetViewportInteraction() const override;
const ManipulatorManagerInterface& GetManipulatorManager() const override;
// make non-const overloads visible
using ManipulatorViewportInteraction::GetViewportInteraction;
using ManipulatorViewportInteraction::GetManipulatorManager;
private:
AZStd::unique_ptr<ViewportInteraction> m_viewportInteraction;
@@ -39,7 +39,8 @@ namespace AzManipulatorTestFramework
AzFramework::ViewportId GetViewportId() const override;
void UpdateVisibility() override;
void SetStickySelect(bool enabled) override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
void SetIconsVisible(bool visible) override;
void SetHelpersVisible(bool visible) override;
// ViewportInteractionRequestBus overrides ...
AzFramework::CameraState GetCameraState() override;
@@ -58,6 +59,9 @@ namespace AzManipulatorTestFramework
float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
bool IconsVisible() const override;
bool HelpersVisible() const override;
// EditorEntityViewportInteractionRequestBus overrides ...
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
@@ -68,10 +72,12 @@ namespace AzManipulatorTestFramework
AzFramework::EntityVisibilityQuery m_entityVisibilityQuery;
AZStd::shared_ptr<AzFramework::DebugDisplayRequests> m_debugDisplayRequests;
AzFramework::CameraState m_cameraState;
float m_gridSize = 1.0f;
float m_angularStep = 0.0f;
bool m_gridSnapping = false;
bool m_angularSnapping = false;
bool m_stickySelect = true;
float m_gridSize = 1.0f;
float m_angularStep = 0.0f;
bool m_iconsVisible = true;
bool m_helpersVisible = true;
};
} // namespace AzManipulatorTestFramework
@@ -82,49 +82,6 @@ namespace AzManipulatorTestFramework
return manipulator;
}
AzToolsFramework::ViewportInteraction::MousePick CreateMousePick(
const AZ::Vector3& origin, const AZ::Vector3& direction, const AzFramework::ScreenPoint& screenPoint)
{
return { origin, direction, screenPoint };
}
AzToolsFramework::ViewportInteraction::MousePick BuildMousePick(
const AzFramework::ScreenPoint& screenPoint, const AzFramework::CameraState& cameraState)
{
const auto nearPlaneWorldPosition = AzFramework::ScreenToWorld(screenPoint, cameraState);
AzToolsFramework::ViewportInteraction::MousePick mousePick;
mousePick.m_screenCoordinates = screenPoint;
mousePick.m_rayOrigin = nearPlaneWorldPosition;
mousePick.m_rayDirection = (nearPlaneWorldPosition - cameraState.m_position).GetNormalized();
return mousePick;
}
MouseInteraction CreateMouseInteraction(
const MousePick& mousePick, MouseButtons buttons, InteractionId interactionId, KeyboardModifiers modifiers)
{
AzToolsFramework::ViewportInteraction::MouseInteraction interaction;
interaction.m_mousePick = mousePick;
interaction.m_mouseButtons = buttons;
interaction.m_interactionId = interactionId;
interaction.m_keyboardModifiers = modifiers;
return interaction;
}
MouseButtons CreateMouseButtons(MouseButton button)
{
MouseButtons buttons;
buttons.m_mouseButtons = static_cast<AZ::u32>(button);
return buttons;
}
MouseInteractionEvent CreateMouseInteractionEvent(const MouseInteraction& mouseInteraction, MouseEvent event)
{
return MouseInteractionEvent(mouseInteraction, event, /*captured=*/false);
}
void DispatchMouseInteractionEvent(const MouseInteractionEvent& event)
{
AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event(
@@ -120,7 +120,8 @@ namespace AzManipulatorTestFramework
void ImmediateModeActionDispatcher::MousePositionImpl(const AzFramework::ScreenPoint& position)
{
const auto cameraState = m_manipulatorViewportInteraction.GetViewportInteraction().GetCameraState();
GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick = BuildMousePick(position, cameraState);
GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick =
AzToolsFramework::ViewportInteraction::BuildMousePick(cameraState, position);
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move;
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
}
@@ -10,6 +10,7 @@
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzManipulatorTestFramework/ViewportInteraction.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
namespace AzManipulatorTestFramework
{
@@ -19,6 +20,9 @@ namespace AzManipulatorTestFramework
AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus::Handler::BusConnect(m_viewportId);
AzToolsFramework::ViewportInteraction::ViewportSettingsRequestBus::Handler::BusConnect(m_viewportId);
AzToolsFramework::ViewportInteraction::EditorEntityViewportInteractionRequestBus::Handler::BusConnect(m_viewportId);
m_cameraState =
AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize);
}
ViewportInteraction::~ViewportInteraction()
@@ -113,6 +117,16 @@ namespace AzManipulatorTestFramework
m_stickySelect = enabled;
}
void ViewportInteraction::SetIconsVisible(const bool visible)
{
m_iconsVisible = visible;
}
void ViewportInteraction::SetHelpersVisible(const bool visible)
{
m_helpersVisible = visible;
}
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
{
return {};
@@ -148,4 +162,14 @@ namespace AzManipulatorTestFramework
{
return 1.0f;
}
bool ViewportInteraction::IconsVisible() const
{
return m_iconsVisible;
}
bool ViewportInteraction::HelpersVisible() const
{
return m_helpersVisible;
}
} // namespace AzManipulatorTestFramework
@@ -8,21 +8,22 @@
#pragma once
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzTest/AzTest.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
namespace UnitTest
{
class LinearManipulatorTestFixture
: public ToolsApplicationFixture
class LinearManipulatorTestFixture : public ToolsApplicationFixture
{
protected:
LinearManipulatorTestFixture(const AzToolsFramework::ManipulatorManagerId& manipulatorManagerId)
: m_manipulatorManagerId(manipulatorManagerId) {}
: m_manipulatorManagerId(manipulatorManagerId)
{
}
void SetUpEditorFixtureImpl() override
{
{
m_linearManipulator = AzManipulatorTestFramework::CreateLinearManipulator(
m_manipulatorManagerId,
/*position=*/AZ::Vector3::CreateZero(),
@@ -31,21 +32,21 @@ namespace UnitTest
// default sanity check call backs
m_linearManipulator->InstallLeftMouseDownCallback(
[this](const AzToolsFramework::LinearManipulator::Action& /*action*/)
{
m_receivedLeftMouseDown = true;
});
{
m_receivedLeftMouseDown = true;
});
m_linearManipulator->InstallMouseMoveCallback(
[this](const AzToolsFramework::LinearManipulator::Action& /*action*/)
{
m_receivedMouseMove = true;
});
{
m_receivedMouseMove = true;
});
m_linearManipulator->InstallLeftMouseUpCallback(
[this](const AzToolsFramework::LinearManipulator::Action& /*action*/)
{
m_receivedLeftMouseUp = true;
});
{
m_receivedLeftMouseUp = true;
});
}
void TearDownEditorFixtureImpl() override
@@ -63,16 +64,16 @@ namespace UnitTest
bool m_receivedLeftMouseUp = false;
// initial world space starting position for mouse interaction
const AzToolsFramework::ViewportInteraction::MousePick m_mouseStartingPositionRay =
AzManipulatorTestFramework::CreateMousePick(
AZ::Vector3(0.0f, -2.0f, 0.0f), AZ::Vector3(0.0f, 1.0f, 0.0f), AzFramework::ScreenPoint( 0,0 ));
const AzToolsFramework::ViewportInteraction::MousePick m_mouseStartingPositionRay{ AZ::Vector3(0.0f, -2.0f, 0.0f),
AZ::Vector3(0.0f, 1.0f, 0.0f),
AzFramework::ScreenPoint(0, 0) };
// left mouse down ray in world space 2 units back from origin looking down +y axis with a null interaction
// id and no keyboard modifiers
AzToolsFramework::ViewportInteraction::MouseInteraction m_interaction =
AzManipulatorTestFramework::CreateMouseInteraction(
AzToolsFramework::ViewportInteraction::BuildMouseInteraction(
m_mouseStartingPositionRay,
AzManipulatorTestFramework::CreateMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::Left),
AzToolsFramework::ViewportInteraction::BuildMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::Left),
AzToolsFramework::ViewportInteraction::InteractionId(AZ::EntityId(0), 0),
AzToolsFramework::ViewportInteraction::KeyboardModifiers(0));
};
@@ -7,6 +7,8 @@
*/
#include "AzManipulatorTestFrameworkTestFixtures.h"
#include <AzToolsFramework/Viewport/ViewportTypes.h>
#include <AzToolsFramework/ViewportSelection/EditorDefaultSelection.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
@@ -34,8 +36,8 @@ namespace UnitTest
TEST_F(AzManipulatorTestFrameworkBusCallTestFixture, ConsumeViewportLeftMouseClick)
{
// given a left mouse down ray in world space
auto event =
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
// consume the mouse down and up events
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
@@ -53,8 +55,8 @@ namespace UnitTest
TEST_F(AzManipulatorTestFrameworkBusCallTestFixture, ConsumeViewportMouseMoveHover)
{
// given a left mouse down ray in world space
const auto event =
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Move);
const auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Move);
// consume the mouse move event
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
@@ -72,8 +74,8 @@ namespace UnitTest
TEST_F(AzManipulatorTestFrameworkBusCallTestFixture, ConsumeViewportMouseMoveActive)
{
// given a left mouse down ray in world space
auto event =
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
// consume the mouse down event
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
@@ -113,8 +115,8 @@ namespace UnitTest
});
// given a left mouse down ray in world space
auto event =
AzManipulatorTestFramework::CreateMouseInteractionEvent(m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
auto event = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
m_interaction, AzToolsFramework::ViewportInteraction::MouseEvent::Down);
// consume the mouse down event
AzManipulatorTestFramework::DispatchMouseInteractionEvent(event);
@@ -26,7 +26,6 @@
#define AZ_TRAIT_DISABLE_FAILED_JOB_BASIC_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_MATH_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_MERGESETTINGSFOLDER_CONFLICTINGSPECIALIZATIONS true
#define AZ_TRAIT_DISABLE_FAILED_MULTIPLAYER_GRIDMATE_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_NETWORKING_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_PHYSICS_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_SAVE_DATA_TESTS true
@@ -13,7 +13,6 @@
#define AZ_TRAIT_UNIT_TEST_ENTITY_ID_GEN_TEST_COUNT 10000
#define AZ_TRAIT_UNIT_TEST_DILLER_TRIGGER_EVENT_COUNT 100000
#define AZ_TRAIT_DISABLE_FAILED_MULTIPLAYER_GRIDMATE_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_NATIVE_WINDOWS_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_EMOTION_FX_TESTS true
#define AZ_TRAIT_DISABLE_FAILED_DLL_TESTS true
+13 -2
View File
@@ -5,9 +5,12 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <ostream>
#include <AzCore/Component/EntityId.h>
#include <AzCore/IO/Path/Path.h>
#include <ostream>
namespace AZ::IO
{
void PrintTo(const AZ::IO::PathView& path, ::std::ostream* os)
@@ -24,4 +27,12 @@ namespace AZ::IO
{
*os << "path: " << AZ::IO::FixedMaxPath(path.Native(), AZ::IO::PosixPathSeparator).MakePreferred().c_str();
}
}
} // namespace AZ::IO
namespace AZ
{
void PrintTo(const AZ::EntityId entityId, ::std::ostream* os)
{
*os << entityId.ToString().c_str();
}
} // namespace AZ
+8
View File
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <iosfwd>
@@ -37,4 +38,11 @@ namespace AZ::IO
void PrintTo(const AZ::IO::FixedMaxPath& path, ::std::ostream* os);
}
namespace AZ
{
class EntityId;
void PrintTo(AZ::EntityId entityId, ::std::ostream* os);
}
#include <AzTest/Printers.inl>
@@ -878,9 +878,6 @@ namespace AzToolsFramework
/// Return the icon texture id (from internal IconManager) for a given entity icon path.
/// This can be passed to DrawTextureLabel to draw an entity icon.
virtual int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) = 0;
/// Returns if the Display Helpers option is toggled on in the Editor.
virtual bool DisplayHelpersVisible() = 0;
};
using EditorRequestBus = AZ::EBus<EditorRequests>;
@@ -73,6 +73,7 @@
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <Entity/EntityUtilityComponent.h>
#include <AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h>
#include <Prefab/ProceduralPrefabSystemComponent.h>
#include <QtWidgets/QMessageBox>
AZ_PUSH_DISABLE_WARNING(4251, "-Wunknown-warning-option") // 4251: 'QFileInfo::d_ptr': class 'QSharedDataPointer<QFileInfoPrivate>' needs to have dll-interface to be used by clients of class 'QFileInfo'
@@ -272,6 +273,7 @@ namespace AzToolsFramework
azrtti_typeid<ReadOnlyEntitySystemComponent>(),
azrtti_typeid<SliceMetadataEntityContextComponent>(),
azrtti_typeid<Prefab::PrefabSystemComponent>(),
azrtti_typeid<Prefab::ProceduralPrefabSystemComponent>(),
azrtti_typeid<EditorEntityFixupComponent>(),
azrtti_typeid<Components::EditorComponentAPIComponent>(),
azrtti_typeid<Components::EditorLevelComponentAPIComponent>(),
@@ -56,6 +56,7 @@
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemComponent.h>
#include <AzToolsFramework/Entity/EntityUtilityComponent.h>
#include <AzToolsFramework/Script/LuaSymbolsReporterSystemComponent.h>
#include <Prefab/ProceduralPrefabSystemComponent.h>
AZ_DEFINE_BUDGET(AzToolsFramework);
@@ -82,6 +83,7 @@ namespace AzToolsFramework
SliceRequestComponent::CreateDescriptor(),
Prefab::PrefabSystemComponent::CreateDescriptor(),
Prefab::EditorPrefabComponent::CreateDescriptor(),
Prefab::ProceduralPrefabSystemComponent::CreateDescriptor(),
Components::EditorEntityActionComponent::CreateDescriptor(),
Components::EditorEntityIconComponent::CreateDescriptor(),
Components::EditorInspectorComponent::CreateDescriptor(),
@@ -21,6 +21,7 @@
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <Prefab/ProceduralPrefabSystemComponentInterface.h>
namespace AzToolsFramework
{
@@ -185,6 +186,20 @@ namespace AzToolsFramework
TemplateReference newTemplateReference = m_prefabSystemComponentInterface->FindTemplate(newTemplateId);
Template& newTemplate = newTemplateReference->get();
if(newTemplate.IsProcedural())
{
auto proceduralPrefabSystemComponentInterface = AZ::Interface<ProceduralPrefabSystemComponentInterface>::Get();
if (!proceduralPrefabSystemComponentInterface)
{
AZ_Error("Prefab", false, "Failed to find ProceduralPrefabSystemComponentInterface. Procedural Prefab will not be tracked for hotloading.");
}
else
{
proceduralPrefabSystemComponentInterface->RegisterProceduralPrefab(relativePath.Native(), newTemplateId);
}
}
// Mark the file as being in progress.
progressedFilePathsSet.emplace(relativePath);
@@ -26,6 +26,14 @@ namespace AzToolsFramework
virtual void OnPrefabTemplateDirtyFlagUpdated(
[[maybe_unused]] TemplateId templateId, [[maybe_unused]] bool status) {}
// Sent after a single template has been removed
// Does not get sent when all templates are being removed at once. Be sure to handle OnAllTemplatesRemoved as well.
virtual void OnTemplateRemoved([[maybe_unused]] TemplateId templateId) {}
// Sent after all templates have been removed
// Does not trigger individual OnTemplateRemoved events
virtual void OnAllTemplatesRemoved() {}
};
using PrefabPublicNotificationBus = AZ::EBus<PrefabPublicNotifications>;
@@ -160,7 +160,7 @@ namespace AzToolsFramework
newInstance->SetTemplateId(newTemplateId);
}
}
void PrefabSystemComponent::PropagateTemplateChanges(TemplateId templateId, InstanceOptionalConstReference instanceToExclude)
{
auto templateIdToLinkIdsIterator = m_templateToLinkIdsMap.find(templateId);
@@ -442,7 +442,7 @@ namespace AzToolsFramework
}
m_templateFilePathToIdMap.emplace(AZStd::make_pair(filePath, newTemplateId));
return newTemplateId;
}
@@ -469,7 +469,7 @@ namespace AzToolsFramework
{
return;
}
m_templateFilePathToIdMap.erase(templateToChange.GetFilePath());
if (!m_templateFilePathToIdMap.try_emplace(filePath, templateId).second)
{
@@ -500,7 +500,7 @@ namespace AzToolsFramework
return;
}
//Remove all Links owned by the Template from TemplateToLinkIdsMap.
Template& templateToDelete = findTemplateResult->get();
const Template::Links& linkIdsToDelete = templateToDelete.GetLinks();
@@ -546,7 +546,7 @@ namespace AzToolsFramework
templateId, templateToDelete.GetFilePath().c_str());
m_templateInstanceMapper.UnregisterTemplate(templateId);
result = m_templateIdMap.erase(templateId) != 0;
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveTemplate - "
@@ -554,7 +554,10 @@ namespace AzToolsFramework
"from Template Id Map.",
templateId, templateToDelete.GetFilePath().c_str());
return;
if (!m_removingAllTemplates)
{
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotificationBus::Events::OnTemplateRemoved, templateId);
}
}
void PrefabSystemComponent::RemoveAllTemplates()
@@ -568,10 +571,15 @@ namespace AzToolsFramework
templateIds.emplace_back(id);
}
m_removingAllTemplates = true;
for (auto id : templateIds)
{
RemoveTemplate(id);
}
m_removingAllTemplates = false;
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotificationBus::Events::OnAllTemplatesRemoved);
}
LinkId PrefabSystemComponent::AddLink(
@@ -859,7 +867,7 @@ namespace AzToolsFramework
void PrefabSystemComponent::SaveAllDirtyTemplates(TemplateId rootTemplateId)
{
AZStd::set<AZ::IO::PathView> dirtyTemplatePaths = GetDirtyTemplatePaths(rootTemplateId);
AZStd::set<AZ::IO::PathView> dirtyTemplatePaths = GetDirtyTemplatePaths(rootTemplateId);
for (AZ::IO::PathView dirtyTemplatePath : dirtyTemplatePaths)
{
@@ -56,7 +56,7 @@ namespace AzToolsFramework
public:
using TargetTemplateIdToLinkIdMap = AZStd::unordered_map<TemplateId, AZStd::pair<AZStd::unordered_set<LinkId>, bool>>;
AZ_COMPONENT(PrefabSystemComponent, "{27203AE6-A398-4614-881B-4EEB5E9B34E9}");
PrefabSystemComponent() = default;
@@ -220,7 +220,7 @@ namespace AzToolsFramework
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity = nullptr,
InstanceOptionalReference parent = AZStd::nullopt, bool shouldCreateLinks = true) override;
PrefabDom& FindTemplateDom(TemplateId templateId) override;
/**
@@ -244,7 +244,7 @@ namespace AzToolsFramework
private:
AZ_DISABLE_COPY_MOVE(PrefabSystemComponent);
/**
* Builds a new Prefab Template out of entities and instances and returns the first instance comprised of
* these entities and instances.
@@ -263,14 +263,14 @@ namespace AzToolsFramework
/**
* Updates all the linked Instances corresponding to the linkIds in the provided queue.
* Queue gets populated with more linkId lists as linked instances are updated. Updating stops when the queue is empty.
*
*
* @param linkIdsQueue A queue of vector of link-Ids to update.
*/
void UpdateLinkedInstances(AZStd::queue<LinkIds>& linkIdsQueue);
/**
* Given a vector of link ids to update, splits them into smaller lists based on the target template id of the links.
*
*
* @param linkIdsToUpdate The list of link ids to update.
* @param targetTemplateIdToLinkIdMap The map of target templateIds to a pair of lists of linkIds and a bool flag indicating
* whether any of the instances of the target template were updated.
@@ -279,9 +279,9 @@ namespace AzToolsFramework
TargetTemplateIdToLinkIdMap& targetTemplateIdToLinkIdMap);
/**
* Updates a single linked instance corresponding to the given link Id and adds more linkIds to the
* Updates a single linked instance corresponding to the given link Id and adds more linkIds to the
* template change propagation queue(linkIdsQueue) when necessary.
*
*
* @param linkIdToUpdate The id of the linked instance to update
* @param targetTemplateIdToLinkIdMap The map of target templateIds to a pair of lists of linkIds and a bool flag indicating
* whether any of the instances of the target template were updated.
@@ -293,7 +293,7 @@ namespace AzToolsFramework
/**
* If all linked instances of a target template are updated and if the content of any of the linked instances changed,
* this method fetches all the linked instances sourced by it and adds their corresponding ids to the LinkIdsQueue.
*
*
* @param targetTemplateIdToLinkIdMap The map of target templateIds to a pair of lists of linkIds and a bool flag indicating
* whether any of the instances of the target template were updated.
* @param targetTemplateId The id of the template, whose linked instances we need to find if the template was updated.
@@ -414,6 +414,9 @@ namespace AzToolsFramework
PrefabPublicRequestHandler m_prefabPublicRequestHandler;
PrefabSystemScriptingHandler m_prefabSystemScriptingHandler;
// If true, individual template-remove messages will be suppressed
bool m_removingAllTemplates = false;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -9,12 +9,11 @@
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <AzFramework/FileFunc/FileFunc.h>
namespace AZ::Prefab
{
static constexpr const char s_useProceduralPrefabsKey[] = "/O3DE/Preferences/Prefabs/UseProceduralPrefabs";
// ProceduralPrefabAsset
ProceduralPrefabAsset::ProceduralPrefabAsset(const AZ::Data::AssetId& assetId)
@@ -58,9 +57,14 @@ namespace AZ::Prefab
bool ProceduralPrefabAsset::UseProceduralPrefabs()
{
bool useProceduralPrefabs = false;
bool result = AZ::SettingsRegistry::Get()->GetObject(useProceduralPrefabs, s_useProceduralPrefabsKey);
return result && useProceduralPrefabs;
bool prefabsEnabled = false;
AzFramework::ApplicationRequests::Bus::Broadcast(
[&prefabsEnabled](AzFramework::ApplicationRequests::Bus::Events* ebus)
{
prefabsEnabled = ebus->IsPrefabSystemEnabled();
});
return prefabsEnabled;
}
// PrefabDomData
@@ -0,0 +1,143 @@
/*
* 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 <AssetCatalog/PlatformAddressedAssetCatalogBus.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/Prefab/ProceduralPrefabSystemComponent.h>
#include <Prefab/PrefabDomUtils.h>
#include <Prefab/PrefabLoaderInterface.h>
#include <Prefab/PrefabSystemComponentInterface.h>
#include <Prefab/Procedural/ProceduralPrefabAsset.h>
namespace AzToolsFramework
{
namespace Prefab
{
void ProceduralPrefabSystemComponent::Reflect(AZ::ReflectContext* context)
{
if (auto serializationContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializationContext->Class<ProceduralPrefabSystemComponent, AZ::Component>();
}
}
void ProceduralPrefabSystemComponent::Activate()
{
AZ::Interface<ProceduralPrefabSystemComponentInterface>::Register(this);
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
}
void ProceduralPrefabSystemComponent::Deactivate()
{
AzFramework::AssetCatalogEventBus::Handler::BusDisconnect();
AZ::Interface<ProceduralPrefabSystemComponentInterface>::Unregister(this);
AZStd::scoped_lock lock(m_lookupMutex);
m_assetIdToTemplateLookup.clear();
}
void ProceduralPrefabSystemComponent::OnCatalogAssetChanged(const AZ::Data::AssetId& assetId)
{
TemplateId templateId = InvalidTemplateId;
{
AZStd::scoped_lock lock(m_lookupMutex);
auto itr = m_assetIdToTemplateLookup.find(assetId);
if (itr != m_assetIdToTemplateLookup.end())
{
templateId = itr->second;
}
}
if (templateId != InvalidTemplateId)
{
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
if (!prefabSystemComponentInterface)
{
AZ_Error("Prefab", false, "Failed to get PrefabSystemComponentInterface");
return;
}
AZStd::string assetPath;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
assetPath, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetPathById, assetId);
auto readResult = AZ::Utils::ReadFile(assetPath, AZStd::numeric_limits<size_t>::max());
if (!readResult.IsSuccess())
{
AZ_Error(
"Prefab", false,
"ProceduralPrefabSystemComponent::OnCatalogAssetChanged - Failed to read Prefab file from '%.*s'."
"Error message: '%s'",
AZ_STRING_ARG(assetPath), readResult.GetError().c_str());
return;
}
AZ::Outcome<PrefabDom, AZStd::string> readPrefabFileResult = AZ::JsonSerializationUtils::ReadJsonString(readResult.TakeValue());
if (!readPrefabFileResult.IsSuccess())
{
AZ_Error(
"Prefab", false,
"ProceduralPrefabSystemComponent::OnCatalogAssetChanged - Failed to read Prefab json from '%.*s'."
"Error message: '%s'",
AZ_STRING_ARG(assetPath), readPrefabFileResult.GetError().c_str());
return;
}
AZ::IO::Path relativePath = AZ::Interface<PrefabLoaderInterface>::Get()->GenerateRelativePath(assetPath.c_str());
PrefabDomPath sourcePath = PrefabDomPath((AZStd::string("/") + PrefabDomUtils::SourceName).c_str());
sourcePath.Set(readPrefabFileResult.GetValue(), relativePath.Native().c_str());
prefabSystemComponentInterface->UpdatePrefabTemplate(templateId, readPrefabFileResult.TakeValue());
}
}
void ProceduralPrefabSystemComponent::OnTemplateRemoved(TemplateId removedTemplateId)
{
AZStd::scoped_lock lock(m_lookupMutex);
for (const auto& [assetId, templateId] : m_assetIdToTemplateLookup)
{
if (templateId == removedTemplateId)
{
m_assetIdToTemplateLookup.erase(assetId);
break;
}
}
}
void ProceduralPrefabSystemComponent::OnAllTemplatesRemoved()
{
AZStd::scoped_lock lock(m_lookupMutex);
m_assetIdToTemplateLookup.clear();
}
void ProceduralPrefabSystemComponent::RegisterProceduralPrefab(const AZStd::string& prefabFilePath, TemplateId templateId)
{
AZ::Data::AssetId assetId;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
assetId, &AZ::Data::AssetCatalogRequestBus::Events::GetAssetIdByPath, prefabFilePath.c_str(),
azrtti_typeid<AZ::Prefab::ProceduralPrefabAsset>(), false);
if (assetId.IsValid())
{
AZStd::scoped_lock lock(m_lookupMutex);
m_assetIdToTemplateLookup[assetId] = templateId;
}
else
{
AZ_Error("Prefab", false, "Failed to find AssetId for prefab %s", prefabFilePath.c_str());
}
}
} // namespace Prefab
} // namespace AzToolsFramework
@@ -0,0 +1,47 @@
/*
* 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/Component.h>
#include <Prefab/ProceduralPrefabSystemComponentInterface.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
namespace AzToolsFramework
{
namespace Prefab
{
class ProceduralPrefabSystemComponent
: public AZ::Component
, ProceduralPrefabSystemComponentInterface
, AzFramework::AssetCatalogEventBus::Handler
, PrefabPublicNotificationBus::Handler
{
public:
AZ_COMPONENT(ProceduralPrefabSystemComponent, "{81211818-088A-49E6-894B-7A11764106B1}");
static void Reflect(AZ::ReflectContext* context);
protected:
void Activate() override;
void Deactivate() override;
void OnCatalogAssetChanged(const AZ::Data::AssetId&) override;
void OnTemplateRemoved(TemplateId templateId) override;
void OnAllTemplatesRemoved() override;
void RegisterProceduralPrefab(const AZStd::string& prefabFilePath, TemplateId templateId) override;
AZStd::mutex m_lookupMutex;
AZStd::unordered_map<AZ::Data::AssetId, TemplateId> m_assetIdToTemplateLookup;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -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
*
*/
#pragma once
#include <AzCore/RTTI/RTTI.h>
#include <AzToolsFramework/Prefab/PrefabIdTypes.h>
namespace AzToolsFramework
{
namespace Prefab
{
class ProceduralPrefabSystemComponentInterface
{
public:
AZ_RTTI(ProceduralPrefabSystemComponentInterface, "{C403B89C-63DD-418C-B821-FBCBE1EF42AE}");
virtual ~ProceduralPrefabSystemComponentInterface() = default;
// Registers a procedural prefab file + templateId so the system can track changes and handle updates
virtual void RegisterProceduralPrefab(const AZStd::string& prefabFilePath, TemplateId templateId) = 0;
};
} // namespace Prefab
} // namespace AzToolsFramework
@@ -26,7 +26,9 @@
#include <AzToolsFramework/Entity/EditorEntityTransformBus.h>
#include <AzToolsFramework/API/EntityPropertyEditorRequestsBus.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/ToolsComponents/TransformComponentBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponentSerializer.h>
@@ -35,9 +37,8 @@
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <QMessageBox>
#include <QMenu>
#include <QMessageBox>
namespace AzToolsFramework
{
@@ -662,7 +663,6 @@ namespace AzToolsFramework
return;
}
// Prevent this from parenting to its own child. Check if this entity is in the new parent's hierarchy.
auto potentialParentTransformComponent = GetTransformComponent(parentId);
if (potentialParentTransformComponent && potentialParentTransformComponent->IsEntityInHierarchy(GetEntityId()))
@@ -931,14 +931,27 @@ namespace AzToolsFramework
{
return AZ::Failure(AZStd::string("You cannot set an entity's parent to itself!"));
}
else
// Don't allow the change if it will result in a cycle hierarchy
auto potentialParentTransformComponent = GetTransformComponent(actualValue);
if (potentialParentTransformComponent && potentialParentTransformComponent->IsEntityInHierarchy(GetEntityId()))
{
// Don't allow the change if it will result in a cycle hierarchy
auto potentialParentTransformComponent = GetTransformComponent(actualValue);
if (potentialParentTransformComponent && potentialParentTransformComponent->IsEntityInHierarchy(GetEntityId()))
{
return AZ::Failure(AZStd::string("You cannot set an entity to be a child of one of its own children!"));
}
return AZ::Failure(AZStd::string("You cannot set an entity to be a child of one of its own children!"));
}
// Don't allow read-only entities to be re-parented at all.
// Also don't allow entities to be parented under read-only entities.
if (auto readOnlyEntityPublicInterface = AZ::Interface<ReadOnlyEntityPublicInterface>::Get();
readOnlyEntityPublicInterface->IsReadOnly(GetEntityId()) || readOnlyEntityPublicInterface->IsReadOnly(actualValue))
{
return AZ::Failure(AZStd::string("You cannot set an entity to be a child of a read-only entity!"));
}
// Don't allow entities to be parented under closed containers.
if (auto containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
!containerEntityInterface->IsContainerOpen(actualValue))
{
return AZ::Failure(AZStd::string("You cannot set an entity to be a child of a closed container!"));
}
return AZ::Success();
@@ -1245,6 +1258,15 @@ namespace AzToolsFramework
void TransformComponent::AddContextMenuActions(QMenu* menu)
{
bool parentEntityIsReadOnly = false;
// If the parent entity is marked as read-only, don't allow actions on this transform.
if (auto readOnlyEntityPublicInterface = AZ::Interface<ReadOnlyEntityPublicInterface>::Get();
readOnlyEntityPublicInterface->IsReadOnly(GetEntityId()))
{
parentEntityIsReadOnly = true;
}
if (menu)
{
if (!menu->actions().empty())
@@ -1266,10 +1288,10 @@ namespace AzToolsFramework
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_Values);
});
resetAction->setEnabled(!m_editorTransform.m_locked);
resetAction->setEnabled(!m_editorTransform.m_locked && !parentEntityIsReadOnly);
QString lockString = m_editorTransform.m_locked ? "Unlock transform values" : "Lock transform values";
menu->addAction(lockString, [this, lockString]()
QAction* lockAction = menu->addAction(lockString, [this, lockString]()
{
{
AzToolsFramework::ScopedUndoBatch undo(lockString.toUtf8().data());
@@ -1278,6 +1300,7 @@ namespace AzToolsFramework
}
AzToolsFramework::ToolsApplicationEvents::Bus::Broadcast(&AzToolsFramework::ToolsApplicationEvents::InvalidatePropertyDisplay, AzToolsFramework::Refresh_AttributesAndValues);
});
lockAction->setEnabled(!parentEntityIsReadOnly);
}
}
}
@@ -548,11 +548,11 @@ namespace AzToolsFramework
AZ::EntityId entityId = GetEntityFromIndex(index);
// Only allow renaming the entity if the UI Handler did not block it.
auto entityUiHandler = m_editorEntityUiInterface->GetHandler(entityId);
auto entityUiHandler = m_editorEntityUiInterface ? m_editorEntityUiInterface->GetHandler(entityId) : nullptr;
bool canRename = !entityUiHandler || entityUiHandler->CanRename(entityId);
// Disable renaming for read-only entities.
bool isReadOnly = m_readOnlyEntityPublicInterface->IsReadOnly(entityId);
bool isReadOnly = m_readOnlyEntityPublicInterface ? m_readOnlyEntityPublicInterface->IsReadOnly(entityId) : false;
Qt::ItemFlags itemFlags = QAbstractItemModel::flags(index);
switch (index.column())
@@ -27,6 +27,7 @@
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <AzToolsFramework/UI/ComponentPalette/ComponentPaletteUtil.hxx>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiHandlerBase.h>
@@ -154,9 +155,15 @@ namespace AzToolsFramework
{
initEntityOutlinerWidgetResources();
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
m_editorEntityUiInterface = AZ::Interface<AzToolsFramework::EditorEntityUiInterface>::Get();
AZ_Assert(m_editorEntityUiInterface != nullptr, "EntityOutlinerWidget requires a EditorEntityUiInterface instance on Initialize.");
m_focusModeInterface = AZ::Interface<AzToolsFramework::FocusModeInterface>::Get();
AZ_Assert(m_focusModeInterface != nullptr, "EntityOutlinerWidget requires a FocusModeInterface instance on Initialize.");
m_readOnlyEntityPublicInterface = AZ::Interface<AzToolsFramework::ReadOnlyEntityPublicInterface>::Get();
AZ_Assert(
(m_readOnlyEntityPublicInterface != nullptr),
@@ -609,23 +616,27 @@ namespace AzToolsFramework
{
AZ::EntityId entityId = m_selectedEntityIds[0];
AZ::EntityId parentId;
EditorEntityInfoRequestBus::EventResult(parentId, entityId, &EditorEntityInfoRequestBus::Events::GetParent);
EntityOrderArray entityOrderArray = GetEntityChildOrder(parentId);
if (entityOrderArray.size() > 1)
// Don't allow moving the entity if it's the focus root.
if (m_focusModeInterface->GetFocusRoot(m_editorEntityContextId) != entityId)
{
if (AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), entityId) != entityOrderArray.end())
{
if (entityOrderArray.front() != entityId)
{
contextMenu->addAction(m_actionToMoveEntityUp);
}
AZ::EntityId parentId;
EditorEntityInfoRequestBus::EventResult(parentId, entityId, &EditorEntityInfoRequestBus::Events::GetParent);
if (entityOrderArray.back() != entityId)
EntityOrderArray entityOrderArray = GetEntityChildOrder(parentId);
if (entityOrderArray.size() > 1)
{
if (AZStd::find(entityOrderArray.begin(), entityOrderArray.end(), entityId) != entityOrderArray.end())
{
contextMenu->addAction(m_actionToMoveEntityDown);
if (entityOrderArray.front() != entityId)
{
contextMenu->addAction(m_actionToMoveEntityUp);
}
if (entityOrderArray.back() != entityId)
{
contextMenu->addAction(m_actionToMoveEntityDown);
}
}
}
}
@@ -43,6 +43,7 @@ namespace AzToolsFramework
class EntityOutlinerListModel;
class EntityOutlinerContainerProxyModel;
class EntityOutlinerSortFilterProxyModel;
class FocusModeInterface;
class ReadOnlyEntityPublicInterface;
namespace EntityOutliner
@@ -204,7 +205,10 @@ namespace AzToolsFramework
EntityOutliner::DisplaySortMode m_sortMode;
bool m_sortContentQueued;
AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityUiInterface* m_editorEntityUiInterface = nullptr;
FocusModeInterface* m_focusModeInterface = nullptr;
ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr;
};
@@ -277,6 +277,26 @@ namespace AzToolsFramework
}
}
bool onlySelectedEntityIsFocusedPrefabContainer = false;
bool onlySelectedEntityIsClosedPrefabContainer = false;
if (selectedEntities.size() == 1)
{
AZ::EntityId selectedEntity = selectedEntities.front();
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
{
if (s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
onlySelectedEntityIsFocusedPrefabContainer = true;
}
else
{
onlySelectedEntityIsClosedPrefabContainer = true;
}
}
}
bool itemWasShown = false;
// Create Prefab
@@ -284,8 +304,7 @@ namespace AzToolsFramework
if (!selectedEntities.empty())
{
// Hide if the only selected entity is the Focused Instance Container
if (selectedEntities.size() > 1 ||
selectedEntities[0] != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))
if (!onlySelectedEntityIsFocusedPrefabContainer)
{
bool layerInSelection = false;
@@ -322,7 +341,7 @@ namespace AzToolsFramework
}
// Instantiate Prefab
if (!readOnlyEntityInSelection)
if (selectedEntities.size() == 1 && !readOnlyEntityInSelection && !onlySelectedEntityIsClosedPrefabContainer)
{
QAction* instantiateAction = menu->addAction(QObject::tr("Instantiate Prefab..."));
instantiateAction->setToolTip(QObject::tr("Instantiates a prefab file in the scene."));
@@ -330,17 +349,15 @@ namespace AzToolsFramework
QObject::connect(
instantiateAction, &QAction::triggered, instantiateAction, [] { ContextMenu_InstantiatePrefab(); });
itemWasShown = true;
}
// Instantiate Procedural Prefab
if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs())
{
QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab..."));
action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab."));
// Instantiate Procedural Prefab
if (AZ::Prefab::ProceduralPrefabAsset::UseProceduralPrefabs() && !readOnlyEntityInSelection)
{
QAction* action = menu->addAction(QObject::tr("Instantiate Procedural Prefab..."));
action->setToolTip(QObject::tr("Instantiates a procedural prefab file in a prefab."));
QObject::connect(
action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); });
QObject::connect(
action, &QAction::triggered, action, [] { ContextMenu_InstantiateProceduralPrefab(); });
}
itemWasShown = true;
}
@@ -362,14 +379,34 @@ namespace AzToolsFramework
{
if (!s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
// Edit Prefab
QAction* editAction = menu->addAction(QObject::tr("Open/Edit Prefab"));
editAction->setShortcut(QKeySequence(Qt::Key_Plus));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
if (s_prefabPublicInterface->IsOwnedByProceduralPrefabInstance(selectedEntity))
{
// Inspect Prefab
QAction* editAction = menu->addAction(QObject::tr("Inspect Procedural Prefab"));
editAction->setShortcut(QKeySequence(Qt::Key_Plus));
editAction->setToolTip(QObject::tr("See the procedural prefab contents in focus mode."));
QObject::connect(editAction, &QAction::triggered, editAction, [selectedEntity] {
ContextMenu_EditPrefab(selectedEntity);
});
QObject::connect(
editAction, &QAction::triggered, editAction,
[selectedEntity]
{
ContextMenu_EditPrefab(selectedEntity);
});
}
else
{
// Edit Prefab
QAction* editAction = menu->addAction(QObject::tr("Open/Edit Prefab"));
editAction->setShortcut(QKeySequence(Qt::Key_Plus));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
QObject::connect(
editAction, &QAction::triggered, editAction,
[selectedEntity]
{
ContextMenu_EditPrefab(selectedEntity);
});
}
}
else
{
@@ -420,21 +457,17 @@ namespace AzToolsFramework
}
// Detach Prefab
if (selectedEntities.size() == 1)
if (onlySelectedEntityIsClosedPrefabContainer)
{
AZ::EntityId selectedEntityId = selectedEntities[0];
AZ::EntityId selectedEntityId = selectedEntities.front();
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntityId) &&
selectedEntityId != s_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(s_editorEntityContextId))
{
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
QObject::connect(
detachPrefabAction, &QAction::triggered, detachPrefabAction,
[selectedEntityId]
{
ContextMenu_DetachPrefab(selectedEntityId);
});
}
QAction* detachPrefabAction = menu->addAction(QObject::tr("Detach Prefab..."));
QObject::connect(
detachPrefabAction, &QAction::triggered, detachPrefabAction,
[selectedEntityId]
{
ContextMenu_DetachPrefab(selectedEntityId);
});
}
}
@@ -75,7 +75,7 @@ namespace AzToolsFramework
void PopulateEditorGlobalContextMenu(QMenu* menu, const AZ::Vector2& point, int flags) override;
// EditorEventsBus overrides ...
void OnEscape();
void OnEscape() override;
// EntityOutlinerSourceDropHandlingBus overrides ...
void HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const override;
@@ -89,8 +89,8 @@ namespace AzToolsFramework
const float rayLength)
{
AZ_Assert(rayLength > 0.0f, "Invalid ray length passed to RefreshRayRequest");
rayRequest.m_startWorldPosition = viewportRay.origin;
rayRequest.m_endWorldPosition = viewportRay.origin + viewportRay.direction * rayLength;
rayRequest.m_startWorldPosition = viewportRay.m_origin;
rayRequest.m_endWorldPosition = viewportRay.m_origin + viewportRay.m_direction * rayLength;
}
AZ::Vector3 FindClosestPickIntersection(
@@ -15,7 +15,6 @@
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ClickDetector.h>
#include <AzFramework/Viewport/ViewportId.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Viewport/ViewportTypes.h>
@@ -151,13 +150,6 @@ namespace AzToolsFramework
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
};
//! A ray projection, originating from a point and extending in a direction specified as a normal.
struct ProjectedViewportRay
{
AZ::Vector3 origin;
AZ::Vector3 direction;
};
//! Requests that can be made to the viewport to query and modify its state.
class ViewportInteractionRequests
{
@@ -183,15 +175,6 @@ namespace AzToolsFramework
//! Type to inherit to implement ViewportInteractionRequests.
using ViewportInteractionRequestBus = AZ::EBus<ViewportInteractionRequests, ViewportEBusTraits>;
//! Utility function to return a viewport ray.
inline ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::CameraState& cameraState, const AzFramework::ScreenPoint& screenPoint)
{
const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPoint, cameraState);
const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized();
return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection };
}
//! Utility function to return a viewport ray using the ViewportInteractionRequestBus.
inline ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint)
@@ -225,6 +208,10 @@ namespace AzToolsFramework
virtual bool StickySelectEnabled() const = 0;
//! Returns the default viewport camera position.
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
//! Returns if icons are visible in the viewport.
virtual bool IconsVisible() const = 0;
//! Returns if viewport helpers (additional debug drawing) are visible in the viewport.
virtual bool HelpersVisible() const = 0;
protected:
~ViewportSettingsRequests() = default;
@@ -20,6 +20,8 @@ namespace AzToolsFramework
constexpr AZStd::string_view ScaleManipulatorBoxHalfExtentSetting = "/Amazon/Preferences/Editor/Manipulator/ScaleManipulatorBoxHalfExtent";
constexpr AZStd::string_view RotationManipulatorRadiusSetting = "/Amazon/Preferences/Editor/Manipulator/RotationManipulatorRadius";
constexpr AZStd::string_view ManipulatorViewBaseScaleSetting = "/Amazon/Preferences/Editor/Manipulator/ViewBaseScale";
constexpr AZStd::string_view IconsVisibleSetting = "/Amazon/Preferences/Editor/IconsVisible";
constexpr AZStd::string_view HelpersVisibleSetting = "/Amazon/Preferences/Editor/HelpersVisible";
bool FlipManipulatorAxesTowardsView()
{
@@ -120,4 +122,24 @@ namespace AzToolsFramework
{
SetRegistry(ManipulatorViewBaseScaleSetting, scale);
}
bool IconsVisible()
{
return GetRegistry(IconsVisibleSetting, true);
}
void SetIconsVisible(const bool visible)
{
SetRegistry(IconsVisibleSetting, visible);
}
bool HelpersVisible()
{
return GetRegistry(HelpersVisibleSetting, true);
}
void SetHelpersVisible(const bool visible)
{
SetRegistry(HelpersVisibleSetting, visible);
}
} // namespace AzToolsFramework
@@ -66,4 +66,10 @@ namespace AzToolsFramework
float ManipulatorViewBaseScale();
void SetManipulatorViewBaseScale(float scale);
bool IconsVisible();
void SetIconsVisible(bool visible);
bool HelpersVisible();
void SetHelpersVisible(bool visible);
} // namespace AzToolsFramework
@@ -49,5 +49,22 @@ namespace AzToolsFramework
->Field("MouseEvent", &MouseInteractionEvent::m_mouseEvent)
->Field("WheelDelta", &MouseInteractionEvent::m_wheelDelta);
}
MouseInteraction BuildMouseInteraction(
const MousePick& mousePick, const MouseButtons buttons, const InteractionId interactionId, const KeyboardModifiers modifiers)
{
MouseInteraction interaction;
interaction.m_mousePick = mousePick;
interaction.m_mouseButtons = buttons;
interaction.m_interactionId = interactionId;
interaction.m_keyboardModifiers = modifiers;
return interaction;
}
MouseInteractionEvent BuildMouseInteractionEvent(
const MouseInteraction& mouseInteraction, const MouseEvent event, const bool cursorCaptured /*= false*/)
{
return MouseInteractionEvent(mouseInteraction, event, cursorCaptured);
}
} // namespace ViewportInteraction
} // namespace AzToolsFramework
@@ -8,11 +8,11 @@
#pragma once
#include "AzFramework/Viewport/ScreenGeometry.h"
#include <AzCore/Component/EntityId.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Viewport/CameraState.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <QPoint>
@@ -20,7 +20,7 @@ namespace AZ
{
class ReflectContext;
class SerializeContext;
}
} // namespace AZ
namespace AzToolsFramework
{
@@ -178,6 +178,12 @@ namespace AzToolsFramework
//! @cond
AZ_TYPE_INFO(MousePick, "{A69B9562-FC8C-4DE7-9137-0FF867B1513D}");
MousePick() = default;
MousePick(const AZ::Vector3& rayOrigin, const AZ::Vector3& rayDirection, const AzFramework::ScreenPoint& screenPoint)
: m_rayOrigin(rayOrigin)
, m_rayDirection(rayDirection)
, m_screenCoordinates(screenPoint)
{
}
//! @endcond
AZ::Vector3 m_rayOrigin = AZ::Vector3::CreateZero(); //!< World space.
@@ -249,6 +255,22 @@ namespace AzToolsFramework
return mouseInteractionEvent.m_wheelDelta;
}
//! A ray projection, originating from a point and extending in a direction specified as a normal.
struct ProjectedViewportRay
{
AZ::Vector3 m_origin;
AZ::Vector3 m_direction;
};
//! Utility function to return a viewport ray.
inline ProjectedViewportRay ViewportScreenToWorldRay(
const AzFramework::CameraState& cameraState, const AzFramework::ScreenPoint& screenPoint)
{
const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPoint, cameraState);
const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized();
return ProjectedViewportRay{ rayOrigin, rayDirection };
}
//! Return QPoint from AzFramework::ScreenPoint.
inline QPoint QPointFromScreenPoint(const AzFramework::ScreenPoint& screenPoint)
{
@@ -301,6 +323,27 @@ namespace AzToolsFramework
return mouseButtons;
}
//! Build a mouse pick from the specified mouse position and camera state.
inline MousePick BuildMousePick(const AzFramework::CameraState& cameraState, const AzFramework::ScreenPoint& screenPoint)
{
const auto ray = ViewportScreenToWorldRay(cameraState, screenPoint);
return MousePick(ray.m_origin, ray.m_direction, screenPoint);
}
//! Create a mouse interaction from the specified pick, buttons, interaction id and keyboard modifiers.
MouseInteraction BuildMouseInteraction(
const MousePick& mousePick, MouseButtons buttons, InteractionId interactionId, KeyboardModifiers modifiers);
//! Create a mouse buttons from the specified mouse button.
inline MouseButtons BuildMouseButtons(const MouseButton button)
{
return MouseButtons(aznumeric_cast<AZ::u32>(button));
}
//! Create a mouse interaction event from the specified interaction and event.
MouseInteractionEvent BuildMouseInteractionEvent(
const MouseInteraction& mouseInteraction, MouseEvent event, bool cursorCaptured = false);
//! Reflect all viewport related types.
void ViewportInteractionReflect(AZ::ReflectContext* context);
} // namespace ViewportInteraction
@@ -52,34 +52,45 @@ AZ_CVAR(
AZ::ConsoleFunctorFlags::Null,
"Use a lock icon when the cursor is over entities that cannot be interacted with");
AZ_CVAR(float, ed_iconMinScale, 0.1f, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum scale for icons in the distance");
AZ_CVAR(float, ed_iconMaxScale, 1.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Maximum scale for icons near the camera");
AZ_CVAR(float, ed_iconCloseDist, 3.0f, nullptr, AZ::ConsoleFunctorFlags::Null, "Distance at which icons are at maximum scale");
AZ_CVAR(float, ed_iconFarDist, 40.f, nullptr, AZ::ConsoleFunctorFlags::Null, "Distance at which icons are at minimum scale");
namespace AzToolsFramework
{
AZ_CLASS_ALLOCATOR_IMPL(EditorHelpers, AZ::SystemAllocator, 0)
static const int s_iconSize = 36; // icon display size (in pixels)
static const float s_iconMinScale = 0.1f; // minimum scale for icons in the distance
static const float s_iconMaxScale = 1.0f; // maximum scale for icons near the camera
static const float s_iconCloseDist = 3.f; // distance at which icons are at maximum scale
static const float s_iconFarDist = 40.f; // distance at which icons are at minimum scale
static const int IconSize = 36; // icon display size (in pixels)
// helper function to wrap EBus call to check if helpers are being displayed
// note: the ['?'] icon in the top right of the editor
static bool HelpersVisible()
static bool HelpersVisible(const AzFramework::ViewportId viewportId)
{
bool helpersVisible = false;
EditorRequestBus::BroadcastResult(helpersVisible, &EditorRequests::DisplayHelpersVisible);
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
helpersVisible, viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::HelpersVisible);
return helpersVisible;
}
// calculate the icon scale based on how far away it is (distanceSq) from a given point
// note: this is mostly likely distance from the camera
static float GetIconScale(const float distSq)
// helper function to wrap EBus call to check if icons are being displayed
static bool IconsVisible(const AzFramework::ViewportId viewportId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
bool iconsVisible = false;
ViewportInteraction::ViewportSettingsRequestBus::EventResult(
iconsVisible, viewportId, &ViewportInteraction::ViewportSettingsRequestBus::Events::IconsVisible);
return iconsVisible;
}
return s_iconMinScale +
(s_iconMaxScale - s_iconMinScale) *
(1.0f - AZ::GetClamp(AZ::GetMax(0.0f, sqrtf(distSq) - s_iconCloseDist) / s_iconFarDist, 0.0f, 1.0f));
float GetIconScale(const float distance)
{
return ed_iconMinScale +
(ed_iconMaxScale - ed_iconMinScale) *
(1.0f - AZ::GetClamp(AZ::GetMax(0.0f, distance - ed_iconCloseDist) / (ed_iconFarDist - ed_iconCloseDist), 0.0f, 1.0f));
}
float GetIconSize(const float distance)
{
return GetIconScale(distance) * IconSize;
}
static void DisplayComponents(
@@ -171,11 +182,14 @@ namespace AzToolsFramework
const int viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId;
const bool helpersVisible = HelpersVisible();
const bool iconsVisible = IconsVisible(viewportId);
const AZ::Matrix3x4 cameraView = AzFramework::CameraView(cameraState);
const AZ::Matrix4x4 cameraProjection = AzFramework::CameraProjection(cameraState);
// selecting new entities
AZ::EntityId entityIdUnderCursor;
float closestDistance = std::numeric_limits<float>::max();
float closestDistance = AZStd::numeric_limits<float>::max();
for (size_t entityCacheIndex = 0; entityCacheIndex < m_entityDataCache->VisibleEntityDataCount(); ++entityCacheIndex)
{
const AZ::EntityId entityId = m_entityDataCache->GetVisibleEntityId(entityCacheIndex);
@@ -185,8 +199,7 @@ namespace AzToolsFramework
continue;
}
// 2d screen space selection - did we click an icon
if (helpersVisible)
if (iconsVisible)
{
// some components choose to hide their icons (e.g. meshes)
// we also do not want to test against icons that may not be showing as they're inside a 'closed' entity container
@@ -197,17 +210,23 @@ namespace AzToolsFramework
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
// selecting based on 2d icon - should only do it when visible and not selected
const AzFramework::ScreenPoint screenPosition = AzFramework::WorldToScreen(entityPosition, cameraState);
const AZ::Vector3 ndcPoint = AzFramework::WorldToScreenNdc(entityPosition, cameraView, cameraProjection);
const AzFramework::ScreenPoint screenPosition =
AzFramework::ScreenPointFromNdc(AZ::Vector3ToVector2(ndcPoint), cameraState.m_viewportSize);
const float distSqFromCamera = cameraState.m_position.GetDistanceSq(entityPosition);
const auto iconRange = static_cast<float>(GetIconScale(distSqFromCamera) * s_iconSize * 0.5f);
const float distanceFromCamera = cameraState.m_position.GetDistance(entityPosition);
const auto iconRange = GetIconSize(distanceFromCamera) * 0.5f;
const auto screenCoords = mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates;
// 2d screen space selection - did we click an icon
if (screenCoords.m_x >= screenPosition.m_x - iconRange && screenCoords.m_x <= screenPosition.m_x + iconRange &&
screenCoords.m_y >= screenPosition.m_y - iconRange && screenCoords.m_y <= screenPosition.m_y + iconRange)
screenCoords.m_y >= screenPosition.m_y - iconRange && screenCoords.m_y <= screenPosition.m_y + iconRange &&
ndcPoint.GetZ() < closestDistance)
{
// use ndc z value for distance here which is in 0-1 range so will most likely 'win' when it comes to the
// distance check (this is what we want as the cursor should always favor icons if they are hovered)
closestDistance = ndcPoint.GetZ();
entityIdUnderCursor = entityId;
break;
}
}
}
@@ -220,9 +239,14 @@ namespace AzToolsFramework
if (AabbIntersectMouseRay(mouseInteraction.m_mouseInteraction, aabb))
{
// if success, pick against specific component
if (PickEntity(entityId, mouseInteraction.m_mouseInteraction, closestDistance, viewportId))
float closestBoundDifference = AZStd::numeric_limits<float>::max();
if (PickEntity(entityId, mouseInteraction.m_mouseInteraction, closestBoundDifference, viewportId))
{
entityIdUnderCursor = entityId;
if (closestBoundDifference < closestDistance)
{
closestDistance = closestBoundDifference;
entityIdUnderCursor = entityId;
}
}
}
}
@@ -276,55 +300,78 @@ namespace AzToolsFramework
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
if (HelpersVisible())
const bool iconsVisible = IconsVisible(viewportInfo.m_viewportId);
const bool helpersVisible = HelpersVisible(viewportInfo.m_viewportId);
auto displayCheck = [this](const size_t entityCacheIndex, const AZ::EntityId entityId)
{
if (!m_entityDataCache->IsVisibleEntityVisible(entityCacheIndex) || !IsSelectableInViewport(entityId))
{
return false;
}
return true;
};
if (helpersVisible)
{
for (size_t entityCacheIndex = 0; entityCacheIndex < m_entityDataCache->VisibleEntityDataCount(); ++entityCacheIndex)
{
const AZ::EntityId entityId = m_entityDataCache->GetVisibleEntityId(entityCacheIndex);
if (!m_entityDataCache->IsVisibleEntityVisible(entityCacheIndex) || !IsSelectableInViewport(entityId))
if (const AZ::EntityId entityId = m_entityDataCache->GetVisibleEntityId(entityCacheIndex);
displayCheck(entityCacheIndex, entityId))
{
continue;
// notify components to display
DisplayComponents(entityId, viewportInfo, debugDisplay);
}
}
}
// notify components to display
DisplayComponents(entityId, viewportInfo, debugDisplay);
if (iconsVisible)
{
auto editorViewportIconDisplay = EditorViewportIconDisplay::Get();
if (!editorViewportIconDisplay)
{
return;
}
if (m_entityDataCache->IsVisibleEntityIconHidden(entityCacheIndex) ||
(m_entityDataCache->IsVisibleEntitySelected(entityCacheIndex) && !showIconCheck(entityId)))
for (size_t entityCacheIndex = 0; entityCacheIndex < m_entityDataCache->VisibleEntityDataCount(); ++entityCacheIndex)
{
if (const AZ::EntityId entityId = m_entityDataCache->GetVisibleEntityId(entityCacheIndex);
displayCheck(entityCacheIndex, entityId))
{
continue;
}
int iconTextureId = 0;
EditorEntityIconComponentRequestBus::EventResult(
iconTextureId, entityId, &EditorEntityIconComponentRequests::GetEntityIconTextureId);
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
const float distSqFromCamera = cameraState.m_position.GetDistanceSq(entityPosition);
const float iconScale = GetIconScale(distSqFromCamera);
const float iconSize = s_iconSize * iconScale;
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
const AZ::Color iconHighlight = [this, entityCacheIndex]()
{
if (m_entityDataCache->IsVisibleEntityLocked(entityCacheIndex))
if (m_entityDataCache->IsVisibleEntityIconHidden(entityCacheIndex) ||
(m_entityDataCache->IsVisibleEntitySelected(entityCacheIndex) && !showIconCheck(entityId)))
{
return AZ::Color(AZ::u8(100), AZ::u8(100), AZ::u8(100), AZ::u8(255));
continue;
}
if (m_entityDataCache->GetVisibleEntityAccent(entityCacheIndex) == ComponentEntityAccentType::Hover)
int iconTextureId = 0;
EditorEntityIconComponentRequestBus::EventResult(
iconTextureId, entityId, &EditorEntityIconComponentRequests::GetEntityIconTextureId);
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
const AZ::Color iconHighlight = [this, entityCacheIndex]()
{
return AZ::Color(AZ::u8(255), AZ::u8(120), AZ::u8(0), AZ::u8(204));
}
if (m_entityDataCache->IsVisibleEntityLocked(entityCacheIndex))
{
return AZ::Color(AZ::u8(100), AZ::u8(100), AZ::u8(100), AZ::u8(255));
}
return AZ::Color(1.0f, 1.0f, 1.0f, 1.0f);
}();
if (m_entityDataCache->GetVisibleEntityAccent(entityCacheIndex) == ComponentEntityAccentType::Hover)
{
return AZ::Color(AZ::u8(255), AZ::u8(120), AZ::u8(0), AZ::u8(204));
}
EditorViewportIconDisplay::Get()->DrawIcon({ viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition,
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace,
AZ::Vector2{ iconSize, iconSize } });
return AZ::Color(1.0f, 1.0f, 1.0f, 1.0f);
}();
const AZ::Vector3& entityPosition = m_entityDataCache->GetVisibleEntityPosition(entityCacheIndex);
const float distanceFromCamera = cameraState.m_position.GetDistance(entityPosition);
const float iconSize = GetIconSize(distanceFromCamera);
editorViewportIconDisplay->DrawIcon({ viewportInfo.m_viewportId, iconTextureId, iconHighlight, entityPosition,
EditorViewportIconDisplayInterface::CoordinateSpace::WorldSpace,
AZ::Vector2{ iconSize, iconSize } });
}
}
}
}
@@ -106,4 +106,12 @@ namespace AzToolsFramework
const EditorVisibleEntityDataCache* m_entityDataCache = nullptr; //!< Entity Data queried by the EditorHelpers.
const FocusModeInterface* m_focusModeInterface = nullptr; //!< API to interact with focus mode functionality.
};
//! Calculate the icon scale based on how far away it is from a given point.
//! @note This is mostly likely distance from the camera.
float GetIconScale(float distance);
//! Calculate the icon size based on how far away it is from a given point.
//! @note This is the base icon size multiplied by the icon scale to give a final viewport size.
float GetIconSize(float distance);
} // namespace AzToolsFramework
@@ -2521,10 +2521,9 @@ namespace AzToolsFramework
AZStd::erase_if(
m_selectedEntityIds,
[readOnlyEntityPublicInterface](auto entityId)
{
return readOnlyEntityPublicInterface->IsReadOnly(entityId);
}
);
{
return readOnlyEntityPublicInterface->IsReadOnly(entityId);
});
}
// note: create/destroy pattern to be addressed
@@ -3285,9 +3284,9 @@ namespace AzToolsFramework
QObject::connect(
action, &QAction::triggered, action,
[this]
{
ToggleCenterPivotSelection();
});
{
ToggleCenterPivotSelection();
});
}
}
@@ -32,6 +32,8 @@ namespace AzToolsFramework
constexpr inline AZ::Crc32 EditReset = AZ_CRC_CE("com.o3de.action.editortransform.editreset");
constexpr inline AZ::Crc32 EditResetManipulator = AZ_CRC_CE("com.o3de.action.editortransform.editresetmanipulator");
constexpr inline AZ::Crc32 ViewportUiVisible = AZ_CRC_CE("com.o3de.action.editortransform.viewportuivisible");
constexpr inline AZ::Crc32 Helpers = AZ_CRC_CE("com.o3de.action.editor.helpers");
constexpr inline AZ::Crc32 Icons = AZ_CRC_CE("com.o3de.action.editor.icons");
//@}
//! Provide interface for EditorTransformComponentSelection requests.
@@ -670,6 +670,9 @@ set(FILES
Prefab/PrefabSystemComponent.h
Prefab/PrefabSystemComponent.cpp
Prefab/PrefabSystemComponentInterface.h
Prefab/ProceduralPrefabSystemComponent.h
Prefab/ProceduralPrefabSystemComponent.cpp
Prefab/ProceduralPrefabSystemComponentInterface.h
Prefab/PrefabSystemScriptingBus.h
Prefab/PrefabSystemScriptingHandler.h
Prefab/PrefabSystemScriptingHandler.cpp
@@ -40,16 +40,12 @@
#include <Tests/BoundsTestComponent.h>
namespace AZ
{
std::ostream& operator<<(std::ostream& os, const EntityId entityId)
{
return os << entityId.ToString().c_str();
}
} // namespace AZ
namespace UnitTest
{
using AzToolsFramework::ViewportInteraction::BuildMouseButtons;
using AzToolsFramework::ViewportInteraction::BuildMouseInteraction;
using AzToolsFramework::ViewportInteraction::BuildMousePick;
AzToolsFramework::EntityIdList SelectedEntities()
{
AzToolsFramework::EntityIdList selectedEntitiesBefore;
@@ -137,6 +133,18 @@ namespace UnitTest
AzToolsFramework::EntityIdList m_entityIds;
};
AZ::EntityId CreateEntityWithBounds(const char* entityName)
{
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity(entityName, &entity);
entity->Deactivate();
entity->CreateComponent<BoundsTestComponent>();
entity->Activate();
return entityId;
}
class EditorTransformComponentSelectionViewportPickingFixture : public ToolsApplicationFixture
{
public:
@@ -146,21 +154,9 @@ namespace UnitTest
// register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus
app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor());
auto createEntityWithBoundsFn = [](const char* entityName)
{
AZ::Entity* entity = nullptr;
AZ::EntityId entityId = CreateDefaultEditorEntity(entityName, &entity);
entity->Deactivate();
entity->CreateComponent<BoundsTestComponent>();
entity->Activate();
return entityId;
};
m_entityId1 = createEntityWithBoundsFn("Entity1");
m_entityId2 = createEntityWithBoundsFn("Entity2");
m_entityId3 = createEntityWithBoundsFn("Entity3");
m_entityId1 = CreateEntityWithBounds("Entity1");
m_entityId2 = CreateEntityWithBounds("Entity2");
m_entityId3 = CreateEntityWithBounds("Entity3");
}
void PositionEntities()
@@ -959,6 +955,9 @@ namespace UnitTest
const auto entity2ScreenPosition = AzFramework::WorldToScreen(AzToolsFramework::GetWorldTranslation(m_entityId2), m_cameraState);
// ensure icons are not enabled to avoid them interfering with bound detection
m_viewportManipulatorInteraction->GetViewportInteraction().SetIconsVisible(false);
// click the entity in the viewport
m_actionDispatcher->SetStickySelect(true)
->CameraState(m_cameraState)
@@ -974,6 +973,137 @@ namespace UnitTest
EXPECT_THAT(selectedEntities, UnorderedElementsAreArray(expectedSelectedEntities));
}
// entity can be selected using icon
TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, CursorOverEntityIconReturnsThatEntityId)
{
const AZ::EntityId boundlessEntityId = CreateDefaultEditorEntity("BoundlessEntity");
// camera (go to position format) -5.00, -8.00, 5.00, 0.00, 0.00
AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(AZ::Vector3(-5.0f, -8.0f, 5.0f)));
// position entity in the world
AZ::TransformBus::Event(boundlessEntityId, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(-5.0f, -1.0f, 5.0f));
const float distanceFromCamera = m_cameraState.m_position.GetDistance(AzToolsFramework::GetWorldTranslation(boundlessEntityId));
const auto quaterIconSize = AzToolsFramework::GetIconSize(distanceFromCamera) * 0.25f;
const auto entity1ScreenPosition =
AzFramework::WorldToScreen(AzToolsFramework::GetWorldTranslation(boundlessEntityId), m_cameraState) +
AzFramework::ScreenVectorFromVector2(AZ::Vector2(quaterIconSize));
AzToolsFramework::EditorVisibleEntityDataCache editorVisibleEntityDataCache;
AzToolsFramework::EditorHelpers editorHelpers(&editorVisibleEntityDataCache);
const auto viewportId = m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId();
const auto mousePick = BuildMousePick(m_cameraState, entity1ScreenPosition);
const auto mouseInteraction = BuildMouseInteraction(
mousePick, BuildMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::None),
AzToolsFramework::ViewportInteraction::InteractionId(AZ::EntityId(), viewportId),
AzToolsFramework::ViewportInteraction::KeyboardModifiers());
const auto mouseInteractionEvent = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Move, false);
// mimic mouse move
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(entity1ScreenPosition);
// simulate hovering over an icon in the viewport
editorVisibleEntityDataCache.CalculateVisibleEntityDatas(AzFramework::ViewportInfo{ viewportId });
auto entityIdUnderCursor = editorHelpers.FindEntityIdUnderCursor(m_cameraState, mouseInteractionEvent);
using ::testing::Eq;
EXPECT_THAT(entityIdUnderCursor.EntityIdUnderCursor(), Eq(boundlessEntityId));
}
// overlapping icons, nearest is detected
TEST_F(EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, CursorOverOverlappingEntityIconsReturnsClosestEntityId)
{
const AZ::EntityId boundlessEntityId1 = CreateDefaultEditorEntity("BoundlessEntity1");
const AZ::EntityId boundlessEntityId2 = CreateDefaultEditorEntity("BoundlessEntity2");
// camera (go to position format) -5.00, -8.00, 5.00, 0.00, 0.00
AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(AZ::Vector3(-5.0f, -8.0f, 5.0f)));
// position entities in the world
AZ::TransformBus::Event(boundlessEntityId1, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(-5.0f, -1.0f, 5.0f));
// note: boundlessEntityId2 is closer to the camera
AZ::TransformBus::Event(boundlessEntityId2, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(-5.0f, -3.0f, 5.0f));
const float distanceFromCamera = m_cameraState.m_position.GetDistance(AzToolsFramework::GetWorldTranslation(boundlessEntityId2));
const auto quaterIconSize = AzToolsFramework::GetIconSize(distanceFromCamera) * 0.25f;
const auto entity2ScreenPosition =
AzFramework::WorldToScreen(AzToolsFramework::GetWorldTranslation(boundlessEntityId2), m_cameraState) +
AzFramework::ScreenVectorFromVector2(AZ::Vector2(quaterIconSize));
AzToolsFramework::EditorVisibleEntityDataCache editorVisibleEntityDataCache;
AzToolsFramework::EditorHelpers editorHelpers(&editorVisibleEntityDataCache);
const auto viewportId = m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId();
const auto mousePick = BuildMousePick(m_cameraState, entity2ScreenPosition);
const auto mouseInteraction = BuildMouseInteraction(
mousePick, BuildMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::None),
AzToolsFramework::ViewportInteraction::InteractionId(AZ::EntityId(), viewportId),
AzToolsFramework::ViewportInteraction::KeyboardModifiers());
const auto mouseInteractionEvent = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Move, false);
// mimic mouse move
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(entity2ScreenPosition);
// simulate hovering over an icon in the viewport
editorVisibleEntityDataCache.CalculateVisibleEntityDatas(AzFramework::ViewportInfo{ viewportId });
auto entityIdUnderCursor = editorHelpers.FindEntityIdUnderCursor(m_cameraState, mouseInteractionEvent);
using ::testing::Eq;
EXPECT_THAT(entityIdUnderCursor.EntityIdUnderCursor(), Eq(boundlessEntityId2));
}
// if an entity with an icon is behind an entity with a bound, the entity with the icon will be selected
// even if the bound is closer (this is because icons are treated as if they are on the near clip plane)
TEST_F(
EditorTransformComponentSelectionViewportPickingManipulatorTestFixture, FurtherAwayEntityWithIconReturnedWhenBoundEntityIsInFront)
{
const AZ::EntityId boundEntityId = CreateEntityWithBounds("BoundEntity");
const AZ::EntityId boundlessEntityId = CreateDefaultEditorEntity("BoundlessEntity");
auto* boundTestComponent = AzToolsFramework::GetEntityById(boundEntityId)->FindComponent<BoundsTestComponent>();
boundTestComponent->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-1.5f, -0.5f, -0.5f), AZ::Vector3(1.5f, 0.5, 0.5f));
// camera (go to position format) -5.00, -8.00, 5.00, 0.00, 0.00
AzFramework::SetCameraTransform(m_cameraState, AZ::Transform::CreateTranslation(AZ::Vector3(-5.0f, -8.0f, 5.0f)));
// position entities in the world
AZ::TransformBus::Event(boundEntityId, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(-4.0f, -3.0f, 5.0f));
// note: boundlessEntityId2 is closer to the camera
AZ::TransformBus::Event(boundlessEntityId, &AZ::TransformBus::Events::SetWorldTranslation, AZ::Vector3(-5.0f, -1.0f, 5.0f));
const float distanceFromCamera = m_cameraState.m_position.GetDistance(AzToolsFramework::GetWorldTranslation(boundlessEntityId));
const auto quaterIconSize = AzToolsFramework::GetIconSize(distanceFromCamera) * 0.25f;
const auto entity2ScreenPosition =
AzFramework::WorldToScreen(AzToolsFramework::GetWorldTranslation(boundlessEntityId), m_cameraState) +
AzFramework::ScreenVectorFromVector2(AZ::Vector2(quaterIconSize));
AzToolsFramework::EditorVisibleEntityDataCache editorVisibleEntityDataCache;
AzToolsFramework::EditorHelpers editorHelpers(&editorVisibleEntityDataCache);
const auto viewportId = m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId();
const auto mousePick = BuildMousePick(m_cameraState, entity2ScreenPosition);
const auto mouseInteraction = BuildMouseInteraction(
mousePick, BuildMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton::None),
AzToolsFramework::ViewportInteraction::InteractionId(AZ::EntityId(), viewportId),
AzToolsFramework::ViewportInteraction::KeyboardModifiers());
const auto mouseInteractionEvent = AzToolsFramework::ViewportInteraction::BuildMouseInteractionEvent(
mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Move, false);
// mimic mouse move
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(entity2ScreenPosition);
// simulate hovering over an icon in the viewport
editorVisibleEntityDataCache.CalculateVisibleEntityDatas(AzFramework::ViewportInfo{ viewportId });
auto entityIdUnderCursor = editorHelpers.FindEntityIdUnderCursor(m_cameraState, mouseInteractionEvent);
using ::testing::Eq;
EXPECT_THAT(entityIdUnderCursor.EntityIdUnderCursor(), Eq(boundlessEntityId));
}
class EditorTransformComponentSelectionViewportPickingManipulatorTestFixtureParam
: public EditorTransformComponentSelectionViewportPickingManipulatorTestFixture
, public ::testing::WithParamInterface<bool>
@@ -1709,8 +1839,9 @@ namespace UnitTest
using MouseInteractionResult = AzToolsFramework::ViewportInteraction::MouseInteractionResult;
public:
WheelEventWidget(QWidget* parent = nullptr)
WheelEventWidget(const AzFramework::ViewportId viewportId, QWidget* parent = nullptr)
: QWidget(parent)
, m_viewportId(viewportId)
{
}
@@ -1719,7 +1850,7 @@ namespace UnitTest
namespace vi = AzToolsFramework::ViewportInteraction;
vi::MouseInteraction mouseInteraction;
mouseInteraction.m_interactionId.m_cameraId = AZ::EntityId();
mouseInteraction.m_interactionId.m_viewportId = 0;
mouseInteraction.m_interactionId.m_viewportId = m_viewportId;
mouseInteraction.m_mouseButtons = vi::BuildMouseButtons(ev->buttons());
mouseInteraction.m_mousePick = vi::MousePick();
mouseInteraction.m_keyboardModifiers = vi::BuildKeyboardModifiers(ev->modifiers());
@@ -1731,15 +1862,15 @@ namespace UnitTest
}
MouseInteractionResult m_mouseInteractionResult;
AzFramework::ViewportId m_viewportId;
};
TEST_F(EditorTransformComponentSelectionFixture, MouseScrollWheelSwitchesTransformMode)
TEST_F(EditorTransformComponentSelectionManipulatorTestFixture, MouseScrollWheelSwitchesTransformMode)
{
using ::testing::Eq;
namespace vi = AzToolsFramework::ViewportInteraction;
using AzToolsFramework::EditorTransformComponentSelectionRequestBus;
const auto transformMode = []()
const auto transformMode = []
{
EditorTransformComponentSelectionRequestBus::Events::Mode transformMode;
EditorTransformComponentSelectionRequestBus::EventResult(
@@ -1752,7 +1883,7 @@ namespace UnitTest
// preconditions
EXPECT_THAT(transformMode(), EditorTransformComponentSelectionRequestBus::Events::Mode::Translation);
auto wheelEventWidget = WheelEventWidget();
auto wheelEventWidget = WheelEventWidget(m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId());
// attach the global event filter to the placeholder widget
AzQtComponents::GlobalEventFilter globalEventFilter(QApplication::instance());
wheelEventWidget.installEventFilter(&globalEventFilter);
@@ -1768,6 +1899,7 @@ namespace UnitTest
// then
// transform mode has changed and mouse event was handled
using ::testing::Eq;
EXPECT_THAT(transformMode(), Eq(EditorTransformComponentSelectionRequestBus::Events::Mode::Rotation));
EXPECT_THAT(wheelEventWidget.m_mouseInteractionResult, Eq(vi::MouseInteractionResult::Viewport));
}
@@ -3100,7 +3232,7 @@ namespace UnitTest
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
const auto viewportRay = AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, initialPositionScreen);
const auto distanceAway = (finalEntityTransform.GetTranslation() - viewportRay.origin).GetLength();
const auto distanceAway = (finalEntityTransform.GetTranslation() - viewportRay.m_origin).GetLength();
// ensure final world positions match
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
@@ -62,7 +62,6 @@ namespace UnitTest
void BrowseForAssets(AssetBrowser::AssetSelectionModel& /*selection*/) override {}
int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) override { AZ_UNUSED(entityIconPath); return 0; }
bool DisplayHelpersVisible() override { return false; }
void GoToSelectedEntitiesInViewports() override
{
@@ -8,12 +8,12 @@
#include <Tests/FocusMode/EditorFocusModeSelectionFixture.h>
namespace AzToolsFramework
namespace UnitTest
{
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionTests_FindHighestSelectableEntityWithNoContainers)
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionFindHighestSelectableEntityWithNoContainers)
{
// When no containers are in the way, the function will just return the entityId of the entity that was clicked.
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
@@ -23,7 +23,7 @@ namespace AzToolsFramework
EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionTests_FindHighestSelectableEntityWithClosedContainer)
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionFindHighestSelectableEntityWithClosedContainer)
{
// If a closed container is an ancestor of the queried entity, the closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]); // Containers are closed by default
@@ -40,7 +40,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionTests_FindHighestSelectableEntityWithOpenContainer)
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionFindHighestSelectableEntityWithOpenContainer)
{
// If a closed container is an ancestor of the queried entity, the closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -58,7 +58,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionTests_FindHighestSelectableEntityWithMultipleClosedContainers)
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionFindHighestSelectableEntityWithMultipleClosedContainers)
{
// If multiple closed containers are ancestors of the queried entity, the highest closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -77,7 +77,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CityEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionTests_FindHighestSelectableEntityWithMultipleContainers)
TEST_F(EditorFocusModeSelectionFixture, ContainerEntitySelectionFindHighestSelectableEntityWithMultipleContainers)
{
// If multiple containers are ancestors of the queried entity, the highest closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -96,4 +96,4 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CityEntityName]);
}
}
} // namespace UnitTest
@@ -8,9 +8,9 @@
#include <Tests/FocusMode/EditorFocusModeFixture.h>
namespace AzToolsFramework
namespace UnitTest
{
TEST_F(EditorFocusModeFixture, ContainerEntityTests_Register)
TEST_F(EditorFocusModeFixture, ContainerEntityRegister)
{
// Registering an entity is successful.
auto outcome = m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CarEntityName]);
@@ -20,7 +20,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CarEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_RegisterTwice)
TEST_F(EditorFocusModeFixture, ContainerEntityRegisterTwice)
{
// Registering an entity twice fails.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CarEntityName]);
@@ -31,7 +31,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CarEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_Unregister)
TEST_F(EditorFocusModeFixture, ContainerEntityUnregister)
{
// Unregistering a container entity is successful.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CarEntityName]);
@@ -39,21 +39,21 @@ namespace AzToolsFramework
EXPECT_TRUE(outcome.IsSuccess());
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_UnregisterRegularEntity)
TEST_F(EditorFocusModeFixture, ContainerEntityUnregisterRegularEntity)
{
// Unregistering an entity that was not previously registered fails.
auto outcome = m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CarEntityName]);
EXPECT_FALSE(outcome.IsSuccess());
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_UnregisterTwice)
TEST_F(EditorFocusModeFixture, ContainerEntityUnregisterTwice)
{
// Unregistering a container entity twice fails.
auto outcome = m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CarEntityName]);
EXPECT_FALSE(outcome.IsSuccess());
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOnRegularEntity)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOnRegularEntity)
{
// If a regular entity is passed, IsContainer returns false.
// Note that we use a different entity than the tests above to validate a completely new EntityId.
@@ -61,7 +61,7 @@ namespace AzToolsFramework
EXPECT_FALSE(isContainer);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOnRegisteredContainer)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOnRegisteredContainer)
{
// If a container entity is passed, IsContainer returns true.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
@@ -72,7 +72,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOnUnRegisteredContainer)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOnUnRegisteredContainer)
{
// If an entity that was previously a container but was then unregistered is passed, IsContainer returns false.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
@@ -82,14 +82,14 @@ namespace AzToolsFramework
EXPECT_FALSE(isContainer);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_SetContainerOpenOnRegularEntity)
TEST_F(EditorFocusModeFixture, ContainerEntitySetContainerOpenOnRegularEntity)
{
// Setting a regular entity to open should return a failure.
auto outcome = m_containerEntityInterface->SetContainerOpen(m_entityMap[StreetEntityName], true);
EXPECT_FALSE(outcome.IsSuccess());
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_SetContainerOpen)
TEST_F(EditorFocusModeFixture, ContainerEntitySetContainerOpen)
{
// Set a container entity to open, and verify the operation was successful.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -100,7 +100,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_SetContainerOpenTwice)
TEST_F(EditorFocusModeFixture, ContainerEntitySetContainerOpenTwice)
{
// Set a container entity to open twice, and verify that does not cause a failure (as intended).
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -112,7 +112,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_SetContainerClosed)
TEST_F(EditorFocusModeFixture, ContainerEntitySetContainerClosed)
{
// Set a container entity to closed, and verify the operation was successful.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -122,16 +122,16 @@ namespace AzToolsFramework
// Restore default state for other tests.
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOpenOnRegularEntity)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOpenOnRegularEntity)
{
// Query open state on a regular entity, and verify it returns true.
// Open containers behave exactly as regular entities, so this is the expected return value.
bool isOpen = m_containerEntityInterface->IsContainerOpen(m_entityMap[CityEntityName]);
EXPECT_TRUE(isOpen);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOpenOnDefaultContainerEntity)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOpenOnDefaultContainerEntity)
{
// Query open state on a newly registered container entity, and verify it returns false.
// Containers are registered closed by default.
@@ -142,8 +142,8 @@ namespace AzToolsFramework
// Restore default state for other tests.
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CityEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOpenOnOpenContainerEntity)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOpenOnOpenContainerEntity)
{
// Query open state on a container entity that was opened, and verify it returns true.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CityEntityName]);
@@ -154,8 +154,8 @@ namespace AzToolsFramework
// Restore default state for other tests.
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CityEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_IsContainerOpenOnClosedContainerEntity)
TEST_F(EditorFocusModeFixture, ContainerEntityIsContainerOpenOnClosedContainerEntity)
{
// Query open state on a container entity that was opened and then closed, and verify it returns false.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[CityEntityName]);
@@ -167,8 +167,8 @@ namespace AzToolsFramework
// Restore default state for other tests.
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CityEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_ContainerOpenStateIsPreserved)
TEST_F(EditorFocusModeFixture, ContainerEntityContainerOpenStateIsPreserved)
{
// Register an entity as container, open it, then unregister it.
// When the entity is registered again, the open state should be preserved.
@@ -184,15 +184,15 @@ namespace AzToolsFramework
// Restore default state for other tests.
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[CityEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_ClearSucceeds)
TEST_F(EditorFocusModeFixture, ContainerEntityClearSucceeds)
{
// The Clear function works if no container is registered.
auto outcome = m_containerEntityInterface->Clear(m_editorEntityContextId);
EXPECT_TRUE(outcome.IsSuccess());
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_ClearFailsIfContainersAreStillRegistered)
TEST_F(EditorFocusModeFixture, ContainerEntityClearFailsIfContainersAreStillRegistered)
{
// The Clear function fails if a container is registered.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[Passenger1EntityName]);
@@ -202,8 +202,8 @@ namespace AzToolsFramework
// Restore default state for other tests.
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[Passenger1EntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_ClearSucceedsIfContainersAreUnregistered)
TEST_F(EditorFocusModeFixture, ContainerEntityClearSucceedsIfContainersAreUnregistered)
{
// The Clear function fails if a container is registered.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[Passenger1EntityName]);
@@ -212,7 +212,7 @@ namespace AzToolsFramework
EXPECT_TRUE(outcome.IsSuccess());
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_ClearDeletesPreservedOpenStates)
TEST_F(EditorFocusModeFixture, ContainerEntityClearDeletesPreservedOpenStates)
{
// Register an entity as container, open it, unregister it, then call clear.
// When the entity is registered again, the open state should not be preserved.
@@ -230,14 +230,14 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[Passenger1EntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_FindHighestSelectableEntityWithNoContainers)
TEST_F(EditorFocusModeFixture, ContainerEntityFindHighestSelectableEntityWithNoContainers)
{
// When no containers are in the way, the function will just return the entityId that was passed to it.
AZ::EntityId selectedEntityId = m_containerEntityInterface->FindHighestSelectableEntity(m_entityMap[Passenger2EntityName]);
EXPECT_EQ(selectedEntityId, m_entityMap[Passenger2EntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_FindHighestSelectableEntityWithClosedContainer)
TEST_F(EditorFocusModeFixture, ContainerEntityFindHighestSelectableEntityWithClosedContainer)
{
// If a closed container is an ancestor of the queried entity, the closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[SportsCarEntityName]); // Containers are closed by default
@@ -248,7 +248,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_FindHighestSelectableEntityWithOpenContainer)
TEST_F(EditorFocusModeFixture, ContainerEntityFindHighestSelectableEntityWithOpenContainer)
{
// If an open container is an ancestor of the queried entity, it is ignored.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
@@ -261,7 +261,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_FindHighestSelectableEntityWithMultipleClosedContainers)
TEST_F(EditorFocusModeFixture, ContainerEntityFindHighestSelectableEntityWithMultipleClosedContainers)
{
// If multiple closed containers are ancestors of the queried entity, the highest closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -275,7 +275,7 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
}
TEST_F(EditorFocusModeFixture, ContainerEntityTests_FindHighestSelectableEntityWithMultipleContainers)
TEST_F(EditorFocusModeFixture, ContainerEntityFindHighestSelectableEntityWithMultipleContainers)
{
// If multiple containers are ancestors of the queried entity, the highest closed container is selected.
m_containerEntityInterface->RegisterEntityAsContainer(m_entityMap[StreetEntityName]);
@@ -289,5 +289,4 @@ namespace AzToolsFramework
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[StreetEntityName]);
m_containerEntityInterface->UnregisterEntityAsContainer(m_entityMap[SportsCarEntityName]);
}
}
} // namespace UnitTest
@@ -12,7 +12,7 @@
#include <Tests/BoundsTestComponent.h>
namespace AzToolsFramework
namespace UnitTest
{
void ClearSelectedEntities()
{
@@ -31,14 +31,14 @@ namespace AzToolsFramework
void EditorFocusModeFixture::SetUpEditorFixtureImpl()
{
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
// in the unit tests.
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
m_containerEntityInterface = AZ::Interface<AzToolsFramework::ContainerEntityInterface>::Get();
ASSERT_TRUE(m_containerEntityInterface != nullptr);
m_focusModeInterface = AZ::Interface<FocusModeInterface>::Get();
m_focusModeInterface = AZ::Interface<AzToolsFramework::FocusModeInterface>::Get();
ASSERT_TRUE(m_focusModeInterface != nullptr);
// register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus
@@ -68,26 +68,26 @@ namespace AzToolsFramework
ClearSelectedEntities();
}
void EditorFocusModeFixture::GenerateTestHierarchy()
void EditorFocusModeFixture::GenerateTestHierarchy()
{
/*
* City
* |_ Street
* |_ Car
* | |_ Passenger
* |_ SportsCar
* |_ Passenger
*/
* City
* |_ Street
* |_ Car
* | |_ Passenger
* |_ SportsCar
* |_ Passenger
*/
m_entityMap[CityEntityName] = CreateEditorEntity(CityEntityName, AZ::EntityId());
m_entityMap[StreetEntityName] = CreateEditorEntity(StreetEntityName, m_entityMap[CityEntityName]);
m_entityMap[CarEntityName] = CreateEditorEntity(CarEntityName, m_entityMap[StreetEntityName]);
m_entityMap[Passenger1EntityName] = CreateEditorEntity(Passenger1EntityName, m_entityMap[CarEntityName]);
m_entityMap[SportsCarEntityName] = CreateEditorEntity(SportsCarEntityName, m_entityMap[StreetEntityName]);
m_entityMap[Passenger2EntityName] = CreateEditorEntity(Passenger2EntityName, m_entityMap[SportsCarEntityName]);
m_entityMap[CityEntityName] = CreateEditorEntity(CityEntityName, AZ::EntityId());
m_entityMap[StreetEntityName] = CreateEditorEntity(StreetEntityName, m_entityMap[CityEntityName]);
m_entityMap[CarEntityName] = CreateEditorEntity(CarEntityName, m_entityMap[StreetEntityName]);
m_entityMap[Passenger1EntityName] = CreateEditorEntity(Passenger1EntityName, m_entityMap[CarEntityName]);
m_entityMap[SportsCarEntityName] = CreateEditorEntity(SportsCarEntityName, m_entityMap[StreetEntityName]);
m_entityMap[Passenger2EntityName] = CreateEditorEntity(Passenger2EntityName, m_entityMap[SportsCarEntityName]);
// Add a BoundsTestComponent to the Car entity.
AZ::Entity* entity = GetEntityById(m_entityMap[CarEntityName]);
AZ::Entity* entity = AzToolsFramework::GetEntityById(m_entityMap[CarEntityName]);
entity->Deactivate();
entity->CreateComponent<UnitTest::BoundsTestComponent>();
@@ -113,4 +113,4 @@ namespace AzToolsFramework
return entity->GetId();
}
}
} // namespace UnitTest
@@ -18,10 +18,9 @@
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
namespace AzToolsFramework
namespace UnitTest
{
class EditorFocusModeFixture
: public UnitTest::ToolsApplicationFixture
class EditorFocusModeFixture : public ToolsApplicationFixture
{
protected:
void SetUpEditorFixtureImpl() override;
@@ -32,8 +31,8 @@ namespace AzToolsFramework
AZStd::unordered_map<AZStd::string, AZ::EntityId> m_entityMap;
ContainerEntityInterface* m_containerEntityInterface = nullptr;
FocusModeInterface* m_focusModeInterface = nullptr;
AzToolsFramework::ContainerEntityInterface* m_containerEntityInterface = nullptr;
AzToolsFramework::FocusModeInterface* m_focusModeInterface = nullptr;
public:
AzToolsFramework::EntityIdList GetSelectedEntities();
@@ -53,4 +52,4 @@ namespace AzToolsFramework
inline static AZ::Vector3 WorldCarEntityPosition = AZ::Vector3(5.0f, 15.0f, 0.0f);
};
}
} // namespace UnitTest
@@ -26,11 +26,17 @@
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/ViewportSelection/EditorVisibleEntityDataCache.h>
namespace AzToolsFramework
namespace UnitTest
{
class EditorFocusModeSelectionFixture : public UnitTest::IndirectCallManipulatorViewportInteractionFixtureMixin<EditorFocusModeFixture>
class EditorFocusModeSelectionFixture : public IndirectCallManipulatorViewportInteractionFixtureMixin<EditorFocusModeFixture>
{
public:
void SetUpEditorFixtureImpl() override
{
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorFocusModeFixture>::SetUpEditorFixtureImpl();
m_viewportManipulatorInteraction->GetViewportInteraction().SetIconsVisible(false);
}
void ClickAtWorldPositionOnViewport(const AZ::Vector3& worldPosition)
{
// Calculate the world position in screen space
@@ -40,4 +46,4 @@ namespace AzToolsFramework
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(carScreenPosition)->MouseLButtonDown()->MouseLButtonUp();
}
};
} // namespace AzToolsFramework
} // namespace UnitTest
@@ -8,9 +8,9 @@
#include <Tests/FocusMode/EditorFocusModeSelectionFixture.h>
namespace AzToolsFramework
namespace UnitTest
{
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnLevel)
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnLevel)
{
// Click on Car Entity
ClickAtWorldPositionOnViewport(WorldCarEntityPosition);
@@ -21,7 +21,7 @@ namespace AzToolsFramework
EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnAncestor)
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnAncestor)
{
// Set the focus on the Street Entity (parent of the test entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
@@ -35,7 +35,7 @@ namespace AzToolsFramework
EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnItself)
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnItself)
{
// Set the focus on the Car Entity (test entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]);
@@ -49,7 +49,7 @@ namespace AzToolsFramework
EXPECT_EQ(selectedEntitiesAfter.front(), m_entityMap[CarEntityName]);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnSibling)
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnSibling)
{
// Set the focus on the SportsCar Entity (sibling of the test entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[SportsCarEntityName]);
@@ -62,7 +62,7 @@ namespace AzToolsFramework
EXPECT_EQ(selectedEntitiesAfter.size(), 0);
}
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionTests_SelectEntityWithFocusOnDescendant)
TEST_F(EditorFocusModeSelectionFixture, EditorFocusModeSelectionSelectEntityWithFocusOnDescendant)
{
// Set the focus on the Passenger1 Entity (child of the entity)
m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger1EntityName]);
@@ -74,4 +74,4 @@ namespace AzToolsFramework
auto selectedEntitiesAfter = GetSelectedEntities();
EXPECT_EQ(selectedEntitiesAfter.size(), 0);
}
}
} // namespace UnitTest
@@ -8,9 +8,9 @@
#include <Tests/FocusMode/EditorFocusModeFixture.h>
namespace AzToolsFramework
namespace UnitTest
{
TEST_F(EditorFocusModeFixture, EditorFocusModeTests_SetFocus)
TEST_F(EditorFocusModeFixture, SetFocus)
{
// When an entity is set as the focus root, GetFocusRoot should return its EntityId.
m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]);
@@ -20,7 +20,7 @@ namespace AzToolsFramework
m_focusModeInterface->ClearFocusRoot(m_editorEntityContextId);
}
TEST_F(EditorFocusModeFixture, EditorFocusModeTests_ClearFocus)
TEST_F(EditorFocusModeFixture, ClearFocus)
{
// Change the value from the default.
m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]);
@@ -30,7 +30,7 @@ namespace AzToolsFramework
EXPECT_EQ(m_focusModeInterface->GetFocusRoot(m_editorEntityContextId), AZ::EntityId());
}
TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_AncestorsDescendants)
TEST_F(EditorFocusModeFixture, IsInFocusSubTreeAncestorsDescendants)
{
// When the focus is set to an entity, all its descendants are in the focus subtree while the ancestors aren't.
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
@@ -43,7 +43,7 @@ namespace AzToolsFramework
EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[Passenger2EntityName]), true);
}
TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Siblings)
TEST_F(EditorFocusModeFixture, IsInFocusSubTreeSiblings)
{
// If the root entity has siblings, they are also outside of the focus subtree.
m_focusModeInterface->SetFocusRoot(m_entityMap[CarEntityName]);
@@ -56,7 +56,7 @@ namespace AzToolsFramework
EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[Passenger2EntityName]), false);
}
TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Leaf)
TEST_F(EditorFocusModeFixture, IsInFocusSubTreeLeaf)
{
// If the root is a leaf, then the focus subtree will consists of just that entity.
m_focusModeInterface->SetFocusRoot(m_entityMap[Passenger2EntityName]);
@@ -69,7 +69,7 @@ namespace AzToolsFramework
EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[Passenger2EntityName]), true);
}
TEST_F(EditorFocusModeFixture, EditorFocusModeTests_IsInFocusSubTree_Clear)
TEST_F(EditorFocusModeFixture, IsInFocusSubTreeClear)
{
// Change the value from the default.
m_focusModeInterface->SetFocusRoot(m_entityMap[StreetEntityName]);
@@ -84,4 +84,4 @@ namespace AzToolsFramework
EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[SportsCarEntityName]), true);
EXPECT_EQ(m_focusModeInterface->IsInFocusSubTree(m_entityMap[Passenger2EntityName]), true);
}
}
} // namespace UnitTest
@@ -0,0 +1,302 @@
/*
* 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/IO/FileIO.h>
#include <AzCore/IO/Path/PathReflect.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Settings/SettingsRegistryImpl.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <Prefab/PrefabTestFixture.h>
#include <Prefab/ProceduralPrefabSystemComponent.h>
#include <Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
namespace UnitTest
{
struct ProceduralPrefabSystemComponentTests
: AllocatorsTestFixture
, AZ::ComponentApplicationBus::Handler
{
void SetUp() override
{
TestRunner::Instance().m_suppressOutput = false;
TestRunner::Instance().m_suppressPrintf = false;
TestRunner::Instance().m_suppressWarnings = false;
TestRunner::Instance().m_suppressErrors = false;
TestRunner::Instance().m_suppressAsserts = false;
AllocatorsTestFixture::SetUp();
AZ::ComponentApplicationBus::Handler::BusConnect();
ASSERT_TRUE(m_temporaryDirectory.IsValid());
m_localFileIo = AZStd::make_unique<AZ::IO::LocalFileIO>();
m_prevIoBase = AZ::IO::FileIOBase::GetInstance();
AZ::IO::FileIOBase::SetInstance(nullptr); // Need to clear the previous instance first
AZ::IO::FileIOBase::SetInstance(m_localFileIo.get());
AZ::JsonSystemComponent::Reflect(&m_jsonContext);
m_settingsRegistry = AZStd::make_unique<AZ::SettingsRegistryImpl>();
AZ::SettingsRegistry::Register(m_settingsRegistry.get());
m_settingsRegistry->Set(AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectPath, m_temporaryDirectory.GetDirectory());
m_prefabSystem = PrefabSystemComponent::CreateDescriptor();
m_procSystem = ProceduralPrefabSystemComponent::CreateDescriptor();
m_prefabSystem->Reflect(&m_context);
m_prefabSystem->Reflect(&m_jsonContext);
m_procSystem->Reflect(&m_context);
m_procSystem->Reflect(&m_jsonContext);
AZ::Entity::Reflect(&m_context);
AZ::Entity::Reflect(&m_jsonContext);
AZ::IO::PathReflect(&m_context);
m_systemEntity = AZStd::make_unique<AZ::Entity>();
m_systemEntity->CreateComponent<PrefabSystemComponent>();
m_systemEntity->CreateComponent<ProceduralPrefabSystemComponent>();
m_systemEntity->Init();
m_systemEntity->Activate();
AZ::Data::AssetManager::Create({});
}
void TearDown() override
{
AZ::Data::AssetManager::Destroy();
m_systemEntity->Deactivate();
m_systemEntity = nullptr;
m_jsonContext.EnableRemoveReflection();
AZ::JsonSystemComponent::Reflect(&m_jsonContext);
m_prefabSystem->Reflect(&m_jsonContext);
m_procSystem->Reflect(&m_jsonContext);
AZ::Entity::Reflect(&m_jsonContext);
m_jsonContext.DisableRemoveReflection();
AZ::IO::FileIOBase::SetInstance(nullptr); // Clear the previous instance first
AZ::IO::FileIOBase::SetInstance(m_prevIoBase);
m_prevIoBase = nullptr;
AZ::SettingsRegistry::Unregister(m_settingsRegistry.get());
AZ::ComponentApplicationBus::Handler::BusDisconnect();
AllocatorsTestFixture::TearDown();
TestRunner::Instance().ResetSuppressionSettingsToDefault();
}
// ComponentApplicationBus
AZ::ComponentApplication* GetApplication() override
{
return nullptr;
}
void RegisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
void UnregisterComponentDescriptor(const AZ::ComponentDescriptor*) override { }
void RegisterEntityAddedEventHandler(AZ::EntityAddedEvent::Handler&) override { }
void RegisterEntityRemovedEventHandler(AZ::EntityRemovedEvent::Handler&) override { }
void RegisterEntityActivatedEventHandler(AZ::EntityActivatedEvent::Handler&) override { }
void RegisterEntityDeactivatedEventHandler(AZ::EntityDeactivatedEvent::Handler&) override { }
void SignalEntityActivated(AZ::Entity*) override { }
void SignalEntityDeactivated(AZ::Entity*) override { }
bool AddEntity(AZ::Entity*) override
{
return true;
}
bool RemoveEntity(AZ::Entity*) override
{
return true;
}
bool DeleteEntity(const AZ::EntityId&) override
{
return true;
}
AZ::Entity* FindEntity(const AZ::EntityId&) override
{
return nullptr;
}
AZ::SerializeContext* GetSerializeContext() override
{
return &m_context;
}
AZ::BehaviorContext* GetBehaviorContext() override
{
return nullptr;
}
AZ::JsonRegistrationContext* GetJsonRegistrationContext() override
{
return &m_jsonContext;
}
const char* GetEngineRoot() const override
{
return nullptr;
}
const char* GetExecutableFolder() const override
{
return nullptr;
}
void EnumerateEntities(const EntityCallback& /*callback*/) override { }
void QueryApplicationType(AZ::ApplicationTypeQuery& /*appType*/) const override { }
////
AZ::ComponentDescriptor* m_prefabSystem{};
AZ::ComponentDescriptor* m_procSystem{};
AZStd::unique_ptr<AZ::SettingsRegistryImpl> m_settingsRegistry;
AZ::SerializeContext m_context;
AZ::JsonRegistrationContext m_jsonContext;
AZStd::unique_ptr<AZ::IO::LocalFileIO> m_localFileIo;
ScopedTemporaryDirectory m_temporaryDirectory;
AZStd::unique_ptr<AZ::Entity> m_systemEntity;
AZ::IO::FileIOBase* m_prevIoBase{};
};
struct MockCatalog : AZ::Data::AssetCatalogRequestBus::Handler
{
static const inline AZ::Data::AssetId TestId{ AZ::Uuid::CreateRandom(), 1234 };
MockCatalog(AZStd::string testFile)
: m_testFile(AZStd::move(testFile))
{
BusConnect();
}
~MockCatalog() override
{
BusDisconnect();
}
AZStd::string GetAssetPathById(const AZ::Data::AssetId& assetId) override
{
if (assetId == TestId)
{
return m_testFile;
}
return "InvalidAssetId";
}
AZ::Data::AssetId GetAssetIdByPath(const char* path, const AZ::Data::AssetType&, bool) override
{
AZ::IO::PathView pathView{ AZStd::string_view(path) };
if (AZ::IO::PathView(m_testFile) == pathView)
{
return TestId;
}
AZ_Error("MockCatalog", false, "Requested path %s does not match expected asset path of %s", path, m_testFile.c_str());
ADD_FAILURE();
return {};
}
AZStd::string m_testFile;
};
struct PrefabPublicNotificationsListener : PrefabPublicNotificationBus::Handler
{
PrefabPublicNotificationsListener()
{
BusConnect();
}
~PrefabPublicNotificationsListener() override
{
BusDisconnect();
}
void OnPrefabInstancePropagationBegin() override
{
m_updated = true;
}
bool m_updated = false;
};
TEST_F(ProceduralPrefabSystemComponentTests, RegisteredPrefabUpdates)
{
const AZStd::string prefabFile = (AZ::IO::Path(m_temporaryDirectory.GetDirectory()) / "test.prefab").Native();
MockCatalog catalog(prefabFile.c_str());
auto proceduralPrefabSystemComponentInterface = AZ::Interface<ProceduralPrefabSystemComponentInterface>::Get();
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
ASSERT_NE(proceduralPrefabSystemComponentInterface, nullptr);
ASSERT_NE(prefabSystemComponentInterface, nullptr);
ASSERT_NE(prefabLoaderInterface, nullptr);
auto entity = aznew AZ::Entity();
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->CreatePrefab({ entity }, {}, prefabFile.c_str());
ASSERT_NE(instance, nullptr);
prefabLoaderInterface->SaveTemplateToFile(instance->GetTemplateId(), prefabFile.c_str());
proceduralPrefabSystemComponentInterface->RegisterProceduralPrefab(prefabFile, instance->GetTemplateId());
AzFramework::AssetCatalogEventBus::Broadcast(&AzFramework::AssetCatalogEventBus::Events::OnCatalogAssetChanged, MockCatalog::TestId);
PrefabPublicNotificationsListener listener;
AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
EXPECT_TRUE(listener.m_updated);
}
TEST_F(ProceduralPrefabSystemComponentTests, UnregisteredPrefabDoesNotUpdate)
{
PrefabPublicNotificationsListener listener;
const AZStd::string prefabFile = (AZ::IO::Path(m_temporaryDirectory.GetDirectory()) / "test.prefab").Native();
MockCatalog catalog(prefabFile.c_str());
auto proceduralPrefabSystemComponentInterface = AZ::Interface<ProceduralPrefabSystemComponentInterface>::Get();
auto prefabSystemComponentInterface = AZ::Interface<PrefabSystemComponentInterface>::Get();
auto prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
ASSERT_NE(proceduralPrefabSystemComponentInterface, nullptr);
ASSERT_NE(prefabSystemComponentInterface, nullptr);
ASSERT_NE(prefabLoaderInterface, nullptr);
auto entity = aznew AZ::Entity();
AZStd::unique_ptr<Instance> instance = prefabSystemComponentInterface->CreatePrefab({ entity }, {}, prefabFile.c_str());
ASSERT_NE(instance, nullptr);
prefabLoaderInterface->SaveTemplateToFile(instance->GetTemplateId(), prefabFile.c_str());
AzFramework::AssetCatalogEventBus::Broadcast(
&AzFramework::AssetCatalogEventBus::Events::OnCatalogAssetChanged, MockCatalog::TestId);
AZ::SystemTickBus::Broadcast(&AZ::SystemTickBus::Events::OnSystemTick);
EXPECT_FALSE(listener.m_updated);
}
}
@@ -137,7 +137,6 @@ namespace UnitTest
void CreateEditorRepresentation(AZ::Entity* entity) override;
void BrowseForAssets(AzToolsFramework::AssetBrowser::AssetSelectionModel& selection) override { AZ_UNUSED(selection); }
int GetIconTextureIdFromEntityIconPath(const AZStd::string& entityIconPath) override { AZ_UNUSED(entityIconPath); return 0; }
bool DisplayHelpersVisible() override { return false; }
/*
* AssetSystemRequestBus
@@ -243,7 +243,6 @@ namespace UnitTest
// These are required by implementing the EditorRequestBus
void BrowseForAssets(AssetBrowser::AssetSelectionModel& /*selection*/) override {}
int GetIconTextureIdFromEntityIconPath([[maybe_unused]] const AZStd::string& entityIconPath) override { return 0; }
bool DisplayHelpersVisible() override { return false; }
public:
EntityPropertyEditor* m_levelEditor;
@@ -105,6 +105,7 @@ set(FILES
Prefab/SpawnableSortEntitiesTests.cpp
Prefab/PrefabScriptingTests.cpp
Prefab/ProceduralPrefabAssetTests.cpp
Prefab/ProceduralPrefabSystemComponentTests.cpp
PropertyIntCtrlCommonTests.cpp
PropertyIntCtrlCommonTests.h
PropertyIntSliderCtrlTests.cpp