Merge branch 'main' into SpawnableEntityIdMapping

This commit is contained in:
AMZN-koppersr
2021-06-04 10:11:47 -07:00
166 changed files with 8833 additions and 9878 deletions
+39 -21
View File
@@ -30,7 +30,7 @@ namespace Platform
using FileHandleType = SystemFile::FileHandleType;
void Seek(FileHandleType handle, const SystemFile* systemFile, SizeType offset, SystemFile::SeekMode mode);
void Seek(FileHandleType handle, const SystemFile* systemFile, SystemFile::SeekSizeType offset, SystemFile::SeekMode mode);
SystemFile::SizeType Tell(FileHandleType handle, const SystemFile* systemFile);
bool Eof(FileHandleType handle, const SystemFile* systemFile);
AZ::u64 ModificationTime(FileHandleType handle, const SystemFile* systemFile);
@@ -68,9 +68,8 @@ void SystemFile::CreatePath(const char* fileName)
}
SystemFile::SystemFile()
: m_handle{ AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE }
{
m_fileName[0] = '\0';
m_handle = AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE;
}
SystemFile::~SystemFile()
@@ -81,6 +80,25 @@ SystemFile::~SystemFile()
}
}
SystemFile::SystemFile(SystemFile&& other)
: SystemFile{}
{
AZStd::swap(m_fileName, other.m_fileName);
AZStd::swap(m_handle, other.m_handle);
}
SystemFile& SystemFile::operator=(SystemFile&& other)
{
// Close the current file and take over the SystemFile handle and filename
Close();
m_fileName = AZStd::move(other.m_fileName);
m_handle = AZStd::move(other.m_handle);
other.m_fileName = {};
other.m_handle = AZ_TRAIT_SYSTEMFILE_INVALID_HANDLE;
return *this;
}
bool SystemFile::Open(const char* fileName, int mode, int platformFlags)
{
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Open - %s", fileName);
@@ -88,42 +106,42 @@ bool SystemFile::Open(const char* fileName, int mode, int platformFlags)
if (fileName) // If we reopen the file we are allowed to have NULL file name
{
if (strlen(fileName) > AZ_ARRAY_SIZE(m_fileName) - 1)
if (strlen(fileName) > m_fileName.max_size())
{
EBUS_EVENT(FileIOEventBus, OnError, this, nullptr, 0);
return false;
}
// store the filename
azsnprintf(m_fileName, AZ_ARRAY_SIZE(m_fileName), "%s", fileName);
m_fileName = fileName;
}
if (FileIOBus::HasHandlers())
{
bool isOpen = false;
bool isHandled = false;
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnOpen, *this, m_fileName, mode, platformFlags, isOpen);
EBUS_EVENT_RESULT(isHandled, FileIOBus, OnOpen, *this, m_fileName.c_str(), mode, platformFlags, isOpen);
if (isHandled)
{
return isOpen;
}
}
AZ_Assert(!IsOpen(), "This file (%s) is already open!", m_fileName);
AZ_Assert(!IsOpen(), "This file (%s) is already open!", m_fileName.c_str());
return PlatformOpen(mode, platformFlags);
}
bool SystemFile::ReOpen(int mode, int platformFlags)
{
AZ_Assert(strlen(m_fileName) > 0, "Missing filename. You must call open first!");
AZ_Assert(!m_fileName.empty(), "Missing filename. You must call open first!");
return Open(0, mode, platformFlags);
}
void SystemFile::Close()
{
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Close - %s", m_fileName);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Close - %s", m_fileName);
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Close - %s", m_fileName.c_str());
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Close - %s", m_fileName.c_str());
if (FileIOBus::HasHandlers())
{
@@ -138,9 +156,9 @@ void SystemFile::Close()
PlatformClose();
}
void SystemFile::Seek(SizeType offset, SeekMode mode)
void SystemFile::Seek(SeekSizeType offset, SeekMode mode)
{
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Seek - %s:%i", m_fileName, offset);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Seek - %s:%i", m_fileName.c_str(), offset);
if (FileIOBus::HasHandlers())
{
@@ -167,15 +185,15 @@ bool SystemFile::Eof()
AZ::u64 SystemFile::ModificationTime()
{
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::ModTime - %s", m_fileName);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::ModTime - %s", m_fileName.c_str());
return Platform::ModificationTime(m_handle, this);
}
SystemFile::SizeType SystemFile::Read(SizeType byteSize, void* buffer)
{
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Read - %s:%i", m_fileName, byteSize);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Read - %s:%i", m_fileName, byteSize);
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Read - %s:%i", m_fileName.c_str(), byteSize);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Read - %s:%i", m_fileName.c_str(), byteSize);
if (FileIOBus::HasHandlers())
{
@@ -193,8 +211,8 @@ SystemFile::SizeType SystemFile::Read(SizeType byteSize, void* buffer)
SystemFile::SizeType SystemFile::Write(const void* buffer, SizeType byteSize)
{
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Write - %s:%i", m_fileName, byteSize);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Write - %s:%i", m_fileName, byteSize);
AZ_PROFILE_INTERVAL_SCOPED(AZ::Debug::ProfileCategory::AzCore, this, "SystemFile::Write - %s:%i", m_fileName.c_str(), byteSize);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Write - %s:%i", m_fileName.c_str(), byteSize);
if (FileIOBus::HasHandlers())
{
@@ -212,14 +230,14 @@ SystemFile::SizeType SystemFile::Write(const void* buffer, SizeType byteSize)
void SystemFile::Flush()
{
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Flush - %s", m_fileName);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Flush - %s", m_fileName.c_str());
Platform::Flush(m_handle, this);
}
SystemFile::SizeType SystemFile::Length() const
{
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Length - %s", m_fileName);
AZ_PROFILE_SCOPE_STALL_DYNAMIC(AZ::Debug::ProfileCategory::AzCore, "SystemFile::Length - %s", m_fileName.c_str());
return Platform::Length(m_handle, this);
}
@@ -379,9 +397,9 @@ namespace
HasPosixEnumOption(PermissionModeFlags::Write);
#undef HasPosixEnumOption
}
}
FileDescriptorRedirector::FileDescriptorRedirector(int sourceFileDescriptor)
: m_sourceFileDescriptor(sourceFileDescriptor)
{
+13 -8
View File
@@ -12,10 +12,11 @@
#pragma once
#include <AzCore/base.h>
#include <AzCore/std/function/function_fwd.h>
#include <AzCore/std/string/string.h>
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/IO/SystemFile_Platform.h>
#include <AzCore/std/function/function_fwd.h>
#include <AzCore/std/string/fixed_string.h>
// Establish a consistent size that works across platforms. It's actually larger than this
// on platforms we support, but this is a good least common denominator
@@ -51,11 +52,15 @@ namespace AZ
};
using SizeType = AZ::IO::Internal::SizeType;
using SeekSizeType = AZ::IO::Internal::SeekSizeType;
using FileHandleType = AZ::IO::Internal::FileHandleType;
SystemFile();
~SystemFile();
SystemFile(SystemFile&&);
SystemFile& operator=(SystemFile&&);
/**
* Opens a file.
* \param fileName full file name including path
@@ -69,7 +74,7 @@ namespace AZ
/// Closes a file, if file already close it has no effect.
void Close();
/// Seek in current file.
void Seek(SizeType offset, SeekMode mode);
void Seek(SeekSizeType offset, SeekMode mode);
/// Get the cursor position in the current file.
SizeType Tell();
/// Is the cursor at the end of the file?
@@ -87,7 +92,7 @@ namespace AZ
/// Return disc offset if possible, otherwise 0
SizeType DiskOffset() const;
/// Return file name or NULL if file is not open.
AZ_FORCE_INLINE const char* Name() const { return m_fileName; }
AZ_FORCE_INLINE const char* Name() const { return m_fileName.c_str(); }
bool IsOpen() const;
/// Return native handle to the file.
@@ -124,12 +129,12 @@ namespace AZ
private:
static void CreatePath(const char * fileName);
bool PlatformOpen(int mode, int platformFlags);
void PlatformClose();
FileHandleType m_handle;
char m_fileName[AZ_MAX_PATH_LEN];
FileHandleType m_handle;
AZ::IO::FixedMaxPathString m_fileName;
};
/**
@@ -127,13 +127,14 @@ namespace AZ
*/
class EditContext
{
public:
/// @cond EXCLUDE_DOCS
class ClassBuilder;
class EnumBuilder;
using ClassInfo = ClassBuilder; ///< @deprecated Use EditContext::ClassBuilder
using EnumInfo = EnumBuilder; ///< @deprecated Use EditContext::EnumBuilder
/// @endcond
public:
AZ_CLASS_ALLOCATOR(EditContext, SystemAllocator, 0);
/**
@@ -186,6 +187,7 @@ namespace AZ
* look at the unit tests and example to see use cases.
*
*/
public:
class ClassBuilder
{
friend EditContext;
@@ -399,6 +401,7 @@ namespace AZ
EnumBuilder* Value(const char* name, E value);
};
private:
typedef AZStd::list<Edit::ClassData> ClassDataListType;
typedef AZStd::unordered_map<AZ::Uuid, Edit::ElementData> EnumDataMapType;
@@ -101,6 +101,9 @@ namespace AZ
class SerializeContext
: public ReflectContext
{
static const unsigned int VersionClassDeprecated = (unsigned int)-1;
public:
/// @cond EXCLUDE_DOCS
friend class EditContext;
class ClassBuilder;
@@ -108,9 +111,6 @@ namespace AZ
/// @endcond
class EnumBuilder;
static const unsigned int VersionClassDeprecated = (unsigned int)-1;
public:
class ClassData;
struct EnumerateInstanceCallContext;
struct ClassElement;
@@ -1131,6 +1131,7 @@ namespace AZ
* ->Version(3,&MyVersionConverter)
* ->Field("data",&MyStruct::m_data);
*/
public:
class ClassBuilder
{
friend class SerializeContext;
@@ -1330,7 +1331,8 @@ namespace AZ
AZStd::vector<AttributeSharedPair, AZStdFunctorAllocator>* m_currentAttributes = nullptr;
};
EditContext* m_editContext; ///< Pointer to optional edit context.
private:
EditContext* m_editContext; ///< Pointer to optional edit context.
UuidToClassMap m_uuidMap; ///< Map for all class in this serialize context
AZStd::unordered_multimap<AZ::Crc32, AZ::Uuid> m_classNameToUuid; /// Map all class names to their uuid
AZStd::unordered_multimap<Uuid, GenericClassInfo*> m_uuidGenericMap; ///< Uuid to ClassData map of reflected classes with GenericTypeInfo
@@ -641,6 +641,8 @@ namespace AZ::SettingsRegistryMergeUtils
}
else
{
// Set the default ProjectUserPath to the <engine-root>/user directory
registry.Set(FilePathKey_ProjectUserPath, (engineRoot / "user").LexicallyNormal().Native());
AZ_TracePrintf("SettingsRegistryMergeUtils",
R"(Project path isn't set in the Settings Registry at "%.*s". Project-related filepaths will not be set)" "\n",
aznumeric_cast<int>(projectPathKey.size()), projectPathKey.data());
@@ -971,6 +971,10 @@ namespace AZ
*/
void RestoreCachedInstances();
/// Returns data flags for use when instantiating an instance of this slice.
/// These data flags include those harvested from the entire slice ancestry.
const DataFlagsPerEntity& GetDataFlagsForInstances() const;
protected:
//////////////////////////////////////////////////////////////////////////
@@ -1004,9 +1008,6 @@ namespace AZ
DataFlagsPerEntity* GetCorrectBundleOfDataFlags(EntityId entityId);
const DataFlagsPerEntity* GetCorrectBundleOfDataFlags(EntityId entityId) const;
/// Returns data flags for use when instantiating an instance of this slice.
/// These data flags include those harvested from the entire slice ancestry.
const DataFlagsPerEntity& GetDataFlagsForInstances() const;
void BuildDataFlagsForInstances();
/**
@@ -101,7 +101,7 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags)
createPath = (mode & SF_OPEN_CREATE_PATH) == SF_OPEN_CREATE_PATH;
}
bool isApkFile = AZ::Android::Utils::IsApkPath(m_fileName);
bool isApkFile = AZ::Android::Utils::IsApkPath(m_fileName.c_str());
if (createPath)
{
@@ -111,19 +111,19 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags)
return false;
}
CreatePath(m_fileName);
CreatePath(m_fileName.c_str());
}
int errorCode = 0;
if (isApkFile)
{
AZ::u64 size = 0;
m_handle = AZ::Android::APKFileHandler::Open(m_fileName, openMode, size);
m_handle = AZ::Android::APKFileHandler::Open(m_fileName.c_str(), openMode, size);
errorCode = EACCES; // general error when a file can't be opened from inside the APK
}
else
{
m_handle = fopen(m_fileName, openMode);
m_handle = fopen(m_fileName.c_str(), openMode);
errorCode = errno;
}
@@ -233,7 +233,7 @@ namespace Platform
}
}
void Seek(FileHandleType handle, const SystemFile* systemFile, SizeType offset, SystemFile::SeekMode mode)
void Seek(FileHandleType handle, const SystemFile* systemFile, SystemFile::SeekSizeType offset, SystemFile::SeekMode mode)
{
if (handle != PlatformSpecificInvalidHandle)
{
@@ -15,6 +15,9 @@
#include <cstdio>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <AzCore/std/typetraits/underlying_type.h>
namespace AZ
{
@@ -23,6 +26,7 @@ namespace AZ
namespace Internal
{
using SizeType = AZ::u64;
using SeekSizeType = AZ::s64;
using FileHandleType = FILE*;
}
@@ -37,7 +41,7 @@ namespace AZ
#else
Temporary = 0, // (Not applicable for this platform) Applies only when used with CREAT. Creates a file as temporary; the file is deleted when the last file descriptor is closed. PermissionMode equired when CREAT is specified.
#endif
Exclusive = O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Exclusive = O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Truncate = O_TRUNC, // Opens a file and truncates it to zero length; the file must have write permission. Cannot be specified with RDONLY.
// Note: The TRUNC flag destroys the contents of the specified file.
@@ -22,9 +22,10 @@ namespace AZ
namespace Internal
{
using SizeType = AZ::u64;
using SeekSizeType = AZ::s64;
using FileHandleType = int;
}
namespace PosixInternal
{
enum class OpenFlags : int
@@ -36,7 +37,7 @@ namespace AZ
#else
Temporary = 0, // (Not applicable for this platform) Applies only when used with CREAT. Creates a file as temporary; the file is deleted when the last file descriptor is closed. PermissionMode equired when CREAT is specified.
#endif
Exclusive = O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Exclusive = O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Truncate = O_TRUNC, // Opens a file and truncates it to zero length; the file must have write permission. Cannot be specified with RDONLY.
// Note: The TRUNC flag destroys the contents of the specified file.
@@ -13,6 +13,9 @@
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <AzCore/std/typetraits/underlying_type.h>
namespace AZ
{
@@ -21,6 +24,7 @@ namespace AZ
namespace Internal
{
using SizeType = AZ::u64;
using SeekSizeType = AZ::s64;
using FileHandleType = int;
}
@@ -35,7 +39,7 @@ namespace AZ
#else
Temporary = 0, // (Not applicable for this platform) Applies only when used with CREAT. Creates a file as temporary; the file is deleted when the last file descriptor is closed. PermissionMode equired when CREAT is specified.
#endif
Exclusive = O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Exclusive = O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Truncate = O_TRUNC, // Opens a file and truncates it to zero length; the file must have write permission. Cannot be specified with RDONLY.
// Note: The TRUNC flag destroys the contents of the specified file.
@@ -13,9 +13,10 @@
#include <AzCore/Module/DynamicModuleHandle.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/IO/SystemFile.h>
#include <AzCore/Memory/OSAllocator.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <dlfcn.h>
#include <libgen.h>
@@ -61,10 +62,11 @@ namespace AZ
// If it doesn't attempt to append the path to the executable path
if (!AZ::IO::SystemFile::Exists(fullFilePath.c_str()))
{
auto candidatePath = Platform::GetModulePath() / fullFilePath;
AZ::IO::FixedMaxPath candidatePath = Platform::GetModulePath() / fullFilePath;
if (AZ::IO::SystemFile::Exists(candidatePath.c_str()))
{
fullFilePath = candidatePath;
m_fileName.assign(candidatePath.Native().c_str(), candidatePath.Native().size());
return;
}
}
@@ -74,19 +76,26 @@ namespace AZ
{
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= fullFilePath;
if (AZ::IO::SystemFile::Exists(projectModulePath.c_str()))
{
fullFilePath = projectModulePath;
m_fileName.assign(projectModulePath.c_str(), projectModulePath.Native().size());
}
}
}
}
m_fileName = AZStd::string_view{fullFilePath.Native()};
else
{
// The module does exist (in 'cwd'), but still needs to be an absolute path for the module to be loaded.
AZStd::optional<AZ::IO::FixedMaxPathString> absPathOptional = AZ::Utils::ConvertToAbsolutePath(m_fileName);
if (absPathOptional.has_value())
{
m_fileName.assign(absPathOptional->c_str(), absPathOptional->size());
}
}
}
~DynamicModuleHandleUnixLike() override
@@ -86,9 +86,9 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags)
if (createPath)
{
CreatePath(m_fileName);
CreatePath(m_fileName.c_str());
}
m_handle = open(m_fileName, desiredAccess, permissions);
m_handle = open(m_fileName.c_str(), desiredAccess, permissions);
if (m_handle == PlatformSpecificInvalidHandle)
{
@@ -119,7 +119,7 @@ namespace Platform
{
using FileHandleType = AZ::IO::SystemFile::FileHandleType;
void Seek(FileHandleType handle, const SystemFile* systemFile, SizeType offset, SystemFile::SeekMode mode)
void Seek(FileHandleType handle, const SystemFile* systemFile, SystemFile::SeekSizeType offset, SystemFile::SeekMode mode)
{
if (handle != PlatformSpecificInvalidHandle)
{
@@ -209,19 +209,19 @@ bool SystemFile::PlatformOpen(int mode, int platformFlags)
if (createPath)
{
CreatePath(m_fileName);
CreatePath(m_fileName.c_str());
}
# ifdef _UNICODE
wchar_t fileNameW[AZ_MAX_PATH_LEN];
size_t numCharsConverted;
m_handle = INVALID_HANDLE_VALUE;
if (mbstowcs_s(&numCharsConverted, fileNameW, m_fileName, AZ_ARRAY_SIZE(fileNameW) - 1) == 0)
if (mbstowcs_s(&numCharsConverted, fileNameW, m_fileName.c_str(), AZ_ARRAY_SIZE(fileNameW) - 1) == 0)
{
m_handle = CreateFileW(fileNameW, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
}
# else //!_UNICODE
m_handle = CreateFile(m_fileName, dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
m_handle = CreateFile(m_fileName.c_str(), dwDesiredAccess, dwShareMode, 0, dwCreationDisposition, dwFlagsAndAttributes, 0);
# endif // !_UNICODE
if (m_handle == INVALID_HANDLE_VALUE)
@@ -261,7 +261,7 @@ namespace Platform
{
using FileHandleType = AZ::IO::SystemFile::FileHandleType;
void Seek(FileHandleType handle, const SystemFile* systemFile, SizeType offset, SystemFile::SeekMode mode)
void Seek(FileHandleType handle, const SystemFile* systemFile, SystemFile::SeekSizeType offset, SystemFile::SeekMode mode)
{
if (handle != PlatformSpecificInvalidHandle)
{
@@ -13,6 +13,9 @@
#include <fcntl.h>
#include <corecrt_io.h>
#include <sys/stat.h>
#include <AzCore/std/typetraits/underlying_type.h>
namespace AZ
{
@@ -21,6 +24,7 @@ namespace AZ
namespace Internal
{
using SizeType = AZ::u64;
using SeekSizeType = AZ::s64;
using FileHandleType = void*;
}
@@ -31,7 +35,7 @@ namespace AZ
Append = _O_APPEND, // Moves the file pointer to the end of the file before every write operation.
Create = _O_CREAT, // Creates a file and opens it for writing. Has no effect if the file specified by filename exists. PermissionMode is required.
Temporary = _O_TEMPORARY, // Applies only when used with CREAT. Creates a file as temporary; the file is deleted when the last file descriptor is closed. PermissionMode equired when CREAT is specified.
Exclusive = _O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Exclusive = _O_EXCL, // Applies only when used with CREAT. Returns an error value if a file specified by filename exists.
Truncate = _O_TRUNC, // Opens a file and truncates it to zero length; the file must have write permission. Cannot be specified with RDONLY.
// Note: The TRUNC flag destroys the contents of the specified file.
@@ -24,9 +24,9 @@ namespace AZ
: public DynamicModuleHandle
{
public:
AZ_CLASS_ALLOCATOR(DynamicModuleHandleWindows, OSAllocator, 0)
AZ_CLASS_ALLOCATOR(DynamicModuleHandleWindows, OSAllocator, 0);
DynamicModuleHandleWindows(const char* fullFileName)
DynamicModuleHandleWindows(const char* fullFileName)
: DynamicModuleHandle(fullFileName)
, m_handle(nullptr)
{
@@ -52,6 +52,7 @@ namespace AZ
if (AZ::IO::SystemFile::Exists(candidatePath.c_str()))
{
m_fileName.assign(candidatePath.Native().c_str(), candidatePath.Native().size());
return;
}
}
}
@@ -65,7 +66,7 @@ namespace AZ
// Therefore an existence check is needed
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
if(AZ::IO::FixedMaxPath projectModulePath;
if (AZ::IO::FixedMaxPath projectModulePath;
settingsRegistry->Get(projectModulePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectConfigurationBinPath))
{
projectModulePath /= AZStd::string_view(m_fileName);
@@ -76,6 +77,15 @@ namespace AZ
}
}
}
else
{
// The module does exist (in 'cwd'), but still needs to be an absolute path for the module to be loaded.
AZStd::optional<AZ::IO::FixedMaxPathString> absPathOptional = AZ::Utils::ConvertToAbsolutePath(m_fileName);
if (absPathOptional.has_value())
{
m_fileName.assign(absPathOptional->c_str(), absPathOptional->size());
}
}
}
~DynamicModuleHandleWindows() override
+8 -8
View File
@@ -1914,7 +1914,7 @@ namespace UnitTest
TEST_F(String, StringView_CompareIsConstexpr)
{
using TypeParam = char;
auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
return "HelloWorld";
};
@@ -1922,7 +1922,7 @@ namespace UnitTest
{
return "HelloPearl";
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1();
constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2();
constexpr basic_string_view<TypeParam> lhsView(compileTimeString1);
constexpr basic_string_view<TypeParam> rhsView(compileTimeString2);
@@ -1937,11 +1937,11 @@ namespace UnitTest
TEST_F(String, StringView_CompareOperatorsAreConstexpr)
{
using TypeParam = char;
auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
auto TestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
return "HelloWorld";
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = TestMakeCompileTimeString1();
constexpr basic_string_view<TypeParam> compareView(compileTimeString1);
static_assert(compareView == "HelloWorld", "string_view operator== comparison has failed");
static_assert(compareView != "MadWorld", "string_view operator!= comparison has failed");
@@ -1955,7 +1955,7 @@ namespace UnitTest
{
auto swap_test_func = []() constexpr -> basic_string_view<TypeParam>
{
constexpr auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
constexpr auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
if constexpr (AZStd::is_same_v<TypeParam, char>)
{
@@ -1977,7 +1977,7 @@ namespace UnitTest
return L"InuWorld";
}
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1();
constexpr const TypeParam* compileTimeString2 = MakeCompileTimeString2();
basic_string_view<TypeParam> lhsView(compileTimeString1);
basic_string_view<TypeParam> rhsView(compileTimeString2);
@@ -2001,7 +2001,7 @@ namespace UnitTest
TYPED_TEST(BasicStringViewConstexprFixture, HashString_FunctionIsConstexpr)
{
auto MakeCompileTimeString1 = []() constexpr -> const TypeParam*
auto ThisTestMakeCompileTimeString1 = []() constexpr -> const TypeParam*
{
if constexpr (AZStd::is_same_v<TypeParam, char>)
{
@@ -2012,7 +2012,7 @@ namespace UnitTest
return L"HelloWorld";
}
};
constexpr const TypeParam* compileTimeString1 = MakeCompileTimeString1();
constexpr const TypeParam* compileTimeString1 = ThisTestMakeCompileTimeString1();
constexpr basic_string_view<TypeParam> hashView(compileTimeString1);
constexpr size_t compileHash = AZStd::hash<basic_string_view<TypeParam>>{}(hashView);
static_assert(compileHash != 0, "Hash of \"HelloWorld\" should not be 0");
@@ -59,7 +59,7 @@ namespace UnitTest
TEST(MATH_Matrix4x4, TestCreateFrom)
{
float testFloats[] =
float thisTestFloats[] =
{
1.0f, 2.0f, 3.0f, 4.0f,
5.0f, 6.0f, 7.0f, 8.0f,
@@ -67,20 +67,20 @@ namespace UnitTest
13.0f, 14.0f, 15.0f, 16.0f
};
float testFloatMtx[16];
Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(testFloats);
Matrix4x4 m1 = Matrix4x4::CreateFromRowMajorFloat16(thisTestFloats);
AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 2.0f, 3.0f, 4.0f));
AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(5.0f, 6.0f, 7.0f, 8.0f));
AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(9.0f, 10.0f, 11.0f, 12.0f));
AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(13.0f, 14.0f, 15.0f, 16.0f));
m1.StoreToRowMajorFloat16(testFloatMtx);
AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0);
m1 = Matrix4x4::CreateFromColumnMajorFloat16(testFloats);
AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0);
m1 = Matrix4x4::CreateFromColumnMajorFloat16(thisTestFloats);
AZ_TEST_ASSERT(m1.GetRow(0) == Vector4(1.0f, 5.0f, 9.0f, 13.0f));
AZ_TEST_ASSERT(m1.GetRow(1) == Vector4(2.0f, 6.0f, 10.0f, 14.0f));
AZ_TEST_ASSERT(m1.GetRow(2) == Vector4(3.0f, 7.0f, 11.0f, 15.0f));
AZ_TEST_ASSERT(m1.GetRow(3) == Vector4(4.0f, 8.0f, 12.0f, 16.0f));
m1.StoreToColumnMajorFloat16(testFloatMtx);
AZ_TEST_ASSERT(memcmp(testFloatMtx, testFloats, sizeof(testFloatMtx)) == 0);
AZ_TEST_ASSERT(memcmp(testFloatMtx, thisTestFloats, sizeof(testFloatMtx)) == 0);
}
TEST(MATH_Matrix4x4, TestCreateFromMatrix3x4)
+12 -12
View File
@@ -119,10 +119,10 @@ namespace UnitTest
TEST(MATH_Obb, Contains)
{
const Vector3 position(1.0f, 2.0f, 3.0f);
const Quaternion rotation = Quaternion::CreateRotationZ(DegToRad(30.0f));
const Vector3 halfLengths(2.0f, 1.0f, 2.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
const Vector3 testPosition(1.0f, 2.0f, 3.0f);
const Quaternion testRotation = Quaternion::CreateRotationZ(DegToRad(30.0f));
const Vector3 testHalfLengths(2.0f, 1.0f, 2.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths);
// test some pairs of points which should be just either side of the Obb boundary
EXPECT_TRUE(obb.Contains(Vector3(1.35f, 3.35f, 3.5f)));
EXPECT_FALSE(obb.Contains(Vector3(1.35f, 3.4f, 3.5f)));
@@ -134,10 +134,10 @@ namespace UnitTest
TEST(MATH_Obb, GetDistance)
{
const Vector3 position(5.0f, 3.0f, 2.0f);
const Quaternion rotation = Quaternion::CreateRotationX(DegToRad(60.0f));
const Vector3 halfLengths(0.5f, 2.0f, 1.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
const Vector3 testPosition(5.0f, 3.0f, 2.0f);
const Quaternion testRotation = Quaternion::CreateRotationX(DegToRad(60.0f));
const Vector3 testHalfLengths(0.5f, 2.0f, 1.5f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths);
EXPECT_NEAR(obb.GetDistance(Vector3(5.3f, 3.2f, 1.8f)), 0.0f, 1e-3f);
EXPECT_NEAR(obb.GetDistance(Vector3(5.1f, 1.1f, 3.7f)), 0.9955f, 1e-3f);
EXPECT_NEAR(obb.GetDistance(Vector3(4.7f, 4.5f, 4.2f)), 0.6553f, 1e-3f);
@@ -146,10 +146,10 @@ namespace UnitTest
TEST(MATH_Obb, GetDistanceSq)
{
const Vector3 position(1.0f, 4.0f, 3.0f);
const Quaternion rotation = Quaternion::CreateRotationY(DegToRad(45.0f));
const Vector3 halfLengths(1.5f, 3.0f, 1.0f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(position, rotation, halfLengths);
const Vector3 testPosition(1.0f, 4.0f, 3.0f);
const Quaternion testRotation = Quaternion::CreateRotationY(DegToRad(45.0f));
const Vector3 testHalfLengths(1.5f, 3.0f, 1.0f);
const Obb obb = Obb::CreateFromPositionRotationAndHalfLengths(testPosition, testRotation, testHalfLengths);
EXPECT_NEAR(obb.GetDistanceSq(Vector3(1.1f, 4.3f, 2.7f)), 0.0f, 1e-3f);
EXPECT_NEAR(obb.GetDistanceSq(Vector3(-0.7f, 3.5f, 2.0f)), 0.8266f, 1e-3f);
EXPECT_NEAR(obb.GetDistanceSq(Vector3(2.4f, 0.5f, 1.5f)), 0.5532f, 1e-3f);
@@ -711,8 +711,8 @@ namespace AzFramework
}
}
AZ::IO::FixedMaxPath projectUserPath;
if (m_settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
if (AZ::IO::FixedMaxPath projectUserPath;
m_settingsRegistry->Get(projectUserPath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_ProjectUserPath))
{
fileIoBase->SetAlias("@user@", projectUserPath.c_str());
AZ::IO::FixedMaxPath projectLogPath = projectUserPath / "log";
@@ -721,6 +721,15 @@ namespace AzFramework
CreateUserCache(projectUserPath, *fileIoBase);
}
else
{
AZ::IO::FixedMaxPath fallbackLogPath = GetEngineRoot();
fallbackLogPath /= "user";
fileIoBase->SetAlias("@user@", fallbackLogPath.c_str());
fallbackLogPath /= "log";
fileIoBase->SetAlias("@log@", fallbackLogPath.c_str());
fileIoBase->CreatePath(fallbackLogPath.c_str());
}
}
}
@@ -12,17 +12,22 @@
#pragma once
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include <AzToolsFramework/Manipulators/LinearManipulator.h>
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
#include <AzToolsFramework/Manipulators/PlanarManipulator.h>
namespace AzManipulatorTestFramework
{
//! Create a linear manipulator with a unit sphere bounds.
//! Create a linear manipulator with a unit sphere bound.
AZStd::shared_ptr<AzToolsFramework::LinearManipulator> CreateLinearManipulator(
const AzToolsFramework::ManipulatorManagerId manipulatorManagerId,
const AZ::Vector3& position = AZ::Vector3::CreateZero(),
const float radius = 1.0f);
const AzToolsFramework::ManipulatorManagerId manipulatorManagerId, const AZ::Vector3& position = AZ::Vector3::CreateZero(),
float radius = 1.0f);
//! Create a planar manipulator with a unit sphere bound.
AZStd::shared_ptr<AzToolsFramework::PlanarManipulator> CreatePlanarManipulator(
const AzToolsFramework::ManipulatorManagerId manipulatorManagerId, 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(
@@ -34,14 +39,12 @@ namespace AzManipulatorTestFramework
//! 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,
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);
AzToolsFramework::ViewportInteraction::MouseButtons CreateMouseButtons(AzToolsFramework::ViewportInteraction::MouseButton button);
//! Create a mouse interaction event from the specified interaction and event.
AzToolsFramework::ViewportInteraction::MouseInteractionEvent CreateMouseInteractionEvent(
@@ -61,5 +64,5 @@ namespace AzManipulatorTestFramework
AzFramework::ScreenPoint GetCameraStateViewportCenter(const AzFramework::CameraState& cameraState);
//! Default viewport size (1080p) in 16:9 aspect ratio.
const auto DefaultViewportSize = AZ::Vector2(1920.0f, 1080.0f);
inline const auto DefaultViewportSize = AZ::Vector2(1920.0f, 1080.0f);
} // namespace AzManipulatorTestFramework
@@ -14,7 +14,6 @@
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
#include <AzToolsFramework/ViewportSelection/EditorTransformComponentSelectionRequestBus.h>
namespace AzManipulatorTestFramework
@@ -28,22 +27,21 @@ namespace AzManipulatorTestFramework
using MouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent;
using MousePick = AzToolsFramework::ViewportInteraction::MousePick;
AZStd::shared_ptr<AzToolsFramework::LinearManipulator> CreateLinearManipulator(
const AzToolsFramework::ManipulatorManagerId manipulatorManagerId,
const AZ::Vector3& position,
const float radius)
// create a default sphere view for a manipulator for simple intersection
template<typename Manipulator>
void SetupManipulatorView(
AZStd::shared_ptr<Manipulator> manipulator, const AzToolsFramework::ManipulatorManagerId manipulatorManagerId,
const AZ::Vector3& position, const float radius)
{
auto manipulator = AzToolsFramework::LinearManipulator::MakeShared(AZ::Transform::CreateIdentity());
manipulator->SetLocalPosition(position);
// unit sphere view
auto sphereView = AzToolsFramework::CreateManipulatorViewSphere(
AZ::Colors::Red, radius,
[](const MouseInteraction& /*mouseInteraction*/, const bool /*mouseOver*/,
const AZ::Color& defaultColor)
{
return defaultColor;
}, true);
[]([[maybe_unused]] const MouseInteraction& mouseInteraction, [[maybe_unused]] const bool mouseOver,
const AZ::Color& defaultColor)
{
return defaultColor;
},
true);
// unit sphere bound
AzToolsFramework::Picking::BoundShapeSphere sphereBound;
@@ -62,6 +60,26 @@ namespace AzManipulatorTestFramework
// this would occur internally when the manipulator is drawn but we must do manually here to ensure that the
// bounds will always be valid upon instantiation
view->RefreshBound(manipulatorManagerId, manipulator->GetManipulatorId(), sphereBound);
}
AZStd::shared_ptr<AzToolsFramework::LinearManipulator> CreateLinearManipulator(
const AzToolsFramework::ManipulatorManagerId manipulatorManagerId, const AZ::Vector3& position, const float radius)
{
auto manipulator = AzToolsFramework::LinearManipulator::MakeShared(AZ::Transform::CreateIdentity());
manipulator->SetLocalPosition(position);
SetupManipulatorView(manipulator, manipulatorManagerId, position, radius);
return manipulator;
}
AZStd::shared_ptr<AzToolsFramework::PlanarManipulator> CreatePlanarManipulator(
const AzToolsFramework::ManipulatorManagerId manipulatorManagerId, const AZ::Vector3& position, const float radius)
{
auto manipulator = AzToolsFramework::PlanarManipulator::MakeShared(AZ::Transform::CreateIdentity());
manipulator->SetLocalPosition(position);
SetupManipulatorView(manipulator, manipulatorManagerId, position, radius);
return manipulator;
}
@@ -104,8 +122,7 @@ namespace AzManipulatorTestFramework
return buttons;
}
MouseInteractionEvent CreateMouseInteractionEvent(
const MouseInteraction& mouseInteraction, MouseEvent event)
MouseInteractionEvent CreateMouseInteractionEvent(const MouseInteraction& mouseInteraction, MouseEvent event)
{
return MouseInteractionEvent(mouseInteraction, event);
}
@@ -114,8 +131,7 @@ namespace AzManipulatorTestFramework
{
AzToolsFramework::EditorInteractionSystemViewportSelectionRequestBus::Event(
AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::ViewportInteraction::InternalMouseViewportRequests::InternalHandleAllMouseInteractions,
event);
&AzToolsFramework::ViewportInteraction::InternalMouseViewportRequests::InternalHandleAllMouseInteractions, event);
}
AzFramework::CameraState SetCameraStatePosition(const AZ::Vector3& position, AzFramework::CameraState& cameraState)
@@ -133,9 +149,7 @@ namespace AzManipulatorTestFramework
AzFramework::ScreenPoint GetCameraStateViewportCenter(const AzFramework::CameraState& cameraState)
{
return {
aznumeric_cast<int>(cameraState.m_viewportSize.GetX() / 2.f),
aznumeric_cast<int>(cameraState.m_viewportSize.GetY() / 2.f)
};
return { aznumeric_cast<int>(cameraState.m_viewportSize.GetX() / 2.f),
aznumeric_cast<int>(cameraState.m_viewportSize.GetY() / 2.f) };
}
} // namespace UnitTest
} // namespace AzManipulatorTestFramework
@@ -10,52 +10,55 @@
*
*/
#include "AzManipulatorTestFrameworkTestFixtures.h"
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzFramework/Viewport/ViewportScreen.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
#include "AzManipulatorTestFrameworkTestFixtures.h"
#include <AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h>
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
#include <AzManipulatorTestFramework/DirectManipulatorViewportInteraction.h>
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
#include <AzToolsFramework/Manipulators/LinearManipulator.h>
#include <AzToolsFramework/Manipulators/PlanarManipulator.h>
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
#include <AZTestShared/Math/MathTestHelpers.h>
namespace UnitTest
{
class GridSnappingFixture
: public ToolsApplicationFixture
class GridSnappingFixture : public ToolsApplicationFixture
{
public:
GridSnappingFixture()
: m_viewportManipulatorInteraction(AZStd::make_unique<AzManipulatorTestFramework::DirectCallManipulatorViewportInteraction>())
, m_actionDispatcher(AZStd::make_unique<AzManipulatorTestFramework::ImmediateModeActionDispatcher>(*m_viewportManipulatorInteraction))
, m_linearManipulator(
AzManipulatorTestFramework::CreateLinearManipulator(
m_viewportManipulatorInteraction->GetManipulatorManager().GetId(),
/*position=*/AZ::Vector3(0.0f, 50.0f, 0.0f),
/*radius=*/m_boundsRadius))
{}
, m_actionDispatcher(
AZStd::make_unique<AzManipulatorTestFramework::ImmediateModeActionDispatcher>(*m_viewportManipulatorInteraction))
{
}
protected:
void SetUpEditorFixtureImpl() override
{
m_cameraState = AzFramework::CreateIdentityDefaultCamera(
AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize);
m_cameraState =
AzFramework::CreateIdentityDefaultCamera(AZ::Vector3::CreateZero(), AzManipulatorTestFramework::DefaultViewportSize);
}
public:
const float m_boundsRadius = 1.0f;
AZStd::unique_ptr<AzManipulatorTestFramework::ManipulatorViewportInteraction> m_viewportManipulatorInteraction;
AZStd::unique_ptr<AzManipulatorTestFramework::ImmediateModeActionDispatcher> m_actionDispatcher;
AZStd::shared_ptr<AzToolsFramework::LinearManipulator> m_linearManipulator;
AzFramework::CameraState m_cameraState;
};
TEST_F(GridSnappingFixture, MouseDownWithSnappingEnabledSnapsToClosestGridSize)
{
AZStd::shared_ptr<AzToolsFramework::LinearManipulator> linearManipulator(AzManipulatorTestFramework::CreateLinearManipulator(
m_viewportManipulatorInteraction->GetManipulatorManager().GetId(),
/*position=*/AZ::Vector3(0.0f, 50.0f, 0.0f),
/*radius=*/m_boundsRadius));
// the initial starting position of the manipulator (in front of the camera)
const auto initialPositionWorld = m_linearManipulator->GetLocalPosition();
const auto initialPositionWorld = linearManipulator->GetLocalPosition();
// where the manipulator should end up (in front and to the left of the camera)
const auto finalPositionWorld = AZ::Vector3(-10.0f, 50.0f, 0.0f);
// perspective scale factor for manipulator distance to camera
@@ -66,21 +69,18 @@ namespace UnitTest
// adjusted final world position taking into account the manipulator position relative to the camera
const auto finalPositionWorldAdjusted = finalPositionWorld - (vectorToInitialPositionWorld * scaledRadiusBound);
// calculate the position in screen space of the initial position of the manipulator
const auto initialPositionScreen =
AzFramework::WorldToScreen(initialPositionWorld, m_cameraState);
const auto initialPositionScreen = AzFramework::WorldToScreen(initialPositionWorld, m_cameraState);
// calculate the position in screen space of the final position of the manipulator
const auto finalPositionScreen = AzFramework::WorldToScreen(finalPositionWorldAdjusted, m_cameraState);
// callback to update the manipulator's current position
m_linearManipulator->InstallMouseMoveCallback(
[this](const AzToolsFramework::LinearManipulator::Action& action)
{
auto pos = action.LocalPosition();
m_linearManipulator->SetLocalPosition(pos);
});
linearManipulator->InstallMouseMoveCallback(
[this, linearManipulator](const AzToolsFramework::LinearManipulator::Action& action)
{
linearManipulator->SetLocalPosition(action.LocalPosition());
});
m_actionDispatcher
->EnableSnapToGrid()
m_actionDispatcher->EnableSnapToGrid()
->GridSize(5.0f)
->CameraState(m_cameraState)
->MousePosition(initialPositionScreen)
@@ -89,7 +89,67 @@ namespace UnitTest
->MousePosition(finalPositionScreen)
->MouseLButtonUp()
->ExpectManipulatorNotBeingInteracted()
->ExpectTrue(m_linearManipulator->GetLocalPosition().IsClose(finalPositionWorld, 0.01f))
;
->ExpectTrue(linearManipulator->GetLocalPosition().IsClose(finalPositionWorld, 0.01f));
}
template<typename Manipulator>
void ValidateManipulatorSnappingBehavior(
AZStd::shared_ptr<Manipulator> manipulator, AzManipulatorTestFramework::ImmediateModeActionDispatcher* actionDispatcher,
const AzFramework::CameraState& cameraState)
{
manipulator->SetLocalOrientation(AZ::Quaternion::CreateFromEulerAnglesDegrees(AZ::Vector3(180.0f, 0.0f, 135.0f)));
// the initial starting position of the manipulator (in front of the camera)
const auto initialPositionWorld = manipulator->GetLocalPosition() + AZ::Vector3::CreateAxisX(0.15f);
// where the manipulator should end up (unmoved)
const auto finalPositionWorld = manipulator->GetLocalPosition();
// where we should move the mouse to
const auto attemptPositionWorld = manipulator->GetLocalPosition() + AZ::Vector3::CreateAxisX(0.35f);
// calculate the position in screen space of the initial position of the manipulator
const auto initialPositionScreen = AzFramework::WorldToScreen(initialPositionWorld, cameraState);
// calculate the position in screen space of the final position of the manipulator
const auto attemptPositionScreen = AzFramework::WorldToScreen(attemptPositionWorld, cameraState);
// callback to update the manipulator's current position
manipulator->InstallMouseMoveCallback(
[manipulator](const typename Manipulator::Action& action)
{
manipulator->SetLocalPosition(action.LocalPosition());
});
actionDispatcher->EnableSnapToGrid()
->GridSize(1.0f)
->CameraState(cameraState)
->MousePosition(initialPositionScreen)
->MouseLButtonDown()
->ExpectManipulatorBeingInteracted()
->MousePosition(attemptPositionScreen)
->MouseLButtonUp()
->ExpectManipulatorNotBeingInteracted()
->ExpectThat(manipulator->GetLocalPosition(), IsCloseTolerance(finalPositionWorld, 0.01f));
}
TEST_F(GridSnappingFixture, MouseDownAndMoveLinearManipulatorDoesNotSnapWithMovementSmallerThanHalfGridSize)
{
AZStd::shared_ptr<AzToolsFramework::LinearManipulator> linearManipulator(AzManipulatorTestFramework::CreateLinearManipulator(
m_viewportManipulatorInteraction->GetManipulatorManager().GetId(),
/*position=*/AZ::Vector3(0.0f, 10.0f, 0.0f),
/*radius=*/m_boundsRadius));
linearManipulator->SetAxis(AZ::Vector3::CreateAxisY());
ValidateManipulatorSnappingBehavior(linearManipulator, m_actionDispatcher.get(), m_cameraState);
}
TEST_F(GridSnappingFixture, MouseDownAndMovePlanarManipulatorDoesNotSnapWithMovementSmallerThanHalfGridSize)
{
AZStd::shared_ptr<AzToolsFramework::PlanarManipulator> planarManipulator(AzManipulatorTestFramework::CreatePlanarManipulator(
m_viewportManipulatorInteraction->GetManipulatorManager().GetId(),
/*position=*/AZ::Vector3(0.0f, 10.0f, 0.0f),
/*radius=*/m_boundsRadius));
planarManipulator->SetAxes(AZ::Vector3::CreateAxisY(), AZ::Vector3::CreateAxisZ());
ValidateManipulatorSnappingBehavior(planarManipulator, m_actionDispatcher.get(), m_cameraState);
}
} // namespace UnitTest
@@ -433,6 +433,11 @@ namespace AzToolsFramework
return m_manipulatorSpaceWithLocalTransform.GetSpace();
}
const AZ::Vector3& Manipulators::GetNonUniformScale() const
{
return m_manipulatorSpaceWithLocalTransform.GetNonUniformScale();
}
void Manipulators::SetSpace(const AZ::Transform& worldFromLocal)
{
m_manipulatorSpaceWithLocalTransform.SetSpace(worldFromLocal);
@@ -192,8 +192,7 @@ namespace AzToolsFramework
/// for each vertex associated with the translation manipulator to use with offset calculations when updating.
template<typename Vertex>
void InitializeVertexLookup(
IndexedTranslationManipulator<Vertex>& translationManipulator,
const AZ::EntityId entityId, const AZ::Vector3& snapOffset)
IndexedTranslationManipulator<Vertex>& translationManipulator, const AZ::EntityId entityId)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
@@ -202,7 +201,7 @@ namespace AzToolsFramework
AZ::FixedVerticesRequestBus<Vertex>::Bind(fixedVertices, entityId);
translationManipulator.Process(
[snapOffset, fixedVertices]
[fixedVertices]
(typename IndexedTranslationManipulator<Vertex>::VertexLookup& vertexLookup)
{
Vertex vertex;
@@ -213,7 +212,7 @@ namespace AzToolsFramework
if (found)
{
vertexLookup.m_start = vertex + AZ::AdaptVertexIn<Vertex>(snapOffset);
vertexLookup.m_start = vertex;
vertexLookup.m_offset = Vertex::CreateZero();
}
});
@@ -250,10 +249,10 @@ namespace AzToolsFramework
// linear manipulator callbacks
m_translationManipulator->m_manipulator.InstallLinearManipulatorMouseDownCallback(
[this](const LinearManipulator::Action& action)
[this]([[maybe_unused]] const LinearManipulator::Action& action)
{
BeginBatchMovement();
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), action.m_start.m_positionSnapOffset);
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
});
m_translationManipulator->m_manipulator.InstallLinearManipulatorMouseMoveCallback(
@@ -264,17 +263,17 @@ namespace AzToolsFramework
});
m_translationManipulator->m_manipulator.InstallLinearManipulatorMouseUpCallback(
[this](const LinearManipulator::Action& /*action*/)
[this]([[maybe_unused]] const LinearManipulator::Action& action)
{
EndBatchMovement();
});
// planar manipulator callbacks
m_translationManipulator->m_manipulator.InstallPlanarManipulatorMouseDownCallback(
[this](const PlanarManipulator::Action& action)
[this]([[maybe_unused]] const PlanarManipulator::Action& action)
{
BeginBatchMovement();
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), action.m_start.m_snapOffset);
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
});
m_translationManipulator->m_manipulator.InstallPlanarManipulatorMouseMoveCallback(
@@ -285,17 +284,17 @@ namespace AzToolsFramework
});
m_translationManipulator->m_manipulator.InstallPlanarManipulatorMouseUpCallback(
[this](const PlanarManipulator::Action& /*action*/)
[this]([[maybe_unused]] const PlanarManipulator::Action& action)
{
EndBatchMovement();
});
// surface manipulator callbacks
m_translationManipulator->m_manipulator.InstallSurfaceManipulatorMouseDownCallback(
[this](const SurfaceManipulator::Action& action)
[this]([[maybe_unused]] const SurfaceManipulator::Action& action)
{
BeginBatchMovement();
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), action.m_start.m_snapOffset);
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
});
m_translationManipulator->m_manipulator.InstallSurfaceManipulatorMouseMoveCallback(
@@ -306,7 +305,7 @@ namespace AzToolsFramework
});
m_translationManipulator->m_manipulator.InstallSurfaceManipulatorMouseUpCallback(
[this](const SurfaceManipulator::Action& /*action*/)
[this]([[maybe_unused]] const SurfaceManipulator::Action& action)
{
EndBatchMovement();
});
@@ -893,7 +892,7 @@ namespace AzToolsFramework
{
BeginBatchMovement();
InitializeVertexLookup(*m_translationManipulator, GetEntityId(), AZ::Vector3::CreateZero());
InitializeVertexLookup(*m_translationManipulator, GetEntityId());
// note: AdaptVertexIn/Out is to ensure we clamp the vertex local Z position to 0 if
// dealing with Vector2s when setting the position of the manipulator.
const AZ::Vector3 localOffset =
@@ -23,8 +23,8 @@ namespace AzToolsFramework
{
LinearManipulator::Starter CalculateLinearManipulationDataStart(
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
const float intersectionDistance, const AzFramework::CameraState& cameraState)
const AZ::Transform& localTransform, const ViewportInteraction::MouseInteraction& interaction, const float intersectionDistance,
const AzFramework::CameraState& cameraState)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
@@ -50,28 +50,9 @@ namespace AzToolsFramework
manipulatorInteraction.m_localRayOrigin, manipulatorInteraction.m_localRayDirection,
localIntersectionPoint, startTransition.m_localNormal, start.m_localHitPosition);
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
// calculate position amount to snap, to align with grid
const AZ::Vector3 positionSnapOffset = snapping && !gridSnapAction.m_localSnapping
? CalculateSnappedOffset(localTransform.GetTranslation(), axis, gridSize * scaleRecip)
: AZ::Vector3::CreateZero();
const AZ::Vector3 localScale = AZ::Vector3(localTransform.GetUniformScale());
const AZ::Quaternion localRotation = QuaternionFromTransformNoScaling(localTransform);
// calculate scale amount to snap, to align to round scale value
const AZ::Vector3 scaleSnapOffset = snapping && !gridSnapAction.m_localSnapping
? localRotation.GetInverseFull().TransformVector(CalculateSnappedOffset(
localRotation.TransformVector(localScale), axis, gridSize * scaleRecip))
: AZ::Vector3::CreateZero();
start.m_screenPosition = interaction.m_mousePick.m_screenCoordinates;
start.m_positionSnapOffset = positionSnapOffset;
start.m_scaleSnapOffset = scaleSnapOffset;
start.m_localPosition = localTransform.GetTranslation() + positionSnapOffset;
start.m_localScale = localScale + scaleSnapOffset;
start.m_localPosition = localTransform.GetTranslation();
start.m_localScale = AZ::Vector3(localTransform.GetUniformScale());;
start.m_localAxis = axis;
// sign to determine which side of the linear axis we pressed
// (useful to know when the visual axis flips to face the camera)
@@ -87,7 +68,7 @@ namespace AzToolsFramework
LinearManipulator::Action CalculateLinearManipulationDataAction(
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction)
const GridSnapParameters& gridSnapParams, const ViewportInteraction::MouseInteraction& interaction)
{
const ManipulatorInteraction manipulatorInteraction =
BuildManipulatorInteraction(
@@ -108,31 +89,34 @@ namespace AzToolsFramework
GetCameraState(interaction.m_interactionId.m_viewportId));
const AZ::Vector3 axis = TransformDirectionNoScaling(localTransform, fixed.m_axis);
// The local positions have been transformed to the reference frame of the object being manipulated. But they appear in the world
// with non-uniform scale applied, and the object being manipulated will want to work with unscaled deltas, so we need to divide by
// the non-uniform scale here.
// the local positions have been transformed to the reference frame of the object being manipulated, but they appear in the world
// with non-uniform scale applied, the object being manipulated will want to work with unscaled deltas, so we need to divide by
// the non-uniform scale here
const AZ::Vector3 hitDelta = (localHitPosition - start.m_localHitPosition) / nonUniformScale;
const AZ::Vector3 unsnappedOffset = axis * axis.Dot(hitDelta);
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal * axis.Dot(manipulatorInteraction.m_nonUniformScaleReciprocal);
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
const float scaleRecip =
manipulatorInteraction.m_scaleReciprocal * fixed.m_axis.Dot(manipulatorInteraction.m_nonUniformScaleReciprocal);
const float gridSize = gridSnapParams.m_gridSize;
const bool snapping = gridSnapParams.m_gridSnap;
LinearManipulator::Action action;
action.m_fixed = fixed;
action.m_start = start;
action.m_current.m_localPositionOffset = snapping
? unsnappedOffset + CalculateSnappedOffset(unsnappedOffset, axis, gridSize * scaleRecip)
? CalculateSnappedAmount(unsnappedOffset, axis, gridSize * scaleRecip)
: unsnappedOffset;
action.m_current.m_screenPosition = interaction.m_mousePick.m_screenCoordinates;
action.m_viewportId = interaction.m_interactionId.m_viewportId;
const AZ::Quaternion localRotation = QuaternionFromTransformNoScaling(localTransform);
const AZ::Vector3 scaledUnsnappedOffset = unsnappedOffset * startTransition.m_screenToWorldScale * NonUniformScaleReciprocal(nonUniformScale);
const AZ::Vector3 scaledUnsnappedOffset =
unsnappedOffset * startTransition.m_screenToWorldScale * NonUniformScaleReciprocal(nonUniformScale);
// how much to adjust the scale based on movement
const AZ::Quaternion invLocalRotation = localRotation.GetInverseFull();
action.m_current.m_localScaleOffset = snapping
? invLocalRotation.TransformVector((scaledUnsnappedOffset + CalculateSnappedOffset(scaledUnsnappedOffset, axis, gridSize * scaleRecip)))
? invLocalRotation.TransformVector(CalculateSnappedAmount(scaledUnsnappedOffset, axis, gridSize * scaleRecip))
: invLocalRotation.TransformVector(scaledUnsnappedOffset);
// record what modifier keys are held during this action
@@ -171,19 +155,18 @@ namespace AzToolsFramework
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_starter = CalculateLinearManipulationDataStart(
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction, rayIntersectionDistance,
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(), interaction, rayIntersectionDistance,
GetCameraState(interaction.m_interactionId.m_viewportId));
if (m_onLeftMouseDownCallback)
{
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onLeftMouseDownCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
m_fixed, m_starter, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(), gridSnapParams, interaction));
}
}
@@ -195,8 +178,8 @@ namespace AzToolsFramework
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_onMouseMoveCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(), gridSnapParams,
interaction));
}
}
@@ -208,8 +191,7 @@ namespace AzToolsFramework
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
m_onLeftMouseUpCallback(CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(), gridSnapParams, interaction));
}
}
@@ -232,8 +214,8 @@ namespace AzToolsFramework
GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
const auto action = CalculateLinearManipulationDataAction(
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
m_fixed, m_starter, TransformUniformScale(GetSpace()), GetNonUniformScale(), GetLocalTransform(), gridSnapParams,
mouseInteraction);
// display the exact hit (ray intersection) of the mouse pick on the manipulator
DrawTransformAxes(
@@ -20,7 +20,7 @@
namespace AzToolsFramework
{
struct GridSnapAction;
struct GridSnapParameters;
/// LinearManipulator serves as a visual tool for users to modify values
/// in one dimension on an axis defined in 3D space.
@@ -68,8 +68,6 @@ namespace AzToolsFramework
AZ::Vector3 m_localScale; ///< The current scale of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The intersection point in local space between the ray and the manipulator when the mouse down event happens.
AZ::Vector3 m_localAxis; ///< The axis in the local space of the manipulator itself.
AZ::Vector3 m_positionSnapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
AZ::Vector3 m_scaleSnapOffset; ///< The snap offset amount to ensure manipulator is aligned to round scale increments.
float m_sign; ///< Used to determine which side of the axis we clicked on in case it's flipped to face the camera.
AzFramework::ScreenPoint m_screenPosition; ///< The initial position in screen space of the manipulator.
};
@@ -91,7 +89,7 @@ namespace AzToolsFramework
ViewportInteraction::KeyboardModifiers m_modifiers;
int m_viewportId; ///< The id of the viewport this manipulator is being used in.
AZ::Vector3 LocalScale() const { return m_start.m_localScale + m_current.m_localScaleOffset; }
AZ::Vector3 LocalScaleOffset() const { return m_start.m_scaleSnapOffset + m_current.m_localScaleOffset; }
AZ::Vector3 LocalScaleOffset() const { return m_current.m_localScaleOffset; }
AZ::Vector3 LocalPosition() const { return m_start.m_localPosition + m_current.m_localPositionOffset; }
AZ::Vector3 LocalPositionOffset() const { return m_current.m_localPositionOffset; }
AZ::Vector2 ScreenOffset() const
@@ -162,11 +160,11 @@ namespace AzToolsFramework
LinearManipulator::Starter CalculateLinearManipulationDataStart(
const LinearManipulator::Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction,
float intersectionDistance, const AzFramework::CameraState& cameraState);
const AZ::Transform& localTransform, const ViewportInteraction::MouseInteraction& interaction, float intersectionDistance,
const AzFramework::CameraState& cameraState);
LinearManipulator::Action CalculateLinearManipulationDataAction(
const LinearManipulator::Fixed& fixed, const LinearManipulator::Starter& starter,
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction);
const GridSnapParameters& gridSnapParams, const ViewportInteraction::MouseInteraction& interaction);
} // namespace AzToolsFramework
@@ -42,12 +42,6 @@ namespace AzToolsFramework
{
}
GridSnapAction::GridSnapAction(const GridSnapParameters& gridSnapParameters, const bool localSnapping)
: m_gridSnapParams(gridSnapParameters)
, m_localSnapping(localSnapping)
{
}
ManipulatorInteraction BuildManipulatorInteraction(
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection)
@@ -57,19 +51,39 @@ namespace AzToolsFramework
return {localFromWorldUniform.TransformPoint(worldRayOrigin),
TransformDirectionNoScaling(localFromWorldUniform, worldRayDirection),
ScaleReciprocal(worldFromLocalUniform),
NonUniformScaleReciprocal(nonUniformScale)};
NonUniformScaleReciprocal(nonUniformScale),
ScaleReciprocal(worldFromLocalUniform)};
}
AZ::Vector3 CalculateSnappedOffset(
const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
struct SnapAdjustment
{
float m_existingSnapDistance; //!< How far to snap up or down to align to the grid.
float m_nextSnapDistance; //!< The snap increment (will return full signed value (grid size) when distance
//!< moved is greater than half of the grid size in either direction).
};
static SnapAdjustment CalculateSnapDistance(const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
{
// calculate total distance along axis
const float axisDistance = axis.Dot(unsnappedPosition);
// round to nearest step size
const float snappedAxisDistance = floorf((axisDistance / size) + 0.5f) * size;
return { axisDistance, snappedAxisDistance };
}
AZ::Vector3 CalculateSnappedOffset(const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
{
const auto snapAdjustment = CalculateSnapDistance(unsnappedPosition, axis, size);
// return offset along axis to snap to step size
return axis * (snappedAxisDistance - axisDistance);
return axis * (snapAdjustment.m_nextSnapDistance - snapAdjustment.m_existingSnapDistance);
}
AZ::Vector3 CalculateSnappedAmount(const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, const float size)
{
const auto snapAdjustment = CalculateSnapDistance(unsnappedPosition, axis, size);
// return offset along axis to snap to step size
return axis * snapAdjustment.m_nextSnapDistance;
}
AZ::Vector3 CalculateSnappedTerrainPosition(
@@ -31,24 +31,15 @@ namespace AzToolsFramework
float m_gridSize;
};
/// Structure to encapsulate the current grid snapping state.
struct GridSnapAction
{
GridSnapAction(const GridSnapParameters& gridSnapParameters, bool localSnapping);
GridSnapParameters m_gridSnapParams;
bool m_localSnapping;
};
/// Structure to hold transformed incoming viewport interaction from world space to manipulator space.
struct ManipulatorInteraction
{
AZ::Vector3 m_localRayOrigin; ///< The ray origin (start) in the reference from of the manipulator.
AZ::Vector3 m_localRayDirection; ///< The ray direction in the reference from of the manipulator.
float m_scaleReciprocal; ///< The scale reciprocal (1.0 / scale) of the transform used to move the
///< ray from world space to local space.
AZ::Vector3 m_nonUniformScaleReciprocal; ///< Handles inverting any non-uniform scale which was applied
///< separately from the transform.
float m_scaleReciprocal; ///< The scale reciprocal (1.0 / scale) of the transform used to move the
///< ray from world space to local space.
};
/// Build a ManipulatorInteraction structure from the incoming viewport interaction.
@@ -56,11 +47,16 @@ namespace AzToolsFramework
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Vector3& worldRayOrigin, const AZ::Vector3& worldRayDirection);
/// Calculate the offset along an axis to adjust a position
/// to stay snapped to a given grid size.
/// Calculate the offset along an axis to adjust a position to stay snapped to a given grid size.
/// @note This is snap up or down to the nearest grid segment (e.g. 0.2 snaps to 0.0 -> delta 0.2,
/// 0.7 snaps to 1.0 -> delta 0.3).
AZ::Vector3 CalculateSnappedOffset(
const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, float size);
/// Return the amount to snap from the starting position given the current grid size.
/// @note A movement of more than half size (in either direction) will cause a snap by size.
AZ::Vector3 CalculateSnappedAmount(const AZ::Vector3& unsnappedPosition, const AZ::Vector3& axis, float size);
/// For a given point on the terrain, calculate the closest xy position snapped to the grid
/// (z position is aligned to terrain height, not snapped to z grid)
AZ::Vector3 CalculateSnappedTerrainPosition(
@@ -59,17 +59,16 @@ namespace AzToolsFramework
const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const ViewportInteraction::MouseInteraction& interaction,
const AZStd::vector<LinearManipulator::Fixed>& fixedAxes,
const AZStd::vector<LinearManipulator::Starter>& starterStates, const GridSnapAction& gridSnapAction)
const AZStd::vector<LinearManipulator::Starter>& starterStates, const GridSnapParameters& gridSnapParams)
{
MultiLinearManipulator::Action action;
action.m_viewportId = interaction.m_interactionId.m_viewportId;
// build up action state for each axis
for (size_t fixedIndex = 0; fixedIndex < fixedAxes.size(); ++fixedIndex)
{
action.m_actions.push_back(
CalculateLinearManipulationDataAction(
fixedAxes[fixedIndex], starterStates[fixedIndex], worldFromLocal, nonUniformScale, localTransform,
gridSnapAction, interaction));
action.m_actions.push_back(CalculateLinearManipulationDataAction(
fixedAxes[fixedIndex], starterStates[fixedIndex], worldFromLocal, nonUniformScale, localTransform, gridSnapParams,
interaction));
}
return action;
@@ -79,8 +78,6 @@ namespace AzToolsFramework
const ViewportInteraction::MouseInteraction& interaction, const float rayIntersectionDistance)
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const AzFramework::CameraState cameraState = GetCameraState(interaction.m_interactionId.m_viewportId);
// build up initial start state for each axis
@@ -88,20 +85,19 @@ namespace AzToolsFramework
{
// note: m_localTransform must not be made uniform as it may contain a local scale we want to snap
const auto linearStart = CalculateLinearManipulationDataStart(
fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction,
rayIntersectionDistance, cameraState);
fixed, worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(), interaction, rayIntersectionDistance,
cameraState);
m_starters.push_back(linearStart);
}
if (m_onLeftMouseDownCallback)
{
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
// pass action containing all linear actions for each axis to handler
m_onLeftMouseDownCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
interaction, m_fixedAxes, m_starters, gridSnapAction));
interaction, m_fixedAxes, m_starters, gridSnapParams));
}
}
@@ -111,11 +107,9 @@ namespace AzToolsFramework
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
m_onMouseMoveCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
interaction, m_fixedAxes, m_starters, gridSnapAction));
interaction, m_fixedAxes, m_starters, gridSnapParams));
}
}
@@ -125,11 +119,9 @@ namespace AzToolsFramework
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
const GridSnapAction gridSnapAction = GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt());
m_onLeftMouseUpCallback(BuildMultiLinearManipulatorAction(
worldFromLocalUniformScale, GetNonUniformScale(), GetLocalTransform(),
interaction, m_fixedAxes, m_starters, gridSnapAction));
interaction, m_fixedAxes, m_starters, gridSnapParams));
m_starters.clear();
}
@@ -20,8 +20,6 @@
namespace AzToolsFramework
{
struct GridSnapAction;
//! MultiLinearManipulator serves as a visual tool for users to modify values
//! in one or more dimensions on axes defined in 3D space.
class MultiLinearManipulator
@@ -22,8 +22,7 @@
namespace AzToolsFramework
{
PlanarManipulator::StartInternal PlanarManipulator::CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const ViewportInteraction::MouseInteraction& interaction, const float intersectionDistance)
{
const ManipulatorInteraction manipulatorInteraction =
@@ -31,8 +30,6 @@ namespace AzToolsFramework
worldFromLocal, nonUniformScale, interaction.m_mousePick.m_rayOrigin, interaction.m_mousePick.m_rayDirection);
const AZ::Vector3 normal = TransformDirectionNoScaling(localTransform, fixed.m_normal);
const AZ::Vector3 axis1 = TransformDirectionNoScaling(localTransform, fixed.m_axis1);
const AZ::Vector3 axis2 = TransformDirectionNoScaling(localTransform, fixed.m_axis2);
// initial intersect point
const AZ::Vector3 localIntersectionPoint =
@@ -43,25 +40,14 @@ namespace AzToolsFramework
manipulatorInteraction.m_localRayOrigin, manipulatorInteraction.m_localRayDirection,
localIntersectionPoint, normal, startInternal.m_localHitPosition);
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
// calculate amount to snap to align with grid
const AZ::Vector3 snapOffset = snapping && !gridSnapAction.m_localSnapping
? CalculateSnappedOffset(localTransform.GetTranslation(), axis1, gridSize * scaleRecip) +
CalculateSnappedOffset(localTransform.GetTranslation(), axis2, gridSize * scaleRecip)
: AZ::Vector3::CreateZero();
startInternal.m_snapOffset = snapOffset;
startInternal.m_localPosition = localTransform.GetTranslation() + snapOffset;
startInternal.m_localPosition = localTransform.GetTranslation();
return startInternal;
}
PlanarManipulator::Action PlanarManipulator::CalculateManipulationDataAction(
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapParameters& gridSnapParams,
const ViewportInteraction::MouseInteraction& interaction)
{
const ManipulatorInteraction manipulatorInteraction =
@@ -88,20 +74,18 @@ namespace AzToolsFramework
const AZ::Vector3 hitDelta = (localHitPosition - startInternal.m_localHitPosition) / nonUniformScale;
const AZ::Vector3 unsnappedOffset = axis1.Dot(hitDelta) * axis1 + axis2.Dot(hitDelta) * axis2;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const AZ::Vector3 nonUniformScaleRecip = manipulatorInteraction.m_nonUniformScaleReciprocal;
const float gridSize = gridSnapAction.m_gridSnapParams.m_gridSize;
const bool snapping = gridSnapAction.m_gridSnapParams.m_gridSnap;
const float scaleRecip = manipulatorInteraction.m_scaleReciprocal;
const float gridSize = gridSnapParams.m_gridSize;
const bool snapping = gridSnapParams.m_gridSnap;
Action action;
action.m_fixed = fixed;
action.m_start.m_localPosition = startInternal.m_localPosition;
action.m_start.m_snapOffset = startInternal.m_snapOffset;
action.m_start.m_localHitPosition = startInternal.m_localHitPosition;
action.m_current.m_localOffset = snapping
? unsnappedOffset +
CalculateSnappedOffset(unsnappedOffset, axis1, gridSize * scaleRecip * nonUniformScaleRecip.Dot(axis1)) +
CalculateSnappedOffset(unsnappedOffset, axis2, gridSize * scaleRecip * nonUniformScaleRecip.Dot(axis2))
? CalculateSnappedAmount(unsnappedOffset, axis1, gridSize * scaleRecip * nonUniformScaleRecip.Dot(fixed.m_axis1)) +
CalculateSnappedAmount(unsnappedOffset, axis2, gridSize * scaleRecip * nonUniformScaleRecip.Dot(fixed.m_axis2))
: unsnappedOffset;
// record what modifier keys are held during this action
@@ -141,18 +125,17 @@ namespace AzToolsFramework
{
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_startInternal = CalculateManipulationDataStart(
m_fixed, worldFromLocalUniformScale, GetNonUniformScale(), TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()),
interaction, rayIntersectionDistance);
if (m_onLeftMouseDownCallback)
{
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
m_onLeftMouseDownCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, worldFromLocalUniformScale, GetNonUniformScale(), TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
gridSnapParams, interaction));
}
}
@@ -164,8 +147,7 @@ namespace AzToolsFramework
m_onMouseMoveCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
TransformNormalizedScale(GetLocalTransform()), gridSnapParams, interaction));
}
}
@@ -177,8 +159,7 @@ namespace AzToolsFramework
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, interaction.m_keyboardModifiers.Alt()), interaction));
TransformNormalizedScale(GetLocalTransform()), gridSnapParams, interaction));
}
}
@@ -195,8 +176,7 @@ namespace AzToolsFramework
const GridSnapParameters gridSnapParams = GridSnapSettings(mouseInteraction.m_interactionId.m_viewportId);
const auto action = CalculateManipulationDataAction(
m_fixed, m_startInternal, TransformUniformScale(GetSpace()), GetNonUniformScale(),
TransformNormalizedScale(GetLocalTransform()),
GridSnapAction(gridSnapParams, mouseInteraction.m_keyboardModifiers.Alt()), mouseInteraction);
TransformNormalizedScale(GetLocalTransform()), gridSnapParams, mouseInteraction);
// display the exact hit (ray intersection) of the mouse pick on the manipulator
DrawTransformAxes(
@@ -21,7 +21,7 @@
namespace AzToolsFramework
{
class ManipulatorView;
struct GridSnapAction;
struct GridSnapParameters;
/// PlanarManipulator serves as a visual tool for users to modify values
/// in two dimension in a plane defined two non-collinear axes in 3D space.
@@ -58,7 +58,6 @@ namespace AzToolsFramework
{
AZ::Vector3 m_localPosition; ///< The current position of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The intersection point in local space between the ray and the manipulator when the mouse down event happens.
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
/// The state of the manipulator during an interaction.
@@ -120,7 +119,6 @@ namespace AzToolsFramework
{
AZ::Vector3 m_localPosition; ///< The starting position of the manipulator in local space.
AZ::Vector3 m_localHitPosition; ///< The intersection point in world space between the ray and the manipulator when the mouse down event happens.
AZ::Vector3 m_snapOffset; ///< The snap offset amount to ensure manipulator is aligned to the grid.
};
Fixed m_fixed;
@@ -134,12 +132,11 @@ namespace AzToolsFramework
static StartInternal CalculateManipulationDataStart(
const Fixed& fixed, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapAction& gridSnapAction,
const ViewportInteraction::MouseInteraction& interaction, float intersectionDistance);
const AZ::Transform& localTransform, const ViewportInteraction::MouseInteraction& interaction, float intersectionDistance);
static Action CalculateManipulationDataAction(
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal,
const AZ::Vector3& nonUniformScale, const AZ::Transform& localTransform,
const GridSnapAction& gridSnapAction, const ViewportInteraction::MouseInteraction& interaction);
const Fixed& fixed, const StartInternal& startInternal, const AZ::Transform& worldFromLocal, const AZ::Vector3& nonUniformScale,
const AZ::Transform& localTransform, const GridSnapParameters& gridSnapParams,
const ViewportInteraction::MouseInteraction& interaction);
};
} // namespace AzToolsFramework
@@ -18,6 +18,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/Instance/TemplateInstanceMapperInterface.h>
@@ -49,8 +50,7 @@ namespace AzToolsFramework
m_alias = GenerateInstanceAlias();
m_containerEntity = containerEntity ? AZStd::move(containerEntity)
: AZStd::make_unique<AZ::Entity>();
EntityAlias containerEntityAlias = GenerateEntityAlias();
RegisterEntity(m_containerEntity->GetId(), containerEntityAlias);
RegisterEntity(m_containerEntity->GetId(), PrefabDomUtils::ContainerEntityName);
}
Instance::~Instance()
@@ -311,8 +311,15 @@ namespace AzToolsFramework
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance)
{
InstanceAlias newInstanceAlias = GenerateInstanceAlias();
return AddInstance(AZStd::move(instance), newInstanceAlias);
}
Instance& Instance::AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias newInstanceAlias)
{
AZ_Assert(instance.get(), "instance argument is nullptr");
AZ_Assert(m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(), "InstanceAlias' unique id collision, this should never happen.");
AZ_Assert(
m_nestedInstances.find(newInstanceAlias) == m_nestedInstances.end(),
"InstanceAlias' unique id collision, this should never happen.");
instance->m_parent = this;
instance->m_alias = newInstanceAlias;
return *(m_nestedInstances[newInstanceAlias] = std::move(instance));
@@ -93,6 +93,7 @@ namespace AzToolsFramework
void Reset();
Instance& AddInstance(AZStd::unique_ptr<Instance> instance);
Instance& AddInstance(AZStd::unique_ptr<Instance> instance, InstanceAlias instanceAlias);
AZStd::unique_ptr<Instance> DetachNestedInstance(const InstanceAlias& instanceAlias);
/**
@@ -173,6 +174,8 @@ namespace AzToolsFramework
static EntityAlias GenerateEntityAlias();
AliasPath GetAbsoluteInstanceAliasPath() const;
static InstanceAlias GenerateInstanceAlias();
protected:
/**
* Gets the entities owned by this instance
@@ -189,8 +192,6 @@ namespace AzToolsFramework
bool RegisterEntity(const AZ::EntityId& entityId, const EntityAlias& entityAlias);
AZStd::unique_ptr<AZ::Entity> DetachEntity(const EntityAlias& entityAlias);
static InstanceAlias GenerateInstanceAlias();
// Provide access to private data members in the serializer
friend class JsonInstanceSerializer;
friend class InstanceEntityIdMapper;
@@ -152,6 +152,30 @@ namespace AzToolsFramework
Instance::EntityList newEntities;
if (PrefabDomUtils::LoadInstanceFromPrefabDom(*instanceToUpdate, newEntities, currentTemplate.GetPrefabDom()))
{
// If a link was created for a nested instance before the changes were propagated,
// then we associate it correctly here
instanceToUpdate->GetNestedInstances([&](AZStd::unique_ptr<Instance>& nestedInstance) {
if (nestedInstance->GetLinkId() != InvalidLinkId)
{
return;
}
for (auto linkId : currentTemplate.GetLinks())
{
LinkReference nestedLink = m_prefabSystemComponentInterface->FindLink(linkId);
if (!nestedLink.has_value())
{
continue;
}
if (nestedLink->get().GetInstanceName() == nestedInstance->GetInstanceAlias())
{
nestedInstance->SetLinkId(linkId);
break;
}
}
});
AzToolsFramework::EditorEntityContextRequestBus::Broadcast(
&AzToolsFramework::EditorEntityContextRequests::HandleEntitiesAdded, newEntities);
}
@@ -297,7 +297,7 @@ namespace AzToolsFramework
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, containerEntityId);
// Update the cache - this prevents these changes from being stored in the regular undo/redo nodes
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter));
m_prefabUndoCache.Store(containerEntityId, AZStd::move(containerEntityDomAfter), parentEntityId);
return AZStd::move(patch);
}
@@ -595,54 +595,199 @@ namespace AzToolsFramework
{
// Create Undo node on entities if they belong to an instance
InstanceOptionalReference owningInstance = m_instanceEntityMapperInterface->FindOwningInstance(entityId);
if (owningInstance.has_value())
if (!owningInstance.has_value())
{
PrefabDom afterState;
AZ::Entity* entity = GetEntityById(entityId);
if (entity)
return;
}
AZ::Entity* entity = GetEntityById(entityId);
if (!entity)
{
m_prefabUndoCache.PurgeCache(entityId);
return;
}
PrefabDom beforeState;
AZ::EntityId beforeParentId;
m_prefabUndoCache.Retrieve(entityId, beforeState, beforeParentId);
PrefabDom afterState;
AZ::EntityId afterParentId;
AZ::TransformBus::EventResult(afterParentId, entityId, &AZ::TransformBus::Events::GetParentId);
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
{
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
bool isNewParentOwnedByDifferentInstance = false;
if (beforeParentId != afterParentId)
{
PrefabDom beforeState;
m_prefabUndoCache.Retrieve(entityId, beforeState);
// If the entity parent changed, verify if the owning instance changed too
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
m_instanceToTemplateInterface->GenerateDomForEntity(afterState, *entity);
PrefabDom patch;
m_instanceToTemplateInterface->GeneratePatch(patch, beforeState, afterState);
if (patch.IsArray() && !patch.Empty() && beforeState.IsObject())
if (beforeOwningInstance.has_value() && afterOwningInstance.has_value() &&
(&beforeOwningInstance->get() != &afterOwningInstance->get()))
{
if (IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId))
{
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(patch, entityId);
// Save these changes as patches to the link
PrefabUndoLinkUpdate* linkUpdate =
aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
linkUpdate->SetParent(parentUndoBatch);
linkUpdate->Capture(patch, owningInstance->get().GetLinkId());
linkUpdate->Redo();
}
else
{
// Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
state->SetParent(parentUndoBatch);
state->Capture(beforeState, afterState, entityId);
state->Redo();
}
isNewParentOwnedByDifferentInstance = true;
}
}
// Update the cache
m_prefabUndoCache.Store(entityId, AZStd::move(afterState));
if (isInstanceContainerEntity)
{
if (isNewParentOwnedByDifferentInstance)
{
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
PrefabDom afterStateafterReparenting;
m_instanceToTemplateInterface->GenerateDomForEntity(afterStateafterReparenting, *entity);
PrefabDom newPatch;
m_instanceToTemplateInterface->GeneratePatch(newPatch, afterState, afterStateafterReparenting);
m_instanceToTemplateInterface->AppendEntityAliasToPatchPaths(newPatch, entityId);
InstanceOptionalReference owningInstanceAfterReparenting =
m_instanceEntityMapperInterface->FindOwningInstance(entityId);
Internal_HandleContainerOverride(
parentUndoBatch, entityId, newPatch, owningInstanceAfterReparenting->get().GetLinkId());
}
else
{
Internal_HandleContainerOverride(
parentUndoBatch, entityId, patch, owningInstance->get().GetLinkId());
}
}
else
{
m_prefabUndoCache.PurgeCache(entityId);
Internal_HandleEntityChange(parentUndoBatch, entityId, beforeState, afterState);
if (isNewParentOwnedByDifferentInstance)
{
Internal_HandleInstanceChange(parentUndoBatch, entity, beforeParentId, afterParentId);
}
}
}
m_prefabUndoCache.UpdateCache(entityId);
}
void PrefabPublicHandler::Internal_HandleContainerOverride(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId)
{
// Save these changes as patches to the link
PrefabUndoLinkUpdate* linkUpdate = aznew PrefabUndoLinkUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
linkUpdate->SetParent(undoBatch);
linkUpdate->Capture(patch, linkId);
linkUpdate->Redo();
}
void PrefabPublicHandler::Internal_HandleEntityChange(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState)
{
// Update the state of the entity
PrefabUndoEntityUpdate* state = aznew PrefabUndoEntityUpdate(AZStd::to_string(static_cast<AZ::u64>(entityId)));
state->SetParent(undoBatch);
state->Capture(beforeState, afterState, entityId);
state->Redo();
}
void PrefabPublicHandler::Internal_HandleInstanceChange(
UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId)
{
// If the entity parent changed, verify if the owning instance changed too
InstanceOptionalReference beforeOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(beforeParentId);
InstanceOptionalReference afterOwningInstance = m_instanceEntityMapperInterface->FindOwningInstance(afterParentId);
EntityList entities;
AZStd::vector<Instance*> instances;
// Retrieve all descendant entities and instances of this entity that belonged to the same owning instance.
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances);
AZStd::vector<AZStd::unique_ptr<Instance>> instanceUniquePtrs;
AZStd::vector<AZStd::pair<Instance*, PrefabDom>> instancePatches;
// Remove Entities and Instances from the prior instance
{
// Remove Instances
for (Instance* nestedInstance : instances)
{
auto linkRef = m_prefabSystemComponentInterface->FindLink(nestedInstance->GetLinkId());
PrefabDom oldLinkPatches;
if (linkRef.has_value())
{
auto patches = linkRef->get().GetLinkPatches();
if (patches.has_value())
{
oldLinkPatches.CopyFrom(patches->get(), oldLinkPatches.GetAllocator());
}
}
auto nestedInstanceUniquePtr = beforeOwningInstance->get().DetachNestedInstance(nestedInstance->GetInstanceAlias());
RemoveLink(nestedInstanceUniquePtr, beforeOwningInstance->get().GetTemplateId(), undoBatch);
instancePatches.emplace_back(AZStd::make_pair(nestedInstanceUniquePtr.get(), AZStd::move(oldLinkPatches)));
instanceUniquePtrs.emplace_back(AZStd::move(nestedInstanceUniquePtr));
}
// Get the previous state of the prior instance for undo/redo purposes
PrefabDom beforeInstanceDomBeforeRemoval;
m_instanceToTemplateInterface->GenerateDomForInstance(beforeInstanceDomBeforeRemoval, beforeOwningInstance->get());
// Remove Entities
for (AZ::Entity* nestedEntity : entities)
{
beforeOwningInstance->get().DetachEntity(nestedEntity->GetId()).release();
}
// Create the Update node for the prior owning instance
// Instance removal will be taken care of from the RemoveLink function for undo/redo purposes
PrefabUndoHelpers::UpdatePrefabInstance(
beforeOwningInstance->get(), "Update prior prefab instance", beforeInstanceDomBeforeRemoval, undoBatch);
}
// Add Entities and Instances to new instance
{
// Add Instances
for (auto& instanceUniquePtr : instanceUniquePtrs)
{
afterOwningInstance->get().AddInstance(AZStd::move(instanceUniquePtr));
}
// Create Links
for (auto& instanceInfo : instancePatches)
{
// Add a new link with the old dom
CreateLink(
*instanceInfo.first, afterOwningInstance->get().GetTemplateId(), undoBatch,
AZStd::move(instanceInfo.second));
}
// Get the previous state of the new instance for undo/redo purposes
PrefabDom afterInstanceDomBeforeAdd;
m_instanceToTemplateInterface->GenerateDomForInstance(afterInstanceDomBeforeAdd, afterOwningInstance->get());
// Add Entities
for (AZ::Entity* nestedEntity : entities)
{
afterOwningInstance->get().AddEntity(*nestedEntity);
}
// Create the Update node for the new owning instance
PrefabUndoHelpers::UpdatePrefabInstance(
afterOwningInstance->get(), "Update new prefab instance", afterInstanceDomBeforeAdd, undoBatch);
}
}
bool PrefabPublicHandler::IsInstanceContainerEntity(AZ::EntityId entityId) const
@@ -754,16 +899,29 @@ namespace AzToolsFramework
if (!EntitiesBelongToSameInstance(entityIds))
{
return AZ::Failure(AZStd::string("Cannot duplicate multiple "
"entities belonging to different instances with one operation."));
return AZ::Failure(AZStd::string("Cannot duplicate multiple entities belonging to different instances with one operation."
"Change your selection to contain entities in the same instance."));
}
// We've already verified the entities are all owned by the same instance,
// so we can just retrieve our instance from the first entity in the list.
InstanceOptionalReference commonEntityOwningInstance = GetOwnerInstanceByEntityId(entityIds[0]);
AZ_Assert(
commonEntityOwningInstance.has_value(),
"Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided");
AZ::EntityId firstEntityIdToDuplicate = entityIds[0];
InstanceOptionalReference commonOwningInstance = GetOwnerInstanceByEntityId(firstEntityIdToDuplicate);
if (!commonOwningInstance.has_value())
{
return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided."));
}
// If the first entity id is a container entity id, then we need to mark its parent as the common owning instance because you
// cannot duplicate an instance from itself.
if (commonOwningInstance->get().GetContainerEntityId() == firstEntityIdToDuplicate)
{
commonOwningInstance = commonOwningInstance->get().GetParentInstance();
}
if (!commonOwningInstance.has_value())
{
return AZ::Failure(AZStd::string("Failed to duplicate : Couldn't get a valid owning instance for the common root entity of the entities provided."));
}
// This will cull out any entities that have ancestors in the list, since we will end up duplicating
// the full nested hierarchy with what is returned from RetrieveAndSortPrefabEntitiesAndInstances
@@ -776,105 +934,63 @@ namespace AzToolsFramework
{
AZ_PROFILE_SCOPE(AZ::Debug::ProfileCategory::AzToolsFramework, "DuplicateEntitiesInInstance::UndoCaptureAndDuplicateEntities");
// Take a snapshot of the instance DOM before we manipulate it
Prefab::PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonEntityOwningInstance->get());
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<Instance*> instances;
// Gather all entities/instances in the hierarchy, but don't detach them because we are duplicating not deleting.
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonEntityOwningInstance->get(), entities, instances);
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
if (!success)
{
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
}
// Make a copy of our before instance DOM where we will add our duplicated entities
Prefab::PrefabDom instanceDomAfter;
// Take a snapshot of the instance DOM before we manipulate it
PrefabDom instanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBefore, commonOwningInstance->get());
// Make a copy of our before instance DOM where we will add our duplicated entities and/or instances
PrefabDom instanceDomAfter;
instanceDomAfter.CopyFrom(instanceDomBefore, instanceDomAfter.GetAllocator());
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
EntityIdList duplicatedEntityAndInstanceIds;
for (AZ::Entity* entity : entities)
{
EntityAliasOptionalReference oldAliasRef = commonEntityOwningInstance->get().GetEntityAlias(entity->GetId());
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
EntityAlias oldAlias = oldAliasRef.value();
// Duplicate any nested entities and instances as requested
AZStd::unordered_map<InstanceAlias, Instance*> newInstanceAliasToOldInstanceMap;
DuplicateNestedEntitiesInInstance(commonOwningInstance->get(),
entities, instanceDomAfter, duplicatedEntityAndInstanceIds);
DuplicateNestedInstancesInInstance(commonOwningInstance->get(),
instances, instanceDomAfter, duplicatedEntityAndInstanceIds,
newInstanceAliasToOldInstanceMap);
// Give this the outer allocator so that the memory reference will be valid when
// it gets used for AddMember
Prefab::PrefabDom entityDomBefore(&instanceDomAfter.GetAllocator());
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
// Keep track of the old alias <-> new alias mapping for this duplicated entity
// so we can fixup references later
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
oldAliasToNewAliasMap.insert(AZStd::make_pair(oldAlias, newEntityAlias));
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
entityDomBefore.Accept(writer);
// Store our duplicated Entity DOM with its new alias as a string
// so that we can fixup entity alias references before adding it
// to the Entities member of our instance DOM
QString entityDomString(buffer.GetString());
aliasToEntityDomMap.insert(AZStd::make_pair(newEntityAlias, entityDomString));
}
auto entitiesIter = instanceDomAfter.FindMember(PrefabDomUtils::EntitiesName);
AZ_Assert(entitiesIter != instanceDomAfter.MemberEnd(), "Instance DOM missing the Entities member.");
// Now that all the duplicated Entity DOMs have been created, we need to iterate
// through them and replace any previous EntityAlias references with the new ones.
// These are more than just parent entity references for nested entities, this will
// also cover any EntityId references that were made in the components between them.
for (auto aliasEntityPair : aliasToEntityDomMap)
{
EntityAlias newEntityAlias = aliasEntityPair.first;
QString newEntityDomString = aliasEntityPair.second;
// Replace all of the old alias references with the new ones
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
// inadvertently replacing a matching string vs. where an actual EntityId is expected
// This will cover both cases where an alias could be used in a normal entity vs. an instance
for (auto aliasMapIter : oldAliasToNewAliasMap)
{
ReplaceOldAliases(newEntityDomString, aliasMapIter.first, aliasMapIter.second);
}
// Create the new Entity DOM from parsing the JSON string
Prefab::PrefabDom entityDomAfter(&instanceDomAfter.GetAllocator());
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
// Add the new Entity DOM to the Entities member of the instance
rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), instanceDomAfter.GetAllocator());
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, instanceDomAfter.GetAllocator());
}
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity duplication");
PrefabUndoInstance* command = aznew PrefabUndoInstance("Entity/Instance duplication");
command->SetParent(undoBatch.GetUndoBatch());
command->Capture(instanceDomBefore, instanceDomAfter, commonEntityOwningInstance->get().GetTemplateId());
command->RunRedo();
command->Capture(instanceDomBefore, instanceDomAfter, commonOwningInstance->get().GetTemplateId());
command->Redo();
EntityIdList duplicatedEntityIds;
for (auto aliasMapIter : oldAliasToNewAliasMap)
// Create links for our duplicated instances (if any were duplicated)
for (auto [newInstanceAlias, oldInstance] : newInstanceAliasToOldInstanceMap)
{
EntityAlias newEntityAlias = aliasMapIter.second;
LinkId oldLinkId = oldInstance->GetLinkId();
auto linkRef = m_prefabSystemComponentInterface->FindLink(oldLinkId);
AZ_Assert(
linkRef.has_value(), "Unable to find link with id '%llu' during instance duplication.",
oldLinkId);
AliasPath absoluteEntityPath = commonEntityOwningInstance->get().GetAbsoluteInstanceAliasPath();
absoluteEntityPath.Append(newEntityAlias);
PrefabDomValueReference linkPatches = linkRef->get().GetLinkPatches();
AZ_Assert(
linkPatches.has_value(), "Link with id '%llu' is missing patches.",
oldLinkId);
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
duplicatedEntityIds.push_back(newEntityId);
PrefabDom linkPatchesCopy;
linkPatchesCopy.CopyFrom(linkPatches->get(), linkPatchesCopy.GetAllocator());
m_prefabSystemComponentInterface->CreateLink(
commonOwningInstance->get().GetTemplateId(), oldInstance->GetTemplateId(), newInstanceAlias, linkPatchesCopy);
}
// Select the duplicated entities
auto selectionUndo = aznew SelectionCommand(duplicatedEntityIds, "Select Duplicated Entities");
// Select the duplicated entities/instances
auto selectionUndo = aznew SelectionCommand(duplicatedEntityAndInstanceIds, "Select Duplicated Entities/Instances");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, selectionUndo);
}
@@ -1363,8 +1479,159 @@ namespace AzToolsFramework
return true;
}
void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
EntityIdList& duplicatedEntityIds)
{
if (entities.empty())
{
return;
}
AZStd::unordered_map<EntityAlias, EntityAlias> oldAliasToNewAliasMap;
AZStd::unordered_map<EntityAlias, QString> aliasToEntityDomMap;
for (AZ::Entity* entity : entities)
{
EntityAliasOptionalReference oldAliasRef = commonOwningInstance.GetEntityAlias(entity->GetId());
AZ_Assert(oldAliasRef.has_value(), "No alias found for Entity in the DOM");
EntityAlias oldAlias = oldAliasRef.value();
// Give this the outer allocator so that the memory reference will be valid when
// it gets used for AddMember
PrefabDom entityDomBefore(&domToAddDuplicatedEntitiesUnder.GetAllocator());
m_instanceToTemplateInterface->GenerateDomForEntity(entityDomBefore, *entity);
// Keep track of the old alias <-> new alias mapping for this duplicated entity
// so we can fixup references later
EntityAlias newEntityAlias = Instance::GenerateEntityAlias();
oldAliasToNewAliasMap.emplace(oldAlias, newEntityAlias);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
entityDomBefore.Accept(writer);
// Store our duplicated Entity DOM with its new alias as a string
// so that we can fixup entity alias references before adding it
// to the Entities member of our instance DOM
QString entityDomString(buffer.GetString());
aliasToEntityDomMap.emplace(newEntityAlias, entityDomString);
}
auto entitiesIter = domToAddDuplicatedEntitiesUnder.FindMember(PrefabDomUtils::EntitiesName);
AZ_Assert(entitiesIter != domToAddDuplicatedEntitiesUnder.MemberEnd(), "Instance DOM missing the Entities member.");
// Now that all the duplicated Entity DOMs have been created, we need to iterate
// through them and replace any previous EntityAlias references with the new ones.
// These are more than just parent entity references for nested entities, this will
// also cover any EntityId references that were made in the components between them.
for (auto [newEntityAlias, newEntityDomString] : aliasToEntityDomMap)
{
// Replace all of the old alias references with the new ones
for (auto [oldAlias, newAlias] : oldAliasToNewAliasMap)
{
ReplaceOldAliases(newEntityDomString, oldAlias, newAlias);
}
// Create the new Entity DOM from parsing the JSON string
PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator());
entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
// Add the new Entity DOM to the Entities member of the instance
rapidjson::Value aliasName(newEntityAlias.c_str(), newEntityAlias.length(), domToAddDuplicatedEntitiesUnder.GetAllocator());
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator());
}
for (auto aliasMapIter : oldAliasToNewAliasMap)
{
EntityAlias newEntityAlias = aliasMapIter.second;
AliasPath absoluteEntityPath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
absoluteEntityPath.Append(newEntityAlias);
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteEntityPath);
duplicatedEntityIds.push_back(newEntityId);
}
}
void PrefabPublicHandler::DuplicateNestedInstancesInInstance(Instance& commonOwningInstance,
const AZStd::vector<Instance*>& instances, PrefabDom& domToAddDuplicatedInstancesUnder,
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<InstanceAlias, Instance*>& newInstanceAliasToOldInstanceMap)
{
if (instances.empty())
{
return;
}
AZStd::unordered_map<InstanceAlias, InstanceAlias> oldInstanceAliasToNewInstanceAliasMap;
AZStd::unordered_map<InstanceAlias, QString> aliasToInstanceDomMap;
for (auto instance : instances)
{
PrefabDom nestedInstanceDomBefore;
m_instanceToTemplateInterface->GenerateDomForInstance(nestedInstanceDomBefore, *instance);
// Keep track of the old alias <-> new alias mapping for this duplicated instance
// so we can fixup references later
InstanceAlias oldAlias = instance->GetInstanceAlias();
InstanceAlias newInstanceAlias = Instance::GenerateInstanceAlias();
oldInstanceAliasToNewInstanceAliasMap.emplace(oldAlias, newInstanceAlias);
// Keep track of our new instance alias with the Instance it was duplicated from,
// so that after all instances are duplicated, we can go back and create links for them
newInstanceAliasToOldInstanceMap.emplace(newInstanceAlias, instance);
rapidjson::StringBuffer buffer;
rapidjson::Writer<rapidjson::StringBuffer> writer(buffer);
nestedInstanceDomBefore.Accept(writer);
// Store our duplicated Instance DOM with its new alias as a string
// so that we can fixup instance alias references before adding it
// to the Instances member of our instance DOM
QString instanceDomString(buffer.GetString());
aliasToInstanceDomMap.emplace(newInstanceAlias, instanceDomString);
}
auto instancesIter = domToAddDuplicatedInstancesUnder.FindMember(PrefabDomUtils::InstancesName);
AZ_Assert(instancesIter != domToAddDuplicatedInstancesUnder.MemberEnd(), "Instance DOM missing the Instances member.");
// Now that all the duplicated Instance DOMs have been created, we need to iterate
// through them and replace any previous InstanceAlias references with the new ones.
for (auto [newInstanceAlias, newInstanceDomString]: aliasToInstanceDomMap)
{
// Replace all of the old alias references with the new ones
for (auto [oldAlias, newAlias] : oldInstanceAliasToNewInstanceAliasMap)
{
ReplaceOldAliases(newInstanceDomString, oldAlias, newAlias);
}
// Create the new Instance DOM from parsing the JSON string
PrefabDom nestedInstanceDomAfter(&domToAddDuplicatedInstancesUnder.GetAllocator());
nestedInstanceDomAfter.Parse(newInstanceDomString.toUtf8().constData());
// Add the new Instance DOM to the Instances member of the instance
rapidjson::Value aliasName(newInstanceAlias.c_str(), newInstanceAlias.length(), domToAddDuplicatedInstancesUnder.GetAllocator());
instancesIter->value.AddMember(AZStd::move(aliasName), nestedInstanceDomAfter, domToAddDuplicatedInstancesUnder.GetAllocator());
}
for (auto aliasMapIter : oldInstanceAliasToNewInstanceAliasMap)
{
InstanceAlias newInstanceAlias = aliasMapIter.second;
AliasPath absoluteInstancePath = commonOwningInstance.GetAbsoluteInstanceAliasPath();
absoluteInstancePath.Append(newInstanceAlias);
AZ::EntityId newEntityId = InstanceEntityIdMapper::GenerateEntityIdForAliasPath(absoluteInstancePath);
duplicatedEntityIds.push_back(newEntityId);
}
}
void PrefabPublicHandler::ReplaceOldAliases(QString& stringToReplace, AZStd::string_view oldAlias, AZStd::string_view newAlias)
{
// Replace all of the old alias references with the new ones
// We bookend the aliases with \" and also with a / as an extra precaution to prevent
// inadvertently replacing a matching string vs. where an actual EntityId is expected
// This will cover both cases where an alias could be used in a normal entity vs. an instance
QString oldAliasQuotes = QString("\"%1\"").arg(oldAlias.data());
QString newAliasQuotes = QString("\"%1\"").arg(newAlias.data());
@@ -73,6 +73,33 @@ namespace AzToolsFramework
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
/**
* Duplicate a list of entities owned by a common owning instance by directly
* copying/modifying their entries in the instance DOM
*
* \param commonOwningInstance The common owning instance of all the entities being duplicated.
* \param entities The list of Entities that will be duplicated.
* \param domToAddDuplicatedEntitiesUnder The DOM of the common owning instance where the duplicated
* entity DOM values will be added to.
* \param duplicatedEntityIds A list of EntityIds corresponding to the entities that were duplicated.
*/
void DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
EntityIdList& duplicatedEntityIds);
/**
* Duplicate a list of instances owned by a common owning instance by directly
* copying/modifying their entries in the instance DOM
*
* \param commonOwningInstance The common owning instance of all the instances being duplicated.
* \param entities The list of Instances that will be duplicated.
* \param domToAddDuplicatedInstancesUnder The DOM of the common owning instance where the duplicated
* instance DOM values will be added to.
* \param duplicatedEntityIds A list of EntityIds corresponding to the instances that were duplicated.
*/
void DuplicateNestedInstancesInInstance(Instance& commonOwningInstance,
const AZStd::vector<Instance*>& instances, PrefabDom& domToAddDuplicatedInstancesUnder,
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<InstanceAlias, Instance*>& newInstanceAliasToOldInstanceMap);
/**
* Applies the correct transform changes to the container entity based on the parent and child entities provided, and returns an appropriate patch.
@@ -90,8 +117,8 @@ namespace AzToolsFramework
/**
* Creates a link between the templates of an instance and its parent.
*
* \param sourceInstance The instance that corresponds to the source template of the link.
* \param targetInstance The id of the target template.
* \param sourceInstance The instance that corresponds to the source template of the link (child).
* \param targetInstance The id of the target template (parent).
* \param undoBatch The undo batch to set as parent for this create link action.
* \param patch The patch to store in the newly created link dom.
* \param isUndoRedoSupportNeeded The flag indicating whether the link should be created with undo/redo support or not.
@@ -134,6 +161,12 @@ namespace AzToolsFramework
bool IsCyclicalDependencyFound(
InstanceOptionalConstReference instance, const AZStd::unordered_set<AZ::IO::Path>& templateSourcePaths);
static void Internal_HandleContainerOverride(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, const PrefabDom& patch, const LinkId linkId);
static void Internal_HandleEntityChange(
UndoSystem::URSequencePoint* undoBatch, AZ::EntityId entityId, PrefabDom& beforeState, PrefabDom& afterState);
void Internal_HandleInstanceChange(UndoSystem::URSequencePoint* undoBatch, AZ::Entity* entity, AZ::EntityId beforeParentId, AZ::EntityId afterParentId);
void UpdateLinkPatchesWithNewEntityAliases(
PrefabDom& linkPatch,
const AZStd::unordered_map<AZ::EntityId, AZStd::string>& oldEntityAliases,
@@ -706,14 +706,14 @@ namespace AzToolsFramework
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from TemplateToLinkIdsMap.",
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str());
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId());
result = RemoveLinkFromTargetTemplate(linkId, link);
AZ_Assert(result,
"Prefab - PrefabSystemComponent::RemoveLink - "
"Failed to remove Link with Id '%llu' for Instance '%s' of source Template with Id '%llu' "
"from target Template with Id '%llu'.",
linkId, link.GetSourceTemplateId(), link.GetInstanceName().c_str(), link.GetTargetTemplateId());
linkId, link.GetInstanceName().c_str(), link.GetSourceTemplateId(), link.GetTargetTemplateId());
m_linkIdMap.erase(linkId);
@@ -73,14 +73,16 @@ namespace AzToolsFramework
}
PrefabDom oldData;
Retrieve(entityId, oldData);
AZ::EntityId oldParentId;
Retrieve(entityId, oldData, oldParentId);
UpdateCache(entityId);
PrefabDom newData;
Retrieve(entityId, newData);
AZ::EntityId newParentId;
Retrieve(entityId, newData, newParentId);
if (newData != oldData)
if (newData != oldData || oldParentId != newParentId)
{
// display a useful message
AZ::Entity* entity = nullptr;
@@ -106,7 +108,7 @@ namespace AzToolsFramework
// Clear out newly generated data and
// replace with original data to ensure debug mode has the same data as profile/release
// in the event of the consistency check failing.
m_entitySavedStates[entityId] = AZStd::move(oldData);
m_entitySavedStates[entityId] = {AZStd::move(oldData), oldParentId};
#endif // ENABLE_UNDOCACHE_CONSISTENCY_CHECKS
}
@@ -140,10 +142,13 @@ namespace AzToolsFramework
return;
}
AZ::EntityId parentId;
AZ::TransformBus::EventResult(parentId, entityId, &AZ::TransformBus::Events::GetParentId);
// Capture it
PrefabDom entityDom;
m_instanceToTemplateInterface->GenerateDomForEntity(entityDom, *entity);
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(entityDom)));
m_entitySavedStates[entityId] = {AZStd::move(entityDom), parentId};
AZLOG("Prefab Undo", "Correctly updated cache for entity of id %llu (%s)", static_cast<AZ::u64>(entityId), entity->GetName().c_str());
@@ -155,7 +160,7 @@ namespace AzToolsFramework
m_entitySavedStates.erase(entityId);
}
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom)
bool PrefabUndoCache::Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId)
{
auto it = m_entitySavedStates.find(entityId);
@@ -164,14 +169,15 @@ namespace AzToolsFramework
return false;
}
outDom = AZStd::move(m_entitySavedStates[entityId]);
outDom = AZStd::move(m_entitySavedStates[entityId].dom);
parentId = m_entitySavedStates[entityId].parentId;
m_entitySavedStates.erase(entityId);
return true;
}
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom)
void PrefabUndoCache::Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId)
{
m_entitySavedStates.emplace(AZStd::make_pair(entityId, AZStd::move(dom)));
m_entitySavedStates[entityId] = {AZStd::move(dom), parentId};
}
void PrefabUndoCache::Clear()
@@ -46,14 +46,19 @@ namespace AzToolsFramework
void Validate(const AZ::EntityId& entityId) override;
// Retrieve the last known state for an entity
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom);
bool Retrieve(const AZ::EntityId& entityId, PrefabDom& outDom, AZ::EntityId& parentId);
// Store dom as the cached state of entityId
void Store(const AZ::EntityId& entityId, PrefabDom&& dom);
void Store(const AZ::EntityId& entityId, PrefabDom&& dom, const AZ::EntityId& parentId);
private:
typedef AZStd::unordered_map<AZ::EntityId, PrefabDom> EntityDomMap;
EntityDomMap m_entitySavedStates;
struct PrefabUndoCacheItem
{
PrefabDom dom;
AZ::EntityId parentId;
};
typedef AZStd::unordered_map<AZ::EntityId, PrefabUndoCacheItem> EntityCache;
EntityCache m_entitySavedStates;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
InstanceToTemplateInterface* m_instanceToTemplateInterface = nullptr;
@@ -37,13 +37,13 @@ namespace AzToolsFramework
axisLength, AzFramework::ViewportColors::XAxisColor, AzFramework::ViewportColors::YAxisColor,
AzFramework::ViewportColors::ZAxisColor);
auto mouseDownCallback = [this](const LinearManipulator::Action& action) {
auto mouseDownCallback = [this]([[maybe_unused]] const LinearManipulator::Action& action)
{
AZ::Vector3 nonUniformScale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleRequestBus::EventResult(
nonUniformScale, m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::GetScale);
m_initialScale = nonUniformScale + action.m_start.m_scaleSnapOffset;
m_initialScale = nonUniformScale;
AZ::NonUniformScaleRequestBus::Event(
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale, m_initialScale);
@@ -51,29 +51,37 @@ namespace AzToolsFramework
m_manipulators->InstallAxisLeftMouseDownCallback(mouseDownCallback);
m_manipulators->InstallAxisMouseMoveCallback([this](const LinearManipulator::Action& action) {
const AZ::Vector3 scaleMultiplier =
(AZ::Vector3::CreateOne() + ((action.LocalScaleOffset() * action.m_start.m_sign) / m_initialScale));
m_manipulators->InstallAxisMouseMoveCallback(
[this](const LinearManipulator::Action& action)
{
const AZ::Vector3 scaleMultiplier =
(AZ::Vector3::CreateOne() + ((action.LocalScaleOffset() * action.m_start.m_sign) / m_initialScale));
AZ::NonUniformScaleRequestBus::Event(
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale,
(scaleMultiplier * m_initialScale).GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale)));
});
AZ::NonUniformScaleRequestBus::Event(
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale,
(scaleMultiplier * m_initialScale)
.GetClamp(AZ::Vector3(AZ::MinTransformScale), AZ::Vector3(AZ::MaxTransformScale)));
});
m_manipulators->InstallUniformLeftMouseDownCallback(mouseDownCallback);
m_manipulators->InstallUniformMouseMoveCallback([this](const LinearManipulator::Action& action) {
const auto sumVectorElements = [](const AZ::Vector3& vec) { return vec.GetX() + vec.GetY() + vec.GetZ(); };
m_manipulators->InstallUniformMouseMoveCallback(
[this](const LinearManipulator::Action& action)
{
const auto sumVectorElements = [](const AZ::Vector3& vec)
{
return vec.GetX() + vec.GetY() + vec.GetZ();
};
const float minScaleMultiplier = AZ::MinTransformScale / m_initialScale.GetMinElement();
const float maxScaleMultiplier = AZ::MaxTransformScale / m_initialScale.GetMaxElement();
const float scaleMultiplier = AZ::GetClamp(
1.0f + sumVectorElements(action.m_start.m_sign * action.LocalScaleOffset() / m_initialScale), minScaleMultiplier,
maxScaleMultiplier);
const float minScaleMultiplier = AZ::MinTransformScale / m_initialScale.GetMinElement();
const float maxScaleMultiplier = AZ::MaxTransformScale / m_initialScale.GetMaxElement();
const float scaleMultiplier = AZ::GetClamp(
1.0f + sumVectorElements(action.m_start.m_sign * action.LocalScaleOffset() / m_initialScale), minScaleMultiplier,
maxScaleMultiplier);
AZ::NonUniformScaleRequestBus::Event(
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale, scaleMultiplier * m_initialScale);
});
AZ::NonUniformScaleRequestBus::Event(
m_entityComponentIdPair.GetEntityId(), &AZ::NonUniformScaleRequests::SetScale, scaleMultiplier * m_initialScale);
});
}
NonUniformScaleComponentMode::~NonUniformScaleComponentMode()
@@ -124,4 +124,15 @@ namespace AzToolsFramework
return cameraState;
}
float GetScreenDisplayScaling(const int viewportId)
{
float scaling = 1.0f;
ViewportInteraction::ViewportInteractionRequestBus::EventResult(
scaling, viewportId,
&ViewportInteraction::ViewportInteractionRequestBus::Events::DeviceScalingFactor);
return scaling;
}
} // namespace AzToolsFramework
@@ -60,6 +60,9 @@ namespace AzToolsFramework
/// Wrapper for EBus call to return the CameraState for a given viewport.
AzFramework::CameraState GetCameraState(int viewportId);
/// Wrapper for EBus call to return the DPI scaling for a given viewport.
float GetScreenDisplayScaling(const int viewportId);
/// A utility to return the center of several points.
/// Take several positions and store the min and max of each in
/// turn - when all points have been added return the center/midpoint.
@@ -423,15 +423,14 @@ namespace AzToolsFramework
}
}
static void InitializeTranslationLookup(
EntityIdManipulators& entityIdManipulators, const AZ::Vector3& snapOffset)
static void InitializeTranslationLookup(EntityIdManipulators& entityIdManipulators)
{
AZ_PROFILE_FUNCTION(AZ::Debug::ProfileCategory::AzToolsFramework);
for (auto& entityIdLookup : entityIdManipulators.m_lookups)
{
entityIdLookup.second.m_initial =
AZ::Transform::CreateTranslation(GetWorldTranslation(entityIdLookup.first) + snapOffset);
AZ::Transform::CreateTranslation(GetWorldTranslation(entityIdLookup.first));
}
}
@@ -820,7 +819,7 @@ namespace AzToolsFramework
// moving with ctrl - setting override
pivotOverrideFrame.m_translationOverride =
entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
InitializeTranslationLookup(entityIdManipulators, -action.LocalPositionOffset());
InitializeTranslationLookup(entityIdManipulators);
}
else
{
@@ -1277,12 +1276,12 @@ namespace AzToolsFramework
// linear
translationManipulators->InstallLinearManipulatorMouseDownCallback(
[this, manipulatorEntityIds](const LinearManipulator::Action& action) mutable
[this, manipulatorEntityIds]([[maybe_unused]] const LinearManipulator::Action& action) mutable
{
// important to sort entityIds based on hierarchy order when updating transforms
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
InitializeTranslationLookup(m_entityIdManipulators, action.m_start.m_positionSnapOffset);
InitializeTranslationLookup(m_entityIdManipulators);
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(
@@ -1302,19 +1301,19 @@ namespace AzToolsFramework
});
translationManipulators->InstallLinearManipulatorMouseUpCallback(
[this](const LinearManipulator::Action& /*action*/) mutable
[this]([[maybe_unused]] const LinearManipulator::Action& action) mutable
{
EndRecordManipulatorCommand();
});
// planar
translationManipulators->InstallPlanarManipulatorMouseDownCallback(
[this, manipulatorEntityIds](const PlanarManipulator::Action& action)
[this, manipulatorEntityIds]([[maybe_unused]] const PlanarManipulator::Action& action)
{
// important to sort entityIds based on hierarchy order when updating transforms
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
InitializeTranslationLookup(m_entityIdManipulators, action.m_start.m_snapOffset);
InitializeTranslationLookup(m_entityIdManipulators);
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(
@@ -1340,11 +1339,11 @@ namespace AzToolsFramework
// surface
translationManipulators->InstallSurfaceManipulatorMouseDownCallback(
[this, manipulatorEntityIds](const SurfaceManipulator::Action& action)
[this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action)
{
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
InitializeTranslationLookup(m_entityIdManipulators, action.m_start.m_snapOffset);
InitializeTranslationLookup(m_entityIdManipulators);
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(
@@ -3326,26 +3325,16 @@ namespace AzToolsFramework
}
static void DrawManipulatorGrid(
AzFramework::DebugDisplayRequests& debugDisplay, const EntityIdManipulators& entityIdManipulators,
const float gridSize, const float localSnapping)
AzFramework::DebugDisplayRequests& debugDisplay, const EntityIdManipulators& entityIdManipulators, const float gridSize)
{
const AZ::Matrix3x3 orientation =
AZ::Matrix3x3::CreateFromTransform(entityIdManipulators.m_manipulators->GetLocalTransform());
const AZ::Vector3 unsnappedTranslation =
const AZ::Vector3 translation =
entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
// calculate the offset to snap by to align the manipulator to the grid
// note: only perform this if we are not snapping in local space
const AZ::Vector3 snappedOffset = !localSnapping
? CalculateSnappedOffset(unsnappedTranslation, orientation.GetBasisX(), gridSize) +
CalculateSnappedOffset(unsnappedTranslation, orientation.GetBasisY(), gridSize)
: AZ::Vector3::CreateZero();
const AZ::Vector3 snappedTranslation = unsnappedTranslation + snappedOffset;
DrawSnappingGrid(
debugDisplay, AZ::Transform::CreateFromMatrix3x3AndTranslation(orientation, snappedTranslation),
debugDisplay, AZ::Transform::CreateFromMatrix3x3AndTranslation(orientation, translation),
gridSize);
}
@@ -3484,7 +3473,7 @@ namespace AzToolsFramework
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportInfo.m_viewportId);
if (gridSnapParams.m_gridSnap && m_entityIdManipulators.m_manipulators)
{
DrawManipulatorGrid(debugDisplay, m_entityIdManipulators, gridSnapParams.m_gridSize, modifiers.Alt());
DrawManipulatorGrid(debugDisplay, m_entityIdManipulators, gridSnapParams.m_gridSize);
}
}
}
@@ -3573,9 +3562,10 @@ namespace AzToolsFramework
debugDisplay.SetLineWidth(1.0f);
const float labelOffset = cl_viewportGizmoAxisLabelOffset;
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize;
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize;
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize;
const float screenScale = GetScreenDisplayScaling(viewportId);
const auto labelXScreenPosition = (gizmoStart + (gizmoAxisX * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelYScreenPosition = (gizmoStart + (gizmoAxisY * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
const auto labelZScreenPosition = (gizmoStart + (gizmoAxisZ * labelOffset)) * editorCameraState.m_viewportSize * screenScale;
// draw the label of of each axis for the gizmo
const float labelSize = cl_viewportGizmoAxisLabelSize;