Merge branch 'upstream/development' into LYN-8514_AutomatedReviewServerLogChecks

This commit is contained in:
Gene Walters
2021-11-23 19:15:53 -08:00
65 changed files with 1239 additions and 463 deletions
@@ -95,7 +95,8 @@ def EntityOutliner_EntityOrdering():
entity_outliner_model.dropMimeData( entity_outliner_model.dropMimeData(
mime_data, QtCore.Qt.MoveAction, target_row, 0, target_index.parent() mime_data, QtCore.Qt.MoveAction, target_row, 0, target_index.parent()
) )
QtWidgets.QApplication.processEvents() # Wait after move to let events (i.e. prefab propagation) process
general.idle_wait(1.0)
# Move an entity before another entity in the order by dragging the source above the target # Move an entity before another entity in the order by dragging the source above the target
move_entity_before = lambda source_name, target_name: _move_entity( move_entity_before = lambda source_name, target_name: _move_entity(
@@ -119,24 +120,24 @@ def EntityOutliner_EntityOrdering():
# Our new entity should be given a name with a number automatically # Our new entity should be given a name with a number automatically
new_entity = f"Entity{i+1}" new_entity = f"Entity{i+1}"
# The new entity should be added to the top of its parent entity # The new entity should be added to the bottom of its parent entity
expected_order = [new_entity] + expected_order expected_order = expected_order + [new_entity]
verify_entities_sorted(expected_order) verify_entities_sorted(expected_order)
# 3) Move "Entity1" to the top of the order # 3) Move "Entity5" to the top of the order
move_entity_before("Entity1", "Entity5") move_entity_before("Entity5", "Entity1")
expected_order = ["Entity1", "Entity5", "Entity4", "Entity3", "Entity2"] expected_order = ["Entity5", "Entity1", "Entity2", "Entity3", "Entity4"]
verify_entities_sorted(expected_order) verify_entities_sorted(expected_order)
# 4) Move "Entity4" to the bottom of the order # 4) Move "Entity2" to the bottom of the order
move_entity_after("Entity4", "Entity2") move_entity_after("Entity2", "Entity4")
expected_order = ["Entity1", "Entity5", "Entity3", "Entity2", "Entity4"] expected_order = ["Entity5", "Entity1", "Entity3", "Entity4", "Entity2"]
verify_entities_sorted(expected_order) verify_entities_sorted(expected_order)
# 5) Add another new entity, ensure the rest of the order is unchanged # 5) Add another new entity, ensure the rest of the order is unchanged
create_entity() create_entity()
expected_order = ["Entity6", "Entity1", "Entity5", "Entity3", "Entity2", "Entity4"] expected_order = ["Entity5", "Entity1", "Entity3", "Entity4", "Entity2", "Entity6"]
verify_entities_sorted(expected_order) verify_entities_sorted(expected_order)
+1 -1
View File
@@ -131,7 +131,7 @@ ly_add_source_properties(
PROPERTY COMPILE_DEFINITIONS PROPERTY COMPILE_DEFINITIONS
VALUES VALUES
O3DE_COPYRIGHT_YEAR=${LY_VERSION_COPYRIGHT_YEAR} O3DE_COPYRIGHT_YEAR=${LY_VERSION_COPYRIGHT_YEAR}
LY_BUILD=${LY_VERSION_BUILD_NUMBER} LY_VERSION_BUILD_NUMBER=${LY_VERSION_BUILD_NUMBER}
${LY_PAL_TOOLS_DEFINES} ${LY_PAL_TOOLS_DEFINES}
) )
ly_add_source_properties( ly_add_source_properties(
+2 -6
View File
@@ -911,13 +911,9 @@ namespace
QWidget* g_splashScreen = nullptr; QWidget* g_splashScreen = nullptr;
} }
QString FormatVersion(const SFileVersion& v) QString FormatVersion([[maybe_unused]] const SFileVersion& v)
{ {
#if defined(LY_BUILD) return QObject::tr("Version %1").arg(LY_VERSION_BUILD_NUMBER);
return QObject::tr("Version %1.%2.%3.%4 - Build %5").arg(v[3]).arg(v[2]).arg(v[1]).arg(v[0]).arg(LY_BUILD);
#else
return QObject::tr("Version %1.%2.%3.%4").arg(v[3]).arg(v[2]).arg(v[1]).arg(v[0]);
#endif
} }
QString FormatRichTextCopyrightNotice() QString FormatRichTextCopyrightNotice()
@@ -130,10 +130,11 @@ namespace AZStd::Internal
//! Constructors //! Constructors
constexpr fixed_trivial_storage() = default; fixed_trivial_storage() = default;
template <typename U, typename = enable_if_t<is_convertible_v<U, T>>> template <typename U, typename = enable_if_t<is_convertible_v<U, T>>>
constexpr fixed_trivial_storage(AZStd::initializer_list<U> ilist) noexcept fixed_trivial_storage(AZStd::initializer_list<U> ilist) noexcept
: m_size(aznumeric_caster(ilist.size()))
{ {
AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity"); AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity");
size_t index{}; size_t index{};
@@ -141,20 +142,19 @@ namespace AZStd::Internal
{ {
m_data[index++] = element; m_data[index++] = element;
} }
resize_no_construct(ilist.size());
} }
constexpr pointer data() noexcept pointer data() noexcept
{ {
return m_data; return m_data;
} }
constexpr const_pointer data() const noexcept const_pointer data() const noexcept
{ {
return m_data; return m_data;
} }
//! Number of elements currently stored. //! Number of elements currently stored.
constexpr size_type size() const noexcept size_type size() const noexcept
{ {
return m_size; return m_size;
} }
@@ -164,12 +164,12 @@ namespace AZStd::Internal
return Capacity; return Capacity;
} }
//! Is the storage empty? //! Is the storage empty?
constexpr bool empty() const noexcept bool empty() const noexcept
{ {
return size() == 0; return size() == 0;
} }
//! Is the storage full? //! Is the storage full?
constexpr bool full() const noexcept bool full() const noexcept
{ {
return size() == capacity(); return size() == capacity();
} }
@@ -186,7 +186,7 @@ namespace AZStd::Internal
//! Increases size of the storage by one. //! Increases size of the storage by one.
//! Always fails for empty storage. //! Always fails for empty storage.
template <typename... Args, typename = enable_if_t<is_constructible_v<T, Args...>>> template <typename... Args, typename = enable_if_t<is_constructible_v<T, Args...>>>
constexpr reference emplace_back(Args&&... args) noexcept reference emplace_back(Args&&... args) noexcept
{ {
AZSTD_CONTAINER_ASSERT(!full(), "emplace_back cannot be invoked on full storage"); AZSTD_CONTAINER_ASSERT(!full(), "emplace_back cannot be invoked on full storage");
reference new_element = *(data() + size()); reference new_element = *(data() + size());
@@ -196,7 +196,7 @@ namespace AZStd::Internal
} }
//! Removes the last element of the storage. //! Removes the last element of the storage.
//! Precondition: size is not empty //! Precondition: size is not empty
constexpr void pop_back() noexcept void pop_back() noexcept
{ {
AZSTD_CONTAINER_ASSERT(!empty(), "pop_back cannot be invoked on empty storage"); AZSTD_CONTAINER_ASSERT(!empty(), "pop_back cannot be invoked on empty storage");
resize_no_construct(size() - 1); resize_no_construct(size() - 1);
@@ -205,7 +205,7 @@ namespace AZStd::Internal
//! removing elements (unsafe). //! removing elements (unsafe).
//! //!
//! Updates the size of the container while checking that the new size is less than capacity //! Updates the size of the container while checking that the new size is less than capacity
constexpr void resize_no_construct(size_t new_size) noexcept void resize_no_construct(size_t new_size) noexcept
{ {
AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity"); AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity");
m_size = aznumeric_cast<size_type>(new_size); m_size = aznumeric_cast<size_type>(new_size);
@@ -215,19 +215,19 @@ namespace AZStd::Internal
//! This does not modify the size of the storage //! This does not modify the size of the storage
//! This is a no-op for trivial types //! This is a no-op for trivial types
template <typename InputIt, typename = enable_if_t<Internal::is_input_iterator_v<InputIt>>> template <typename InputIt, typename = enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr void unsafe_destroy(InputIt, InputIt) noexcept void unsafe_destroy(InputIt, InputIt) noexcept
{ {
} }
//! Destructs all elements of the storage. //! Destructs all elements of the storage.
//! This does not modify the size of the storage //! This does not modify the size of the storage
//! This is a no-op for trivial types //! This is a no-op for trivial types
constexpr void unsafe_destroy_all() noexcept void unsafe_destroy_all() noexcept
{ {
} }
private: private:
T m_data[Capacity]{}; T m_data[Capacity];
size_type m_size{}; size_type m_size{};
}; };
@@ -245,7 +245,7 @@ namespace AZStd::Internal
using reference = T&; using reference = T&;
using const_reference = const T&; using const_reference = const T&;
constexpr fixed_non_trivial_storage() = default; fixed_non_trivial_storage() = default;
~fixed_non_trivial_storage() noexcept ~fixed_non_trivial_storage() noexcept
{ {
@@ -253,7 +253,7 @@ namespace AZStd::Internal
} }
template <typename U, typename = enable_if_t<is_convertible_v<U, T>>> template <typename U, typename = enable_if_t<is_convertible_v<U, T>>>
constexpr fixed_non_trivial_storage(AZStd::initializer_list<U> ilist) noexcept(noexcept(emplace_back(AZStd::declval<U>()))) fixed_non_trivial_storage(AZStd::initializer_list<U> ilist) noexcept(noexcept(emplace_back(AZStd::declval<U>())))
{ {
AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity"); AZSTD_CONTAINER_ASSERT(ilist.size() <= capacity(), "Initializer list cannot be larger than storage capacity");
for (const U& element : ilist) for (const U& element : ilist)
@@ -272,7 +272,7 @@ namespace AZStd::Internal
} }
//! Number of elements currently stored. //! Number of elements currently stored.
constexpr size_type size() const noexcept size_type size() const noexcept
{ {
return m_size; return m_size;
} }
@@ -282,12 +282,12 @@ namespace AZStd::Internal
return Capacity; return Capacity;
} }
//! Is the storage empty? //! Is the storage empty?
constexpr bool empty() const noexcept bool empty() const noexcept
{ {
return size() == 0; return size() == 0;
} }
//! Is the storage full? //! Is the storage full?
constexpr bool full() const noexcept bool full() const noexcept
{ {
return size() == capacity(); return size() == capacity();
} }
@@ -325,7 +325,7 @@ namespace AZStd::Internal
//! removing elements (unsafe). //! removing elements (unsafe).
//! //!
//! Updates the size of the container while checking that the new size is less than capacity //! Updates the size of the container while checking that the new size is less than capacity
constexpr void resize_no_construct(size_t new_size) noexcept void resize_no_construct(size_t new_size) noexcept
{ {
AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity"); AZSTD_CONTAINER_ASSERT(new_size <= capacity(), "New size cannot be larger than capacity");
m_size = aznumeric_cast<size_type>(new_size); m_size = aznumeric_cast<size_type>(new_size);
@@ -402,23 +402,23 @@ namespace AZStd
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// 23.2.4.1 construct/copy/destroy // 23.2.4.1 construct/copy/destroy
constexpr fixed_vector() = default; fixed_vector() = default;
constexpr explicit fixed_vector(size_type numElements, const_reference value = value_type()) explicit fixed_vector(size_type numElements, const_reference value = value_type())
{ {
resize_no_construct(numElements); resize_no_construct(numElements);
AZStd::uninitialized_fill_n(data(), numElements, value); AZStd::uninitialized_fill_n(data(), numElements, value);
} }
template <class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>> template <class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr fixed_vector(InputIt first, InputIt last) fixed_vector(InputIt first, InputIt last)
{ {
resize_no_construct(AZStd::distance(first, last)); resize_no_construct(AZStd::distance(first, last));
AZStd::uninitialized_copy(first, last, data()); AZStd::uninitialized_copy(first, last, data());
} }
constexpr fixed_vector(const fixed_vector& rhs) fixed_vector(const fixed_vector& rhs)
{ {
resize_no_construct(rhs.size()); resize_no_construct(rhs.size());
AZStd::uninitialized_copy(rhs.data(), rhs.data() + rhs.size(), data()); AZStd::uninitialized_copy(rhs.data(), rhs.data() + rhs.size(), data());
@@ -428,7 +428,7 @@ namespace AZStd
// It performs an AZStd::move on each of the fixed_vector elements instead // It performs an AZStd::move on each of the fixed_vector elements instead
// of swapping pointers to the allocted memory address // of swapping pointers to the allocted memory address
// as it is unable to perform that operations due to the storage being baked into the container // as it is unable to perform that operations due to the storage being baked into the container
constexpr fixed_vector(fixed_vector&& rhs) fixed_vector(fixed_vector&& rhs)
{ {
resize_no_construct(rhs.size()); resize_no_construct(rhs.size());
AZStd::uninitialized_move(rhs.data(), rhs.data() + rhs.size(), data()); AZStd::uninitialized_move(rhs.data(), rhs.data() + rhs.size(), data());
@@ -440,7 +440,7 @@ namespace AZStd
// into a fixed_vector given that the type in question isn't the same type as this fixed_vector type // into a fixed_vector given that the type in question isn't the same type as this fixed_vector type
template <typename VectorContainer, typename = AZStd::enable_if_t<!AZStd::is_same_v<VectorContainer, fixed_vector> template <typename VectorContainer, typename = AZStd::enable_if_t<!AZStd::is_same_v<VectorContainer, fixed_vector>
&& !AZStd::is_convertible_v<VectorContainer, size_type>>> && !AZStd::is_convertible_v<VectorContainer, size_type>>>
constexpr fixed_vector(VectorContainer&& rhs) fixed_vector(VectorContainer&& rhs)
{ {
constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v<VectorContainer> constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v<VectorContainer>
|| AZStd::is_const_v<VectorContainer>; || AZStd::is_const_v<VectorContainer>;
@@ -459,12 +459,12 @@ namespace AZStd
} }
} }
constexpr fixed_vector(AZStd::initializer_list<value_type> ilist) fixed_vector(AZStd::initializer_list<value_type> ilist)
: base_type(ilist) : base_type(ilist)
{ {
} }
constexpr fixed_vector& operator=(const fixed_vector& rhs) fixed_vector& operator=(const fixed_vector& rhs)
{ {
if (this == &rhs) if (this == &rhs)
{ {
@@ -475,7 +475,7 @@ namespace AZStd
return assign_helper(rhs); return assign_helper(rhs);
} }
constexpr fixed_vector& operator=(fixed_vector&& rhs) fixed_vector& operator=(fixed_vector&& rhs)
{ {
if (this == &rhs) if (this == &rhs)
{ {
@@ -487,23 +487,23 @@ namespace AZStd
} }
template <typename VectorContainer> template <typename VectorContainer>
constexpr AZStd::enable_if_t<!AZStd::is_same_v<AZStd::remove_cvref_t<VectorContainer>, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs) AZStd::enable_if_t<!AZStd::is_same_v<AZStd::remove_cvref_t<VectorContainer>, fixed_vector>, fixed_vector>& operator=(VectorContainer&& rhs)
{ {
return assign_helper(AZStd::forward<VectorContainer>(rhs)); return assign_helper(AZStd::forward<VectorContainer>(rhs));
} }
constexpr iterator begin() { return iterator(data()); } iterator begin() { return iterator(data()); }
constexpr const_iterator begin() const { return const_iterator(data()); } const_iterator begin() const { return const_iterator(data()); }
constexpr const_iterator cbegin() const { return const_iterator(data()); } const_iterator cbegin() const { return const_iterator(data()); }
constexpr iterator end() { return iterator(data() + size()); } iterator end() { return iterator(data() + size()); }
constexpr const_iterator end() const { return const_iterator(data() + size()); } const_iterator end() const { return const_iterator(data() + size()); }
constexpr const_iterator cend() const { return const_iterator(data() + size()); } const_iterator cend() const { return const_iterator(data() + size()); }
constexpr reverse_iterator rbegin() { return reverse_iterator(end()); } reverse_iterator rbegin() { return reverse_iterator(end()); }
constexpr const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator rbegin() const { return const_reverse_iterator(end()); }
constexpr const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); } const_reverse_iterator crbegin() const { return const_reverse_iterator(end()); }
constexpr reverse_iterator rend() { return reverse_iterator(begin()); } reverse_iterator rend() { return reverse_iterator(begin()); }
constexpr const_reverse_iterator rend() const { return const_reverse_iterator(begin()); } const_reverse_iterator rend() const { return const_reverse_iterator(begin()); }
constexpr const_reverse_iterator crend() const { return const_reverse_iterator(begin()); } const_reverse_iterator crend() const { return const_reverse_iterator(begin()); }
// bring in fixed_vector_storage functions into scope // bring in fixed_vector_storage functions into scope
using base_type::data; using base_type::data;
@@ -514,7 +514,7 @@ namespace AZStd
// extension method // extension method
using base_type::resize_no_construct; using base_type::resize_no_construct;
constexpr size_type size() const noexcept size_type size() const noexcept
{ {
return base_type::size(); return base_type::size();
} }
@@ -527,12 +527,12 @@ namespace AZStd
return base_type::max_size(); return base_type::max_size();
} }
constexpr void resize(size_type newSize) void resize(size_type newSize)
{ {
return resize(newSize, value_type{}); return resize(newSize, value_type{});
} }
constexpr void resize(size_type newSize, const_reference value) void resize(size_type newSize, const_reference value)
{ {
size_type dataSize = size(); size_type dataSize = size();
if (dataSize < newSize) if (dataSize < newSize)
@@ -547,7 +547,7 @@ namespace AZStd
// Removes unused capacity - For fixed_vector this only asserts // Removes unused capacity - For fixed_vector this only asserts
// that the supplied capacity is not longer than the fixed_vector capacity // that the supplied capacity is not longer than the fixed_vector capacity
constexpr void reserve(size_type newCapacity) void reserve(size_type newCapacity)
{ {
// No-op - Implemented to provide consistent std::vector // No-op - Implemented to provide consistent std::vector
AZSTD_CONTAINER_ASSERT(newCapacity <= capacity(), AZSTD_CONTAINER_ASSERT(newCapacity <= capacity(),
@@ -556,79 +556,79 @@ namespace AZStd
} }
// Removes unused capacity - For fixed_vector this does nothing // Removes unused capacity - For fixed_vector this does nothing
constexpr void shrink_to_fit() void shrink_to_fit()
{ {
// No-op - Implemented to provide consistent std::vector // No-op - Implemented to provide consistent std::vector
} }
constexpr reference at(size_type position) reference at(size_type position)
{ {
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position); return *(data() + position);
} }
constexpr const_reference at(size_type position) const const_reference at(size_type position) const
{ {
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position); return *(data() + position);
} }
constexpr reference operator[](size_type position) reference operator[](size_type position)
{ {
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position); return *(data() + position);
} }
constexpr const_reference operator[](size_type position) const const_reference operator[](size_type position) const
{ {
AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range"); AZSTD_CONTAINER_ASSERT(position < size(), "AZStd::fixed_vector<>::at - position is out of range");
return *(data() + position); return *(data() + position);
} }
constexpr reference front() reference front()
{ {
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!"); AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!");
return *data(); return *data();
} }
constexpr const_reference front() const const_reference front() const
{ {
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!"); AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::front - container is empty!");
return *data(); return *data();
} }
constexpr reference back() reference back()
{ {
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!"); AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!");
return *(data() + size() - 1); return *(data() + size() - 1);
} }
constexpr const_reference back() const const_reference back() const
{ {
AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!"); AZSTD_CONTAINER_ASSERT(!empty(), "AZStd::fixed_vector<>::back - container is empty!");
return *(data() + size() - 1); return *(data() + size() - 1);
} }
constexpr void push_back(const_reference value) void push_back(const_reference value)
{ {
emplace_back(value); emplace_back(value);
} }
constexpr void assign(size_type numElements, const_reference value) void assign(size_type numElements, const_reference value)
{ {
clear(); clear();
insert(end(), numElements, value); insert(end(), numElements, value);
} }
template <class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>> template <class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr void assign(InputIt first, InputIt last) void assign(InputIt first, InputIt last)
{ {
clear(); clear();
insert(end(), first, last); insert(end(), first, last);
} }
constexpr void assign(AZStd::initializer_list<value_type> ilist) void assign(AZStd::initializer_list<value_type> ilist)
{ {
assign(ilist.begin(), ilist.end()); assign(ilist.begin(), ilist.end());
} }
template <typename... Args, typename = AZStd::enable_if_t<is_constructible_v<T, Args...>>> template <typename... Args, typename = AZStd::enable_if_t<is_constructible_v<T, Args...>>>
constexpr iterator emplace(const_iterator insertPos, Args&&... args) iterator emplace(const_iterator insertPos, Args&&... args)
{ {
AZSTD_CONTAINER_ASSERT(!full(), "Cannot emplace on a full fixed_vector"); AZSTD_CONTAINER_ASSERT(!full(), "Cannot emplace on a full fixed_vector");
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
@@ -645,18 +645,18 @@ namespace AZStd
AZStd::construct_at(insertPosPtr, AZStd::forward<Args>(args)...); AZStd::construct_at(insertPosPtr, AZStd::forward<Args>(args)...);
return iterator(insertPosPtr); return iterator(insertPosPtr);
} }
constexpr iterator insert(const_iterator insertPos, const_reference value) iterator insert(const_iterator insertPos, const_reference value)
{ {
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
return emplace(insertPos, value); return emplace(insertPos, value);
} }
constexpr iterator insert(const_iterator insertPos, value_type&& value) iterator insert(const_iterator insertPos, value_type&& value)
{ {
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
return emplace(insertPos, AZStd::move(value)); return emplace(insertPos, AZStd::move(value));
} }
constexpr void insert(const_iterator insertPos, size_type numElements, const_reference value) void insert(const_iterator insertPos, size_type numElements, const_reference value)
{ {
if (numElements == 0) if (numElements == 0)
{ {
@@ -708,24 +708,24 @@ namespace AZStd
} }
template<class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>> template<class InputIt, typename = AZStd::enable_if_t<Internal::is_input_iterator_v<InputIt>>>
constexpr void insert(const_iterator insertPos, InputIt first, InputIt last) void insert(const_iterator insertPos, InputIt first, InputIt last)
{ {
// specialize for iterator categories. // specialize for iterator categories.
AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container"); AZSTD_CONTAINER_ASSERT(insertPos >= cbegin() && insertPos <= cend(), "insert position must be in range of container");
insert_iter(insertPos, first, last, typename iterator_traits<InputIt>::iterator_category()); insert_iter(insertPos, first, last, typename iterator_traits<InputIt>::iterator_category());
}; };
constexpr void insert(const_iterator insertPos, AZStd::initializer_list<value_type> ilist) void insert(const_iterator insertPos, AZStd::initializer_list<value_type> ilist)
{ {
insert(insertPos, ilist.begin(), ilist.end()); insert(insertPos, ilist.begin(), ilist.end());
} }
constexpr iterator erase(const_iterator elementIter) iterator erase(const_iterator elementIter)
{ {
return erase(elementIter, elementIter + 1); return erase(elementIter, elementIter + 1);
} }
constexpr iterator erase(const_iterator first, const_iterator last) iterator erase(const_iterator first, const_iterator last)
{ {
AZSTD_CONTAINER_ASSERT(first >= cbegin() && last <= cend(), "erase iterator must be inside the range of fixed_vector container"); AZSTD_CONTAINER_ASSERT(first >= cbegin() && last <= cend(), "erase iterator must be inside the range of fixed_vector container");
iterator dataStart = begin(); iterator dataStart = begin();
@@ -741,12 +741,12 @@ namespace AZStd
return dataStart + offset; return dataStart + offset;
} }
constexpr void clear() void clear()
{ {
base_type::unsafe_destroy_all(); base_type::unsafe_destroy_all();
resize_no_construct(0); resize_no_construct(0);
} }
constexpr void swap(fixed_vector& rhs) void swap(fixed_vector& rhs)
{ {
// Fixed containers cannot swap pointers, they need to do full copies. // Fixed containers cannot swap pointers, they need to do full copies.
// The strategy is to extend the smaller fixed_vector to be the size // The strategy is to extend the smaller fixed_vector to be the size
@@ -776,12 +776,12 @@ namespace AZStd
} }
// Validate container status. // Validate container status.
constexpr bool validate() const bool validate() const
{ {
return size() <= max_size(); return size() <= max_size();
} }
// Validate iterator. // Validate iterator.
constexpr int validate_iterator(const_iterator iter) const int validate_iterator(const_iterator iter) const
{ {
const_pointer start = data(); const_pointer start = data();
const_pointer end = data() + size(); const_pointer end = data() + size();
@@ -799,19 +799,19 @@ namespace AZStd
} }
// pushes back an empty without a provided instance. // pushes back an empty without a provided instance.
constexpr void push_back() void push_back()
{ {
emplace_back(); emplace_back();
} }
constexpr void leak_and_reset() void leak_and_reset()
{ {
resize_no_construct(0); resize_no_construct(0);
} }
private: private:
template <typename VectorContainer> template <typename VectorContainer>
constexpr fixed_vector& assign_helper(VectorContainer&& rhs) fixed_vector& assign_helper(VectorContainer&& rhs)
{ {
constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v<VectorContainer> constexpr bool is_const_or_lvalue_reference = AZStd::is_lvalue_reference_v<VectorContainer>
|| AZStd::is_const_v<VectorContainer>; || AZStd::is_const_v<VectorContainer>;
@@ -872,7 +872,7 @@ namespace AZStd
} }
template<class Iterator> template<class Iterator>
constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&) void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const forward_iterator_tag&)
{ {
size_type numElements = AZStd::distance(first, last); size_type numElements = AZStd::distance(first, last);
if (numElements == 0) if (numElements == 0)
@@ -923,7 +923,7 @@ namespace AZStd
} }
template<class Iterator> template<class Iterator>
constexpr void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&) void insert_iter(const_iterator insertPos, Iterator first, Iterator last, const input_iterator_tag&)
{ {
iterator dataStart = data(); iterator dataStart = data();
size_type offset = AZStd::distance(dataStart, insertPos); size_type offset = AZStd::distance(dataStart, insertPos);
@@ -753,7 +753,7 @@ namespace UnitTest
TEST_F(Arrays, FixedVectorCanCopyAndMoveWithDifferentCapacity) TEST_F(Arrays, FixedVectorCanCopyAndMoveWithDifferentCapacity)
{ {
constexpr AZStd::fixed_vector<int, 32> sourceVector{ 1,2,3,4,5 }; AZStd::fixed_vector<int, 32> sourceVector{ 1,2,3,4,5 };
AZStd::fixed_vector<int, 8> copyConstructVector{ sourceVector }; AZStd::fixed_vector<int, 8> copyConstructVector{ sourceVector };
EXPECT_EQ(sourceVector, copyConstructVector); EXPECT_EQ(sourceVector, copyConstructVector);
@@ -768,32 +768,32 @@ namespace UnitTest
AZStd::fixed_vector<int, 16> moveAssignVector = AZStd::move(moveConstructVector); AZStd::fixed_vector<int, 16> moveAssignVector = AZStd::move(moveConstructVector);
constexpr AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 }; AZStd::fixed_vector expectedVector{ 1,2,3,4,5,6 };
EXPECT_EQ(expectedVector, moveAssignVector); EXPECT_EQ(expectedVector, moveAssignVector);
} }
TEST_F(Arrays, FixedVectorComparisonOperatorsSucceedAsExpected) TEST_F(Arrays, FixedVectorComparisonOperatorsSucceedAsExpected)
{ {
constexpr AZStd::fixed_vector<int, 32> testVector{ 1,2,3,4,5 }; AZStd::fixed_vector<int, 32> testVector{ 1,2,3,4,5 };
constexpr AZStd::fixed_vector<int, 32> equalVector{ 1,2,3,4,5 }; AZStd::fixed_vector<int, 32> equalVector{ 1,2,3,4,5 };
constexpr AZStd::fixed_vector<int, 32> notEqualVectorDifferentSize{ 1,2,3,4,5,6 }; AZStd::fixed_vector<int, 32> notEqualVectorDifferentSize{ 1,2,3,4,5,6 };
constexpr AZStd::fixed_vector<int, 32> lessVector{ 1,2,3,4,4 }; AZStd::fixed_vector<int, 32> lessVector{ 1,2,3,4,4 };
constexpr AZStd::fixed_vector<int, 32> greaterVectorDifferentSize{ 1,2,3,4,5, 1 }; AZStd::fixed_vector<int, 32> greaterVectorDifferentSize{ 1,2,3,4,5, 1 };
static_assert(testVector == equalVector); EXPECT_EQ(testVector, equalVector);
static_assert(testVector != notEqualVectorDifferentSize); EXPECT_NE(testVector, notEqualVectorDifferentSize);
static_assert(testVector != lessVector); EXPECT_NE(testVector, lessVector);
static_assert(lessVector < testVector); EXPECT_LT(lessVector, testVector);
static_assert(lessVector < greaterVectorDifferentSize); EXPECT_LT(lessVector, greaterVectorDifferentSize);
static_assert(lessVector <= lessVector); EXPECT_LE(lessVector, lessVector);
static_assert(lessVector <= testVector); EXPECT_LE(lessVector, testVector);
static_assert(lessVector <= greaterVectorDifferentSize); EXPECT_LE(lessVector, greaterVectorDifferentSize);
static_assert(testVector > lessVector); EXPECT_GT(testVector, lessVector);
static_assert(testVector > lessVector); EXPECT_GT(testVector, lessVector);
static_assert(notEqualVectorDifferentSize > testVector); EXPECT_GT(notEqualVectorDifferentSize, testVector);
static_assert(testVector >= testVector); EXPECT_GE(testVector, testVector);
static_assert(testVector >= lessVector); EXPECT_GE(testVector, lessVector);
static_assert(greaterVectorDifferentSize > lessVector); EXPECT_GT(greaterVectorDifferentSize, lessVector);
} }
TEST_F(Arrays, VectorSwap) TEST_F(Arrays, VectorSwap)
@@ -239,12 +239,14 @@ namespace AzToolsFramework
, m_isInIsolationMode(false) , m_isInIsolationMode(false)
{ {
ToolsApplicationRequests::Bus::Handler::BusConnect(); ToolsApplicationRequests::Bus::Handler::BusConnect();
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusConnect();
m_undoCache.RegisterToUndoCacheInterface(); m_undoCache.RegisterToUndoCacheInterface();
} }
ToolsApplication::~ToolsApplication() ToolsApplication::~ToolsApplication()
{ {
AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler::BusDisconnect();
ToolsApplicationRequests::Bus::Handler::BusDisconnect(); ToolsApplicationRequests::Bus::Handler::BusDisconnect();
Stop(); Stop();
} }
@@ -566,6 +568,12 @@ namespace AzToolsFramework
void ToolsApplication::MarkEntitySelected(AZ::EntityId entityId) void ToolsApplication::MarkEntitySelected(AZ::EntityId entityId)
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected."); AZ_Assert(entityId.IsValid(), "Invalid entity Id being marked as selected.");
EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId); EntityIdList::iterator foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
@@ -585,6 +593,11 @@ namespace AzToolsFramework
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
EntityIdList entitiesSelected; EntityIdList entitiesSelected;
entitiesSelected.reserve(entitiesToSelect.size()); entitiesSelected.reserve(entitiesToSelect.size());
@@ -608,6 +621,12 @@ namespace AzToolsFramework
void ToolsApplication::MarkEntityDeselected(AZ::EntityId entityId) void ToolsApplication::MarkEntityDeselected(AZ::EntityId entityId)
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId); auto foundIter = AZStd::find(m_selectedEntities.begin(), m_selectedEntities.end(), entityId);
if (foundIter != m_selectedEntities.end()) if (foundIter != m_selectedEntities.end())
{ {
@@ -625,6 +644,11 @@ namespace AzToolsFramework
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged); ToolsApplicationEvents::Bus::Broadcast(&ToolsApplicationEvents::BeforeEntitySelectionChanged);
EntityIdSet entitySetToDeselect(entitiesToDeselect.begin(), entitiesToDeselect.end()); EntityIdSet entitySetToDeselect(entitiesToDeselect.begin(), entitiesToDeselect.end());
@@ -679,6 +703,11 @@ namespace AzToolsFramework
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(AzToolsFramework);
if (m_freezeSelectionUpdates)
{
return;
}
// We're setting the selection set as a batch from an external caller. // We're setting the selection set as a batch from an external caller.
// * Filter out any unselectable entities // * Filter out any unselectable entities
// * Calculate selection/deselection delta so we can notify specific entities only on change. // * Calculate selection/deselection delta so we can notify specific entities only on change.
@@ -1569,6 +1598,16 @@ namespace AzToolsFramework
} }
} }
void ToolsApplication::OnPrefabInstancePropagationBegin()
{
m_freezeSelectionUpdates = true;
}
void ToolsApplication::OnPrefabInstancePropagationEnd()
{
m_freezeSelectionUpdates = false;
}
void ToolsApplication::CreateUndosForDirtyEntities() void ToolsApplication::CreateUndosForDirtyEntities()
{ {
AZ_PROFILE_FUNCTION(AzToolsFramework); AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -16,6 +16,7 @@
#include <AzToolsFramework/API/EditorEntityAPI.h> #include <AzToolsFramework/API/EditorEntityAPI.h>
#include <AzToolsFramework/Application/EditorEntityManager.h> #include <AzToolsFramework/Application/EditorEntityManager.h>
#include <AzToolsFramework/Commands/PreemptiveUndoCache.h> #include <AzToolsFramework/Commands/PreemptiveUndoCache.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#pragma once #pragma once
@@ -29,6 +30,7 @@ namespace AzToolsFramework
class ToolsApplication class ToolsApplication
: public AzFramework::Application : public AzFramework::Application
, public ToolsApplicationRequests::Bus::Handler , public ToolsApplicationRequests::Bus::Handler
, public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
{ {
public: public:
AZ_RTTI(ToolsApplication, "{2895561E-BE90-4CC3-8370-DD46FCF74C01}", AzFramework::Application); AZ_RTTI(ToolsApplication, "{2895561E-BE90-4CC3-8370-DD46FCF74C01}", AzFramework::Application);
@@ -169,6 +171,14 @@ namespace AzToolsFramework
}; };
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// PrefabPublicNotificationBus::Handler
void OnPrefabInstancePropagationBegin() override;
void OnPrefabInstancePropagationEnd() override;
//////////////////////////////////////////////////////////////////////////
void CreateUndosForDirtyEntities(); void CreateUndosForDirtyEntities();
void ConsistencyCheckUndoCache(); void ConsistencyCheckUndoCache();
AZ::Aabb m_selectionBounds; AZ::Aabb m_selectionBounds;
@@ -181,6 +191,7 @@ namespace AzToolsFramework
bool m_isDuringUndoRedo; bool m_isDuringUndoRedo;
bool m_isInIsolationMode; bool m_isInIsolationMode;
EntityIdSet m_isolatedEntityIdSet; EntityIdSet m_isolatedEntityIdSet;
bool m_freezeSelectionUpdates = false;
EditorEntityAPI* m_editorEntityAPI = nullptr; EditorEntityAPI* m_editorEntityAPI = nullptr;
@@ -449,16 +449,23 @@ namespace AzToolsFramework
AZStd::unordered_map<AZ::EntityId, AZStd::pair<AZ::EntityId, AZ::u64>>::const_iterator orderItr = m_savedOrderInfo.find(childId); AZStd::unordered_map<AZ::EntityId, AZStd::pair<AZ::EntityId, AZ::u64>>::const_iterator orderItr = m_savedOrderInfo.find(childId);
if (orderItr != m_savedOrderInfo.end() && orderItr->second.first == parentId) if (orderItr != m_savedOrderInfo.end() && orderItr->second.first == parentId)
{ {
bool sortOrderUpdated = AzToolsFramework::RecoverEntitySortInfo(parentId, childId, orderItr->second.second); bool isPrefabEnabled = false;
m_savedOrderInfo.erase(childId); AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
// force notify the child sort order changed on the parent entity info, but only if the restore didn't actually modify // If prefabs are enabled, rely on the component to do a sanity check instead of restoring the order from the model
// the order internally (and sent ChildEntityOrderArrayUpdated). that may seem heavy handed, and it is, but necessary if (!isPrefabEnabled)
// to combat scenarios when the initial override detection returns a false positive (see comment about IDH comparisons
// in OnChildSortOrderChanged) and the slice instance source-to-live mapping hasn't been fully reconstructed yet.
if (!sortOrderUpdated)
{ {
parentInfo.OnChildSortOrderChanged(); bool sortOrderUpdated = AzToolsFramework::RecoverEntitySortInfo(parentId, childId, orderItr->second.second);
m_savedOrderInfo.erase(childId);
// force notify the child sort order changed on the parent entity info, but only if the restore didn't actually modify
// the order internally (and sent ChildEntityOrderArrayUpdated). that may seem heavy handed, and it is, but necessary
// to combat scenarios when the initial override detection returns a false positive (see comment about IDH comparisons
// in OnChildSortOrderChanged) and the slice instance source-to-live mapping hasn't been fully reconstructed yet.
if (!sortOrderUpdated)
{
parentInfo.OnChildSortOrderChanged();
}
} }
} }
else else
@@ -8,11 +8,17 @@
#include "EditorEntitySortComponent.h" #include "EditorEntitySortComponent.h"
#include "EditorEntityInfoBus.h" #include "EditorEntityInfoBus.h"
#include "EditorEntityHelpers.h" #include "EditorEntityHelpers.h"
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Debug/Profiler.h> #include <AzCore/Debug/Profiler.h>
#include <AzCore/Serialization/EditContext.h> #include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/std/sort.h> #include <AzCore/std/sort.h>
#include <AzFramework/API/ApplicationAPI.h> #include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h> #include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponentSerializer.h>
static_assert(sizeof(AZ::u64) == sizeof(AZ::EntityId), "We use AZ::EntityId for Persistent ID, which is a u64 under the hood. These must be the same size otherwise the persistent id will have to be rewritten"); static_assert(sizeof(AZ::u64) == sizeof(AZ::EntityId), "We use AZ::EntityId for Persistent ID, which is a u64 under the hood. These must be the same size otherwise the persistent id will have to be rewritten");
@@ -51,6 +57,12 @@ namespace AzToolsFramework
; ;
} }
} }
AZ::JsonRegistrationContext* jsonRegistration = azrtti_cast<AZ::JsonRegistrationContext*>(context);
if (jsonRegistration)
{
jsonRegistration->Serializer<JsonEditorEntitySortComponentSerializer>()->HandlesType<EditorEntitySortComponent>();
}
} }
void EditorEntitySortComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services) void EditorEntitySortComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& services)
@@ -167,9 +179,6 @@ namespace AzToolsFramework
} }
MarkDirtyAndSendChangedEvent(); MarkDirtyAndSendChangedEvent();
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
return true; return true;
} }
@@ -187,6 +196,10 @@ namespace AzToolsFramework
else else
{ {
EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition(); EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition();
if (insertPosition != m_childEntityOrderArray.end())
{
++insertPosition;
}
retval = AddChildEntityInternal(entityId, false, insertPosition); retval = AddChildEntityInternal(entityId, false, insertPosition);
} }
@@ -220,9 +233,6 @@ namespace AzToolsFramework
MarkDirtyAndSendChangedEvent(); MarkDirtyAndSendChangedEvent();
// Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
return true; return true;
} }
return false; return false;
@@ -272,6 +282,12 @@ namespace AzToolsFramework
void EditorEntitySortComponent::OnPrefabInstancePropagationEnd() void EditorEntitySortComponent::OnPrefabInstancePropagationEnd()
{ {
m_ignoreIncomingOrderChanges = false; m_ignoreIncomingOrderChanges = false;
if (m_shouldSanityCheckStateAfterPropagation)
{
SanitizeOrderEntryArray();
m_shouldSanityCheckStateAfterPropagation = false;
}
} }
void EditorEntitySortComponent::MarkDirtyAndSendChangedEvent() void EditorEntitySortComponent::MarkDirtyAndSendChangedEvent()
@@ -280,14 +296,8 @@ namespace AzToolsFramework
// one of the event listeners needs to build the InstanceDataHierarchy // one of the event listeners needs to build the InstanceDataHierarchy
m_entityOrderIsDirty = true; m_entityOrderIsDirty = true;
// Force an immediate update for prefabs, which won't receive PrepareSave // Use the ToolsApplication to mark the entity dirty, this will only do something if we already have an undo batch
bool isPrefabEnabled = false; ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::AddDirtyEntity, GetEntityId());
AzFramework::ApplicationRequests::Bus::BroadcastResult(
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabEnabled)
{
PrepareSave();
}
EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated); EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated);
} }
@@ -308,9 +318,8 @@ namespace AzToolsFramework
isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); isPrefabEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled);
if (isPrefabEnabled) if (isPrefabEnabled)
{ {
PostLoad(); m_shouldSanityCheckStateAfterPropagation = true;
} }
// Send out that the order for our entity is now updated // Send out that the order for our entity is now updated
EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated); EditorEntitySortNotificationBus::Event(GetEntityId(), &EditorEntitySortNotificationBus::Events::ChildEntityOrderArrayUpdated);
} }
@@ -336,6 +345,73 @@ namespace AzToolsFramework
m_entityOrderIsDirty = false; m_entityOrderIsDirty = false;
} }
void EditorEntitySortComponent::SanitizeOrderEntryArray()
{
bool shouldEmitDirtyState = false;
// Remove invalid and duplicate entries that point at non-existent entities
AZStd::unordered_set<AZ::EntityId> duplicateIds;
for (auto it = m_childEntityOrderArray.begin(); it != m_childEntityOrderArray.end();)
{
if (!it->IsValid() || GetEntityById(*it) == nullptr || duplicateIds.contains(*it))
{
it = m_childEntityOrderArray.erase(it);
shouldEmitDirtyState = true;
}
else
{
duplicateIds.insert(*it);
++it;
}
}
// Append any missing children
EntityIdList children;
AZ::TransformBus::EventResult(children, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (auto it = m_childEntityOrderArray.begin(); it != m_childEntityOrderArray.end(); ++it)
{
if (auto removedChildrenIt = AZStd::remove(children.begin(), children.end(), *it); removedChildrenIt != children.end())
{
children.erase(removedChildrenIt);
}
}
AZStd::sort(children.begin(), children.end(), [](AZ::EntityId lhs, AZ::EntityId rhs)
{
return GetEntityById(lhs)->GetName() < GetEntityById(rhs)->GetName();
});
if (!children.empty())
{
shouldEmitDirtyState = true;
EntityOrderArray::iterator insertPosition = GetFirstSelectedEntityPosition();
if (insertPosition != m_childEntityOrderArray.end())
{
++insertPosition;
}
m_childEntityOrderArray.insert(insertPosition, children.begin(), children.end());
}
// Clear out the vector to be rebuilt from persistent id
m_childEntityOrderEntryArray.resize(m_childEntityOrderArray.size());
for (size_t i = 0; i < m_childEntityOrderArray.size(); ++i)
{
m_childEntityOrderEntryArray[i] = {
m_childEntityOrderArray[i],
static_cast<AZ::u64>(i)
};
}
RebuildEntityOrderCache();
if (shouldEmitDirtyState)
{
ToolsApplicationRequests::Bus::Broadcast(&ToolsApplicationRequests::Bus::Events::AddDirtyEntity, GetEntityId());
}
m_entityOrderIsDirty = false;
}
void EditorEntitySortComponent::PostLoad() void EditorEntitySortComponent::PostLoad()
{ {
// Clear out the vector to be rebuilt from persistent id // Clear out the vector to be rebuilt from persistent id
@@ -383,7 +459,7 @@ namespace AzToolsFramework
firstSelectedEntityPos = selectedEntityPos < firstSelectedEntityPos ? selectedEntityPos : firstSelectedEntityPos; firstSelectedEntityPos = selectedEntityPos < firstSelectedEntityPos ? selectedEntityPos : firstSelectedEntityPos;
} }
return firstSelectedEntityPos == m_childEntityOrderArray.end() ? m_childEntityOrderArray.begin() : firstSelectedEntityPos; return firstSelectedEntityPos;
} }
} }
} // namespace AzToolsFramework } // namespace AzToolsFramework
@@ -23,6 +23,8 @@ namespace AzToolsFramework
, public EditorEntityContextNotificationBus::Handler , public EditorEntityContextNotificationBus::Handler
, public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler , public AzToolsFramework::Prefab::PrefabPublicNotificationBus::Handler
{ {
friend class JsonEditorEntitySortComponentSerializer;
public: public:
AZ_COMPONENT(EditorEntitySortComponent, "{6EA1E03D-68B2-466D-97F7-83998C8C27F0}", EditorComponentBase); AZ_COMPONENT(EditorEntitySortComponent, "{6EA1E03D-68B2-466D-97F7-83998C8C27F0}", EditorComponentBase);
@@ -64,6 +66,8 @@ namespace AzToolsFramework
void PrepareSave(); void PrepareSave();
void PostLoad(); void PostLoad();
void SanitizeOrderEntryArray();
class EntitySortSerializationEvents class EntitySortSerializationEvents
: public AZ::SerializeContext::IEventHandler : public AZ::SerializeContext::IEventHandler
{ {
@@ -112,6 +116,7 @@ namespace AzToolsFramework
bool m_entityOrderIsDirty = true; ///< This flag indicates our stored serialization order data is out of date and must be rebuilt before serialization occurs bool m_entityOrderIsDirty = true; ///< This flag indicates our stored serialization order data is out of date and must be rebuilt before serialization occurs
bool m_ignoreIncomingOrderChanges = false; ///< This is set when prefab propagation occurs so that non-authored order changes can be ignored bool m_ignoreIncomingOrderChanges = false; ///< This is set when prefab propagation occurs so that non-authored order changes can be ignored
bool m_shouldSanityCheckStateAfterPropagation = false; //< This is set after activation, to queue a cleanup of any invalid state after the next prefab propagation.
}; };
} }
} // namespace AzToolsFramework } // namespace AzToolsFramework
@@ -0,0 +1,137 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
#include <AzCore/std/sort.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponentSerializer.h>
namespace AzToolsFramework::Components
{
AZ_CLASS_ALLOCATOR_IMPL(JsonEditorEntitySortComponentSerializer, AZ::SystemAllocator, 0);
AZ::JsonSerializationResult::Result JsonEditorEntitySortComponentSerializer::Load(
void* outputValue,
[[maybe_unused]] const AZ::Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorEntitySortComponent>() == outputValueTypeId,
"Unable to deserialize EditorEntitySortComponent from json because the provided type is %s.",
outputValueTypeId.ToString<AZStd::string>().c_str());
EditorEntitySortComponent* sortComponentInstance = reinterpret_cast<EditorEntitySortComponent*>(outputValue);
AZ_Assert(sortComponentInstance, "Output value for JsonEditorEntitySortComponentSerializer can't be null.");
JSR::ResultCode result(JSR::Tasks::ReadField);
{
JSR::ResultCode componentIdLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_id, azrtti_typeid<decltype(sortComponentInstance->m_id)>(), inputValue,
"Id", context);
result.Combine(componentIdLoadResult);
}
{
sortComponentInstance->m_childEntityOrderArray.clear();
JSR::ResultCode enryLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_childEntityOrderArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderArray)>(), inputValue, "Child Entity Order",
context);
// Migrate ChildEntityOrderEntryArray -> ChildEntityOrderArray
if (sortComponentInstance->m_childEntityOrderArray.empty())
{
enryLoadResult = ContinueLoadingFromJsonObjectField(
&sortComponentInstance->m_childEntityOrderEntryArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderEntryArray)>(), inputValue,
"ChildEntityOrderEntryArray", context);
AZStd::sort(
sortComponentInstance->m_childEntityOrderEntryArray.begin(),
sortComponentInstance->m_childEntityOrderEntryArray.end(),
[](const EditorEntitySortComponent::EntityOrderEntry& lhs,
const EditorEntitySortComponent::EntityOrderEntry& rhs) -> bool
{
return lhs.m_sortIndex < rhs.m_sortIndex;
});
// Sort by index and copy to the order array, any duplicates or invalid entries will be cleaned up by the sanitization pass
sortComponentInstance->m_childEntityOrderArray.resize(sortComponentInstance->m_childEntityOrderEntryArray.size());
for (size_t i = 0; i < sortComponentInstance->m_childEntityOrderEntryArray.size(); ++i)
{
sortComponentInstance->m_childEntityOrderArray[i] = sortComponentInstance->m_childEntityOrderEntryArray[i].m_entityId;
}
}
sortComponentInstance->RebuildEntityOrderCache();
result.Combine(enryLoadResult);
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully loaded EditorEntitySortComponent information."
: "Failed to load EditorEntitySortComponent information.");
}
AZ::JsonSerializationResult::Result JsonEditorEntitySortComponentSerializer::Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
[[maybe_unused]] const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context)
{
namespace JSR = AZ::JsonSerializationResult;
AZ_Assert(
azrtti_typeid<EditorEntitySortComponent>() == valueTypeId,
"Unable to Serialize EditorEntitySortComponent because the provided type is %s.",
valueTypeId.ToString<AZStd::string>().c_str());
const EditorEntitySortComponent* sortComponentInstance = reinterpret_cast<const EditorEntitySortComponent*>(inputValue);
AZ_Assert(sortComponentInstance, "Input value for JsonEditorEntitySortComponentSerializer can't be null.");
const EditorEntitySortComponent* defaultsortComponentInstance =
reinterpret_cast<const EditorEntitySortComponent*>(defaultValue);
JSR::ResultCode result(JSR::Tasks::WriteValue);
{
AZ::ScopedContextPath subPathName(context, "m_id");
const AZ::ComponentId* componentId = &sortComponentInstance->m_id;
const AZ::ComponentId* defaultComponentId =
defaultsortComponentInstance ? &defaultsortComponentInstance->m_id : nullptr;
JSR::ResultCode resultComponentId = ContinueStoringToJsonObjectField(
outputValue, "Id", componentId, defaultComponentId, azrtti_typeid<decltype(sortComponentInstance->m_id)>(),
context);
result.Combine(resultComponentId);
}
{
AZ::ScopedContextPath subPathName(context, "m_childEntityOrderArray");
const EntityOrderArray* childEntityOrderArray = &sortComponentInstance->m_childEntityOrderArray;
const EntityOrderArray* defaultChildEntityOrderArray =
defaultsortComponentInstance ? &defaultsortComponentInstance->m_childEntityOrderArray : nullptr;
JSR::ResultCode resultParentEntityId = ContinueStoringToJsonObjectField(
outputValue, "Child Entity Order", childEntityOrderArray, defaultChildEntityOrderArray,
azrtti_typeid<decltype(sortComponentInstance->m_childEntityOrderArray)>(), context);
result.Combine(resultParentEntityId);
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Successfully stored EditorEntitySortComponent information."
: "Failed to store EditorEntitySortComponent information.");
}
} // namespace AzToolsFramework::Components
@@ -0,0 +1,31 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Memory/Memory.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
namespace AzToolsFramework::Components
{
class JsonEditorEntitySortComponentSerializer
: public AZ::BaseJsonSerializer
{
public:
AZ_RTTI(JsonEditorEntitySortComponentSerializer, "{5104782E-B34F-4D87-B1DF-BDFB1AF20D58}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
AZ::JsonSerializationResult::Result Load(
void* outputValue, const AZ::Uuid& outputValueTypeId, const rapidjson::Value& inputValue,
AZ::JsonDeserializerContext& context) override;
AZ::JsonSerializationResult::Result Store(
rapidjson::Value& outputValue, const void* inputValue, const void* defaultValue, const AZ::Uuid& valueTypeId,
AZ::JsonSerializerContext& context) override;
};
} // namespace AzToolsFramework::Components
@@ -20,6 +20,7 @@
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h> #include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h> #include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h> #include <AzToolsFramework/Prefab/Template/Template.h>
#include <AzToolsFramework/Prefab/PrefabPublicRequestBus.h>
namespace AzToolsFramework namespace AzToolsFramework
{ {
@@ -244,10 +245,10 @@ namespace AzToolsFramework
selectedEntityIds.erase(entityIdIterator--); selectedEntityIds.erase(entityIdIterator--);
} }
} }
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
// Notify Propagation has ended // Notify Propagation has ended, then update selection (which is frozen during propagation, so this order matters)
PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotifications::OnPrefabInstancePropagationEnd); PrefabPublicNotificationBus::Broadcast(&PrefabPublicNotifications::OnPrefabInstancePropagationEnd);
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequests::SetSelectedEntities, selectedEntityIds);
} }
m_updatingTemplateInstancesInQueue = false; m_updatingTemplateInstancesInQueue = false;
@@ -28,6 +28,9 @@ namespace AzToolsFramework
inline static const char* EntityIdName = "Id"; inline static const char* EntityIdName = "Id";
inline static const char* EntitiesName = "Entities"; inline static const char* EntitiesName = "Entities";
inline static const char* ContainerEntityName = "ContainerEntity"; inline static const char* ContainerEntityName = "ContainerEntity";
inline static const char* ComponentsName = "Components";
inline static const char* EntityOrderName = "Child Entity Order";
inline static const char* TypeName = "$type";
/** /**
* Find Prefab value from given parent value and target value's name. * Find Prefab value from given parent value and target value's name.
@@ -212,7 +212,17 @@ namespace AzToolsFramework::Prefab
AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{ {
return m_focusedInstanceContainerEntityId; if (m_focusedInstanceContainerEntityId.IsValid())
{
return m_focusedInstanceContainerEntityId;
}
if (auto instance = GetReferenceFromContainerEntityId(m_focusedInstanceContainerEntityId); instance.has_value())
{
return instance->get().GetContainerEntityId();
}
return AZ::EntityId();
} }
bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const
@@ -366,7 +376,7 @@ namespace AzToolsFramework::Prefab
size_t index = 0; size_t index = 0;
size_t maxIndex = m_instanceFocusHierarchy.size() - 1; size_t maxIndex = m_instanceFocusHierarchy.size() - 1;
for (const AZ::EntityId containerEntityId : m_instanceFocusHierarchy) for (const AZ::EntityId& containerEntityId : m_instanceFocusHierarchy)
{ {
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
if (instance.has_value()) if (instance.has_value())
@@ -404,7 +414,7 @@ namespace AzToolsFramework::Prefab
return; return;
} }
for (const AZ::EntityId containerEntityId : instances) for (const AZ::EntityId& containerEntityId : instances)
{ {
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
@@ -423,7 +433,7 @@ namespace AzToolsFramework::Prefab
return; return;
} }
for (const AZ::EntityId containerEntityId : instances) for (const AZ::EntityId& containerEntityId : instances)
{ {
InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId); InstanceOptionalReference instance = GetReferenceFromContainerEntityId(containerEntityId);
@@ -9,6 +9,7 @@
#include <AzCore/Component/TransformBus.h> #include <AzCore/Component/TransformBus.h>
#include <AzCore/JSON/stringbuffer.h> #include <AzCore/JSON/stringbuffer.h>
#include <AzCore/JSON/writer.h> #include <AzCore/JSON/writer.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Utils/TypeHash.h> #include <AzCore/Utils/TypeHash.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h> #include <AzToolsFramework/API/ToolsApplicationAPI.h>
@@ -29,6 +30,7 @@
#include <AzToolsFramework/Prefab/PrefabUndo.h> #include <AzToolsFramework/Prefab/PrefabUndo.h>
#include <AzToolsFramework/Prefab/PrefabUndoHelpers.h> #include <AzToolsFramework/Prefab/PrefabUndoHelpers.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h> #include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
#include <QString> #include <QString>
@@ -595,8 +597,41 @@ namespace AzToolsFramework
Instance& entityOwningInstance = owningInstanceOfParentEntity->get(); Instance& entityOwningInstance = owningInstanceOfParentEntity->get();
// Get the template for our owning instance from the root prefab DOM and use that to generate our patch
AZStd::vector<InstanceOptionalConstReference> pathOfInstances;
InstanceOptionalReference rootInstance = owningInstanceOfParentEntity;
while (rootInstance->get().GetParentInstance() != AZStd::nullopt)
{
pathOfInstances.emplace_back(rootInstance);
rootInstance = rootInstance->get().GetParentInstance();
}
AZStd::string aliasPathResult = "";
for (auto instanceIter = pathOfInstances.rbegin(); instanceIter != pathOfInstances.rend(); ++instanceIter)
{
aliasPathResult.append("/Instances/");
aliasPathResult.append((*instanceIter)->get().GetInstanceAlias());
}
PrefabDomPath rootPrefabDomPath(aliasPathResult.c_str());
PrefabDom& rootPrefabTemplateDom = m_prefabSystemComponentInterface->FindTemplateDom(rootInstance->get().GetTemplateId());
auto instanceDomFromRootValue = rootPrefabDomPath.Get(rootPrefabTemplateDom);
if (!instanceDomFromRootValue)
{
return AZ::Failure<AZStd::string>("Could not load Instance DOM from the top level ancestor's DOM.");
}
PrefabDomValueReference instanceDomFromRoot = *instanceDomFromRootValue;
if (!instanceDomFromRoot.has_value())
{
return AZ::Failure<AZStd::string>("Could not load Instance DOM from the top level ancestor's DOM.");
}
PrefabDom instanceDomBeforeUpdate; PrefabDom instanceDomBeforeUpdate;
m_instanceToTemplateInterface->GenerateDomForInstance(instanceDomBeforeUpdate, entityOwningInstance); instanceDomBeforeUpdate.CopyFrom(instanceDomFromRoot.value().get(), instanceDomBeforeUpdate.GetAllocator());
ScopedUndoBatch undoBatch("Add Entity"); ScopedUndoBatch undoBatch("Add Entity");
@@ -674,6 +709,9 @@ namespace AzToolsFramework
bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId); bool isInstanceContainerEntity = IsInstanceContainerEntity(entityId) && !IsLevelInstanceContainerEntity(entityId);
bool isNewParentOwnedByDifferentInstance = false; bool isNewParentOwnedByDifferentInstance = false;
bool isInFocusTree = m_prefabFocusPublicInterface->IsOwningPrefabInFocusHierarchy(entityId);
bool isOwnedByFocusedPrefabInstance = m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId);
if (beforeParentId != afterParentId) if (beforeParentId != afterParentId)
{ {
// If the entity parent changed, verify if the owning instance changed too // If the entity parent changed, verify if the owning instance changed too
@@ -727,7 +765,7 @@ namespace AzToolsFramework
} }
} }
if (isInstanceContainerEntity) if (isInFocusTree && !isOwnedByFocusedPrefabInstance)
{ {
if (isNewParentOwnedByDifferentInstance) if (isNewParentOwnedByDifferentInstance)
{ {
@@ -1648,6 +1686,144 @@ namespace AzToolsFramework
return true; return true;
} }
void PrefabPublicHandler::AddNewEntityToSortOrder(
Instance& owningInstance,
PrefabDom& domToAddEntityUnder,
const EntityAlias& parentEntityAlias,
const EntityAlias& entityToAddAlias)
{
// Find the parent entity to get its sort order component
auto findParentEntity = [&]() -> rapidjson::Value*
{
if (auto containerEntityIter = domToAddEntityUnder.FindMember(PrefabDomUtils::ContainerEntityName);
containerEntityIter != domToAddEntityUnder.MemberEnd())
{
if (parentEntityAlias == containerEntityIter->value[PrefabDomUtils::EntityIdName].GetString())
{
return &containerEntityIter->value;
}
}
if (auto entitiesIter = domToAddEntityUnder.FindMember(PrefabDomUtils::EntitiesName);
entitiesIter != domToAddEntityUnder.MemberEnd())
{
for (auto entityIter = entitiesIter->value.MemberBegin(); entityIter != entitiesIter->value.MemberEnd(); ++entityIter)
{
if (parentEntityAlias == entityIter->value[PrefabDomUtils::EntityIdName].GetString())
{
return &entityIter->value;
}
}
}
return nullptr;
};
rapidjson::Value* parentEntityValue = findParentEntity();
if (parentEntityValue == nullptr)
{
return;
}
// Get the list of selected entities, we'll insert our duplicated entities after the last selected
// sibling in their parent's list, e.g. for:
// - Entity1
// - Entity2 (selected)
// - Entity3
// - Entity4 (selected)
// - Entity5
// Our duplicate selection command would create duplicate Entity2 and Entity4 and insert them after Entity4:
// - Entity1
// - Entity2
// - Entity3
// - Entity4
// - Entity2 (new, selected after duplicate)
// - Entity4 (new, selected after duplicate)
// - Entity5
AzToolsFramework::EntityIdList selectedEntities;
AzToolsFramework::ToolsApplicationRequestBus::BroadcastResult(
selectedEntities, &AzToolsFramework::ToolsApplicationRequests::GetSelectedEntities);
// Find the EditorEntitySortComponent DOM
auto componentsIter = parentEntityValue->FindMember(PrefabDomUtils::ComponentsName);
if (componentsIter == parentEntityValue->MemberEnd())
{
return;
}
for (auto componentIter = componentsIter->value.MemberBegin(); componentIter != componentIter->value.MemberEnd();
++componentIter)
{
// Check the component type
auto typeFieldIter = componentIter->value.FindMember(PrefabDomUtils::TypeName);
if (typeFieldIter == componentIter->value.MemberEnd())
{
continue;
}
AZ::JsonDeserializerSettings jsonDeserializerSettings;
AZ::Uuid typeId = AZ::Uuid::CreateNull();
AZ::JsonSerialization::LoadTypeId(typeId, typeFieldIter->value);
if (typeId != azrtti_typeid<Components::EditorEntitySortComponent>())
{
continue;
}
// Check for the entity order field
auto orderMembersIter = componentIter->value.FindMember(PrefabDomUtils::EntityOrderName);
if (orderMembersIter == componentIter->value.MemberEnd() || !orderMembersIter->value.IsArray())
{
continue;
}
// Scan for the last selected entity in the list (if any) to determine where to add our entries
rapidjson::Value newOrder(rapidjson::kArrayType);
auto insertValuesAfter = orderMembersIter->value.End();
for (auto orderMemberIter = orderMembersIter->value.Begin(); orderMemberIter != orderMembersIter->value.End();
++orderMemberIter)
{
if (!orderMemberIter->IsString())
{
continue;
}
const char* value = orderMemberIter->GetString();
for (AZ::EntityId selectedEntity : selectedEntities)
{
auto alias = owningInstance.GetEntityAlias(selectedEntity);
if (alias.has_value() && alias.value().get() == value)
{
insertValuesAfter = orderMemberIter;
break;
}
}
}
// Construct our new array with the new order - insertion may happen at end, so check for that in the loop itself
for (auto orderMemberIter = orderMembersIter->value.Begin();; ++orderMemberIter)
{
if (orderMemberIter != orderMembersIter->value.End())
{
newOrder.PushBack(orderMemberIter->Move(), domToAddEntityUnder.GetAllocator());
}
if (orderMemberIter == insertValuesAfter)
{
newOrder.PushBack(
rapidjson::Value(entityToAddAlias.c_str(), domToAddEntityUnder.GetAllocator()),
domToAddEntityUnder.GetAllocator());
}
if (orderMemberIter == orderMembersIter->value.End())
{
break;
}
}
// Replace the order with our newly constructed one
orderMembersIter->value.Swap(newOrder);
break;
}
}
void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance, void PrefabPublicHandler::DuplicateNestedEntitiesInInstance(Instance& commonOwningInstance,
const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder, const AZStd::vector<AZ::Entity*>& entities, PrefabDom& domToAddDuplicatedEntitiesUnder,
EntityIdList& duplicatedEntityIds, AZStd::unordered_map<EntityAlias, EntityAlias>& oldAliasToNewAliasMap) EntityIdList& duplicatedEntityIds, AZStd::unordered_map<EntityAlias, EntityAlias>& oldAliasToNewAliasMap)
@@ -1705,6 +1881,73 @@ namespace AzToolsFramework
PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator()); PrefabDom entityDomAfter(&domToAddDuplicatedEntitiesUnder.GetAllocator());
entityDomAfter.Parse(newEntityDomString.toUtf8().constData()); entityDomAfter.Parse(newEntityDomString.toUtf8().constData());
EntityAlias parentEntityAlias;
if (auto componentsIter = entityDomAfter.FindMember(PrefabDomUtils::ComponentsName);
componentsIter != entityDomAfter.MemberEnd())
{
auto checkComponent = [&](const rapidjson::Value& value) -> bool
{
if (!value.IsObject())
{
return false;
}
// Check the component type
auto typeFieldIter = value.FindMember(PrefabDomUtils::TypeName);
if (typeFieldIter == value.MemberEnd())
{
return false;
}
AZ::JsonDeserializerSettings jsonDeserializerSettings;
AZ::Uuid typeId = AZ::Uuid::CreateNull();
AZ::JsonSerialization::LoadTypeId(typeId, typeFieldIter->value);
// Prefabs get serialized with the Editor transform component type, check for that
if (typeId != azrtti_typeid<Components::TransformComponent>())
{
return false;
}
if (auto parentEntityIter = value.FindMember("Parent Entity");
parentEntityIter != value.MemberEnd())
{
parentEntityAlias = parentEntityIter->value.GetString();
return true;
}
return false;
};
if (componentsIter->value.IsObject())
{
for (auto componentIter = componentsIter->value.MemberBegin(); componentIter != componentsIter->value.MemberEnd();
++componentIter)
{
if (checkComponent(componentIter->value))
{
break;
}
}
}
else if (componentsIter->value.IsArray())
{
for (auto componentIter = componentsIter->value.Begin(); componentIter != componentsIter->value.End();
++componentIter)
{
if (checkComponent(*componentIter))
{
break;
}
}
}
}
// Insert our entity into its parent's sort order
if (!parentEntityAlias.empty())
{
AddNewEntityToSortOrder(commonOwningInstance, domToAddDuplicatedEntitiesUnder, parentEntityAlias, newEntityAlias);
}
// Add the new Entity DOM to the Entities member of the instance // Add the new Entity DOM to the Entities member of the instance
rapidjson::Value aliasName(newEntityAlias.c_str(), static_cast<rapidjson::SizeType>(newEntityAlias.length()), domToAddDuplicatedEntitiesUnder.GetAllocator()); rapidjson::Value aliasName(newEntityAlias.c_str(), static_cast<rapidjson::SizeType>(newEntityAlias.length()), domToAddDuplicatedEntitiesUnder.GetAllocator());
entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator()); entitiesIter->value.AddMember(AZStd::move(aliasName), entityDomAfter, domToAddDuplicatedEntitiesUnder.GetAllocator());
@@ -78,6 +78,8 @@ namespace AzToolsFramework
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const; InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const; bool EntitiesBelongToSameInstance(const EntityIdList& entityIds) const;
void AddNewEntityToSortOrder(Instance& owningInstance, PrefabDom& domToAddEntityUnder,
const EntityAlias& parentEntityAlias, const EntityAlias& entityToAddAlias);
/** /**
* Duplicate a list of entities owned by a common owning instance by directly * Duplicate a list of entities owned by a common owning instance by directly
@@ -1599,7 +1599,6 @@ namespace AzToolsFramework
for (size_t entityIndex = 1; entityIndex < m_selectedEntityIds.size(); ++entityIndex) for (size_t entityIndex = 1; entityIndex < m_selectedEntityIds.size(); ++entityIndex)
{ {
entity = GetSelectedEntityById(m_selectedEntityIds[entityIndex]); entity = GetSelectedEntityById(m_selectedEntityIds[entityIndex]);
AZ_Assert(entity, "Entity id selected for display but no such entity exists");
if (!entity) if (!entity)
{ {
continue; continue;
@@ -150,10 +150,7 @@ namespace AzToolsFramework
TypeBeingHandled actualValue = instance; TypeBeingHandled actualValue = instance;
for (int idx = 0; idx < m_common.GetElementCount(); ++idx) for (int idx = 0; idx < m_common.GetElementCount(); ++idx)
{ {
if (elements[idx]->wasValueEditedByUser()) actualValue.SetElement(idx, static_cast<float>(elements[idx]->getValue()));
{
actualValue.SetElement(idx, static_cast<float>(elements[idx]->getValue()));
}
} }
instance = actualValue; instance = actualValue;
} }
@@ -148,6 +148,8 @@ set(FILES
Entity/EditorEntitySortBus.h Entity/EditorEntitySortBus.h
Entity/EditorEntitySortComponent.cpp Entity/EditorEntitySortComponent.cpp
Entity/EditorEntitySortComponent.h Entity/EditorEntitySortComponent.h
Entity/EditorEntitySortComponentSerializer.cpp
Entity/EditorEntitySortComponentSerializer.h
Entity/EditorEntityTransformBus.h Entity/EditorEntityTransformBus.h
Entity/PrefabEditorEntityOwnershipInterface.h Entity/PrefabEditorEntityOwnershipInterface.h
Entity/PrefabEditorEntityOwnershipService.h Entity/PrefabEditorEntityOwnershipService.h
@@ -19,7 +19,7 @@ namespace O3DE::ProjectManager
{ {
// Attempt to use the Ninja build system if it is installed (described in the o3de documentation) if possible, // Attempt to use the Ninja build system if it is installed (described in the o3de documentation) if possible,
// otherwise default to the the default for Linux (Unix Makefiles) // otherwise default to the the default for Linux (Unix Makefiles)
auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"}, QProcessEnvironment::systemEnvironment()); auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"});
QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles"; QString cmakeGenerator = (whichNinjaResult.IsSuccess()) ? "Ninja Multi-Config" : "Unix Makefiles";
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess()); bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
@@ -38,7 +38,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<QStringList, QString> ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const AZ::Outcome<QStringList, QString> ProjectBuilderWorker::ConstructCmakeBuildCommandArguments() const
{ {
auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"}, QProcessEnvironment::systemEnvironment()); auto whichNinjaResult = ProjectUtils::ExecuteCommandResult("which", QStringList{"ninja"});
bool compileProfileOnBuild = (whichNinjaResult.IsSuccess()); bool compileProfileOnBuild = (whichNinjaResult.IsSuccess());
QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix); QString targetBuildPath = QDir(m_projectInfo.m_path).filePath(ProjectBuildPathPostfix);
QString launcherTargetName = m_projectInfo.m_projectName + ".GameLauncher"; QString launcherTargetName = m_projectInfo.m_projectName + ".GameLauncher";
@@ -19,16 +19,15 @@ namespace O3DE::ProjectManager
// The list of clang C/C++ compiler command lines to validate on the host Linux system // The list of clang C/C++ compiler command lines to validate on the host Linux system
const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"}; const QStringList SupportedClangVersions = {"13", "12", "11", "10", "9", "8", "7", "6.0"};
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment() AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
{ {
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); return AZ::Success();
return AZ::Success(currentEnvironment);
} }
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform() AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
{ {
// Validate that cmake is installed and is in the command line // Validate that cmake is installed and is in the command line
auto whichCMakeResult = ProjectUtils::ExecuteCommandResult("which", QStringList{ProjectCMakeCommand}, QProcessEnvironment::systemEnvironment()); auto whichCMakeResult = ProjectUtils::ExecuteCommandResult("which", QStringList{ProjectCMakeCommand});
if (!whichCMakeResult.IsSuccess()) if (!whichCMakeResult.IsSuccess())
{ {
return AZ::Failure(QObject::tr("CMake not found. <br><br>" return AZ::Failure(QObject::tr("CMake not found. <br><br>"
@@ -39,8 +38,8 @@ namespace O3DE::ProjectManager
// Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE. // Look for the first compatible version of clang. The list below will contain the known clang compilers that have been tested for O3DE.
for (const QString& supportClangVersion : SupportedClangVersions) for (const QString& supportClangVersion : SupportedClangVersions)
{ {
auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment()); auto whichClangResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang-%1").arg(supportClangVersion)});
auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)}, QProcessEnvironment::systemEnvironment()); auto whichClangPPResult = ProjectUtils::ExecuteCommandResult("which", QStringList{QString("clang++-%1").arg(supportClangVersion)});
if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess()) if (whichClangResult.IsSuccess() && whichClangPPResult.IsSuccess())
{ {
return AZ::Success(QString("clang-%1").arg(supportClangVersion)); return AZ::Success(QString("clang-%1").arg(supportClangVersion));
@@ -54,7 +53,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath) AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)
{ {
AZ::Outcome processEnvResult = GetCommandLineProcessEnvironment(); AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
if (!processEnvResult.IsSuccess()) if (!processEnvResult.IsSuccess())
{ {
return AZ::Failure(processEnvResult.GetError()); return AZ::Failure(processEnvResult.GetError());
@@ -68,7 +67,6 @@ namespace O3DE::ProjectManager
} }
QProcess process; QProcess process;
process.setProcessEnvironment(processEnvResult.GetValue());
// if the project build path is relative, it should be relative to the project path // if the project build path is relative, it should be relative to the project path
process.setWorkingDirectory(projectPath); process.setWorkingDirectory(projectPath);
@@ -88,7 +86,6 @@ namespace O3DE::ProjectManager
return ExecuteCommandResultModalDialog( return ExecuteCommandResultModalDialog(
QString("%1/python/get_python.sh").arg(engineRoot), QString("%1/python/get_python.sh").arg(engineRoot),
{}, {},
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script...")); QObject::tr("Running get_python script..."));
} }
@@ -19,16 +19,14 @@ namespace O3DE::ProjectManager
{ {
AZ::Outcome<QString, QString> QueryInstalledCmakeFullPath() AZ::Outcome<QString, QString> QueryInstalledCmakeFullPath()
{ {
auto environmentRequest = ProjectUtils::GetCommandLineProcessEnvironment(); auto environmentRequest = ProjectUtils::SetupCommandLineProcessEnvironment();
if (!environmentRequest.IsSuccess()) if (!environmentRequest.IsSuccess())
{ {
return AZ::Failure(environmentRequest.GetError()); return AZ::Failure(environmentRequest.GetError());
} }
auto currentEnvironment = environmentRequest.GetValue();
auto queryCmakeInstalled = ProjectUtils::ExecuteCommandResult("which", auto queryCmakeInstalled = ProjectUtils::ExecuteCommandResult("which",
QStringList{ProjectCMakeCommand}, QStringList{ProjectCMakeCommand});
currentEnvironment);
if (!queryCmakeInstalled.IsSuccess()) if (!queryCmakeInstalled.IsSuccess())
{ {
return AZ::Failure(QObject::tr("Unable to detect CMake on this host.")); return AZ::Failure(QObject::tr("Unable to detect CMake on this host."));
@@ -18,28 +18,36 @@ namespace O3DE::ProjectManager
{ {
namespace ProjectUtils namespace ProjectUtils
{ {
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment() AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
{ {
// For CMake on Mac, if its installed through home-brew, then it will be installed // For CMake on Mac, if its installed through home-brew, then it will be installed
// under /usr/local/bin, which may not be in the system PATH environment. // under /usr/local/bin, which may not be in the system PATH environment.
// Add that path for the command line process so that it will be able to locate // Add that path for the command line process so that it will be able to locate
// a home-brew installed version of CMake // a home-brew installed version of CMake
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); QString pathEnv = qEnvironmentVariable("PATH");
QString pathValue = currentEnvironment.value("PATH"); QStringList pathEnvList = pathEnv.split(":");
pathValue += ":/usr/local/bin"; if (!pathEnvList.contains("/usr/local/bin"))
currentEnvironment.insert("PATH", pathValue); {
return AZ::Success(currentEnvironment); pathEnv += ":/usr/local/bin";
if (!qputenv("PATH", pathEnv.toStdString().c_str()))
{
return AZ::Failure(QObject::tr("Failed to set PATH environment variable"));
}
}
return AZ::Success();
} }
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform() AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
{ {
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
QString pathValue = currentEnvironment.value("PATH"); if (!processEnvResult.IsSuccess())
pathValue += ":/usr/local/bin"; {
currentEnvironment.insert("PATH", pathValue); return AZ::Failure(processEnvResult.GetError());
}
// Validate that we have cmake installed first // Validate that we have cmake installed first
auto queryCmakeInstalled = ExecuteCommandResult("which", QStringList{ProjectCMakeCommand}, currentEnvironment); auto queryCmakeInstalled = ExecuteCommandResult("which", QStringList{ProjectCMakeCommand});
if (!queryCmakeInstalled.IsSuccess()) if (!queryCmakeInstalled.IsSuccess())
{ {
return AZ::Failure(QObject::tr("Unable to detect CMake on this host.")); return AZ::Failure(QObject::tr("Unable to detect CMake on this host."));
@@ -47,7 +55,7 @@ namespace O3DE::ProjectManager
QString cmakeInstalledPath = queryCmakeInstalled.GetValue().split("\n")[0]; QString cmakeInstalledPath = queryCmakeInstalled.GetValue().split("\n")[0];
// Query the version of the installed cmake // Query the version of the installed cmake
auto queryCmakeVersionQuery = ExecuteCommandResult(cmakeInstalledPath, QStringList{"-version"}, currentEnvironment); auto queryCmakeVersionQuery = ExecuteCommandResult(cmakeInstalledPath, QStringList{"-version"});
if (!queryCmakeVersionQuery.IsSuccess()) if (!queryCmakeVersionQuery.IsSuccess())
{ {
return AZ::Failure(QObject::tr("Unable to determine the version of CMake on this host.")); return AZ::Failure(QObject::tr("Unable to determine the version of CMake on this host."));
@@ -55,7 +63,7 @@ namespace O3DE::ProjectManager
AZ_TracePrintf("Project Manager", "Cmake version %s detected.", queryCmakeVersionQuery.GetValue().split("\n")[0].toUtf8().constData()); AZ_TracePrintf("Project Manager", "Cmake version %s detected.", queryCmakeVersionQuery.GetValue().split("\n")[0].toUtf8().constData());
// Query for the version of xcodebuild (if installed) // Query for the version of xcodebuild (if installed)
auto queryXcodeBuildVersion = ExecuteCommandResult("xcodebuild", QStringList{"-version"}, currentEnvironment); auto queryXcodeBuildVersion = ExecuteCommandResult("xcodebuild", QStringList{"-version"});
if (!queryCmakeInstalled.IsSuccess()) if (!queryCmakeInstalled.IsSuccess())
{ {
return AZ::Failure(QObject::tr("Unable to detect XCodeBuilder on this host.")); return AZ::Failure(QObject::tr("Unable to detect XCodeBuilder on this host."));
@@ -104,7 +112,6 @@ namespace O3DE::ProjectManager
return ExecuteCommandResultModalDialog( return ExecuteCommandResultModalDialog(
QString("%1/python/get_python.sh").arg(engineRoot), QString("%1/python/get_python.sh").arg(engineRoot),
{}, {},
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script...")); QObject::tr("Running get_python script..."));
} }
@@ -21,7 +21,7 @@ namespace O3DE::ProjectManager
{ {
namespace ProjectUtils namespace ProjectUtils
{ {
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment() AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment()
{ {
// Use the engine path to insert a path for cmake // Use the engine path to insert a path for cmake
auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo(); auto engineInfoResult = PythonBindingsInterface::Get()->GetEngineInfo();
@@ -31,26 +31,34 @@ namespace O3DE::ProjectManager
} }
auto engineInfo = engineInfoResult.GetValue(); auto engineInfo = engineInfoResult.GetValue();
QProcessEnvironment currentEnvironment(QProcessEnvironment::systemEnvironment()); // Append cmake path to the current environment PATH incase it is missing, since if
// we are starting CMake itself the current application needs to find it using Path
// Append cmake path to PATH incase it is missing // This also takes affect for all child processes.
QDir cmakePath(engineInfo.m_path); QDir cmakePath(engineInfo.m_path);
cmakePath.cd("cmake/runtime/bin"); cmakePath.cd("cmake/runtime/bin");
QString pathValue = currentEnvironment.value("PATH"); QString pathEnv = qEnvironmentVariable("Path");
pathValue += ";" + cmakePath.path(); QStringList pathEnvList = pathEnv.split(";");
currentEnvironment.insert("PATH", pathValue); if (!pathEnvList.contains(cmakePath.path()))
return AZ::Success(currentEnvironment); {
pathEnv += ";" + cmakePath.path();
if (!qputenv("Path", pathEnv.toStdString().c_str()))
{
return AZ::Failure(QObject::tr("Failed to set Path environment variable"));
}
}
return AZ::Success();
} }
AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform() AZ::Outcome<QString, QString> FindSupportedCompilerForPlatform()
{ {
// Validate that cmake is installed // Validate that cmake is installed
auto cmakeProcessEnvResult = GetCommandLineProcessEnvironment(); auto cmakeProcessEnvResult = SetupCommandLineProcessEnvironment();
if (!cmakeProcessEnvResult.IsSuccess()) if (!cmakeProcessEnvResult.IsSuccess())
{ {
return AZ::Failure(cmakeProcessEnvResult.GetError()); return AZ::Failure(cmakeProcessEnvResult.GetError());
} }
auto cmakeVersionQueryResult = ExecuteCommandResult("cmake", QStringList{"--version"}, cmakeProcessEnvResult.GetValue()); auto cmakeVersionQueryResult = ExecuteCommandResult("cmake", QStringList{"--version"});
if (!cmakeVersionQueryResult.IsSuccess()) if (!cmakeVersionQueryResult.IsSuccess())
{ {
return AZ::Failure(QObject::tr("CMake not found. \n\n" return AZ::Failure(QObject::tr("CMake not found. \n\n"
@@ -104,7 +112,7 @@ namespace O3DE::ProjectManager
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath) AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath)
{ {
AZ::Outcome processEnvResult = GetCommandLineProcessEnvironment(); AZ::Outcome processEnvResult = SetupCommandLineProcessEnvironment();
if (!processEnvResult.IsSuccess()) if (!processEnvResult.IsSuccess())
{ {
return AZ::Failure(processEnvResult.GetError()); return AZ::Failure(processEnvResult.GetError());
@@ -118,7 +126,6 @@ namespace O3DE::ProjectManager
} }
QProcess process; QProcess process;
process.setProcessEnvironment(processEnvResult.GetValue());
// if the project build path is relative, it should be relative to the project path // if the project build path is relative, it should be relative to the project path
process.setWorkingDirectory(projectPath); process.setWorkingDirectory(projectPath);
@@ -139,7 +146,6 @@ namespace O3DE::ProjectManager
return ExecuteCommandResultModalDialog( return ExecuteCommandResultModalDialog(
"cmd.exe", "cmd.exe",
QStringList{"/c", batPath}, QStringList{"/c", batPath},
QProcessEnvironment::systemEnvironment(),
QObject::tr("Running get_python script...")); QObject::tr("Running get_python script..."));
} }
@@ -157,7 +163,7 @@ namespace O3DE::ProjectManager
.arg(shortcutPath) .arg(shortcutPath)
.arg(targetPath) .arg(targetPath)
.arg(arguments.join(' ')); .arg(arguments.join(' '));
auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg}, QProcessEnvironment::systemEnvironment()); auto createShortcutResult = ExecuteCommandResult(cmd, QStringList{"-Command", arg});
if (!createShortcutResult.IsSuccess()) if (!createShortcutResult.IsSuccess())
{ {
return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>" return AZ::Failure(QObject::tr("Failed to create desktop shortcut %1 <br><br>"
@@ -117,18 +117,16 @@ namespace O3DE::ProjectManager
// Show some kind of progress with very approximate estimates // Show some kind of progress with very approximate estimates
UpdateProgress(++m_progressEstimate); UpdateProgress(++m_progressEstimate);
auto currentEnvironmentRequest = ProjectUtils::GetCommandLineProcessEnvironment(); auto currentEnvironmentRequest = ProjectUtils::SetupCommandLineProcessEnvironment();
if (!currentEnvironmentRequest.IsSuccess()) if (!currentEnvironmentRequest.IsSuccess())
{ {
QStringToAZTracePrint(currentEnvironmentRequest.GetError()); QStringToAZTracePrint(currentEnvironmentRequest.GetError());
return AZ::Failure(currentEnvironmentRequest.GetError()); return AZ::Failure(currentEnvironmentRequest.GetError());
} }
QProcessEnvironment currentEnvironment = currentEnvironmentRequest.GetValue();
m_configProjectProcess = new QProcess(this); m_configProjectProcess = new QProcess(this);
m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels); m_configProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path); m_configProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
m_configProjectProcess->setProcessEnvironment(currentEnvironment);
auto cmakeGenerateArgumentsResult = ConstructCmakeGenerateProjectArguments(engineInfo.m_thirdPartyPath); auto cmakeGenerateArgumentsResult = ConstructCmakeGenerateProjectArguments(engineInfo.m_thirdPartyPath);
if (!cmakeGenerateArgumentsResult.IsSuccess()) if (!cmakeGenerateArgumentsResult.IsSuccess())
@@ -181,7 +179,6 @@ namespace O3DE::ProjectManager
m_buildProjectProcess = new QProcess(this); m_buildProjectProcess = new QProcess(this);
m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels); m_buildProjectProcess->setProcessChannelMode(QProcess::MergedChannels);
m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path); m_buildProjectProcess->setWorkingDirectory(m_projectInfo.m_path);
m_buildProjectProcess->setProcessEnvironment(currentEnvironment);
auto cmakeBuildArgumentsResult = ConstructCmakeBuildCommandArguments(); auto cmakeBuildArgumentsResult = ConstructCmakeBuildCommandArguments();
if (!cmakeBuildArgumentsResult.IsSuccess()) if (!cmakeBuildArgumentsResult.IsSuccess())
@@ -520,12 +520,10 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResultModalDialog( AZ::Outcome<QString, QString> ExecuteCommandResultModalDialog(
const QString& cmd, const QString& cmd,
const QStringList& arguments, const QStringList& arguments,
const QProcessEnvironment& processEnv,
const QString& title) const QString& title)
{ {
QString resultOutput; QString resultOutput;
QProcess execProcess; QProcess execProcess;
execProcess.setProcessEnvironment(processEnv);
execProcess.setProcessChannelMode(QProcess::MergedChannels); execProcess.setProcessChannelMode(QProcess::MergedChannels);
QProgressDialog dialog(title, QObject::tr("Cancel"), /*minimum=*/0, /*maximum=*/0); QProgressDialog dialog(title, QObject::tr("Cancel"), /*minimum=*/0, /*maximum=*/0);
@@ -611,11 +609,9 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResult( AZ::Outcome<QString, QString> ExecuteCommandResult(
const QString& cmd, const QString& cmd,
const QStringList& arguments, const QStringList& arguments,
const QProcessEnvironment& processEnv,
int commandTimeoutSeconds /*= ProjectCommandLineTimeoutSeconds*/) int commandTimeoutSeconds /*= ProjectCommandLineTimeoutSeconds*/)
{ {
QProcess execProcess; QProcess execProcess;
execProcess.setProcessEnvironment(processEnv);
execProcess.setProcessChannelMode(QProcess::MergedChannels); execProcess.setProcessChannelMode(QProcess::MergedChannels);
execProcess.start(cmd, arguments); execProcess.start(cmd, arguments);
if (!execProcess.waitForStarted()) if (!execProcess.waitForStarted())
@@ -47,7 +47,6 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResult( AZ::Outcome<QString, QString> ExecuteCommandResult(
const QString& cmd, const QString& cmd,
const QStringList& arguments, const QStringList& arguments,
const QProcessEnvironment& processEnv,
int commandTimeoutSeconds = ProjectCommandLineTimeoutSeconds); int commandTimeoutSeconds = ProjectCommandLineTimeoutSeconds);
/** /**
@@ -61,10 +60,9 @@ namespace O3DE::ProjectManager
AZ::Outcome<QString, QString> ExecuteCommandResultModalDialog( AZ::Outcome<QString, QString> ExecuteCommandResultModalDialog(
const QString& cmd, const QString& cmd,
const QStringList& arguments, const QStringList& arguments,
const QProcessEnvironment& processEnv,
const QString& title); const QString& title);
AZ::Outcome<QProcessEnvironment, QString> GetCommandLineProcessEnvironment(); AZ::Outcome<void, QString> SetupCommandLineProcessEnvironment();
AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath); AZ::Outcome<QString, QString> GetProjectBuildPath(const QString& projectPath);
AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath); AZ::Outcome<void, QString> OpenCMakeGUI(const QString& projectPath);
AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath); AZ::Outcome<QString, QString> RunGetPythonScript(const QString& enginePath);
+28 -16
View File
@@ -60,6 +60,9 @@ ly_create_alias(
) )
if (PAL_TRAIT_BUILD_HOST_TOOLS) if (PAL_TRAIT_BUILD_HOST_TOOLS)
include(${CMAKE_CURRENT_SOURCE_DIR}/Platform/${PAL_PLATFORM_NAME}/PAL_traits_editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake)
ly_add_target( ly_add_target(
NAME AWSCore.Editor.Static STATIC NAME AWSCore.Editor.Static STATIC
NAMESPACE Gem NAMESPACE Gem
@@ -97,22 +100,31 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
Gem::AWSCore.Editor.Static Gem::AWSCore.Editor.Static
) )
# This target is not a real gem module if (PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL)
# It is not meant to be loaded by the ModuleManager in C++
ly_add_target( # This target is not a real gem module
NAME AWSCore.ResourceMappingTool MODULE # It is not meant to be loaded by the ModuleManager in C++
NAMESPACE Gem ly_add_target(
OUTPUT_SUBDIRECTORY AWSCoreEditorQtBin NAME AWSCore.ResourceMappingTool MODULE
FILES_CMAKE NAMESPACE Gem
awscore_resourcemappingtool_files.cmake OUTPUT_SUBDIRECTORY AWSCoreEditorQtBin
INCLUDE_DIRECTORIES FILES_CMAKE
PRIVATE awscore_resourcemappingtool_files.cmake
Include/Private INCLUDE_DIRECTORIES
BUILD_DEPENDENCIES PRIVATE
PRIVATE Include/Private
Gem::AWSCore.Editor.Static BUILD_DEPENDENCIES
) PRIVATE
ly_add_dependencies(AWSCore.Editor AWSCore.ResourceMappingTool) Gem::AWSCore.Editor.Static
RUNTIME_DEPENDENCIES
3rdParty::pyside2
)
ly_add_dependencies(AWSCore.Editor AWSCore.ResourceMappingTool)
ly_install_directory(DIRECTORIES Tools/ResourceMappingTool)
endif()
# Builders and Tools (such as the Editor use AWSCore.Editor) use the .Editor module above. # Builders and Tools (such as the Editor use AWSCore.Editor) use the .Editor module above.
ly_create_alias( ly_create_alias(
@@ -13,6 +13,8 @@
#include <QAction> #include <QAction>
#include <QObject> #include <QObject>
#include "AWSCoreEditor_Traits_Platform.h"
namespace AWSCore namespace AWSCore
{ {
class AWSCoreResourceMappingToolAction class AWSCoreResourceMappingToolAction
@@ -22,7 +24,7 @@ namespace AWSCore
static constexpr const char AWSCoreResourceMappingToolActionName[] = "AWSCoreResourceMappingToolAction"; static constexpr const char AWSCoreResourceMappingToolActionName[] = "AWSCoreResourceMappingToolAction";
static constexpr const char ResourceMappingToolDirectoryPath[] = "Gems/AWSCore/Code/Tools/ResourceMappingTool"; static constexpr const char ResourceMappingToolDirectoryPath[] = "Gems/AWSCore/Code/Tools/ResourceMappingTool";
static constexpr const char ResourceMappingToolLogDirectoryPath[] = "user/log/"; static constexpr const char ResourceMappingToolLogDirectoryPath[] = "user/log/";
static constexpr const char EngineWindowsPythonEntryScriptPath[] = "python/python.cmd"; static constexpr const char EngineWindowsPythonEntryScriptPath[] = AWSCORE_EDITOR_PYTHON_COMMAND;
AWSCoreResourceMappingToolAction(const QString& text, QObject* parent = nullptr); AWSCoreResourceMappingToolAction(const QString& text, QObject* parent = nullptr);
@@ -7,4 +7,6 @@
*/ */
#pragma once #pragma once
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 0 #define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 1
#define AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT ""
#define AWSCORE_EDITOR_PYTHON_COMMAND "python/python.sh"
@@ -0,0 +1,9 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL TRUE)
@@ -8,3 +8,5 @@
#pragma once #pragma once
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 0 #define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 0
#define AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT ""
#define AWSCORE_EDITOR_PYTHON_COMMAND "python/python.sh"
@@ -0,0 +1,9 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL FALSE)
@@ -8,3 +8,5 @@
#pragma once #pragma once
#define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 1 #define AWSCORE_EDITOR_RESOURCE_MAPPING_TOOL_ENABLED 1
#define AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT "debug "
#define AWSCORE_EDITOR_PYTHON_COMMAND "python/python.cmd"
@@ -0,0 +1,9 @@
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set(PAL_TRAIT_ENABLE_RESOURCE_MAPPING_TOOL TRUE)
@@ -58,7 +58,7 @@ namespace AWSCore
if (m_isDebug) if (m_isDebug)
{ {
return AZStd::string::format( return AZStd::string::format(
"\"%s\" debug -B \"%s\" --binaries-path \"%s\" --debug --profile \"%s\" --config-path \"%s\" --log-path \"%s\"", "\"%s\" " AWSCORE_EDITOR_PYTHON_DEBUG_ARGUMENT "-B \"%s\" --binaries-path \"%s\" --debug --profile \"%s\" --config-path \"%s\" --log-path \"%s\"",
m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(), m_enginePythonEntryPath.c_str(), m_toolScriptPath.c_str(), m_toolQtBinDirectoryPath.c_str(),
profileName.c_str(), m_toolConfigDirectoryPath.c_str(), m_toolLogDirectoryPath.c_str()); profileName.c_str(), m_toolConfigDirectoryPath.c_str(), m_toolLogDirectoryPath.c_str());
} }
@@ -39,6 +39,16 @@ Follow cmake instructions to configure your project, for example:
``` ```
$ python\python.cmd debug Gems\AWSCore\Code\Tools\ResourceMappingTool\resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>\bin\debug\AWSCoreEditorQtBin $ python\python.cmd debug Gems\AWSCore\Code\Tools\ResourceMappingTool\resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>\bin\debug\AWSCoreEditorQtBin
``` ```
* Linux
* release mode
```
$ python/python.sh Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>/bin/profile/AWSCoreEditorQtBin
```
* debug mode
```
$ python/python.sh Gems/AWSCore/Code/Tools/ResourceMappingTool/resource_mapping_tool.py --binaries_path <PATH_TO_BUILD_FOLDER>/bin/debug/AWSCoreEditorQtBin
```
* Note - Editor is integrated with the same engine python environment to launch Resource Mapping Tool. If it is failed to launch the tool * Note - Editor is integrated with the same engine python environment to launch Resource Mapping Tool. If it is failed to launch the tool
in Editor, please follow above steps to make sure expected scripts/binaries are present. in Editor, please follow above steps to make sure expected scripts/binaries are present.
@@ -21,6 +21,7 @@ argument_parser.add_argument('--debug', action='store_true', help='Execute on de
argument_parser.add_argument('--log-path', help='Path to resource mapping tool logging directory ' argument_parser.add_argument('--log-path', help='Path to resource mapping tool logging directory '
'(if not provided, logging file will be located at tool directory)') '(if not provided, logging file will be located at tool directory)')
argument_parser.add_argument('--profile', default='default', help='Named AWS profile to use for querying AWS resources') argument_parser.add_argument('--profile', default='default', help='Named AWS profile to use for querying AWS resources')
arguments: Namespace = argument_parser.parse_args() arguments: Namespace = argument_parser.parse_args()
# logging setup # logging setup
@@ -5,6 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
import platform
from typing import List from typing import List
from unittest import TestCase from unittest import TestCase
from unittest.mock import (ANY, call, MagicMock, patch) from unittest.mock import (ANY, call, MagicMock, patch)
@@ -27,14 +28,24 @@ class TestEnvironmentUtils(TestCase):
self.addCleanup(os_pathsep_patcher.stop) self.addCleanup(os_pathsep_patcher.stop)
self._mock_os_pathsep: MagicMock = os_pathsep_patcher.start() self._mock_os_pathsep: MagicMock = os_pathsep_patcher.start()
def test_setup_qt_environment_global_flag_is_set(self) -> None: @patch('os.path.exists')
@patch('ctypes.CDLL')
def test_setup_qt_environment_global_flag_is_set(self, mock_os_path_exists, mock_ctype_cdll) -> None:
mock_os_path_exists.return_value = True
environment_utils.setup_qt_environment("dummy") environment_utils.setup_qt_environment("dummy")
self._mock_os_environ.copy.assert_called_once() self._mock_os_environ.copy.assert_called_once()
self._mock_os_pathsep.join.assert_called_once() self._mock_os_pathsep.join.assert_called_once()
assert environment_utils.is_qt_linked() is True assert environment_utils.is_qt_linked() is True
if platform.system() == 'Linux':
mock_os_path_exists.assert_called()
def test_cleanup_qt_environment_global_flag_is_set(self) -> None: @patch('os.path.exists')
@patch('ctypes.CDLL')
def test_cleanup_qt_environment_global_flag_is_set(self, mock_os_path_exists, mock_ctype_cdll) -> None:
mock_os_path_exists.return_value = True
environment_utils.setup_qt_environment("dummy") environment_utils.setup_qt_environment("dummy")
assert environment_utils.is_qt_linked() is True assert environment_utils.is_qt_linked() is True
environment_utils.cleanup_qt_environment() environment_utils.cleanup_qt_environment()
assert environment_utils.is_qt_linked() is False assert environment_utils.is_qt_linked() is False
if platform.system() == 'Linux':
mock_os_path_exists.assert_called()
@@ -7,6 +7,7 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
import logging import logging
import os import os
import platform
from typing import Dict from typing import Dict
from utils import file_utils from utils import file_utils
@@ -38,6 +39,20 @@ def setup_qt_environment(bin_path: str) -> None:
new_path = os.pathsep.join([binaries_path, path]) new_path = os.pathsep.join([binaries_path, path])
os.environ['PATH'] = new_path os.environ['PATH'] = new_path
# On Linux, we need to load pyside2 and related modules as well
if platform.system() == 'Linux':
import ctypes
preload_shared_libs = [f'{bin_path}/libpyside2.abi3.so.5.14',
f'{bin_path}/libQt5Widgets.so.5']
for preload_shared_lib in preload_shared_libs:
if not os.path.exists(preload_shared_lib):
logger.error(f"Cannot find required shared library at {preload_shared_lib}")
return
else:
ctypes.CDLL(preload_shared_lib)
global qt_binaries_linked global qt_binaries_linked
qt_binaries_linked = True qt_binaries_linked = True
@@ -8,12 +8,16 @@
#include <PrefabGroup/ProceduralAssetHandler.h> #include <PrefabGroup/ProceduralAssetHandler.h>
#include <AzCore/Asset/AssetTypeInfoBus.h> #include <AzCore/Asset/AssetTypeInfoBus.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/std/smart_ptr/make_shared.h> #include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/FileFunc/FileFunc.h> #include <AzFramework/FileFunc/FileFunc.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h> #include <AzQtComponents/Components/Widgets/FileDialog.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserBus.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h> #include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h> #include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzCore/Serialization/Json/JsonUtils.h> #include <QMenu>
namespace AZ::Prefab namespace AZ::Prefab
{ {
@@ -21,6 +25,7 @@ namespace AZ::Prefab
class PrefabGroupAssetHandler::AssetTypeInfoHandler final class PrefabGroupAssetHandler::AssetTypeInfoHandler final
: public AZ::AssetTypeInfoBus::Handler : public AZ::AssetTypeInfoBus::Handler
, protected AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
{ {
public: public:
AZ_CLASS_ALLOCATOR(AssetTypeInfoHandler, AZ::SystemAllocator, 0); AZ_CLASS_ALLOCATOR(AssetTypeInfoHandler, AZ::SystemAllocator, 0);
@@ -31,15 +36,21 @@ namespace AZ::Prefab
const char* GetGroup() const override; const char* GetGroup() const override;
const char* GetBrowserIcon() const override; const char* GetBrowserIcon() const override;
void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override; void GetAssetTypeExtensions(AZStd::vector<AZStd::string>& extensions) override;
// AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler
void AddContextMenuActions(QWidget* caller, QMenu* menu, const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries) override;
bool SaveAsAuthoredPrefab(const AZ::Data::AssetId& assetId, const char* destinationFilename);
}; };
PrefabGroupAssetHandler::AssetTypeInfoHandler::AssetTypeInfoHandler() PrefabGroupAssetHandler::AssetTypeInfoHandler::AssetTypeInfoHandler()
{ {
AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<ProceduralPrefabAsset>()); AZ::AssetTypeInfoBus::Handler::BusConnect(azrtti_typeid<ProceduralPrefabAsset>());
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusConnect();
} }
PrefabGroupAssetHandler::AssetTypeInfoHandler::~AssetTypeInfoHandler() PrefabGroupAssetHandler::AssetTypeInfoHandler::~AssetTypeInfoHandler()
{ {
AzToolsFramework::AssetBrowser::AssetBrowserInteractionNotificationBus::Handler::BusDisconnect();
AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<ProceduralPrefabAsset>()); AZ::AssetTypeInfoBus::Handler::BusDisconnect(azrtti_typeid<ProceduralPrefabAsset>());
} }
@@ -68,6 +79,84 @@ namespace AZ::Prefab
extensions.push_back(PrefabGroupAssetHandler::s_Extension); extensions.push_back(PrefabGroupAssetHandler::s_Extension);
} }
void PrefabGroupAssetHandler::AssetTypeInfoHandler::AddContextMenuActions(
[[maybe_unused]] QWidget* caller,
QMenu* menu,
const AZStd::vector<AzToolsFramework::AssetBrowser::AssetBrowserEntry*>& entries)
{
using namespace AzToolsFramework::AssetBrowser;
auto entryIt = AZStd::find_if
(
entries.begin(),
entries.end(),
[](const AssetBrowserEntry* entry) -> bool
{
return entry->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product;
}
);
if (entryIt == entries.end())
{
return;
}
else if ((*entryIt)->GetEntryType() == AssetBrowserEntry::AssetEntryType::Product)
{
ProductAssetBrowserEntry* product = azrtti_cast<ProductAssetBrowserEntry*>(*entryIt);
if (product->GetAssetType() == azrtti_typeid<ProceduralPrefabAsset>())
{
AZ::Data::AssetId assetId = product->GetAssetId();
menu->addAction("Save as Prefab...", [assetId, this]()
{
QString filePath = AzQtComponents::FileDialog::GetSaveFileName(nullptr, QString("Save to file"), "", QString("Prefab file (*.prefab)"));
if (filePath.isEmpty())
{
return;
}
if (SaveAsAuthoredPrefab(assetId, filePath.toUtf8().data()))
{
AZ_Printf("Prefab", "Prefab was saved to a .prefab file %s", filePath.toUtf8().data());
}
});
}
}
}
bool PrefabGroupAssetHandler::AssetTypeInfoHandler::SaveAsAuthoredPrefab(const AZ::Data::AssetId& assetId, const char* destinationFilename)
{
using namespace AzToolsFramework::Prefab;
using namespace AZ::Data;
auto procPrefabAsset = AssetManager::Instance().GetAsset<ProceduralPrefabAsset>(assetId, AssetLoadBehavior::Default);
const auto status = AssetManager::Instance().BlockUntilLoadComplete(procPrefabAsset);
if (status != AssetData::AssetStatus::Ready)
{
return false;
}
auto* prefabLoaderInterface = AZ::Interface<PrefabLoaderInterface>::Get();
if (!prefabLoaderInterface)
{
return false;
}
const auto templateId = procPrefabAsset.GetAs<ProceduralPrefabAsset>()->GetTemplateId();
AZStd::string outputJson;
if (prefabLoaderInterface->SaveTemplateToString(templateId, outputJson) == false)
{
return false;
}
const auto fileMode = AZ::IO::OpenMode::ModeWrite | AZ::IO::OpenMode::ModeCreatePath | AZ::IO::OpenMode::ModeText;
AZ::IO::FileIOStream outputFileStream;
if (outputFileStream.Open(destinationFilename, fileMode) == false)
{
return false;
}
outputFileStream.Write(outputJson.size(), outputJson.data());
return true;
}
// PrefabGroupAssetHandler // PrefabGroupAssetHandler
AZStd::string_view PrefabGroupAssetHandler::s_Extension{ "procprefab" }; AZStd::string_view PrefabGroupAssetHandler::s_Extension{ "procprefab" };
@@ -1,92 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ScriptEventReferencesComponent.h"
namespace ScriptEvents
{
namespace Components
{
void ScriptEventReferencesComponent::Reflect(AZ::ReflectContext* context)
{
if (AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
// The Script Event References component is no longer necessary, as all Script Event assets
// will be properly loaded as needed.
serializeContext->ClassDeprecate("ScriptEventReferencesComponent", "{D0F440AC-32D4-49EC-8B93-860B188266A6}");
}
}
void ScriptEventReferencesComponent::Activate()
{
for (auto& scriptEventReferences : m_scriptEventAssets)
{
const auto& asset = scriptEventReferences.GetAsset();
if (asset)
{
if (!AZ::Data::AssetBus::MultiHandler::BusIsConnectedId(asset.GetId()))
{
AZ::Data::AssetBus::MultiHandler::BusConnect(asset.GetId());
}
// Load the asset if it's not ready
if (!asset.IsReady())
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, asset.GetId());
if (assetInfo.m_assetId.IsValid())
{
AZ::Data::AssetManager::Instance().GetAsset(asset.GetId(), azrtti_typeid<ScriptEventsAsset>(), AZ::Data::AssetLoadBehavior::Default)
.BlockUntilLoadComplete();
}
}
}
else
{
AZ_Warning("Script Events", false, "ScriptEventReferencesComponent could not find Script Event asset: %s", scriptEventReferences.GetDefinition() ? scriptEventReferences.GetDefinition()->GetName().c_str() : scriptEventReferences.GetAsset().GetId().ToString<AZStd::string>().c_str());
}
}
}
void ScriptEventReferencesComponent::Deactivate()
{
for (auto& scriptEventReferences : m_scriptEventAssets)
{
const auto& asset = scriptEventReferences.GetAsset();
if (asset)
{
AZ::Data::AssetBus::MultiHandler::BusDisconnect(asset.GetId());
}
}
}
void ScriptEventReferencesComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC("ScriptEventReference", 0x3df92d40));
}
void ScriptEventReferencesComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC("ScriptEventReference", 0x3df92d40));
}
void ScriptEventReferencesComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC("LuaScriptService", 0x21d76c4b));
}
void ScriptEventReferencesComponent::OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset)
{
if (ScriptEventsAsset* scriptEventAsset = asset.GetAs<ScriptEventsAsset>())
{
scriptEventAsset->m_definition.RegisterInternal();
}
}
}
}
@@ -1,44 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <ScriptEvents/ScriptEventsAssetRef.h>
#include <AzCore/Asset/AssetCommon.h>
namespace ScriptEvents
{
namespace Components
{
class ScriptEventReferencesComponent
: public AZ::Component
, private AZ::Data::AssetBus::MultiHandler
{
public:
AZ_COMPONENT(ScriptEventReferencesComponent, "{D0F440AC-32D4-49EC-8B93-860B188266A6}", AZ::Component);
//////////////////////////////////////////////////////////////////////////
// AZ::Component
void Init() override {}
void Activate() override;
void Deactivate() override;
//////////////////////////////////////////////////////////////////////////
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
static void GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent);
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
static void Reflect(AZ::ReflectContext* reflection);
AZStd::vector<ScriptEvents::ScriptEventsAssetRef> m_scriptEventAssets;
};
}
}
@@ -9,7 +9,6 @@
#include <ScriptEvents/ScriptEventsGem.h> #include <ScriptEvents/ScriptEventsGem.h>
#include <Source/Editor/ScriptEventsSystemEditorComponent.h> #include <Source/Editor/ScriptEventsSystemEditorComponent.h>
#include <ScriptEvents/Components/ScriptEventReferencesComponent.h>
#include <Builder/ScriptEventsBuilderComponent.h> #include <Builder/ScriptEventsBuilderComponent.h>
#include <ScriptEvents/ScriptEventsBus.h> #include <ScriptEvents/ScriptEventsBus.h>
@@ -74,7 +73,6 @@ namespace ScriptEvents
m_descriptors.insert(m_descriptors.end(), { m_descriptors.insert(m_descriptors.end(), {
ScriptEventsEditor::ScriptEventEditorSystemComponent::CreateDescriptor(), ScriptEventsEditor::ScriptEventEditorSystemComponent::CreateDescriptor(),
ScriptEvents::Components::ScriptEventReferencesComponent::CreateDescriptor(),
ScriptEventsBuilder::ScriptEventsBuilderComponent::CreateDescriptor(), ScriptEventsBuilder::ScriptEventsBuilderComponent::CreateDescriptor(),
}); });
} }
@@ -12,8 +12,6 @@
#include <ScriptEvents/ScriptEventsGem.h> #include <ScriptEvents/ScriptEventsGem.h>
#include <ScriptEvents/Components/ScriptEventReferencesComponent.h>
namespace ScriptEvents namespace ScriptEvents
{ {
ScriptEventsModule::ScriptEventsModule() ScriptEventsModule::ScriptEventsModule()
@@ -23,8 +21,7 @@ namespace ScriptEvents
ScriptEventModuleConfigurationRequestBus::Handler::BusConnect(); ScriptEventModuleConfigurationRequestBus::Handler::BusConnect();
m_descriptors.insert(m_descriptors.end(), { m_descriptors.insert(m_descriptors.end(), {
ScriptEvents::ScriptEventsSystemComponent::CreateDescriptor(), ScriptEvents::ScriptEventsSystemComponent::CreateDescriptor()
ScriptEvents::Components::ScriptEventReferencesComponent::CreateDescriptor(),
}); });
} }
@@ -42,6 +42,4 @@ set(FILES
Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventsBindingBus.h Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventsBindingBus.h
Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventBinding.h Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventBinding.h
Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventBinding.cpp Include/ScriptEvents/Internal/BehaviorContextBinding/ScriptEventBinding.cpp
Include/ScriptEvents/Components/ScriptEventReferencesComponent.h
Include/ScriptEvents/Components/ScriptEventReferencesComponent.cpp
) )
@@ -2,5 +2,10 @@
"description": "", "description": "",
"materialType": "PbrTerrain.materialtype", "materialType": "PbrTerrain.materialtype",
"parentMaterial": "", "parentMaterial": "",
"propertyLayoutVersion": 1 "propertyLayoutVersion": 1,
"properties": {
"baseColor": {
"color": [ 0.18, 0.18, 0.18 ]
}
}
} }
@@ -88,6 +88,19 @@
} }
} }
], ],
"baseColor": [
{
"name": "color",
"displayName": "Color",
"description": "Color is displayed as sRGB but the values are stored as linear color.",
"type": "Color",
"defaultValue": [ 1.0, 1.0, 1.0 ],
"connection": {
"type": "ShaderInput",
"name": "m_baseColor"
}
}
],
"settings": [ "settings": [
{ {
"id": "detailTextureMultiplier", "id": "detailTextureMultiplier",
@@ -93,10 +93,6 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
{ {
float m_detailTextureMultiplier;
float m_detailFadeDistance;
float m_detailFadeLength;
Sampler m_sampler Sampler m_sampler
{ {
AddressU = Wrap; AddressU = Wrap;
@@ -109,22 +105,11 @@ ShaderResourceGroup TerrainMaterialSrg : SRG_PerMaterial
// Base Color // Base Color
float3 m_baseColor; float3 m_baseColor;
float m_baseColorFactor;
Texture2D m_baseColorMap;
// Normal // Detail Material Properties
Texture2D m_normalMap; float m_detailTextureMultiplier;
bool m_flipNormalX; float m_detailFadeDistance;
bool m_flipNormalY; float m_detailFadeLength;
float m_normalFactor;
// Roughness
Texture2D m_roughnessMap;
float m_roughnessFactor;
// Specular
Texture2D m_specularF0Map;
float m_specularF0Factor;
} }
option bool o_useTerrainSmoothing = false; option bool o_useTerrainSmoothing = false;
@@ -57,7 +57,30 @@ DetailSurface GetDefaultDetailSurface()
return surface; return surface;
} }
void WeightDetailSurface(inout DetailSurface surface, in float weight)
{
surface.m_color *= weight;
surface.m_normal *= weight;
surface.m_roughness *= weight;
surface.m_specularF0 *= weight;
surface.m_metalness *= weight;
surface.m_occlusion *= weight;
surface.m_height *= weight;
}
void AddDetailSurface(inout DetailSurface surface, in DetailSurface surfaceToAdd)
{
surface.m_color += surfaceToAdd.m_color;
surface.m_normal += surfaceToAdd.m_normal;
surface.m_roughness += surfaceToAdd.m_roughness;
surface.m_specularF0 += surfaceToAdd.m_specularF0;
surface.m_metalness += surfaceToAdd.m_metalness;
surface.m_occlusion += surfaceToAdd.m_occlusion;
surface.m_height += surfaceToAdd.m_height;
}
// Detail material index getters // Detail material index getters
uint GetDetailColorIndex(TerrainSrg::DetailMaterialData materialData) uint GetDetailColorIndex(TerrainSrg::DetailMaterialData materialData)
{ {
return materialData.m_colorNormalImageIndices & 0x0000FFFF; return materialData.m_colorNormalImageIndices & 0x0000FFFF;
@@ -95,22 +118,22 @@ uint GetDetailHeightIndex(TerrainSrg::DetailMaterialData materialData)
// Detail material value getters // Detail material value getters
float3 GetDetailColor(TerrainSrg::DetailMaterialData materialData, float2 uv) float3 GetDetailColor(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float3 color = materialData.m_baseColor; float3 color = materialData.m_baseColor;
if ((materialData.m_flags & DetailTextureFlags::UseTextureBaseColor) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureBaseColor) > 0)
{ {
color = TerrainSrg::m_detailTextures[GetDetailColorIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).rgb; color = TerrainSrg::m_detailTextures[GetDetailColorIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).rgb;
} }
return color * materialData.m_baseColorFactor; return color * materialData.m_baseColorFactor;
} }
float3 GetDetailNormal(TerrainSrg::DetailMaterialData materialData, float2 uv) float3 GetDetailNormal(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float2 normal = float2(0.0, 0.0); float2 normal = float2(0.0, 0.0);
if ((materialData.m_flags & DetailTextureFlags::UseTextureNormal) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureNormal) > 0)
{ {
normal = TerrainSrg::m_detailTextures[GetDetailNormalIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).rg; normal = TerrainSrg::m_detailTextures[GetDetailNormalIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).rg;
} }
// X and Y are inverted here to be consistent with SampleNormalXY in NormalInput.azsli. // X and Y are inverted here to be consistent with SampleNormalXY in NormalInput.azsli.
@@ -125,53 +148,53 @@ float3 GetDetailNormal(TerrainSrg::DetailMaterialData materialData, float2 uv)
return GetTangentSpaceNormal(normal, materialData.m_normalFactor); return GetTangentSpaceNormal(normal, materialData.m_normalFactor);
} }
float GetDetailRoughness(TerrainSrg::DetailMaterialData materialData, float2 uv) float GetDetailRoughness(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float roughness = materialData.m_roughnessScale; float roughness = materialData.m_roughnessScale;
if ((materialData.m_flags & DetailTextureFlags::UseTextureRoughness) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureRoughness) > 0)
{ {
roughness = TerrainSrg::m_detailTextures[GetDetailRoughnessIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r; roughness = TerrainSrg::m_detailTextures[GetDetailRoughnessIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).r;
roughness = materialData.m_roughnessBias + roughness * materialData.m_roughnessScale; roughness = materialData.m_roughnessBias + roughness * materialData.m_roughnessScale;
} }
return roughness; return roughness;
} }
float GetDetailMetalness(TerrainSrg::DetailMaterialData materialData, float2 uv) float GetDetailMetalness(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float metalness = 1.0; float metalness = 1.0;
if ((materialData.m_flags & DetailTextureFlags::UseTextureMetallic) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureMetallic) > 0)
{ {
metalness = TerrainSrg::m_detailTextures[GetDetailMetalnessIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r; metalness = TerrainSrg::m_detailTextures[GetDetailMetalnessIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).r;
} }
return metalness * materialData.m_metalFactor; return metalness * materialData.m_metalFactor;
} }
float GetDetailSpecularF0(TerrainSrg::DetailMaterialData materialData, float2 uv) float GetDetailSpecularF0(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float specularF0 = 1.0; float specularF0 = 1.0;
if ((materialData.m_flags & DetailTextureFlags::UseTextureSpecularF0) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureSpecularF0) > 0)
{ {
specularF0 = TerrainSrg::m_detailTextures[GetDetailSpecularF0Index(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r; specularF0 = TerrainSrg::m_detailTextures[GetDetailSpecularF0Index(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).r;
} }
return specularF0 * materialData.m_specularF0Factor; return specularF0 * materialData.m_specularF0Factor;
} }
float GetDetailOcclusion(TerrainSrg::DetailMaterialData materialData, float2 uv) float GetDetailOcclusion(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float occlusion = 1.0; float occlusion = 1.0;
if ((materialData.m_flags & DetailTextureFlags::UseTextureOcclusion) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureOcclusion) > 0)
{ {
occlusion = TerrainSrg::m_detailTextures[GetDetailOcclusionIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r; occlusion = TerrainSrg::m_detailTextures[GetDetailOcclusionIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).r;
} }
return occlusion * materialData.m_occlusionFactor; return occlusion * materialData.m_occlusionFactor;
} }
float GetDetailHeight(TerrainSrg::DetailMaterialData materialData, float2 uv) float GetDetailHeight(TerrainSrg::DetailMaterialData materialData, float2 uv, float2 ddx, float2 ddy)
{ {
float height = materialData.m_heightFactor; float height = materialData.m_heightFactor;
if ((materialData.m_flags & DetailTextureFlags::UseTextureHeight) > 0) if ((materialData.m_flags & DetailTextureFlags::UseTextureHeight) > 0)
{ {
height = TerrainSrg::m_detailTextures[GetDetailHeightIndex(materialData)].Sample(TerrainMaterialSrg::m_sampler, uv).r; height = TerrainSrg::m_detailTextures[GetDetailHeightIndex(materialData)].SampleGrad(TerrainMaterialSrg::m_sampler, uv, ddx, ddy).r;
height = materialData.m_heightOffset + height * materialData.m_heightFactor; height = materialData.m_heightOffset + height * materialData.m_heightFactor;
} }
return height; return height;
@@ -181,15 +204,19 @@ void GetDetailSurfaceForMaterial(inout DetailSurface surface, uint materialId, f
{ {
TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId]; TerrainSrg::DetailMaterialData detailMaterialData = TerrainSrg::m_detailMaterialData[materialId];
surface.m_color = GetDetailColor(detailMaterialData, uv); float2 uvDdx = ddx(uv);
surface.m_normal = GetDetailNormal(detailMaterialData, uv); float2 uvDdy = ddy(uv);
surface.m_roughness = GetDetailRoughness(detailMaterialData, uv);
surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, uv); surface.m_color = GetDetailColor(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_metalness = GetDetailMetalness(detailMaterialData, uv); surface.m_normal = GetDetailNormal(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_occlusion = GetDetailOcclusion(detailMaterialData, uv); surface.m_roughness = GetDetailRoughness(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_height = GetDetailHeight(detailMaterialData, uv); surface.m_specularF0 = GetDetailSpecularF0(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_metalness = GetDetailMetalness(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_occlusion = GetDetailOcclusion(detailMaterialData, uv, uvDdx, uvDdy);
surface.m_height = GetDetailHeight(detailMaterialData, uv, uvDdx, uvDdy);
} }
// Debugs the detail material by choosing a random color per material ID and rendering it without blending.
void GetDebugDetailSurface(inout DetailSurface surface, uint material1, uint material2, float blend, float2 idUv) void GetDebugDetailSurface(inout DetailSurface surface, uint material1, uint material2, float blend, float2 idUv)
{ {
float3 material1Color = float3(0.1, 0.1, 0.1); float3 material1Color = float3(0.1, 0.1, 0.1);
@@ -210,7 +237,7 @@ void GetDebugDetailSurface(inout DetailSurface surface, uint material1, uint mat
surface.m_color = lerp(material1Color, material2Color, blend); surface.m_color = lerp(material1Color, material2Color, blend);
float seamBlend = 0.0; float seamBlend = 0.0;
const float halfLineWidth = 1.0 / 2048.0; const float halfLineWidth = 1.0 / 2048.0;
if (any(abs(idUv) % 1.0 < halfLineWidth) || any(abs(idUv) % 1.0 > 1.0 - halfLineWidth)) if (any(frac(abs(idUv)) < halfLineWidth) || any(frac(abs(idUv)) > 1.0 - halfLineWidth))
{ {
seamBlend = 1.0; seamBlend = 1.0;
} }
@@ -225,26 +252,114 @@ void GetDebugDetailSurface(inout DetailSurface surface, uint material1, uint mat
surface.m_height = 0.5; surface.m_height = 0.5;
} }
bool GetDetailSurface(inout DetailSurface surface, float2 idUv, float2 uv) //Blend a single detail material sample (with two possible material ids) onto a DetailSurface.
void BlendDetailMaterial(inout DetailSurface surface, uint material1, uint material2, float blend, float2 detailUv, float weight)
{ {
uint4 material1 = TerrainSrg::m_detailMaterialIdImage.GatherRed(TerrainSrg::DetailSampler, idUv, 0).xyzw; DetailSurface tempSurface;
uint4 material2 = TerrainSrg::m_detailMaterialIdImage.GatherGreen(TerrainSrg::DetailSampler, idUv, 0).xyzw; GetDetailSurfaceForMaterial(tempSurface, material1, detailUv);
WeightDetailSurface(tempSurface, weight * (1.0 - blend));
AddDetailSurface(surface, tempSurface);
if (material2 != 0xFF)
{
GetDetailSurfaceForMaterial(tempSurface, material2, detailUv);
WeightDetailSurface(tempSurface, weight * blend);
AddDetailSurface(surface, tempSurface);
}
}
const float maxBlendAmount = 0xFF; /*
Populates a DetailSurface with material data gathered form the 4 nearest samples to detailMaterialIdUv. The weight
of each detail material's contribution is calculated based on the distance to the center point for that sample (for
instance, if detailMaterialIdUv falls perfectly in-between all 4 samples, then each sample will be weighed at 25%).
Each sample can have two different detail materials defined with a blend value to determine their relative contribution.
The detailUv is used for sampling the textures of each detail material.
*/
bool GetDetailSurface(inout DetailSurface surface, float2 detailMaterialIdUv, float2 detailUv)
{
float2 textureSize;
TerrainSrg::m_detailMaterialIdImage.GetDimensions(textureSize.x, textureSize.y);
float2 detailMaterialIdCoord = detailMaterialIdUv * textureSize; // uv -> pixel coordinate
// detailMaterialIdCoord could be negative, so add textureSize to ensure it is positive
detailMaterialIdCoord += textureSize;
// The detail material id texture wraps since the "center" point can be anywhere in the texture, so mod by texturesize
int2 detailMaterailIdTopLeft = int2(detailMaterialIdCoord) % textureSize;
int2 detailMaterailIdBottomRight = (int2(detailMaterialIdCoord) + 1) % textureSize;
// Using Load() to gather the nearest 4 samples (Gather4() isn't used because of precision issues with uvs).
uint4 s1 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdTopLeft.x, detailMaterailIdBottomRight.y, 0));
uint4 s2 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdBottomRight, 0));
uint4 s3 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdBottomRight.x, detailMaterailIdTopLeft.y, 0));
uint4 s4 = TerrainSrg::m_detailMaterialIdImage.Load(int3(detailMaterailIdTopLeft, 0));
uint4 material1 = uint4(s1.x, s2.x, s3.x, s4.x);
uint4 material2 = uint4(s1.y, s2.y, s3.y, s4.y);
// convert integer of 0-255 to float of 0-1. // convert integer of 0-255 to float of 0-1.
float4 blends = float4(TerrainSrg::m_detailMaterialIdImage.GatherBlue(TerrainSrg::DetailSampler, idUv, 0).xyzw) / maxBlendAmount; const float maxBlendAmount = 0xFF;
float4 blends = float4(s1.z, s2.z, s3.z, s4.z) / maxBlendAmount;
// Calculate weight based on proximity to detail material samples
float2 gatherWeight = frac(detailMaterialIdCoord);
// Adjust the gather weight for better interpolation by (3x^2 - 2x^3). This helps avoid diamond-shaped artifacts in binlinear filtering.
gatherWeight = gatherWeight * gatherWeight * (3.0 - 2.0 * gatherWeight);
if (o_debugDetailMaterialIds) if (o_debugDetailMaterialIds)
{ {
float2 idUv = (detailMaterialIdCoord + gatherWeight - 0.5) / textureSize;
GetDebugDetailSurface(surface, material1.x, material2.x, blends.x, idUv); GetDebugDetailSurface(surface, material1.x, material2.x, blends.x, idUv);
return true; return true;
} }
if (material1.x == 0xFF) // If any sample has no materials, give up.
if (any(material1 == 0xFF))
{ {
return false; return false;
} }
GetDetailSurfaceForMaterial(surface, material1.x, uv); if (all(material1.x == material1.yzw) && all(material2.x == material2.yzw))
{
// Fast path for same material ids
GetDetailSurfaceForMaterial(surface, material1.x, detailUv);
if (material2.x != 0xFF)
{
float4 material2Blends = 1.0 - blends;
DetailSurface tempSurface;
float weight =
((1.0 - gatherWeight.x) * gatherWeight.y * material2Blends.x) +
(gatherWeight.x * gatherWeight.y * material2Blends.y) +
(gatherWeight.x * (1.0 - gatherWeight.y) * material2Blends.z) +
((1.0 - gatherWeight.x) * (1.0 - gatherWeight.y) * material2Blends.w);
WeightDetailSurface(surface, weight);
GetDetailSurfaceForMaterial(tempSurface, material2.x, detailUv);
WeightDetailSurface(tempSurface, 1.0 - weight);
AddDetailSurface(surface, tempSurface);
}
}
else
{
surface = (DetailSurface)0;
// X
float weight = (1.0 - gatherWeight.x) * gatherWeight.y;
BlendDetailMaterial(surface, material1.x, material2.x, blends.x, detailUv, weight);
// Y
weight = gatherWeight.x * gatherWeight.y;
BlendDetailMaterial(surface, material1.y, material2.y, blends.y, detailUv, weight);
// Z
weight = gatherWeight.x * (1.0 - gatherWeight.y);
BlendDetailMaterial(surface, material1.z, material2.z, blends.z, detailUv, weight);
// W
weight = (1.0 - gatherWeight.x) * (1.0 - gatherWeight.y);
BlendDetailMaterial(surface, material1.w, material2.w, blends.w, detailUv, weight);
}
surface.m_normal = normalize(surface.m_normal);
return true; return true;
} }
@@ -109,9 +109,10 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
{ {
bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX; bool flipX = ObjectSrg::m_macroMaterialData[i].m_flipNormalX;
bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY; bool flipY = ObjectSrg::m_macroMaterialData[i].m_flipNormalY;
bool factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor; float factor = ObjectSrg::m_macroMaterialData[i].m_normalFactor;
macroNormal = GetNormalInputTS(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler,
macroUv, flipX, flipY, CreateIdentity3x3(), true, factor); float2 sampledValue = SampleNormalXY(ObjectSrg::m_macroNormalMap[i], TerrainMaterialSrg::m_sampler, macroUv, flipX, flipY);
macroNormal = normalize(GetTangentSpaceNormal_Unnormalized(sampledValue.xy, factor));
} }
break; break;
} }
@@ -129,7 +130,7 @@ ForwardPassOutput TerrainPBR_MainPassPS(VSOutput IN)
// Check to make sure we're inside the detail texture's bounds and within where detail textures should be drawn. // Check to make sure we're inside the detail texture's bounds and within where detail textures should be drawn.
if (detailFactor < 1.0 && all(detailRegionUv > TerrainSrg::m_detailHalfPixelUv) && all(detailRegionUv < 1.0 - TerrainSrg::m_detailHalfPixelUv)) if (detailFactor < 1.0 && all(detailRegionUv > TerrainSrg::m_detailHalfPixelUv) && all(detailRegionUv < 1.0 - TerrainSrg::m_detailHalfPixelUv))
{ {
detailRegionUv += TerrainSrg::m_detailMaterialIdImageCenter - (0.5); detailRegionUv += TerrainSrg::m_detailMaterialIdImageCenter - 0.5;
hasDetailSurface = GetDetailSurface(detailSurface, detailRegionUv, detailUv); hasDetailSurface = GetDetailSurface(detailSurface, detailRegionUv, detailUv);
} }
@@ -1,6 +1,12 @@
{ {
"Source" : "./TerrainPBR_ForwardPass.azsl", "Source" : "./TerrainPBR_ForwardPass.azsl",
"CompilerHints" :
{
"DisableOptimizations" : false,
"GenerateDebugInfo" : false
},
"DepthStencilState" : "DepthStencilState" :
{ {
"Depth" : "Depth" :
@@ -17,16 +17,6 @@ ShaderResourceGroupSemantic SRG_Terrain
ShaderResourceGroup TerrainSrg : SRG_Terrain ShaderResourceGroup TerrainSrg : SRG_Terrain
{ {
Sampler DetailSampler
{
AddressU = Wrap;
AddressV = Wrap;
MinFilter = Point;
MagFilter = Point;
MipFilter = Point;
};
struct DetailMaterialData struct DetailMaterialData
{ {
// Uv // Uv
@@ -40,15 +40,17 @@ namespace Terrain
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "") ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMin, "World Bounds (Min)", "")
// Temporary constraint until the rest of the Terrain system is updated to support larger worlds. // Temporary constraint until the rest of the Terrain system is updated to support larger worlds.
->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainWorldConfig::ValidateWorldMin)
->Attribute(AZ::Edit::Attributes::Min, -2048.0f) ->Attribute(AZ::Edit::Attributes::Min, -2048.0f)
->Attribute(AZ::Edit::Attributes::Max, 2048.0f) ->Attribute(AZ::Edit::Attributes::Max, 2048.0f)
->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "") ->DataElement(AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_worldMax, "World Bounds (Max)", "")
// Temporary constraint until the rest of the Terrain system is updated to support larger worlds. // Temporary constraint until the rest of the Terrain system is updated to support larger worlds.
->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainWorldConfig::ValidateWorldMax)
->Attribute(AZ::Edit::Attributes::Min, -2048.0f) ->Attribute(AZ::Edit::Attributes::Min, -2048.0f)
->Attribute(AZ::Edit::Attributes::Max, 2048.0f) ->Attribute(AZ::Edit::Attributes::Max, 2048.0f)
->DataElement( ->DataElement(
AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "") AZ::Edit::UIHandlers::Default, &TerrainWorldConfig::m_heightQueryResolution, "Height Query Resolution (m)", "")
; ->Attribute(AZ::Edit::Attributes::ChangeValidate, &TerrainWorldConfig::ValidateWorldHeight);
} }
} }
} }
@@ -128,4 +130,42 @@ namespace Terrain
} }
return false; return false;
} }
}
float TerrainWorldConfig::NumberOfSamples(AZ::Vector3* min, AZ::Vector3* max, AZ::Vector2* heightQuery)
{
float numberOfSamples = ((max->GetX() - min->GetX()) / heightQuery->GetX()) * ((max->GetY() - min->GetY()) / heightQuery->GetY());
return numberOfSamples;
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::DetermineMessage(float numSamples)
{
const float maximumSamplesAllowed = 8.0f * 1024.0f * 1024.0f;
if (numSamples < maximumSamplesAllowed)
{
return AZ::Success();
}
return AZ::Failure(AZStd::string("The number of samples exceeds the maximum allowed."));
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::ValidateWorldMin(void* newValue, [[maybe_unused]]const AZ::Uuid& valueType)
{
AZ::Vector3 minValue = *static_cast<AZ::Vector3*>(newValue);
return DetermineMessage(NumberOfSamples(&minValue, &m_worldMax, &m_heightQueryResolution));
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::ValidateWorldMax(void* newValue, [[maybe_unused]] const AZ::Uuid& valueType)
{
AZ::Vector3 maxValue = *static_cast<AZ::Vector3*>(newValue);
return DetermineMessage(NumberOfSamples(&m_worldMin, &maxValue, &m_heightQueryResolution));
}
AZ::Outcome<void, AZStd::string> TerrainWorldConfig::ValidateWorldHeight(void* newValue, [[maybe_unused]] const AZ::Uuid& valueType)
{
AZ::Vector2 heightValue = *static_cast<AZ::Vector2*>(newValue);
return DetermineMessage(NumberOfSamples(&m_worldMin, &m_worldMax, &heightValue));
}
} // namespace Terrain
@@ -32,6 +32,14 @@ namespace Terrain
AZ::Vector3 m_worldMin{ 0.0f, 0.0f, 0.0f }; AZ::Vector3 m_worldMin{ 0.0f, 0.0f, 0.0f };
AZ::Vector3 m_worldMax{ 1024.0f, 1024.0f, 1024.0f }; AZ::Vector3 m_worldMax{ 1024.0f, 1024.0f, 1024.0f };
AZ::Vector2 m_heightQueryResolution{ 1.0f, 1.0f }; AZ::Vector2 m_heightQueryResolution{ 1.0f, 1.0f };
private:
AZ::Outcome<void, AZStd::string> ValidateWorldMin(void* newValue, const AZ::Uuid& valueType);
AZ::Outcome<void, AZStd::string> ValidateWorldMax(void* newValue, const AZ::Uuid& valueType);
AZ::Outcome<void, AZStd::string> ValidateWorldHeight(void* newValue, const AZ::Uuid& valueType);
float NumberOfSamples(AZ::Vector3* min, AZ::Vector3* max, AZ::Vector2* heightQuery);
AZ::Outcome<void, AZStd::string> DetermineMessage(float numSamples);
}; };
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Provides height data for a region to the terrain system"; static constexpr const char* const s_componentDescription = "Provides height data for a region to the terrain system";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainHeight.svg"; static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainHeight.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainHeight.svg"; static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainHeight.svg";
static constexpr const char* const s_helpUrl = ""; static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/height_gradient_list/";
}; };
} }
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Defines a terrain region for use by the terrain system"; static constexpr const char* const s_componentDescription = "Defines a terrain region for use by the terrain system";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg"; static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainLayerSpawner.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg"; static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainLayerSpawner.svg";
static constexpr const char* const s_helpUrl = ""; static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/layer_spawner/";
}; };
} }
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Provides a mapping between gradients and surface tags for use by the terrain system."; static constexpr const char* const s_componentDescription = "Provides a mapping between gradients and surface tags for use by the terrain system.";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceGradientList.svg"; static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceGradientList.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg"; static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceGradientList.svg";
static constexpr const char* const s_helpUrl = ""; static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/surface-gradient-list/";
}; };
} }
@@ -27,6 +27,6 @@ namespace Terrain
static constexpr const char* const s_componentDescription = "Provides a mapping between surface tags and render materials."; static constexpr const char* const s_componentDescription = "Provides a mapping between surface tags and render materials.";
static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceMaterials.svg"; static constexpr const char* const s_icon = "Editor/Icons/Components/TerrainSurfaceMaterials.svg";
static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg"; static constexpr const char* const s_viewportIcon = "Editor/Icons/Components/Viewport/TerrainSurfaceMaterials.svg";
static constexpr const char* const s_helpUrl = ""; static constexpr const char* const s_helpUrl = "https://o3de.org/docs/user-guide/components/reference/terrain/surface-material-list/";
}; };
} }
@@ -10,6 +10,7 @@ cmake_policy(SET CMP0012 NEW) # new policy for the if that evaluates a boolean o
function(ly_copy source_file target_directory) function(ly_copy source_file target_directory)
cmake_path(GET source_file FILENAME target_filename) cmake_path(GET source_file FILENAME target_filename)
cmake_PATH(GET source_file EXTENSION target_filename_ext)
cmake_path(APPEND target_file "${target_directory}" "${target_filename}") cmake_path(APPEND target_file "${target_directory}" "${target_filename}")
cmake_path(COMPARE "${source_file}" EQUAL "${target_file}" same_location) cmake_path(COMPARE "${source_file}" EQUAL "${target_file}" same_location)
if(NOT ${same_location}) if(NOT ${same_location})
+16 -12
View File
@@ -8,8 +8,7 @@ REM
REM REM
REM To get recursive folder creation REM To get recursive folder creation
SETLOCAL EnableExtensions SETLOCAL EnableExtensions EnableDelayedExpansion
SETLOCAL EnableDelayedExpansion
where /Q cmake where /Q cmake
IF NOT %ERRORLEVEL%==0 ( IF NOT %ERRORLEVEL%==0 (
@@ -22,21 +21,26 @@ IF NOT "%COMMAND_CWD%"=="" (
CD %COMMAND_CWD% CD %COMMAND_CWD%
) )
REM Jenkins reports MSB8029 when TMP/TEMP is not defined, define a dummy folder REM Ending the local environment to be able to propagate the TMP/TEMP variables to the calling script
IF NOT "%TMP%"=="" ( ENDLOCAL
IF NOT "%WORKSPACE_TMP%"=="" (
SET TMP=%WORKSPACE_TMP% REM Jenkins does not defined TMP
SET TEMP=%WORKSPACE_TMP% IF "%TMP%"=="" (
IF "%WORKSPACE%"=="" (
SET TMP=%APPDATA%\Local\Temp
SET TEMP=%APPDATA%\Local\Temp
) ELSE ( ) ELSE (
SET TMP=%cd%/temp SET TMP=%WORKSPACE%\Temp
SET TEMP=%cd%/temp SET TEMP=%WORKSPACE%\Temp
REM This folder may not be created in the workspace
IF NOT EXIST "!TMP!" (
MKDIR "!TMP!"
)
) )
) )
IF NOT EXIST "!TMP!" (
MKDIR "!TMP!"
)
EXIT /b 0 EXIT /b 0
:error :error
ENDLOCAL
EXIT /b 1 EXIT /b 1
@@ -17,14 +17,6 @@ IF NOT EXIST %OUTPUT_DIRECTORY% (
) )
PUSHD %OUTPUT_DIRECTORY% PUSHD %OUTPUT_DIRECTORY%
REM Override the temporary directory used by wix to the workspace (if we have a WORKSPACE_TMP)
IF NOT "%WORKSPACE_TMP%"=="" (
SET "WIX_TEMP=!WORKSPACE_TMP!/wix"
IF NOT EXIST "!WIX_TEMP!" (
MKDIR "!WIX_TEMP!"
)
)
REM Make sure we are using the CMake version of CPack and not the one that comes with chocolatey REM Make sure we are using the CMake version of CPack and not the one that comes with chocolatey
SET CPACK_PATH= SET CPACK_PATH=
IF "%LY_CMAKE_PATH%"=="" ( IF "%LY_CMAKE_PATH%"=="" (
Binary file not shown.
+27
View File
@@ -0,0 +1,27 @@
#!/bin/bash
#
# Copyright (c) Contributors to the Open 3D Engine Project.
# For complete copyright and license terms please see the LICENSE at the root of this distribution.
#
# SPDX-License-Identifier: Apache-2.0 OR MIT
#
#
set +x
export GPG_TTY=$(tty) # Required to pass valid tty during ssh sessions
file=$1
# dpkg-sig depends on a valid and trusted GPG private key. This also assumes a private key password has already been cached via gpg-agent
# If you do need to pass a password, a gpg argument can be added to the command:
# dpkg-sig -k $fingerprint -g "--pinentry-mode loopback --passphrase $pass" --sign builder
fingerprint=$(gpg --list-keys --with-colons | awk -F: '/fpr:/ {print $10}' | tail -n1) #Get the last certificate in the list, which is the signing cert
if [ -z $fingerprint ]; then
echo "No valid certs found. Exiting with 1"
exit 1
fi
echo "Signing with $fingerprint"
dpkg-sig -k $fingerprint --sign builder $file
dpkg-sig --verify $file && echo "Signing $file complete!"