Merge branch 'development' into jillich/EmfxAabbImprovements

This commit is contained in:
Benjamin Jillich
2021-08-02 10:58:20 +02:00
144 changed files with 5033 additions and 3724 deletions
@@ -0,0 +1,102 @@
"""
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
Hydra script that is used to create a new level with a default rendering setup.
After the level is setup, screenshots are diffed against golden images are used to verify pass/fail results of the test.
See the run() function for more in-depth test info.
"""
import os
import sys
import azlmbr.legacy.general as general
sys.path.append(os.path.join(azlmbr.paths.devroot, "AutomatedTesting", "Gem", "PythonTests"))
import editor_python_test_tools.hydra_editor_utils as hydra
from editor_python_test_tools.editor_test_helper import EditorTestHelper
from atom_renderer.atom_utils.benchmark_utils import BenchmarkHelper
SCREEN_WIDTH = 1280
SCREEN_HEIGHT = 720
DEGREE_RADIAN_FACTOR = 0.0174533
helper = EditorTestHelper(log_prefix="Test_Atom_BasicLevelSetup")
def run():
"""
1. View -> Layouts -> Restore Default Layout, sets the viewport to ratio 16:9 @ 1280 x 720
2. Runs console command r_DisplayInfo = 0
3. Opens AtomFeatureIntegrationBenchmark level
4. Initializes benchmark helper with benchmark name to capture benchmark metadata.
5. Idles for 100 frames, then collects pass timings for 100 frames.
:return: None
"""
def initial_viewport_setup(screen_width, screen_height):
general.set_viewport_size(screen_width, screen_height)
general.update_viewport()
helper.wait_for_condition(
function=lambda: helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1)
and helper.isclose(a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1),
timeout_in_seconds=4.0
)
result = helper.isclose(a=general.get_viewport_size().x, b=SCREEN_WIDTH, rel_tol=0.1) and helper.isclose(
a=general.get_viewport_size().y, b=SCREEN_HEIGHT, rel_tol=0.1)
general.log(general.get_viewport_size().x)
general.log(general.get_viewport_size().y)
general.log(general.get_viewport_size().z)
general.log(f"Viewport is set to the expected size: {result}")
general.run_console("r_DisplayInfo = 0")
def after_level_load():
"""Function to call after creating/opening a level to ensure it loads."""
# Give everything a second to initialize.
general.idle_enable(True)
general.idle_wait(1.0)
general.update_viewport()
general.idle_wait(0.5) # half a second is more than enough for updating the viewport.
# Close out problematic windows, FPS meters, and anti-aliasing.
if general.is_helpers_shown(): # Turn off the helper gizmos if visible
general.toggle_helpers()
general.idle_wait(1.0)
if general.is_pane_visible("Error Report"): # Close Error Report windows that block focus.
general.close_pane("Error Report")
if general.is_pane_visible("Error Log"): # Close Error Log windows that block focus.
general.close_pane("Error Log")
general.idle_wait(1.0)
general.run_console("r_displayInfo=0")
general.run_console("r_antialiasingmode=0")
general.idle_wait(1.0)
return True
# Wait for Editor idle loop before executing Python hydra scripts.
general.idle_enable(True)
general.open_level_no_prompt("AtomFeatureIntegrationBenchmark")
# Basic setup after opening level.
after_level_load()
initial_viewport_setup(SCREEN_WIDTH, SCREEN_HEIGHT)
general.enter_game_mode()
general.idle_wait(1.0)
helper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=2.0)
benchmarker = BenchmarkHelper("AtomFeatureIntegrationBenchmark")
benchmarker.capture_benchmark_metadata()
general.idle_wait_frames(100)
for i in range(1, 101):
benchmarker.capture_pass_timestamp(i)
general.exit_game_mode()
helper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=2.0)
general.log("Capturing complete.")
if __name__ == "__main__":
run()
@@ -0,0 +1,84 @@
"""
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
"""
import azlmbr.atom
import azlmbr.legacy.general as general
FOLDER_PATH = '@user@/Scripts/PerformanceBenchmarks'
METADATA_FILE = 'benchmark_metadata.json'
class BenchmarkHelper(object):
"""
A helper to capture benchmark data.
"""
def __init__(self, benchmark_name):
super().__init__()
self.benchmark_name = benchmark_name
self.output_path = f'{FOLDER_PATH}/{benchmark_name}'
self.done = False
self.capturedData = False
self.max_frames_to_wait = 200
def capture_benchmark_metadata(self):
"""
Capture benchmark metadata and block further execution until it has been written to the disk.
"""
self.handler = azlmbr.atom.ProfilingCaptureNotificationBusHandler()
self.handler.connect()
self.handler.add_callback('OnCaptureBenchmarkMetadataFinished', self.on_data_captured)
self.done = False
self.capturedData = False
success = azlmbr.atom.ProfilingCaptureRequestBus(
azlmbr.bus.Broadcast, "CaptureBenchmarkMetadata", self.benchmark_name, f'{self.output_path}/{METADATA_FILE}'
)
if success:
self.wait_until_data()
general.log('Benchmark metadata captured.')
else:
general.log('Failed to capture benchmark metadata.')
return self.capturedData
def capture_pass_timestamp(self, frame_number):
"""
Capture pass timestamps and block further execution until it has been written to the disk.
"""
self.handler = azlmbr.atom.ProfilingCaptureNotificationBusHandler()
self.handler.connect()
self.handler.add_callback('OnCaptureQueryTimestampFinished', self.on_data_captured)
self.done = False
self.capturedData = False
success = azlmbr.atom.ProfilingCaptureRequestBus(
azlmbr.bus.Broadcast, "CapturePassTimestamp", f'{self.output_path}/frame{frame_number}_timestamps.json')
if success:
self.wait_until_data()
general.log('Pass timestamps captured.')
else:
general.log('Failed to capture pass timestamps.')
return self.capturedData
def on_data_captured(self, parameters):
# the parameters come in as a tuple
if parameters[0]:
general.log('Captured data successfully.')
self.capturedData = True
else:
general.log('Failed to capture data.')
self.done = True
self.handler.disconnect()
def wait_until_data(self):
frames_waited = 0
while self.done == False:
general.idle_wait_frames(1)
if frames_waited > self.max_frames_to_wait:
general.log('Timed out while waiting for the data to be captured')
self.handler.disconnect()
break
else:
frames_waited = frames_waited + 1
general.log(f'(waited {frames_waited} frames)')
@@ -14,6 +14,7 @@ import pytest
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.image.screenshot_compare_qssim import qssim as compare_screenshots
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
import editor_python_test_tools.hydra_test_utils as hydra
logger = logging.getLogger(__name__)
@@ -83,3 +84,43 @@ class TestAllComponentsIndepthTests(object):
for test_screenshot, golden_screenshot in zip(test_screenshots, golden_images):
compare_screenshots(test_screenshot, golden_screenshot)
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ["windows_editor"])
@pytest.mark.parametrize("level", ["AtomFeatureIntegrationBenchmark"])
class TestPerformanceBenchmarkSuite(object):
def test_AtomFeatureIntegrationBenchmark(
self, request, editor, workspace, rhi, project, launcher_platform, level):
"""
Please review the hydra script run by this test for more specific test info.
Tests the performance of the Simple level.
"""
expected_lines = [
"Benchmark metadata captured.",
"Pass timestamps captured.",
"Capturing complete.",
"Captured data successfully."
]
unexpected_lines = [
"Failed to capture data.",
"Failed to capture pass timestamps.",
"Failed to capture benchmark metadata."
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"hydra_GPUTest_AtomFeatureIntegrationBenchmark.py",
timeout=EDITOR_TIMEOUT,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
cfg_args=[level],
null_renderer=False,
)
aggregator = BenchmarkDataAggregator(workspace, logger, 'periodic')
aggregator.upload_metrics(rhi)
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:5232563c3ff322669808ac4daeda3d822e4ef8c9c87db0fa245f0f9c9c34aada
size 23379
@@ -0,0 +1,6 @@
<download name="AtomFeatureIntegrationBenchmark" type="Map">
<index src="filelist.xml" dest="filelist.xml"/>
<files>
<file src="level.pak" dest="level.pak" size="154A" md5="da50266269914f05d06d80f0025953fc"/>
</files>
</download>
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e075be2cb7cf5aa98e3503c1119b94c3098b35500c98c4db32d025c9e1afa52d
size 5450
@@ -0,0 +1,12 @@
495.045,510.96,35.8437,-0.166,0,-1.82124
4.79827,4.71364,64.7838,-1.41886,0,2.48964
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
+2
View File
@@ -163,6 +163,8 @@ ly_add_target(
editor_files.cmake
PLATFORM_INCLUDE_FILES
Platform/${PAL_PLATFORM_NAME}/editor_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
TARGET_PROPERTIES
LY_INSTALL_GENERATE_RUN_TARGET TRUE
BUILD_DEPENDENCIES
PRIVATE
3rdParty::Qt::Core
+28 -24
View File
@@ -415,33 +415,37 @@ namespace Editor
}
// Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system.
// These events are now consumed both in and out of game mode.
if (msg->message == WM_INPUT)
// These events are only broadcast in game mode. In Editor mode, RenderViewportWidget creates synthetic
// keyboard and mouse events via Qt.
if (GetIEditor()->IsInGameMode())
{
UINT rawInputSize;
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
LPBYTE rawInputBytes = rawInputBytesArray.data();
const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
CRY_ASSERT(bytesCopied == rawInputSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
CRY_ASSERT(rawInput);
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput);
return false;
}
else if (msg->message == WM_DEVICECHANGE)
{
if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED
if (msg->message == WM_INPUT)
{
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent);
UINT rawInputSize;
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
AZStd::array<BYTE, sizeof(RAWINPUT)> rawInputBytesArray;
LPBYTE rawInputBytes = rawInputBytesArray.data();
const UINT bytesCopied = GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
CRY_ASSERT(bytesCopied == rawInputSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
CRY_ASSERT(rawInput);
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputEvent, *rawInput);
return false;
}
else if (msg->message == WM_DEVICECHANGE)
{
if (msg->wParam == 0x0007) // DBT_DEVNODES_CHANGED
{
AzFramework::RawInputNotificationBusWindows::Broadcast(&AzFramework::RawInputNotificationsWindows::OnRawInputDeviceChangeEvent);
}
return true;
}
return true;
}
return false;
+2 -2
View File
@@ -20,11 +20,11 @@ struct AffineParts
Vec3 scale; //!< Stretch factors.
float fDet; //!< Sign of determinant.
/** Decompose matrix to its affnie parts.
/** Decompose matrix to its affine parts.
*/
void Decompose(const Matrix34& mat);
/** Decompose matrix to its affnie parts.
/** Decompose matrix to its affine parts.
Assume there`s no stretch rotation.
*/
void SpectralDecompose(const Matrix34& mat);
-7
View File
@@ -1,7 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Label="PropertySheets" />
<PropertyGroup>
<DisableFastUpToDateCheck>True</DisableFastUpToDateCheck>
</PropertyGroup>
</Project>
@@ -84,7 +84,7 @@ namespace AZ
else
{
AZ::Debug::Trace::Instance().Assert(__FILE__, __LINE__, AZ_FUNCTION_SIGNATURE,
"Bus has multiple threads in its callstack records. Configure MutexType on the bus, or don't send to it from multiple threads");
"Bus %s has multiple threads in its callstack records. Configure MutexType on the bus, or don't send to it from multiple threads", BusType::GetName());
}
}
+3 -3
View File
@@ -268,7 +268,7 @@ namespace AZ
m_messages.pop();
if (numMessages == 1)
{
m_messages.get_container().clear(); // If it was the last message, free all memory.
m_messages = {};
}
}
//////////////////////////////////////////////////////////////////////////
@@ -280,7 +280,7 @@ namespace AZ
void Clear()
{
AZStd::lock_guard<MutexType> lock(m_messagesMutex);
m_messages.get_container().clear();
m_messages = {};
}
void SetActive(bool isActive)
@@ -289,7 +289,7 @@ namespace AZ
m_isActive = isActive;
if (!m_isActive)
{
m_messages.get_container().clear();
m_messages = {};
}
};
@@ -42,8 +42,6 @@ namespace AZStd
class unordered_multiset;
template<AZStd::size_t NumBits>
class bitset;
template<class T, class Container/* = AZStd::deque<T>*/ >
class stack;
template<class T>
class intrusive_ptr;
@@ -57,6 +57,7 @@ namespace AZ::Internal
// and avoid all this logic.
using namespace AZ::SettingsRegistryMergeUtils;
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
AZ::IO::FixedMaxPath engineRoot;
if (auto engineManifestPath = AZ::Utils::GetEngineManifestPath(); !engineManifestPath.empty())
@@ -72,45 +73,16 @@ namespace AZ::Internal
struct EngineInfo
{
AZ::IO::FixedMaxPath m_path;
AZ::SettingsRegistryInterface::FixedValueString m_moniker;
FixedValueString m_moniker;
};
struct EnginePathsVisitor : public AZ::SettingsRegistryInterface::Visitor
{
void Visit(
[[maybe_unused]] AZStd::string_view path, [[maybe_unused]] AZStd::string_view valueName,
[[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName,
[[maybe_unused]] AZ::SettingsRegistryInterface::Type type, AZStd::string_view value) override
{
m_enginePaths.emplace_back(EngineInfo{AZ::IO::FixedMaxPath{value}.LexicallyNormal(), {}});
}
AZ::SettingsRegistryInterface::VisitResponse Traverse(
[[maybe_unused]] AZStd::string_view path, AZStd::string_view valueName,
AZ::SettingsRegistryInterface::VisitAction action, AZ::SettingsRegistryInterface::Type type) override
{
auto response = AZ::SettingsRegistryInterface::VisitResponse::Continue;
if (action == AZ::SettingsRegistryInterface::VisitAction::Begin)
{
if (type == AZ::SettingsRegistryInterface::Type::Array)
{
if (valueName.compare("engines") != 0)
{
response = AZ::SettingsRegistryInterface::VisitResponse::Skip;
}
}
}
else if (action == AZ::SettingsRegistryInterface::VisitAction::Value)
{
if (type == AZ::SettingsRegistryInterface::Type::String)
{
if (valueName.compare("path") != 0)
{
response = AZ::SettingsRegistryInterface::VisitResponse::Skip;
}
}
}
return response;
m_enginePaths.emplace_back(EngineInfo{ AZ::IO::FixedMaxPath{value}.LexicallyNormal(), FixedValueString{valueName} });
}
AZStd::vector<EngineInfo> m_enginePaths{};
@@ -119,11 +91,11 @@ namespace AZ::Internal
EnginePathsVisitor pathVisitor;
if (manifestLoaded)
{
auto enginePathsKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engines", EngineManifestRootKey);
auto enginePathsKey = FixedValueString::format("%s/engines_path", EngineManifestRootKey);
settingsRegistry.Visit(pathVisitor, enginePathsKey);
}
const auto engineMonikerKey = AZ::SettingsRegistryInterface::FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
const auto engineMonikerKey = FixedValueString::format("%s/engine_name", EngineSettingsRootKey);
AZStd::set<AZ::IO::FixedMaxPath> projectPathsNotFound;
@@ -135,7 +107,15 @@ namespace AZ::Internal
if (settingsRegistry.MergeSettingsFile(
engineSettingsPath.Native(), AZ::SettingsRegistryInterface::Format::JsonMergePatch, EngineSettingsRootKey))
{
settingsRegistry.Get(engineInfo.m_moniker, engineMonikerKey);
FixedValueString engineName;
settingsRegistry.Get(engineName, engineMonikerKey);
AZ_Warning("SettingsRegistryMergeUtils",engineInfo.m_moniker == engineName,
R"(The engine name key "%s" mapped to engine path "%s" within the global manifest of "%s")"
R"( does not match the "engine_name" field "%s" in the engine.json)" "\n"
"This engine should be re-registered.",
engineInfo.m_moniker.c_str(), engineInfo.m_path.c_str(), engineManifestPath.c_str(),
engineName.c_str())
engineInfo.m_moniker = engineName;
}
}
@@ -5,206 +5,17 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZSTD_QUEUE_H
#define AZSTD_QUEUE_H 1
#pragma once
#include <AzCore/std/containers/deque.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/functional_basic.h>
#include <queue>
namespace AZStd
{
/**
* FIFO queue complaint with \ref CStd (23.2.3.1)
* The only extension we have is that we allow access
* to the underlying container via: get_container function.
* Check the queue \ref AZStdExamples.
*/
template<class T, class Container = AZStd::deque<T> >
class queue
{
enum
{
CONTAINER_VERSION = 1
};
public:
typedef queue<T, Container> this_type;
typedef Container container_type;
typedef typename Container::value_type value_type;
typedef typename Container::size_type size_type;
typedef typename Container::reference reference;
typedef typename Container::const_reference const_reference;
AZ_FORCE_INLINE queue() {}
AZ_FORCE_INLINE explicit queue(const container_type& container)
: m_container(container) {}
AZ_FORCE_INLINE bool empty() const { return m_container.empty(); }
AZ_FORCE_INLINE size_type size() const { return m_container.size(); }
AZ_FORCE_INLINE reference front() { return m_container.front(); }
AZ_FORCE_INLINE const_reference front() const { return m_container.front(); }
AZ_FORCE_INLINE reference back() { return m_container.back(); }
AZ_FORCE_INLINE const_reference back() const { return m_container.back(); }
AZ_FORCE_INLINE void push(const value_type& value) { m_container.push_back(value); }
AZ_FORCE_INLINE void pop() { m_container.pop_front(); }
AZ_FORCE_INLINE void push() { m_container.push_back(); }
AZ_FORCE_INLINE queue(this_type&& rhs)
: m_container(AZStd::move(rhs.m_container)) {}
AZ_FORCE_INLINE explicit queue(Container&& container)
: m_container(AZStd::move(container)) {}
this_type& operator=(this_type&& rhs)
{
m_container = AZStd::move(rhs.m_container);
return (*this);
}
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); }
template<class... Args>
void emplace(Args&&... args) { m_container.emplace_back(AZStd::forward<Args>(args)...); }
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); }
AZ_FORCE_INLINE Container& get_container() { return m_container; }
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
protected:
Container m_container;
};
// queue TEMPLATE FUNCTIONS
template<class T, class Container>
AZ_FORCE_INLINE bool operator==(const AZStd::queue<T, Container>& left, const AZStd::queue<T, Container>& right)
{
return left.get_container() == right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE bool operator!=(const AZStd::queue<T, Container>& left, const AZStd::queue<T, Container>& right)
{
return left.get_container() != right.get_container();
}
/* template<class T, class Container>
AZ_FORCE_INLINE bool operator<(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() < right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE bool operator>(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() > right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE operator<=(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() <= right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE bool operator>=(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() >= right.get_container();
}*/
/**
* Priority queue is complaint with \ref CStd (23.2.3.2)
* The only extension we have is that we allow access
* to the underlying container via: get_container function.
* Check the priority_queue \ref AZStdExamples.
*/
template<class T, class Container = AZStd::vector<T>, class Predicate = AZStd::less<typename Container::value_type> >
class priority_queue
{
enum
{
CONTAINER_VERSION = 1
};
public:
typedef priority_queue<T, Container, Predicate> this_type;
typedef Container container_type;
typedef typename Container::value_type value_type;
typedef typename Container::size_type size_type;
typedef typename Container::reference reference;
typedef typename Container::const_reference const_reference;
AZ_FORCE_INLINE priority_queue() {}
AZ_FORCE_INLINE explicit priority_queue(const Predicate& comp)
: m_comp(comp) {}
AZ_FORCE_INLINE priority_queue(const Predicate& comp, const container_type& container)
: m_container(container)
, m_comp(comp)
{
// construct by copying specified container, comparator
AZStd::make_heap(m_container.begin(), m_container.end(), comp);
}
template<class InputIterator>
AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last)
: m_container(first, last)
, m_comp()
{
AZStd::make_heap(m_container.begin(), m_container.end(), m_comp);
}
template<class InputIterator>
AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last, const Predicate& comp)
: m_container(first, last)
, m_comp(comp)
{ // construct by copying [_First, _Last), specified comparator
AZStd::make_heap(m_container.begin(), m_container.end(), m_comp);
}
template<class InputIterator>
AZ_FORCE_INLINE priority_queue(InputIterator first, InputIterator last, const Predicate& comp, const container_type& container)
: m_container(container)
, m_comp(comp)
{ // construct by copying [_First, _Last), container, and comparator
m_container.insert(m_container.end(), first, last);
AZStd::make_heap(m_container.begin(), m_container.end(), m_comp);
}
AZ_FORCE_INLINE bool empty() const { return m_container.empty(); }
AZ_FORCE_INLINE size_type size() const { return m_container.size(); }
AZ_FORCE_INLINE const_reference top() const { return m_container.front(); }
AZ_FORCE_INLINE reference top() { return m_container.front(); }
AZ_FORCE_INLINE void push(const value_type& value)
{
m_container.push_back(value);
AZStd::push_heap(m_container.begin(), m_container.end(), m_comp);
}
AZ_FORCE_INLINE void pop()
{
AZStd::pop_heap(m_container.begin(), m_container.end(), m_comp);
m_container.pop_back();
}
AZ_FORCE_INLINE priority_queue(this_type&& rhs)
: m_container(AZStd::move(rhs.m_container))
, m_comp(AZStd::move(rhs.m_comp)) {}
AZ_FORCE_INLINE explicit priority_queue(const Predicate& pred, Container&& container)
: m_container(AZStd::move(container))
, m_comp(pred) {}
this_type& operator=(this_type&& rhs)
{
m_container = AZStd::move(rhs.m_container);
m_comp = AZStd::move(rhs.m_comp);
return (*this);
}
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); }
template<class Args>
void emplace(Args&& args) { m_container.emplace_back(AZStd::forward<Args>(args)); AZStd::push_heap(m_container.begin(), m_container.end(), m_comp); }
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); AZStd::swap(m_comp, rhs.m_comp); }
AZ_FORCE_INLINE Container& get_container() { return m_container; }
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
protected:
Container m_container;
Predicate m_comp;
};
template<class T, class Container = AZStd::deque<T>>
using queue = std::queue<T, Container>;
template<class T, class Container = AZStd::vector<T>, class Compare = AZStd::less<typename Container::value_type>>
using priority_queue = std::priority_queue<T, Container, Compare>;
}
#endif // AZSTD_QUEUE_H
#pragma once
@@ -5,103 +5,13 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZSTD_STACK_H
#define AZSTD_STACK_H 1
#pragma once
#include <AzCore/std/containers/deque.h>
#include <stack>
namespace AZStd
{
/**
* Stack container is complaint with \ref CStd (23.2.3.3)
* The only extension we have is that we allow access
* to the underlying container via: get_container function.
* Check the stack \ref AZStdExamples.
*/
template<class T, class Container = AZStd::deque<T> >
class stack
{
enum
{
CONTAINER_VERSION = 1
};
public:
typedef stack<T, Container> this_type;
typedef Container container_type;
typedef typename Container::value_type value_type;
typedef typename Container::size_type size_type;
typedef typename Container::reference reference;
typedef typename Container::const_reference const_reference;
AZ_FORCE_INLINE stack() {}
AZ_FORCE_INLINE explicit stack(const container_type& container)
: m_container(container) {}
AZ_FORCE_INLINE bool empty() const { return m_container.empty(); }
AZ_FORCE_INLINE size_type size() const { return m_container.size(); }
AZ_FORCE_INLINE reference top() { return m_container.back(); }
AZ_FORCE_INLINE const_reference top() const { return m_container.back(); }
AZ_FORCE_INLINE reference back() { return m_container.back(); }
AZ_FORCE_INLINE const_reference back() const { return m_container.back(); }
AZ_FORCE_INLINE void push(const value_type& value) { m_container.push_back(value); }
AZ_FORCE_INLINE void pop() { m_container.pop_back(); }
AZ_FORCE_INLINE void push() { m_container.push_back(); }
AZ_FORCE_INLINE stack(this_type&& rhs)
: m_container(AZStd::move(rhs.m_container)) {}
AZ_FORCE_INLINE explicit stack(Container&& container)
: m_container(AZStd::move(container)) {}
this_type& operator=(this_type&& rhs) { m_container = AZStd::move(rhs.m_container); return *this; }
void push(value_type&& value) { m_container.push_back(AZStd::move(value)); }
template<class Args>
void emplace(Args&& args) { m_container.emplace_back(AZStd::forward<Args>(args)); }
void swap(this_type&& rhs) { m_container.swap(AZStd::move(rhs.m_container)); }
void swap(this_type& rhs) { AZStd::swap(m_container, rhs.m_container); }
AZ_FORCE_INLINE Container& get_container() { return m_container; }
AZ_FORCE_INLINE const Container& get_container() const { return m_container; }
protected:
Container m_container;
};
// queue TEMPLATE FUNCTIONS
template<class T, class Container>
AZ_FORCE_INLINE bool operator==(const AZStd::stack<T, Container>& left, const AZStd::stack<T, Container>& right)
{
return left.get_container() == right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE bool operator!=(const AZStd::stack<T, Container>& left, const AZStd::stack<T, Container>& right)
{
return left.get_container() != right.get_container();
}
/* template<class T, class Container>
AZ_FORCE_INLINE bool operator<(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() < right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE bool operator>(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() > right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE operator<=(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() <= right.get_container();
}
template<class T, class Container>
AZ_FORCE_INLINE bool operator>=(const queue<T,Container>& left, const queue<T,Container>& right)
{
return left.get_container() >= right.get_container();
}*/
template<class T, class Container = AZStd::deque<T>>
using stack = std::stack<T, Container>;
}
#endif // AZSTD_STACK_H
#pragma once
@@ -298,7 +298,7 @@ namespace UnitTest
AZ_TEST_ASSERT(int_queue.empty());
AZ_TEST_ASSERT(int_queue.size() == 0);
// Queue uses deque as default container, so try to contruct to queue from a deque.
// Queue uses deque as default container, so try to construct to queue from a deque.
deque<int> container(40, 10);
int_queue_type int_queue2(container);
AZ_TEST_ASSERT(!int_queue2.empty());
@@ -324,7 +324,7 @@ namespace UnitTest
AZ_TEST_ASSERT(int_queue2.size() == 40);
AZ_TEST_ASSERT(int_queue2.back() == 20);
int_queue.push();
int_queue.emplace();
AZ_TEST_ASSERT(!int_queue.empty());
AZ_TEST_ASSERT(int_queue.size() == 1);
@@ -423,7 +423,7 @@ namespace UnitTest
AZ_TEST_ASSERT(int_stack2.size() == 40);
AZ_TEST_ASSERT(int_stack2.top() == 10);
int_stack.push();
int_stack.emplace();
AZ_TEST_ASSERT(!int_stack.empty());
AZ_TEST_ASSERT(int_stack.size() == 1);
// StackContainerTest-End
@@ -669,4 +669,19 @@ namespace UnitTest
++iteration;
}
}
using StackContainerTestFixture = ScopedAllocatorSetupFixture;
TEST_F(StackContainerTestFixture, StackEmplaceOperator_SupportsZeroOrMoreArguments)
{
using TestPairType = AZStd::pair<int, int>;
AZStd::stack<TestPairType> testStack;
testStack.emplace();
testStack.emplace(1);
testStack.emplace(2, 3);
using ContainerType = typename AZStd::stack<TestPairType>::container_type;
AZStd::stack<TestPairType> expectedStack(ContainerType{ TestPairType{ 0, 0 }, TestPairType{ 1, 0 }, TestPairType{ 2, 3 } });
EXPECT_EQ(expectedStack, testStack);
}
}
@@ -123,6 +123,12 @@ namespace AzPhysics
->Field("Kinematic", &RigidBodyConfiguration::m_kinematic)
->Field("CCD Enabled", &RigidBodyConfiguration::m_ccdEnabled)
->Field("Compute Mass", &RigidBodyConfiguration::m_computeMass)
->Field("Lock Linear X", &RigidBodyConfiguration::m_lockLinearX)
->Field("Lock Linear Y", &RigidBodyConfiguration::m_lockLinearY)
->Field("Lock Linear Z", &RigidBodyConfiguration::m_lockLinearZ)
->Field("Lock Angular X", &RigidBodyConfiguration::m_lockAngularX)
->Field("Lock Angular Y", &RigidBodyConfiguration::m_lockAngularY)
->Field("Lock Angular Z", &RigidBodyConfiguration::m_lockAngularZ)
->Field("Mass", &RigidBodyConfiguration::m_mass)
->Field("Compute COM", &RigidBodyConfiguration::m_computeCenterOfMass)
->Field("Centre of mass offset", &RigidBodyConfiguration::m_centerOfMassOffset)
@@ -62,6 +62,16 @@ namespace AzPhysics
bool m_computeInertiaTensor = true;
bool m_computeMass = true;
// Flags to restrict motion along specific world-space axes.
bool m_lockLinearX = false;
bool m_lockLinearY = false;
bool m_lockLinearZ = false;
// Flags to restrict rotation around specific world-space axes.
bool m_lockAngularX = false;
bool m_lockAngularY = false;
bool m_lockAngularZ = false;
//! If set, non-simulated shapes will also be included in the mass properties calculation.
bool m_includeAllShapesInMassCalculation = false;
@@ -32,7 +32,7 @@ namespace UnitTest
void TestDebugDisplayRequests::DrawWireBox(const AZ::Vector3& min, const AZ::Vector3& max)
{
const AZ::Transform& tm = m_transforms.back();
const AZ::Transform& tm = m_transforms.top();
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), min.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), min.GetY(), max.GetZ())));
m_points.push_back(tm.TransformPoint(AZ::Vector3(min.GetX(), max.GetY(), min.GetZ())));
@@ -50,7 +50,7 @@ namespace UnitTest
void TestDebugDisplayRequests::DrawWireQuad(float width, float height)
{
const AZ::Transform& tm = m_transforms.back();
const AZ::Transform& tm = m_transforms.top();
m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, -0.5f * height)));
m_points.push_back(tm.TransformPoint(AZ::Vector3(-0.5f * width, 0.0f, 0.5f * height)));
m_points.push_back(tm.TransformPoint(AZ::Vector3(0.5f * width, 0.0f, -0.5f * height)));
@@ -64,7 +64,7 @@ namespace UnitTest
void TestDebugDisplayRequests::DrawPoints(const AZStd::vector<AZ::Vector3>& points)
{
const AZ::Transform& tm = m_transforms.back();
const AZ::Transform& tm = m_transforms.top();
for (const auto& point : points)
{
m_points.push_back(tm.TransformPoint(point));
@@ -100,7 +100,7 @@ namespace UnitTest
void TestDebugDisplayRequests::PushMatrix(const AZ::Transform& tm)
{
m_transforms.push(m_transforms.back() * tm);
m_transforms.push(m_transforms.top() * tm);
}
void TestDebugDisplayRequests::PopMatrix()
@@ -481,7 +481,7 @@ namespace AzFramework
if (!m_freeOctreeNodes.empty())
{
// Take a free block of child nodes from our free list
ExtractPageAndOffsetFromIndex(m_freeOctreeNodes.back(), nextChildPage, nextChildOffset);
ExtractPageAndOffsetFromIndex(m_freeOctreeNodes.top(), nextChildPage, nextChildOffset);
m_freeOctreeNodes.pop();
}
else
@@ -35,7 +35,7 @@ namespace AzFramework
class LinuxXcbConnectionManager
{
public:
AZ_RTTI(LinuxXcbConnectionManager, "{649951316-3626-4C9D-9DCA-2E7ABF84C0A9}");
AZ_RTTI(LinuxXcbConnectionManager, "{1F756E14-8D74-42FD-843C-4863307710DB}");
virtual ~LinuxXcbConnectionManager() = default;
@@ -177,7 +177,7 @@ namespace AzToolsFramework
PrefabDomUtils::ApplyPatches(templateDomReference, templateDomReference.GetAllocator(), providedPatch);
//trigger propagation
if (result.GetOutcome() != AZ::JsonSerializationResult::Outcomes::Success)
if (result.GetProcessing() != AZ::JsonSerializationResult::Processing::Completed)
{
AZ_Error("Prefab", false, "Patch was not successfully applied.");
return false;
@@ -90,10 +90,11 @@ namespace AzToolsFramework
AZStd::unordered_map<Instance*, PrefabDom> nestedInstanceLinkPatchesMap;
// Retrieve all entities affected and identify Instances
if (!RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonRootEntityOwningInstance->get(), entities, instances))
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
inputEntityList, commonRootEntityOwningInstance->get(), entities, instances);
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
{
return AZ::Failure(
AZStd::string("Could not create a new prefab out of the entities provided - invalid selection."));
return retrieveEntitiesAndInstancesOutcome;
}
AZStd::unordered_map<AZ::EntityId, AZStd::string> oldEntityAliases;
@@ -646,7 +647,12 @@ namespace AzToolsFramework
{
// Retrieve all nested instances that are part of the subtree under the current entity.
EntityList entities;
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instancesInvolved);
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
{ entity }, beforeOwningInstance->get(), entities, instancesInvolved);
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
{
return retrieveEntitiesAndInstancesOutcome;
}
}
for (Instance* instance : instancesInvolved)
@@ -748,7 +754,9 @@ namespace AzToolsFramework
AZStd::vector<Instance*> instances;
// Retrieve all descendant entities and instances of this entity that belonged to the same owning instance.
RetrieveAndSortPrefabEntitiesAndInstances({ entity }, beforeOwningInstance->get(), entities, instances);
PrefabOperationResult retrieveEntitiesAndInstancesOutcome = RetrieveAndSortPrefabEntitiesAndInstances(
{ entity }, beforeOwningInstance->get(), entities, instances);
AZ_Error("Prefab", retrieveEntitiesAndInstancesOutcome.IsSuccess(), retrieveEntitiesAndInstancesOutcome.GetError().data());
AZStd::vector<AZStd::unique_ptr<Instance>> instanceUniquePtrs;
AZStd::vector<AZStd::pair<Instance*, PrefabDom>> instancePatches;
@@ -981,11 +989,12 @@ namespace AzToolsFramework
AZStd::vector<Instance*> instances;
EntityList inputEntityList = EntityIdSetToEntityList(duplicationSet);
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
if (!success)
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
{
return AZ::Failure(AZStd::string("Failed to retrieve entities and instances from the given list of entity ids for duplication"));
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
}
// Take a snapshot of the instance DOM before we manipulate it
@@ -1128,11 +1137,12 @@ namespace AzToolsFramework
AZStd::vector<AZ::Entity*> entities;
AZStd::vector<Instance*> instances;
bool success = RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
PrefabOperationResult retrieveEntitiesAndInstancesOutcome =
RetrieveAndSortPrefabEntitiesAndInstances(inputEntityList, commonOwningInstance->get(), entities, instances);
if (!success)
if (!retrieveEntitiesAndInstancesOutcome.IsSuccess())
{
return AZ::Failure(AZStd::string("DeleteEntitiesAndAllDescendantsInInstance"));
return AZStd::move(retrieveEntitiesAndInstancesOutcome);
}
for (AZ::Entity* entity : entities)
@@ -1405,13 +1415,16 @@ namespace AzToolsFramework
return nullptr;
}
bool PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const
PrefabOperationResult PrefabPublicHandler::RetrieveAndSortPrefabEntitiesAndInstances(
const EntityList& inputEntities,
Instance& commonRootEntityOwningInstance,
EntityList& outEntities,
AZStd::vector<Instance*>& outInstances) const
{
if (inputEntities.size() == 0)
{
return false;
return AZ::Failure(
AZStd::string("An empty list of input entities is provided to retrieve the prefab entities and instances."));
}
AZStd::queue<AZ::Entity*> entityQueue;
@@ -1438,8 +1451,8 @@ namespace AzToolsFramework
AZ_Assert(
owningInstance.has_value(),
"An error occurred while retrieving entities and prefab instances : "
"Owning instance of entity with id '%llu' couldn't be found",
entity->GetId());
"Owning instance of entity with name '%s' and id '%llu' couldn't be found",
entity->GetName().c_str(), static_cast<AZ::u64>(entity->GetId()));
// Check if this entity is owned by the same instance owning the root.
if (&owningInstance->get() == &commonRootEntityOwningInstance)
@@ -1480,7 +1493,10 @@ namespace AzToolsFramework
else
{
// This can only happen if one entity does not share the common root!
return false;
return AZ::Failure(AZStd::string::format(
"Entity with name '%s' and id '%llu' has an owning instance that doesn't belong to the instance "
"hierarchy of the selected entities.",
entity->GetName().c_str(), static_cast<AZ::u64>(entity->GetId())));
}
}
}
@@ -1501,7 +1517,12 @@ namespace AzToolsFramework
outInstances.push_back(instancePtr);
}
return (outEntities.size() + outInstances.size()) > 0;
if ((outEntities.size() + outInstances.size()) == 0)
{
return AZ::Failure(
AZStd::string("An empty list of entities and prefab instances were retrieved from the selected entities"));
}
return AZ::Success();
}
EntityIdList PrefabPublicHandler::GenerateEntityIdListWithoutLevelInstance(
@@ -64,8 +64,11 @@ namespace AzToolsFramework
private:
PrefabOperationResult DeleteFromInstance(const EntityIdList& entityIds, bool deleteDescendants);
bool RetrieveAndSortPrefabEntitiesAndInstances(const EntityList& inputEntities, Instance& commonRootEntityOwningInstance,
EntityList& outEntities, AZStd::vector<Instance*>& outInstances) const;
PrefabOperationResult RetrieveAndSortPrefabEntitiesAndInstances(
const EntityList& inputEntities,
Instance& commonRootEntityOwningInstance,
EntityList& outEntities,
AZStd::vector<Instance*>& outInstances) const;
EntityIdList GenerateEntityIdListWithoutLevelInstance(const EntityIdList& entityIds) const;
InstanceOptionalReference GetOwnerInstanceByEntityId(AZ::EntityId entityId) const;
@@ -1688,12 +1688,12 @@ namespace GridMate
return; //No connections to update
}
bool updateRate = false;
AZ::u32 minRateBytesPerSecond = m_connByCongestionState.top().m_rate;
AZ::u32 minRateBytesPerSecond = m_connByCongestionState.front().m_rate;
//const AZ::u32 old = minRateBytesPerSecond; //For debugging
auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id);
auto connIt = AZStd::find(m_connByCongestionState.begin(), m_connByCongestionState.end(), id);
if ( connIt == m_connByCongestionState.get_container().end())
if ( connIt == m_connByCongestionState.end())
{
return; //Already disconnected
}
@@ -1708,11 +1708,11 @@ namespace GridMate
//If new min or old min increased, rebuild the heap and send an update
if (bytesPerSecond < minRateBytesPerSecond
|| (id == m_connByCongestionState.top().m_connection && bytesPerSecond > minRateBytesPerSecond))
|| (id == m_connByCongestionState.front().m_connection && bytesPerSecond > minRateBytesPerSecond))
{
updateRate = true;
minRateBytesPerSecond = bytesPerSecond;
AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end());
AZStd::make_heap(m_connByCongestionState.begin(), m_connByCongestionState.end());
}
}
@@ -459,7 +459,7 @@ namespace GridMate
}
};
static bool k_enableBackPressure;
AZStd::priority_queue<RateConnectionPair> m_connByCongestionState; ///< Connections priority queue sorted by congestion window
AZStd::vector<RateConnectionPair> m_connByCongestionState; ///< Connections priority queue sorted by congestion window
/***
* Updates connection's rate in priority and updates send limit
*
@@ -479,7 +479,9 @@ namespace GridMate
}
AZ_Assert(carrier, "NULL carrier!");
m_connByCongestionState.emplace(RateConnectionPair(AZ::u32(1500), id)); //default to 1500Bps (ex 1 Ethernet frame/second minimum)
m_connByCongestionState.emplace_back(AZ::u32(1500), id); //default to 1500Bps (ex 1 Ethernet frame/second minimum)
// Restore the heap property after pushing back another element
AZStd::push_heap(m_connByCongestionState.begin(), m_connByCongestionState.end());
}
void OnDisconnect(Carrier* carrier, ConnectionID id, CarrierDisconnectReason reason) override
{
@@ -490,17 +492,17 @@ namespace GridMate
}
AZ_Assert(carrier, "NULL carrier!");
auto connIt = AZStd::find(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end(), id);
if (connIt != m_connByCongestionState.get_container().end())
auto connIt = AZStd::find(m_connByCongestionState.begin(), m_connByCongestionState.end(), id);
if (connIt != m_connByCongestionState.end())
{
//Since we are using a weakly sorted heap, we need to re-generate when the top is removed
bool remake = (connIt == m_connByCongestionState.get_container().begin());
bool remake = (connIt == m_connByCongestionState.begin());
m_connByCongestionState.get_container().erase(connIt);
m_connByCongestionState.erase(connIt);
if (remake)
{
AZStd::make_heap(m_connByCongestionState.get_container().begin(), m_connByCongestionState.get_container().end());
AZStd::make_heap(m_connByCongestionState.begin(), m_connByCongestionState.end());
}
}
}
@@ -0,0 +1,160 @@
----------------------------------------------------------------------------------------------------
--
-- 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
--
--
--
----------------------------------------------------------------------------------------------------
local FindMaterialAssignmentTest =
{
Properties =
{
Textures =
{
"materials/presets/macbeth/05_blue_flower_srgb.tif.streamingimage",
"materials/presets/macbeth/06_bluish_green_srgb.tif.streamingimage",
"materials/presets/macbeth/09_moderate_red_srgb.tif.streamingimage",
"materials/presets/macbeth/11_yellow_green_srgb.tif.streamingimage",
"materials/presets/macbeth/12_orange_yellow_srgb.tif.streamingimage",
"materials/presets/macbeth/17_magenta_srgb.tif.streamingimage"
},
},
}
function randomColor()
return Color(math.random(), math.random(), math.random(), 1.0)
end
function randomDir()
dir = {}
for i = 1, 3 do
lerpDir = math.random()
if lerpDir < 0.5 then
table.insert(dir, -1.0)
else
table.insert(dir, 1.0)
end
end
return dir
end
function FindMaterialAssignmentTest:OnActivate()
self.timer = 0.0
self.totalTime = 0.0
self.totalTimeMax = 200.0
self.timeUpdate = 2.0
self.colors = {}
self.lerpDirs = {}
self.assignmentIds =
{
MaterialComponentRequestBus.Event.FindMaterialAssignmentId(self.entityId, -1, "lambert"),
}
for index = 1, #self.assignmentIds do
local id = self.assignmentIds[index]
if (id ~= nil) then
self.colors[index] = randomColor()
self.lerpDirs[index] = randomDir()
end
end
self.tickBusHandler = TickBus.Connect(self);
end
function FindMaterialAssignmentTest:UpdateFactor(assignmentId)
local propertyName = Name("baseColor.factor")
local propertyValue = math.random()
MaterialComponentRequestBus.Event.SetPropertyOverride(self.entityId, assignmentId, propertyName, propertyValue);
end
function FindMaterialAssignmentTest:UpdateColor(assignmentId, color)
local propertyName = Name("baseColor.color")
local propertyValue = color
MaterialComponentRequestBus.Event.SetPropertyOverride(self.entityId, assignmentId, propertyName, propertyValue);
end
function FindMaterialAssignmentTest:UpdateTexture(assignmentId)
if (#self.Properties.Textures > 0) then
local propertyName = Name("baseColor.textureMap")
local textureName = self.Properties.Textures[ math.random( #self.Properties.Textures ) ]
Debug.Log(textureName)
local textureAssetId = AssetCatalogRequestBus.Broadcast.GetAssetIdByPath(textureName, Uuid(), false)
MaterialComponentRequestBus.Event.SetPropertyOverride(self.entityId, assignmentId, propertyName, textureAssetId);
end
end
function FindMaterialAssignmentTest:UpdateProperties()
Debug.Log("Overriding properties...")
for index = 1, #self.assignmentIds do
local id = self.assignmentIds[index]
if (id ~= nil) then
self:UpdateFactor(id)
self:UpdateTexture(id)
end
end
end
function FindMaterialAssignmentTest:ClearProperties()
Debug.Log("Clearing properties...")
MaterialComponentRequestBus.Event.ClearAllPropertyOverrides(self.entityId);
end
function lerpColor(color, lerpDir, deltaTime)
local lerpSpeed = 0.5
color.r = color.r + deltaTime * lerpDir[1] * lerpSpeed
if color.r > 1.0 then
color.r = 1.0
lerpDir[1] = -1.0
elseif color.r < 0 then
color.r = 0
lerpDir[1] = 1.0
end
color.g = color.g + deltaTime * lerpDir[2] * lerpSpeed
if color.g > 1.0 then
color.g = 1.0
lerpDir[2] = -1.0
elseif color.g < 0 then
color.g = 0
lerpDir[2] = 1.0
end
color.b = color.b + deltaTime * lerpDir[3] * lerpSpeed
if color.b > 1.0 then
color.b = 1.0
lerpDir[3] = -1.0
elseif color.b < 0 then
color.b = 0
lerpDir[3] = 1.0
end
end
function FindMaterialAssignmentTest:lerpColors(deltaTime)
for index = 1, #self.assignmentIds do
local id = self.assignmentIds[index]
if (id ~= nil) then
lerpColor(self.colors[index], self.lerpDirs[index], deltaTime)
self:UpdateColor(id, self.colors[index])
end
end
end
function FindMaterialAssignmentTest:OnTick(deltaTime, timePoint)
self.timer = self.timer + deltaTime
self.totalTime = self.totalTime + deltaTime
self:lerpColors(deltaTime)
if (self.timer > self.timeUpdate and self.totalTime < self.totalTimeMax) then
self.timer = self.timer - self.timeUpdate
self:UpdateProperties()
elseif self.totalTime > self.totalTimeMax then
self:ClearProperties()
self.tickBusHandler:Disconnect(self);
end
end
return FindMaterialAssignmentTest
@@ -53,10 +53,9 @@ function PropertyOverrideTest:OnActivate()
self.originalAssignments = MaterialComponentRequestBus.Event.GetOriginalMaterialAssignments(self.entityId);
self.assignmentIds = self.originalAssignments:GetKeys()
for index = 0, self.assignmentIds:Size() do
local idOutcome = self.assignmentIds:At(index)
if (idOutcome:IsSuccess()) then
local id = idOutcome:GetValue()
for index = 1, self.assignmentIds:GetSize() do
local id = self.assignmentIds[index]
if (id ~= nil) then
self.colors[index] = randomColor()
self.lerpDirs[index] = randomDir()
end
@@ -88,10 +87,9 @@ end
function PropertyOverrideTest:UpdateProperties()
Debug.Log("Overriding properties...")
for index = 0, self.assignmentIds:Size() do
local idOutcome = self.assignmentIds:At(index)
if (idOutcome:IsSuccess()) then
local id = idOutcome:GetValue()
for index = 1, self.assignmentIds:GetSize() do
local id = self.assignmentIds[index]
if (id ~= nil) then
self:UpdateFactor(id)
self:UpdateTexture(id)
end
@@ -134,10 +132,9 @@ function lerpColor(color, lerpDir, deltaTime)
end
function PropertyOverrideTest:lerpColors(deltaTime)
for index = 0, self.assignmentIds:Size() do
local idOutcome = self.assignmentIds:At(index)
if (idOutcome:IsSuccess()) then
local id = idOutcome:GetValue()
for index = 1, self.assignmentIds:GetSize() do
local id = self.assignmentIds[index]
if (id ~= nil) then
lerpColor(self.colors[index], self.lerpDirs[index], deltaTime)
self:UpdateColor(id, self.colors[index])
end
@@ -5,6 +5,7 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Atom/Feature/Material/MaterialAssignmentId.h>
@@ -63,5 +64,8 @@ namespace AZ
//! Utility function for generating a set of available material assignments in a model
MaterialAssignmentMap GetMaterialAssignmentsFromModel(Data::Instance<AZ::RPI::Model> model);
//! Find an assignment id corresponding to the lod and label substring filters
MaterialAssignmentId FindMaterialAssignmentIdInModel(
const Data::Instance<AZ::RPI::Model> model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter);
} // namespace Render
} // namespace AZ
@@ -124,6 +124,18 @@ namespace AZ
->Field("AcesParameterOverrides", &DisplayMapperConfigurationDescriptor::m_acesParameterOverrides)
;
}
if (auto* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<DisplayMapperOperationType>()
->Enum<(uint32_t)DisplayMapperOperationType::Aces>("DisplayMapperOperationType_Aces")
->Enum<(uint32_t)DisplayMapperOperationType::AcesLut>("DisplayMapperOperationType_AcesLut")
->Enum<(uint32_t)DisplayMapperOperationType::Passthrough>("DisplayMapperOperationType_Passthrough")
->Enum<(uint32_t)DisplayMapperOperationType::GammaSRGB>("DisplayMapperOperationType_GammaSRGB")
->Enum<(uint32_t)DisplayMapperOperationType::Reinhard>("DisplayMapperOperationType_Reinhard")
->Enum<(uint32_t)DisplayMapperOperationType::Invalid>("DisplayMapperOperationType_Invalid")
;
}
}
void DisplayMapperPassData::Reflect(ReflectContext* context)
@@ -166,5 +166,48 @@ namespace AZ
return materials;
}
MaterialAssignmentId FindMaterialAssignmentIdInLod(
const Data::Instance<AZ::RPI::ModelLod>& lod, const MaterialAssignmentLodIndex lodIndex, const AZStd::string& labelFilter)
{
for (const AZ::RPI::ModelLod::Mesh& mesh : lod->GetMeshes())
{
if (mesh.m_material && mesh.m_material->GetAssetId().IsValid())
{
AZ::Data::AssetInfo assetInfo;
AZ::Data::AssetCatalogRequestBus::BroadcastResult(
assetInfo, &AZ::Data::AssetCatalogRequests::GetAssetInfoById, mesh.m_material->GetAssetId());
if (assetInfo.m_assetId.IsValid() && AZ::StringFunc::Contains(assetInfo.m_relativePath, labelFilter, true))
{
return MaterialAssignmentId::CreateFromLodAndAsset(lodIndex, mesh.m_material->GetAssetId());
}
}
}
return MaterialAssignmentId();
}
MaterialAssignmentId FindMaterialAssignmentIdInModel(
const Data::Instance<AZ::RPI::Model> model, const MaterialAssignmentLodIndex lodFilter, const AZStd::string& labelFilter)
{
if (model && !labelFilter.empty())
{
if (lodFilter < model->GetLodCount())
{
return FindMaterialAssignmentIdInLod(model->GetLods()[lodFilter], lodFilter, labelFilter);
}
for (size_t lodIndex = 0; lodIndex < model->GetLodCount(); ++lodIndex)
{
const MaterialAssignmentId result =
FindMaterialAssignmentIdInLod(model->GetLods()[lodIndex], MaterialAssignmentId::NonLodIndex, labelFilter);
if (!result.IsDefault())
{
return result;
}
}
}
return MaterialAssignmentId();
}
} // namespace Render
} // namespace AZ
@@ -81,6 +81,15 @@ namespace AZ
AZ_RTTI(SwapChain, "{888B64A5-D956-406F-9C33-CF6A54FC41B0}", Object);
#if defined(PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB)
// On Linux platforms that uses XCB, a resize may occur in the swap chain but the command queue may still
// reference the original surface. This flag is a temporary fix to make sure that all the swap chains
// have finished their resize events before presenting the command queue.
// [GFX TODO][GHI - 2678]
AZStd::atomic_bool m_resized{ false };
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
protected:
SwapChain();
@@ -126,6 +126,7 @@ namespace AZ
ResultCode FrameGraph::End()
{
AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraph: End");
ResultCode resultCode = ValidateEnd();
if (resultCode != ResultCode::Success)
{
@@ -72,6 +72,7 @@ namespace AZ
void FrameGraphExecuter::Begin(const FrameGraph& frameGraph)
{
AZ_TRACE_METHOD();
AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphExecuter: Begin");
BeginInternal(frameGraph);
}
@@ -6,6 +6,7 @@
*
*/
#include <Atom/RHI/CpuProfiler.h>
#include <Atom/RHI/PipelineStateCache.h>
#include <Atom/RHI/Factory.h>
#include <AzCore/std/sort.h>
@@ -205,6 +206,7 @@ namespace AZ
void PipelineStateCache::Compact()
{
AZ_ATOM_PROFILE_FUNCTION("RHI", "PipelineStateCache: Compact");
AZStd::unique_lock<AZStd::shared_mutex> lock(m_mutex);
// Merge the pending cache into the read-only cache.
+1 -1
View File
@@ -223,7 +223,7 @@ namespace AZ
* own RHI scopes to the frame scheduler. This happens prior to the RPI pass graph registration.
*/
{
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "RHISystem :FrameUpdate: OnFramePrepare");
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RHI", "RHISystem: FrameUpdate: OnFramePrepare");
RHISystemNotificationBus::Broadcast(&RHISystemNotificationBus::Events::OnFramePrepare, m_frameScheduler);
}
@@ -164,6 +164,10 @@ namespace AZ
m_currentImageIndex = 0;
}
#if defined(PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB)
m_resized.store(true);
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
return resultCode;
}
@@ -469,6 +469,8 @@ namespace AZ
ResourceTransitionLoggerNull logger(imageFrameAttachment.GetId());
#endif
AZ_ATOM_PROFILE_FUNCTION("RHI", "FrameGraphCompiler: CompileImageBarriers (DX12)");
Image& image = static_cast<Image&>(*imageFrameAttachment.GetImage());
RHI::ImageScopeAttachment* scopeAttachment = imageFrameAttachment.GetFirstScopeAttachment();
@@ -42,6 +42,15 @@ namespace AZ
void CommandQueue::ExecuteWork(const RHI::ExecuteWorkRequest& rhiRequest)
{
#if defined(PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB)
for (RHI::SwapChain* swapChain : rhiRequest.m_swapChainsToPresent)
{
if (!swapChain->m_resized)
{
return;
}
}
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
const ExecuteWorkRequest& request = static_cast<const ExecuteWorkRequest&>(rhiRequest);
QueueCommand([=](void* queue)
{
@@ -720,6 +720,7 @@ namespace AZ
void CullingScene::BeginCulling(const AZStd::vector<ViewPtr>& views)
{
AZ_ATOM_PROFILE_FUNCTION("RPI", "CullingScene: BeginCulling");
m_cullDataConcurrencyCheck.soft_lock();
m_debugCtx.ResetCullStats();
@@ -298,6 +298,7 @@ namespace AZ
void PassSystem::ProcessQueuedChanges()
{
AZ_ATOM_PROFILE_FUNCTION("RPI", "PassSystem: ProcessQueuedChanges");
RemovePasses();
BuildPasses();
InitializePasses();
@@ -313,7 +314,11 @@ namespace AZ
m_state = PassSystemState::Rendering;
Pass::FramePrepareParams params{ &frameGraphBuilder };
m_rootPass->FrameBegin(params);
{
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Pass: FrameBegin");
m_rootPass->FrameBegin(params);
}
}
void PassSystem::FrameEnd()
@@ -408,6 +408,7 @@ namespace AZ
{
AZ_PROFILE_SCOPE(Debug::ProfileCategory::AzRender, "m_srgCallback");
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "ShaderResourceGroupCallback: SrgCallback");
// Set values for scene srg
if (m_srg && m_srgCallback)
{
@@ -418,7 +419,7 @@ namespace AZ
// Get active pipelines which need to be rendered and notify them frame started
AZStd::vector<RenderPipelinePtr> activePipelines;
{
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "OnStartFrame");
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "Scene: OnStartFrame");
for (auto& pipeline : m_pipelines)
{
if (pipeline->NeedsRender())
@@ -483,6 +484,7 @@ namespace AZ
{
AZ_PROFILE_SCOPE(Debug::ProfileCategory::AzRender, "CollectDrawPackets");
AZ_ATOM_PROFILE_TIME_GROUP_REGION("RPI", "CollectDrawPackets");
AZ::JobCompletion* collectDrawPacketsCompletion = aznew AZ::JobCompletion();
// Launch FeatureProcessor::Render() jobs
@@ -0,0 +1,115 @@
/*
* 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 <AtomToolsFramework/Communication/LocalServer.h>
#include <AtomToolsFramework/Communication/LocalSocket.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzQtComponents/Application/AzQtApplication.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/Logger/TraceLogger.h>
#include <QTimer>
namespace AtomToolsFramework
{
//! Base class for Atom tools to inherit from
class AtomToolsApplication
: public AzFramework::Application
, public AzQtComponents::AzQtApplication
, protected AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
, protected AzFramework::AssetSystemStatusBus::Handler
, protected AzToolsFramework::EditorPythonConsoleNotificationBus::Handler
, protected AZ::UserSettingsOwnerRequestBus::Handler
{
public:
AZ_TYPE_INFO(AtomTools::AtomToolsApplication, "{A0DF25BA-6F74-4F11-9F85-0F99278D5986}");
using Base = AzFramework::Application;
AtomToolsApplication(int* argc, char*** argv);
//////////////////////////////////////////////////////////////////////////
// AzFramework::Application
void CreateReflectionManager() override;
void Reflect(AZ::ReflectContext* context) override;
void RegisterCoreComponents() override;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
void CreateStaticModules(AZStd::vector<AZ::Module*>& outModules) override;
const char* GetCurrentConfigurationName() const override;
void StartCommon(AZ::Entity* systemEntity) override;
void Tick(float deltaOverride = -1.f) override;
void Stop() override;
protected:
//////////////////////////////////////////////////////////////////////////
// AssetDatabaseRequestsBus::Handler overrides...
bool GetAssetDatabaseLocation(AZStd::string& result) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzFramework::Application overrides...
void Destroy() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzFramework::AssetSystemStatusBus::Handler overrides...
void AssetSystemAvailable() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::ComponentApplication overrides...
void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::UserSettingsOwnerRequestBus::Handler overrides...
void SaveSettings() override;
//////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// EditorPythonConsoleNotificationBus::Handler overrides...
void OnTraceMessage(AZStd::string_view message) override;
void OnErrorMessage(AZStd::string_view message) override;
void OnExceptionMessage(AZStd::string_view message) override;
////////////////////////////////////////////////////////////////////////
//! Executable target name generally used as a prefix for logging and other saved files
virtual AZStd::string GetBuildTargetName() const;
//! List of filters for assets that need to be pre-built to run the application
virtual AZStd::vector<AZStd::string> GetCriticalAssetFilters() const;
virtual void LoadSettings();
virtual void UnloadSettings();
virtual void CompileCriticalAssets();
virtual void ProcessCommandLine(const AZ::CommandLine& commandLine);
virtual bool LaunchDiscoveryService();
virtual void StartInternal();
static void PyIdleWaitFrames(uint32_t frames);
AzToolsFramework::TraceLogger m_traceLogger;
//! Local user settings are used to store material browser tree expansion state
AZ::UserSettingsProvider m_localUserSettings;
//! Are local settings loaded
bool m_activatedLocalUserSettings = false;
QTimer m_timer;
AtomToolsFramework::LocalSocket m_socket;
AtomToolsFramework::LocalServer m_server;
};
} // namespace AtomToolsFramework
@@ -0,0 +1,512 @@
/*
* 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 <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AtomToolsFramework/Application/AtomToolsApplication.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Utils/Utils.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AzToolsFrameworkModule.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QMessageBox>
#include <QObject>
AZ_POP_DISABLE_WARNING
namespace AtomToolsFramework
{
AZStd::string AtomToolsApplication::GetBuildTargetName() const
{
return AZStd::string("AtomTools");
}
const char* AtomToolsApplication::GetCurrentConfigurationName() const
{
#if defined(_RELEASE)
return "ReleaseAtomTools";
#elif defined(_DEBUG)
return "DebugAtomTools";
#else
return "ProfileAtomTools";
#endif
}
AtomToolsApplication::AtomToolsApplication(int* argc, char*** argv)
: Application(argc, argv)
, AzQtApplication(*argc, *argv)
{
connect(&m_timer, &QTimer::timeout, this, [&]()
{
this->PumpSystemEventLoopUntilEmpty();
this->Tick();
});
}
void AtomToolsApplication::CreateReflectionManager()
{
Base::CreateReflectionManager();
GetSerializeContext()->CreateEditContext();
}
void AtomToolsApplication::Reflect(AZ::ReflectContext* context)
{
Base::Reflect(context);
AzToolsFramework::AssetBrowser::AssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::RootAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry::Reflect(context);
AzToolsFramework::QTreeViewWithStateSaving::Reflect(context);
AzToolsFramework::QWidgetSavedState::Reflect(context);
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
auto targetName = GetBuildTargetName();
// this will put these methods into the 'azlmbr.AtomTools.general' module
auto addGeneral = [targetName](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
{
methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, targetName);
};
// The reflection here is based on patterns in CryEditPythonHandler::Reflect
addGeneral(behaviorContext->Method(
"idle_wait_frames", &AtomToolsApplication::PyIdleWaitFrames, nullptr,
"Waits idling for a frames. Primarily used for auto-testing."));
}
}
void AtomToolsApplication::RegisterCoreComponents()
{
Base::RegisterCoreComponents();
RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::AssetSystem::AssetSystemComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::PerforceComponent::CreateDescriptor());
}
AZ::ComponentTypeList AtomToolsApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = Base::GetRequiredSystemComponents();
components.insert(
components.end(),
{
azrtti_typeid<AzToolsFramework::AssetBrowser::AssetBrowserComponent>(),
azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerComponent>(),
azrtti_typeid<AzToolsFramework::Components::PropertyManagerComponent>(),
azrtti_typeid<AzToolsFramework::PerforceComponent>(),
});
return components;
}
void AtomToolsApplication::CreateStaticModules(AZStd::vector<AZ::Module*>& outModules)
{
Base::CreateStaticModules(outModules);
outModules.push_back(aznew AzToolsFramework::AzToolsFrameworkModule);
}
void AtomToolsApplication::StartCommon(AZ::Entity* systemEntity)
{
AzFramework::AssetSystemStatusBus::Handler::BusConnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect();
Base::StartCommon(systemEntity);
StartInternal();
m_timer.start();
}
void AtomToolsApplication::Destroy()
{
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
Base::Destroy();
}
AZStd::vector<AZStd::string> AtomToolsApplication::GetCriticalAssetFilters() const
{
return AZStd::vector<AZStd::string>({});
}
void AtomToolsApplication::AssetSystemAvailable()
{
bool connectedToAssetProcessor = false;
// When the AssetProcessor is already launched it should take less than a second to perform a connection
// but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
// and able to negotiate a connection when running a debug build
// and to negotiate a connection
auto targetName = GetBuildTargetName();
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
connectionSettings.m_connectionDirection =
AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
connectionSettings.m_connectionIdentifier = GetBuildTargetName();
connectionSettings.m_loggingCallback = [targetName]([[maybe_unused]] AZStd::string_view logData)
{
AZ_TracePrintf(targetName.c_str(), "%.*s", aznumeric_cast<int>(logData.size()), logData.data());
};
AzFramework::AssetSystemRequestBus::BroadcastResult(
connectedToAssetProcessor, &AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
if (connectedToAssetProcessor)
{
CompileCriticalAssets();
}
AzFramework::AssetSystemStatusBus::Handler::BusDisconnect();
}
void AtomToolsApplication::CompileCriticalAssets()
{
AZ_TracePrintf(GetBuildTargetName().c_str(), "Compiling critical assets.\n");
QStringList failedAssets;
// Forced asset processor to synchronously process all critical assets
// Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick.
// So the asset id won't be found right after CompileAssetSync call.
for (const AZStd::string& assetFilters : GetCriticalAssetFilters())
{
AZ_TracePrintf(GetBuildTargetName().c_str(), "Compiling critical asset matching: %s.\n", assetFilters.c_str());
// Wait for the asset be compiled
AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown;
AzFramework::AssetSystemRequestBus::BroadcastResult(
status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, assetFilters);
if (status != AzFramework::AssetSystem::AssetStatus_Compiled)
{
failedAssets.append(assetFilters.c_str());
}
}
if (!failedAssets.empty())
{
QMessageBox::critical(
activeWindow(), QString("Failed to compile critical assets"),
QString("Failed to compile the following critical assets:\n%1\n%2")
.arg(failedAssets.join(",\n"))
.arg("Make sure this is an Atom project."));
ExitMainLoop();
}
}
void AtomToolsApplication::SaveSettings()
{
if (m_activatedLocalUserSettings)
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN] = "";
AZStd::string fileName = "@user@/" + GetBuildTargetName() + "UserSettings.xml";
AZ::IO::FileIOBase::GetInstance()->ResolvePath(
fileName.c_str(), resolvedPath, AZ_ARRAY_SIZE(resolvedPath));
m_localUserSettings.Save(resolvedPath, context);
}
}
void AtomToolsApplication::LoadSettings()
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN] = "";
AZStd::string fileName = "@user@/" + GetBuildTargetName() + "UserSettings.xml";
AZ::IO::FileIOBase::GetInstance()->ResolvePath(fileName.c_str(), resolvedPath, AZ_MAX_PATH_LEN);
m_localUserSettings.Load(resolvedPath, context);
m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL);
AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL);
m_activatedLocalUserSettings = true;
}
void AtomToolsApplication::UnloadSettings()
{
if (m_activatedLocalUserSettings)
{
SaveSettings();
m_localUserSettings.Deactivate();
AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect();
m_activatedLocalUserSettings = false;
}
}
void AtomToolsApplication::ProcessCommandLine(const AZ::CommandLine& commandLine)
{
const AZStd::string timeoputSwitchName = "timeout";
if (commandLine.HasSwitch(timeoputSwitchName))
{
const AZStd::string& timeoutValue = commandLine.GetSwitchValue(timeoputSwitchName, 0);
const uint32_t timeoutInMs = atoi(timeoutValue.c_str());
AZ_Printf(GetBuildTargetName().c_str(), "Timeout scheduled, shutting down in %u ms", timeoutInMs);
QTimer::singleShot(
timeoutInMs,
[this]
{
AZ_Printf(GetBuildTargetName().c_str(), "Timeout reached, shutting down");
ExitMainLoop();
});
}
// Process command line options for running one or more python scripts on startup
const AZStd::string runPythonScriptSwitchName = "runpython";
size_t runPythonScriptCount = commandLine.GetNumSwitchValues(runPythonScriptSwitchName);
for (size_t runPythonScriptIndex = 0; runPythonScriptIndex < runPythonScriptCount; ++runPythonScriptIndex)
{
const AZStd::string runPythonScriptPath = commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex);
AZStd::vector<AZStd::string_view> runPythonArgs;
AZ_Printf(GetBuildTargetName().c_str(), "Launching script: %s", runPythonScriptPath.c_str());
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, runPythonScriptPath, runPythonArgs);
}
const AZStd::string exitAfterCommandsSwitchName = "exitaftercommands";
if (commandLine.HasSwitch(exitAfterCommandsSwitchName))
{
ExitMainLoop();
}
}
bool AtomToolsApplication::LaunchDiscoveryService()
{
// Determine if this is the first launch of the tool by attempting to connect to a running server
if (m_socket.Connect(QApplication::applicationName()))
{
// If the server was located, the application is already running.
// Forward commandline options to other application instance.
QByteArray buffer;
buffer.append("ProcessCommandLine:");
// Add the command line options from this process to the message, skipping the executable path
for (int argi = 1; argi < m_argC; ++argi)
{
buffer.append(QString(m_argV[argi]).append("\n").toUtf8());
}
// Inject command line option to always bring the main window to the foreground
buffer.append("--activatewindow\n");
m_socket.Send(buffer);
m_socket.Disconnect();
return false;
}
// Setup server to handle basic commands
m_server.SetReadHandler(
[this](const QByteArray& buffer)
{
// Handle commmand line params from connected socket
if (buffer.startsWith("ProcessCommandLine:"))
{
// Remove header and parse commands
AZStd::string params(buffer.data(), buffer.size());
params = params.substr(strlen("ProcessCommandLine:"));
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(params, tokens, "\n");
if (!tokens.empty())
{
AZ::CommandLine commandLine;
commandLine.Parse(tokens);
ProcessCommandLine(commandLine);
}
}
});
// Launch local server
if (!m_server.Connect(QApplication::applicationName()))
{
return false;
}
return true;
}
void AtomToolsApplication::StartInternal()
{
if (WasExitMainLoopRequested())
{
return;
}
AZStd::string fileName = GetBuildTargetName() + ".log";
m_traceLogger.WriteStartupLog(fileName.c_str());
if (!LaunchDiscoveryService())
{
ExitMainLoop();
return;
}
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(
&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml");
AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets();
LoadSettings();
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
// The PythonSystemComponent does not call StartPython to allow for lazy python initialization, so start it here
// The PythonSystemComponent will call StopPython when it deactivates, so we do not need our own corresponding call to
// StopPython
editorPythonEventsInterface->StartPython();
}
// Delay execution of commands and scripts post initialization
QTimer::singleShot(
0,
[this]()
{
ProcessCommandLine(m_commandLine);
});
}
bool AtomToolsApplication::GetAssetDatabaseLocation(AZStd::string& result)
{
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
AZ::IO::FixedMaxPath assetDatabaseSqlitePath;
if (settingsRegistry &&
settingsRegistry->Get(assetDatabaseSqlitePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
{
assetDatabaseSqlitePath /= "assetdb.sqlite";
result = AZStd::string_view(assetDatabaseSqlitePath.Native());
return true;
}
return false;
}
void AtomToolsApplication::Tick(float deltaOverride)
{
TickSystem();
Base::Tick(deltaOverride);
if (WasExitMainLoopRequested())
{
m_timer.disconnect();
quit();
}
}
void AtomToolsApplication::Stop()
{
UnloadSettings();
Base::Stop();
}
void AtomToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
{
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game;
}
void AtomToolsApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message)
{
#if defined(AZ_ENABLE_TRACING)
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(
message, lines, "\n",
false, // Keep empty strings
false // Keep space strings
);
for (auto& line : lines)
{
AZ_TracePrintf(GetBuildTargetName().c_str(), "Python: %s\n", line.c_str());
}
#endif
}
void AtomToolsApplication::OnErrorMessage(AZStd::string_view message)
{
// Use AZ_TracePrintf instead of AZ_Error or AZ_Warning to avoid all the metadata noise
OnTraceMessage(message);
}
void AtomToolsApplication::OnExceptionMessage([[maybe_unused]] AZStd::string_view message)
{
AZ_Error(GetBuildTargetName().c_str(), false, "Python: " AZ_STRING_FORMAT, AZ_STRING_ARG(message));
}
// Copied from PyIdleWaitFrames in CryEdit.cpp
void AtomToolsApplication::PyIdleWaitFrames(uint32_t frames)
{
struct Ticker : public AZ::TickBus::Handler
{
Ticker(QEventLoop* loop, uint32_t targetFrames)
: m_loop(loop)
, m_targetFrames(targetFrames)
{
AZ::TickBus::Handler::BusConnect();
}
~Ticker()
{
AZ::TickBus::Handler::BusDisconnect();
}
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override
{
AZ_UNUSED(deltaTime);
AZ_UNUSED(time);
if (++m_elapsedFrames == m_targetFrames)
{
m_loop->quit();
}
}
QEventLoop* m_loop = nullptr;
uint32_t m_elapsedFrames = 0;
uint32_t m_targetFrames = 0;
};
QEventLoop loop;
Ticker ticker(&loop, frames);
loop.exec();
}
} // namespace AtomToolsFramework
@@ -7,6 +7,7 @@
#
set(FILES
Include/AtomToolsFramework/Application/AtomToolsApplication.h
Include/AtomToolsFramework/Communication/LocalServer.h
Include/AtomToolsFramework/Communication/LocalSocket.h
Include/AtomToolsFramework/Debug/TraceRecorder.h
@@ -23,6 +24,7 @@ set(FILES
Include/AtomToolsFramework/Viewport/RenderViewportWidget.h
Include/AtomToolsFramework/Viewport/ModularViewportCameraController.h
Include/AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h
Source/Application/AtomToolsApplication.cpp
Source/Communication/LocalServer.cpp
Source/Communication/LocalSocket.cpp
Source/Debug/TraceRecorder.cpp
@@ -8,56 +8,53 @@
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/AzToolsFrameworkModule.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AzToolsFrameworkModule.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
#include <AtomToolsFramework/Util/Util.h>
#include <Atom/Document/MaterialDocumentSystemRequestBus.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <Source/MaterialEditorApplication.h>
#include <Atom/Document/MaterialDocumentModule.h>
#include <Atom/Viewport/MaterialViewportModule.h>
#include <Atom/Window/MaterialEditorWindowFactoryRequestBus.h>
#include <Atom/Window/MaterialEditorWindowModule.h>
#include <Atom/Window/MaterialEditorWindowRequestBus.h>
#include <MaterialEditorApplication.h>
#include <MaterialEditor_Traits_Platform.h>
#include <Atom/Document/MaterialDocumentModule.h>
#include <Atom/Document/MaterialDocumentSystemRequestBus.h>
#include <Atom/Viewport/MaterialViewportModule.h>
#include <Atom/Window/MaterialEditorWindowModule.h>
#include <Atom/Window/MaterialEditorWindowFactoryRequestBus.h>
#include <Atom/Window/MaterialEditorWindowRequestBus.h>
#include <AzCore/Utils/Utils.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QObject>
#include <QMessageBox>
#include <QObject>
AZ_POP_DISABLE_WARNING
namespace MaterialEditor
{
//! This function returns the build system target name of "MaterialEditor
AZStd::string_view GetBuildTargetName()
AZStd::string MaterialEditorApplication::GetBuildTargetName() const
{
#if !defined (LY_CMAKE_TARGET)
#if !defined(LY_CMAKE_TARGET)
#error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target"
#endif
return AZStd::string_view{ LY_CMAKE_TARGET };
return AZStd::string{ LY_CMAKE_TARGET };
}
const char* MaterialEditorApplication::GetCurrentConfigurationName() const
@@ -72,19 +69,12 @@ namespace MaterialEditor
}
MaterialEditorApplication::MaterialEditorApplication(int* argc, char*** argv)
: Application(argc, argv)
, AzQtApplication(*argc, *argv)
: AtomToolsApplication(argc, argv)
{
QApplication::setApplicationName("O3DE Material Editor");
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*AZ::SettingsRegistry::Get(), GetBuildTargetName());
connect(&m_timer, &QTimer::timeout, this, [&]()
{
this->PumpSystemEventLoopUntilEmpty();
this->Tick();
});
}
MaterialEditorApplication::~MaterialEditorApplication()
@@ -94,88 +84,14 @@ namespace MaterialEditor
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
}
void MaterialEditorApplication::CreateReflectionManager()
{
Application::CreateReflectionManager();
GetSerializeContext()->CreateEditContext();
}
void MaterialEditorApplication::Reflect(AZ::ReflectContext* context)
{
Application::Reflect(context);
AzToolsFramework::AssetBrowser::AssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::RootAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry::Reflect(context);
AzToolsFramework::QTreeViewWithStateSaving::Reflect(context);
AzToolsFramework::QWidgetSavedState::Reflect(context);
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
// this will put these methods into the 'azlmbr.materialeditor.general' module
auto addGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
{
methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "materialeditor.general");
};
// The reflection here is based on patterns in CryEditPythonHandler::Reflect
addGeneral(behaviorContext->Method("idle_wait_frames", &MaterialEditorApplication::PyIdleWaitFrames, nullptr, "Waits idling for a frames. Primarily used for auto-testing."));
}
}
void MaterialEditorApplication::RegisterCoreComponents()
{
Application::RegisterCoreComponents();
RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::AssetSystem::AssetSystemComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::PerforceComponent::CreateDescriptor());
}
AZ::ComponentTypeList MaterialEditorApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = Application::GetRequiredSystemComponents();
components.insert(components.end(), {
azrtti_typeid<AzToolsFramework::AssetBrowser::AssetBrowserComponent>(),
azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerComponent>(),
azrtti_typeid<AzToolsFramework::Components::PropertyManagerComponent>(),
azrtti_typeid<AzToolsFramework::PerforceComponent>(),
});
return components;
}
void MaterialEditorApplication::CreateStaticModules(AZStd::vector<AZ::Module*>& outModules)
{
Application::CreateStaticModules(outModules);
outModules.push_back(aznew AzToolsFramework::AzToolsFrameworkModule);
Base::CreateStaticModules(outModules);
outModules.push_back(aznew MaterialDocumentModule);
outModules.push_back(aznew MaterialViewportModule);
outModules.push_back(aznew MaterialEditorWindowModule);
}
void MaterialEditorApplication::StartCommon(AZ::Entity* systemEntity)
{
{
//[GFX TODO][ATOM-408] This needs to be updated in some way to support the MaterialViewport render widget
}
AzFramework::AssetSystemStatusBus::Handler::BusConnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect();
AzFramework::Application::StartCommon(systemEntity);
StartInternal();
m_timer.start();
}
void MaterialEditorApplication::OnMaterialEditorWindowClosing()
{
ExitMainLoop();
@@ -187,120 +103,14 @@ namespace MaterialEditor
MaterialEditor::MaterialEditorWindowFactoryRequestBus::Broadcast(
&MaterialEditor::MaterialEditorWindowFactoryRequestBus::Handler::DestroyMaterialEditorWindow);
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
MaterialEditorWindowNotificationBus::Handler::BusDisconnect();
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
m_logFile = {};
m_startupLogSink = {};
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
Application::Destroy();
Base::Destroy();
}
void MaterialEditorApplication::AssetSystemAvailable()
AZStd::vector<AZStd::string> MaterialEditorApplication::GetCriticalAssetFilters() const
{
bool connectedToAssetProcessor = false;
// When the AssetProcessor is already launched it should take less than a second to perform a connection
// but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
// and able to negotiate a connection when running a debug build
// and to negotiate a connection
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
connectionSettings.m_connectionIdentifier = "MaterialEditor";
connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData)
{
AZ_TracePrintf("Material Editor", "%.*s", aznumeric_cast<int>(logData.size()), logData.data());
};
AzFramework::AssetSystemRequestBus::BroadcastResult(connectedToAssetProcessor,
&AzFramework::AssetSystemRequestBus::Events::EstablishAssetProcessorConnection, connectionSettings);
if (connectedToAssetProcessor)
{
CompileCriticalAssets();
}
AzFramework::AssetSystemStatusBus::Handler::BusDisconnect();
}
void MaterialEditorApplication::CompileCriticalAssets()
{
AZ_TracePrintf("MaterialEditor", "Compiling critical assets.\n");
// List of common asset filters for things that need to be compiled to run the material editor
// Some of these things will not be necessary once we have proper support for queued asset loading and reloading
const AZStd::string assetFiltersArray[] =
{
"passes/",
"config/",
"MaterialEditor/",
};
QStringList failedAssets;
// Forced asset processor to synchronously process all critical assets
// Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick.
// So the asset id won't be found right after CompileAssetSync call.
for (const AZStd::string& assetFilters : assetFiltersArray)
{
AZ_TracePrintf("MaterialEditor", "Compiling critical asset matching: %s.\n", assetFilters.c_str());
// Wait for the asset be compiled
AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown;
AzFramework::AssetSystemRequestBus::BroadcastResult(
status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, assetFilters);
if (status != AzFramework::AssetSystem::AssetStatus_Compiled)
{
failedAssets.append(assetFilters.c_str());
}
}
if (!failedAssets.empty())
{
QMessageBox::critical(activeWindow(),
QString("Failed to compile critical assets"),
QString("Failed to compile the following critical assets:\n%1\n%2")
.arg(failedAssets.join(",\n"))
.arg("Make sure this is an Atom project."));
ExitMainLoop();
}
}
void MaterialEditorApplication::SaveSettings()
{
if (m_activatedLocalUserSettings)
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN] = "";
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/MaterialEditorUserSettings.xml", resolvedPath, AZ_ARRAY_SIZE(resolvedPath));
m_localUserSettings.Save(resolvedPath, context);
}
}
bool MaterialEditorApplication::OnOutput(const char* window, const char* message)
{
// Suppress spam from the Source Control system
if (0 == strncmp(window, AzToolsFramework::SCC_WINDOW, AZ_ARRAY_SIZE(AzToolsFramework::SCC_WINDOW)))
{
return true;
}
if (m_logFile)
{
m_logFile->AppendLog(AzFramework::LogFile::SEV_NORMAL, window, message);
}
else
{
m_startupLogSink.push_back({ window, message });
}
return false;
return AZStd::vector<AZStd::string>({ "passes/", "config/", "MaterialEditor" });
}
void MaterialEditorApplication::ProcessCommandLine(const AZ::CommandLine& commandLine)
@@ -312,195 +122,27 @@ namespace MaterialEditor
&MaterialEditor::MaterialEditorWindowRequestBus::Handler::ActivateWindow);
}
const AZStd::string timeoputSwitchName = "timeout";
if (commandLine.HasSwitch(timeoputSwitchName))
{
const AZStd::string& timeoutValue = commandLine.GetSwitchValue(timeoputSwitchName, 0);
const uint32_t timeoutInMs = atoi(timeoutValue.c_str());
AZ_Printf("MaterialEditor", "Timeout scheduled, shutting down in %u ms", timeoutInMs);
QTimer::singleShot(timeoutInMs, [this] {
AZ_Printf("MaterialEditor", "Timeout reached, shutting down");
ExitMainLoop();
});
}
// Process command line options for running one or more python scripts on startup
const AZStd::string runPythonScriptSwitchName = "runpython";
size_t runPythonScriptCount = commandLine.GetNumSwitchValues(runPythonScriptSwitchName);
for (size_t runPythonScriptIndex = 0; runPythonScriptIndex < runPythonScriptCount; ++runPythonScriptIndex)
{
const AZStd::string runPythonScriptPath = commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex);
AZStd::vector<AZStd::string_view> runPythonArgs;
AZ_Printf("MaterialEditor", "Launching script: %s", runPythonScriptPath.c_str());
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs,
runPythonScriptPath,
runPythonArgs);
}
// Process command line options for opening one or more material documents on startup
size_t openDocumentCount = commandLine.GetNumMiscValues();
for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex)
{
const AZStd::string openDocumentPath = commandLine.GetMiscValue(openDocumentIndex);
AZ_Printf("MaterialEditor", "Opening document: %s", openDocumentPath.c_str());
AZ_Printf(GetBuildTargetName().c_str(), "Opening document: %s", openDocumentPath.c_str());
MaterialDocumentSystemRequestBus::Broadcast(&MaterialDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath);
}
const AZStd::string exitAfterCommandsSwitchName = "exitaftercommands";
if (commandLine.HasSwitch(exitAfterCommandsSwitchName))
{
ExitMainLoop();
}
}
void MaterialEditorApplication::LoadSettings()
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN] = "";
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_MAX_PATH_LEN);
m_localUserSettings.Load(resolvedPath, context);
m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL);
AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL);
m_activatedLocalUserSettings = true;
}
void MaterialEditorApplication::UnloadSettings()
{
if (m_activatedLocalUserSettings)
{
SaveSettings();
m_localUserSettings.Deactivate();
AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect();
m_activatedLocalUserSettings = false;
}
}
bool MaterialEditorApplication::LaunchDiscoveryService()
{
// Determine if this is the first launch of the tool by attempting to connect to a running server
if (m_socket.Connect(QApplication::applicationName()))
{
// If the server was located, the application is already running.
// Forward commandline options to other application instance.
QByteArray buffer;
buffer.append("ProcessCommandLine:");
// Add the command line options from this process to the message, skipping the executable path
for (int argi = 1; argi < m_argC; ++argi)
{
buffer.append(QString(m_argV[argi]).append("\n").toUtf8());
}
// Inject command line option to always bring the main window to the foreground
buffer.append("--activatewindow\n");
m_socket.Send(buffer);
m_socket.Disconnect();
return false;
}
// Setup server to handle basic commands
m_server.SetReadHandler([this](const QByteArray& buffer) {
// Handle commmand line params from connected socket
if (buffer.startsWith("ProcessCommandLine:"))
{
// Remove header and parse commands
AZStd::string params(buffer.data(), buffer.size());
params = params.substr(strlen("ProcessCommandLine:"));
AZStd::vector<AZStd::string> tokens;
AZ::StringFunc::Tokenize(params, tokens, "\n");
if (!tokens.empty())
{
AZ::CommandLine commandLine;
commandLine.Parse(tokens);
ProcessCommandLine(commandLine);
}
}
});
// Launch local server
if (!m_server.Connect(QApplication::applicationName()))
{
return false;
}
return true;
Base::ProcessCommandLine(commandLine);
}
void MaterialEditorApplication::StartInternal()
{
if (WasExitMainLoopRequested())
{
return;
}
m_traceLogger.WriteStartupLog("MaterialEditor.log");
if (!LaunchDiscoveryService())
{
ExitMainLoop();
return;
}
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml");
AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets();
LoadSettings();
Base::StartInternal();
MaterialEditorWindowNotificationBus::Handler::BusConnect();
MaterialEditor::MaterialEditorWindowFactoryRequestBus::Broadcast(
&MaterialEditor::MaterialEditorWindowFactoryRequestBus::Handler::CreateMaterialEditorWindow);
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
// The PythonSystemComponent does not call StartPython to allow for lazy python initialization, so start it here
// The PythonSystemComponent will call StopPython when it deactivates, so we do not need our own corresponding call to StopPython
editorPythonEventsInterface->StartPython();
}
// Delay execution of commands and scripts post initialization
QTimer::singleShot(0, [this]() { ProcessCommandLine(m_commandLine); });
}
bool MaterialEditorApplication::GetAssetDatabaseLocation(AZStd::string& result)
{
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
AZ::IO::FixedMaxPath assetDatabaseSqlitePath;
if (settingsRegistry && settingsRegistry->Get(assetDatabaseSqlitePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
{
assetDatabaseSqlitePath /= "assetdb.sqlite";
result = AZStd::string_view(assetDatabaseSqlitePath.Native());
return true;
}
return false;
}
void MaterialEditorApplication::Tick(float deltaOverride)
{
TickSystem();
Application::Tick(deltaOverride);
if (WasExitMainLoopRequested())
{
m_timer.disconnect();
quit();
}
}
void MaterialEditorApplication::Stop()
@@ -508,76 +150,6 @@ namespace MaterialEditor
MaterialEditor::MaterialEditorWindowFactoryRequestBus::Broadcast(
&MaterialEditor::MaterialEditorWindowFactoryRequestBus::Handler::DestroyMaterialEditorWindow);
UnloadSettings();
AzFramework::Application::Stop();
Base::Stop();
}
void MaterialEditorApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
{
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game;
}
void MaterialEditorApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message)
{
#if defined(AZ_ENABLE_TRACING)
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(
message,
lines,
"\n",
false, // Keep empty strings
false // Keep space strings
);
for (auto& line : lines)
{
AZ_TracePrintf("MaterialEditor", "Python: %s\n", line.c_str());
}
#endif
}
void MaterialEditorApplication::OnErrorMessage(AZStd::string_view message)
{
// Use AZ_TracePrintf instead of AZ_Error or AZ_Warning to avoid all the metadata noise
OnTraceMessage(message);
}
void MaterialEditorApplication::OnExceptionMessage([[maybe_unused]] AZStd::string_view message)
{
AZ_Error("MaterialEditor", false, "Python: " AZ_STRING_FORMAT, AZ_STRING_ARG(message));
}
// Copied from PyIdleWaitFrames in CryEdit.cpp
void MaterialEditorApplication::PyIdleWaitFrames(uint32_t frames)
{
struct Ticker : public AZ::TickBus::Handler
{
Ticker(QEventLoop* loop, uint32_t targetFrames) : m_loop(loop), m_targetFrames(targetFrames)
{
AZ::TickBus::Handler::BusConnect();
}
~Ticker()
{
AZ::TickBus::Handler::BusDisconnect();
}
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override
{
AZ_UNUSED(deltaTime);
AZ_UNUSED(time);
if (++m_elapsedFrames == m_targetFrames)
{
m_loop->quit();
}
}
QEventLoop* m_loop = nullptr;
uint32_t m_elapsedFrames = 0;
uint32_t m_targetFrames = 0;
};
QEventLoop loop;
Ticker ticker(&loop, frames);
loop.exec();
}
} // namespace MaterialEditor
@@ -10,19 +10,7 @@
#include <Atom/Document/MaterialDocumentSystemRequestBus.h>
#include <Atom/Window/MaterialEditorWindowNotificationBus.h>
#include <AtomToolsFramework/Communication/LocalServer.h>
#include <AtomToolsFramework/Communication/LocalSocket.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/Logging/LogFile.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/Logger/TraceLogger.h>
#include <AzQtComponents/Application/AzQtApplication.h>
#include <AtomToolsFramework/Application/AtomToolsApplication.h>
#include <QTimer>
@@ -31,41 +19,24 @@ namespace MaterialEditor
class MaterialThumbnailRenderer;
class MaterialEditorApplication
: public AzFramework::Application
, public AzQtComponents::AzQtApplication
, private AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
: public AtomToolsFramework::AtomToolsApplication
, private MaterialEditorWindowNotificationBus::Handler
, private AzFramework::AssetSystemStatusBus::Handler
, private AZ::UserSettingsOwnerRequestBus::Handler
, private AZ::Debug::TraceMessageBus::Handler
, private AzToolsFramework::EditorPythonConsoleNotificationBus::Handler
{
public:
AZ_TYPE_INFO(MaterialEditor::MaterialEditorApplication, "{30F90CA5-1253-49B5-8143-19CEE37E22BB}");
using Base = AzFramework::Application;
using Base = AtomToolsFramework::AtomToolsApplication;
MaterialEditorApplication(int* argc, char*** argv);
virtual ~MaterialEditorApplication();
//////////////////////////////////////////////////////////////////////////
// AzFramework::Application
void CreateReflectionManager() override;
void Reflect(AZ::ReflectContext* context) override;
void RegisterCoreComponents() override;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
void CreateStaticModules(AZStd::vector<AZ::Module*>& outModules) override;
const char* GetCurrentConfigurationName() const override;
void StartCommon(AZ::Entity* systemEntity) override;
void Tick(float deltaOverride = -1.f) override;
void Stop() override;
private:
//////////////////////////////////////////////////////////////////////////
// AssetDatabaseRequestsBus::Handler overrides...
bool GetAssetDatabaseLocation(AZStd::string& result) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// MaterialEditorWindowNotificationBus::Handler overrides...
void OnMaterialEditorWindowClosing() override;
@@ -76,66 +47,9 @@ namespace MaterialEditor
void Destroy() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::ComponentApplication overrides...
void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override;
//////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// EditorPythonConsoleNotificationBus::Handler overrides...
void OnTraceMessage(AZStd::string_view message) override;
void OnErrorMessage(AZStd::string_view message) override;
void OnExceptionMessage(AZStd::string_view message) override;
////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzFramework::AssetSystemStatusBus::Handler overrides...
void AssetSystemAvailable() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::UserSettingsOwnerRequestBus::Handler overrides...
void SaveSettings() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::Debug::TraceMessageBus::Handler overrides...
bool OnOutput(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
void CompileCriticalAssets();
void ProcessCommandLine(const AZ::CommandLine& commandLine);
void LoadSettings();
void UnloadSettings();
bool LaunchDiscoveryService();
void StartInternal();
static void PyIdleWaitFrames(uint32_t frames);
struct LogMessage
{
AZStd::string window;
AZStd::string message;
};
AZStd::vector<LogMessage> m_startupLogSink;
AZStd::unique_ptr<AzFramework::LogFile> m_logFile;
AzToolsFramework::TraceLogger m_traceLogger;
//! Local user settings are used to store material browser tree expansion state
AZ::UserSettingsProvider m_localUserSettings;
//! Are local settings loaded
bool m_activatedLocalUserSettings = false;
QTimer m_timer;
AtomToolsFramework::LocalSocket m_socket;
AtomToolsFramework::LocalServer m_server;
};
void ProcessCommandLine(const AZ::CommandLine& commandLine) override;
void StartInternal() override;
AZStd::string GetBuildTargetName() const override;
AZStd::vector<AZStd::string> GetCriticalAssetFilters() const override;
};
} // namespace MaterialEditor
@@ -324,7 +324,7 @@ namespace MaterialEditor
{
if (!preset)
{
AZ_Error("MaterialViewportRenderer", false, "Attempting to set invalid lighting preset.");
AZ_Warning("MaterialViewportRenderer", false, "Attempting to set invalid lighting preset.");
return;
}
@@ -365,13 +365,13 @@ namespace MaterialEditor
{
if (!preset)
{
AZ_Error("MaterialViewportRenderer", false, "Attempting to set invalid model preset.");
AZ_Warning("MaterialViewportRenderer", false, "Attempting to set invalid model preset.");
return;
}
if (!preset->m_modelAsset.GetId().IsValid())
{
AZ_Error("MaterialViewportRenderer", false, "Attempting to set invalid model for preset: '%s'\n.", preset->m_displayName.c_str());
AZ_Warning("MaterialViewportRenderer", false, "Attempting to set invalid model for preset: '%s'\n.", preset->m_displayName.c_str());
return;
}
@@ -6,51 +6,48 @@
*
*/
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
#include <AzFramework/IO/LocalFileIO.h>
#include <AzFramework/Network/AssetProcessorConnection.h>
#include <AzToolsFramework/AzToolsFrameworkModule.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/API/EditorPythonRunnerRequestsBus.h>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/Asset/AssetSystemComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserComponent.h>
#include <AzToolsFramework/AssetBrowser/AssetBrowserEntry.h>
#include <AzToolsFramework/AzToolsFrameworkModule.h>
#include <AzToolsFramework/SourceControl/PerforceComponent.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AzToolsFramework/Thumbnails/ThumbnailerComponent.h>
#include <AzToolsFramework/UI/PropertyEditor/PropertyManagerComponent.h>
#include <AzToolsFramework/SourceControl/SourceControlAPI.h>
#include <AtomToolsFramework/Util/Util.h>
#include <AzToolsFramework/UI/UICore/QTreeViewStateSaver.hxx>
#include <AzToolsFramework/UI/UICore/QWidgetSavedState.h>
#include <Atom/RPI.Edit/Common/AssetUtils.h>
#include <Atom/RPI.Public/RPISystemInterface.h>
#include <AtomToolsFramework/Util/Util.h>
#include <Source/ShaderManagementConsoleApplication.h>
#include <ShaderManagementConsoleApplication.h>
#include <ShaderManagementConsole_Traits_Platform.h>
#include <Atom/Document/ShaderManagementConsoleDocumentModule.h>
#include <Atom/Document/ShaderManagementConsoleDocumentSystemRequestBus.h>
#include <Atom/Window/ShaderManagementConsoleWindowModule.h>
#include <Atom/Window/ShaderManagementConsoleWindowRequestBus.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/Utils/Utils.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QFileInfo>
#include <QObject>
#include <QMessageBox>
#include <QObject>
AZ_POP_DISABLE_WARNING
namespace ShaderManagementConsole
{
AZStd::string_view GetBuildTargetName()
AZStd::string ShaderManagementConsoleApplication::GetBuildTargetName() const
{
#if !defined (LY_CMAKE_TARGET)
#if !defined(LY_CMAKE_TARGET)
#error "LY_CMAKE_TARGET must be defined in order to add this source file to a CMake executable target"
#endif
return AZStd::string_view{ LY_CMAKE_TARGET };
@@ -68,99 +65,22 @@ namespace ShaderManagementConsole
}
ShaderManagementConsoleApplication::ShaderManagementConsoleApplication(int* argc, char*** argv)
: Application(argc, argv)
, AzQtApplication(*argc, *argv)
: AtomToolsApplication(argc, argv)
{
QApplication::setApplicationName("O3DE Shader Management Console");
// The settings registry has been created at this point, so add the CMake target
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddBuildSystemTargetSpecialization(
*AZ::SettingsRegistry::Get(), GetBuildTargetName());
connect(&m_timer, &QTimer::timeout, this, [&]()
{
this->PumpSystemEventLoopUntilEmpty();
this->Tick();
});
}
void ShaderManagementConsoleApplication::CreateReflectionManager()
{
Application::CreateReflectionManager();
GetSerializeContext()->CreateEditContext();
}
void ShaderManagementConsoleApplication::Reflect(AZ::ReflectContext* context)
{
Application::Reflect(context);
AzToolsFramework::AssetBrowser::AssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::RootAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::FolderAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::SourceAssetBrowserEntry::Reflect(context);
AzToolsFramework::AssetBrowser::ProductAssetBrowserEntry::Reflect(context);
AzToolsFramework::QTreeViewWithStateSaving::Reflect(context);
AzToolsFramework::QWidgetSavedState::Reflect(context);
if (auto behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
// this will put these methods into the 'azlmbr.shadermanagementconsole.general' module
auto addGeneral = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
{
methodBuilder->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Category, "Editor")
->Attribute(AZ::Script::Attributes::Module, "shadermanagementconsole.general");
};
// The reflection here is based on patterns in CryEditPythonHandler::Reflect
addGeneral(behaviorContext->Method("idle_wait_frames", &ShaderManagementConsoleApplication::PyIdleWaitFrames, nullptr, "Waits idling for a frames. Primarily used for auto-testing."));
}
}
void ShaderManagementConsoleApplication::RegisterCoreComponents()
{
Application::RegisterCoreComponents();
RegisterComponentDescriptor(AzToolsFramework::AssetBrowser::AssetBrowserComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Thumbnailer::ThumbnailerComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::Components::PropertyManagerComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::AssetSystem::AssetSystemComponent::CreateDescriptor());
RegisterComponentDescriptor(AzToolsFramework::PerforceComponent::CreateDescriptor());
}
AZ::ComponentTypeList ShaderManagementConsoleApplication::GetRequiredSystemComponents() const
{
AZ::ComponentTypeList components = Application::GetRequiredSystemComponents();
components.insert(components.end(), {
azrtti_typeid<AzToolsFramework::AssetBrowser::AssetBrowserComponent>(),
azrtti_typeid<AzToolsFramework::Thumbnailer::ThumbnailerComponent>(),
azrtti_typeid<AzToolsFramework::Components::PropertyManagerComponent>(),
azrtti_typeid<AzToolsFramework::PerforceComponent>(),
});
return components;
}
void ShaderManagementConsoleApplication::CreateStaticModules(AZStd::vector<AZ::Module*>& outModules)
{
Application::CreateStaticModules(outModules);
outModules.push_back(aznew AzToolsFramework::AzToolsFrameworkModule);
Base::CreateStaticModules(outModules);
outModules.push_back(aznew ShaderManagementConsoleDocumentModule);
outModules.push_back(aznew ShaderManagementConsoleWindowModule);
}
void ShaderManagementConsoleApplication::StartCommon(AZ::Entity* systemEntity)
{
AzFramework::AssetSystemStatusBus::Handler::BusConnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusConnect();
AzFramework::Application::StartCommon(systemEntity);
StartInternal();
m_timer.start();
}
void ShaderManagementConsoleApplication::OnShaderManagementConsoleWindowClosing()
{
ExitMainLoop();
@@ -171,113 +91,17 @@ namespace ShaderManagementConsole
void ShaderManagementConsoleApplication::Destroy()
{
// before modules are unloaded, destroy UI to free up any assets it cached
ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast(&ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::DestroyShaderManagementConsoleWindow);
ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast(
&ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::DestroyShaderManagementConsoleWindow);
ShaderManagementConsoleWindowNotificationBus::Handler::BusDisconnect();
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
AzFramework::AssetSystemRequestBus::Broadcast(&AzFramework::AssetSystem::AssetSystemRequests::StartDisconnectingAssetProcessor);
Application::Destroy();
Base::Destroy();
}
void ShaderManagementConsoleApplication::AssetSystemAvailable()
AZStd::vector<AZStd::string> ShaderManagementConsoleApplication::GetCriticalAssetFilters() const
{
// Try connect to AP first before try to launch it manually.
bool connected = false;
auto ConnectToAssetProcessorWithIdentifier = [&connected](AzFramework::AssetSystem::AssetSystemRequests* assetSystemRequests)
{
// When the AssetProcessor is already launched it should take less than a second to perform a connection
// but when the AssetProcessor needs to be launch it could take up to 15 seconds to have the AssetProcessor initialize
// and able to negotiate a connection when running a debug build
// and to negotiate a connection
AzFramework::AssetSystem::ConnectionSettings connectionSettings;
AzFramework::AssetSystem::ReadConnectionSettingsFromSettingsRegistry(connectionSettings);
connectionSettings.m_connectionDirection = AzFramework::AssetSystem::ConnectionSettings::ConnectionDirection::ConnectToAssetProcessor;
connectionSettings.m_connectionIdentifier = "Shader Management Console";
connectionSettings.m_loggingCallback = []([[maybe_unused]] AZStd::string_view logData)
{
AZ_TracePrintf("Shader Management Console", "%.*s", aznumeric_cast<int>(logData.size()), logData.data());
};
connected = assetSystemRequests->EstablishAssetProcessorConnection(connectionSettings);
};
AzFramework::AssetSystemRequestBus::Broadcast(ConnectToAssetProcessorWithIdentifier);
if (connected)
{
CompileCriticalAssets();
}
AzFramework::AssetSystemStatusBus::Handler::BusDisconnect();
}
void ShaderManagementConsoleApplication::CompileCriticalAssets()
{
AZ_TracePrintf("Shader Management Console", "Compiling critical assets.\n");
// List of common asset filters for things that need to be compiled to run
// Some of these things will not be necessary once we have proper support for queued asset loading and reloading
const AZStd::string assetFilterss[] =
{
"passes/",
"config/",
};
QStringList failedAssets;
// Forced asset processor to synchronously process all critical assets
// Note: with AssetManager's current implementation, a compiled asset won't be added in asset registry until next system tick.
// So the asset id won't be found right after CompileAssetSync call.
for (const AZStd::string& assetFilters : assetFilterss)
{
AZ_TracePrintf("Shader Management Console", "Compiling critical asset matching: %s.\n", assetFilters.c_str());
// Wait for the asset be compiled
AzFramework::AssetSystem::AssetStatus status = AzFramework::AssetSystem::AssetStatus_Unknown;
AzFramework::AssetSystemRequestBus::BroadcastResult(
status, &AzFramework::AssetSystemRequestBus::Events::CompileAssetSync, assetFilters);
if (status != AzFramework::AssetSystem::AssetStatus_Compiled)
{
failedAssets.append(assetFilters.c_str());
}
}
if (!failedAssets.empty())
{
QMessageBox::critical(activeWindow(),
QString("Failed to compile critical assets"),
QString("Failed to compile the following critical assets:\n%1\n%2")
.arg(failedAssets.join(",\n"))
.arg("Make sure this is an Atom project."));
m_closing = true;
}
}
void ShaderManagementConsoleApplication::SaveSettings()
{
if (m_activatedLocalUserSettings)
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN] = "";
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_ARRAY_SIZE(resolvedPath));
m_localUserSettings.Save(resolvedPath, context);
}
}
bool ShaderManagementConsoleApplication::OnPrintf(const char* window, const char* /*message*/)
{
// Suppress spam from the Source Control system
if (0 == strncmp(window, AzToolsFramework::SCC_WINDOW, AZ_ARRAY_SIZE(AzToolsFramework::SCC_WINDOW)))
{
return true;
}
return false;
return AZStd::vector<AZStd::string>({ "passes/", "config/" });
}
void ShaderManagementConsoleApplication::ProcessCommandLine()
@@ -290,9 +114,7 @@ namespace ShaderManagementConsole
const AZStd::string runPythonScriptPath = m_commandLine.GetSwitchValue(runPythonScriptSwitchName, runPythonScriptIndex);
AZStd::vector<AZStd::string_view> runPythonArgs;
AzToolsFramework::EditorPythonRunnerRequestBus::Broadcast(
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs,
runPythonScriptPath,
runPythonArgs);
&AzToolsFramework::EditorPythonRunnerRequestBus::Events::ExecuteByFilenameWithArgs, runPythonScriptPath, runPythonArgs);
}
// Process command line options for opening one or more documents on startup
@@ -300,177 +122,18 @@ namespace ShaderManagementConsole
for (size_t openDocumentIndex = 0; openDocumentIndex < openDocumentCount; ++openDocumentIndex)
{
const AZStd::string openDocumentPath = m_commandLine.GetMiscValue(openDocumentIndex);
ShaderManagementConsoleDocumentSystemRequestBus::Broadcast(&ShaderManagementConsoleDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath);
ShaderManagementConsoleDocumentSystemRequestBus::Broadcast(
&ShaderManagementConsoleDocumentSystemRequestBus::Events::OpenDocument, openDocumentPath);
}
}
void ShaderManagementConsoleApplication::LoadSettings()
{
AZ::SerializeContext* context = nullptr;
AZ::ComponentApplicationBus::BroadcastResult(context, &AZ::ComponentApplicationRequests::GetSerializeContext);
AZ_Assert(context, "No serialize context");
char resolvedPath[AZ_MAX_PATH_LEN] = "";
AZ::IO::FileIOBase::GetInstance()->ResolvePath("@user@/EditorUserSettings.xml", resolvedPath, AZ_MAX_PATH_LEN);
m_localUserSettings.Load(resolvedPath, context);
m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL);
AZ::UserSettingsOwnerRequestBus::Handler::BusConnect(AZ::UserSettings::CT_LOCAL);
m_activatedLocalUserSettings = true;
}
void ShaderManagementConsoleApplication::UnloadSettings()
{
if (m_activatedLocalUserSettings)
{
SaveSettings();
m_localUserSettings.Deactivate();
AZ::UserSettingsOwnerRequestBus::Handler::BusDisconnect();
m_activatedLocalUserSettings = false;
}
}
bool ShaderManagementConsoleApplication::LaunchDiscoveryService()
{
const QStringList arguments = { "-fail_silently" };
return AtomToolsFramework::LaunchTool("GridHub", AZ_TRAIT_SHADER_MANAGEMENT_CONSOLE_EXT, arguments);
}
void ShaderManagementConsoleApplication::StartInternal()
{
if (m_closing)
{
return;
}
m_traceLogger.WriteStartupLog("ShaderManagementConsole.log");
//[GFX TODO][ATOM-415] Try to factor out some of this stuff with AtomSampleViewerApplication
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusConnect();
AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotificationBus::Broadcast(&AzToolsFramework::AssetBrowser::AssetDatabaseLocationNotifications::OnDatabaseInitialized);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml");
AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets();
LoadSettings();
LaunchDiscoveryService();
Base::StartInternal();
ShaderManagementConsoleWindowNotificationBus::Handler::BusConnect();
ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast(&ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::CreateShaderManagementConsoleWindow);
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
if (editorPythonEventsInterface)
{
// The PythonSystemComponent does not call StartPython to allow for lazy python initialization, so start it here
// The PythonSystemComponent will call StopPython when it deactivates, so we do not need our own corresponding call to StopPython
editorPythonEventsInterface->StartPython();
}
ProcessCommandLine();
ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Broadcast(
&ShaderManagementConsole::ShaderManagementConsoleWindowRequestBus::Handler::CreateShaderManagementConsoleWindow);
}
bool ShaderManagementConsoleApplication::GetAssetDatabaseLocation(AZStd::string& result)
{
AZ::SettingsRegistryInterface* settingsRegistry = AZ::SettingsRegistry::Get();
AZ::IO::FixedMaxPath assetDatabaseSqlitePath;
if (settingsRegistry && settingsRegistry->Get(assetDatabaseSqlitePath.Native(), AZ::SettingsRegistryMergeUtils::FilePathKey_CacheProjectRootFolder))
{
assetDatabaseSqlitePath /= "assetdb.sqlite";
result = AZStd::string_view(assetDatabaseSqlitePath.Native());
return true;
}
return false;
}
void ShaderManagementConsoleApplication::Tick(float deltaOverride)
{
TickSystem();
Application::Tick(deltaOverride);
if (m_closing)
{
m_timer.disconnect();
quit();
}
}
void ShaderManagementConsoleApplication::Stop()
{
UnloadSettings();
AzFramework::Application::Stop();
}
void ShaderManagementConsoleApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
{
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game;
}
void ShaderManagementConsoleApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message)
{
#if defined(AZ_ENABLE_TRACING)
AZStd::vector<AZStd::string> lines;
AzFramework::StringFunc::Tokenize(
message,
lines,
"\n",
false, // Keep empty strings
false // Keep space strings
);
for (auto& line : lines)
{
AZ_TracePrintf("Shader Management Console", "Python: %s\n", line.c_str());
}
#endif
}
void ShaderManagementConsoleApplication::OnErrorMessage(AZStd::string_view message)
{
// Use AZ_TracePrintf instead of AZ_Error or AZ_Warning to avoid all the metadata noise
OnTraceMessage(message);
}
void ShaderManagementConsoleApplication::OnExceptionMessage([[maybe_unused]] AZStd::string_view message)
{
AZ_Error("Shader Management Console", false, "Python: " AZ_STRING_FORMAT, AZ_STRING_ARG(message));
}
// Copied from PyIdleWaitFrames in CryEdit.cpp
void ShaderManagementConsoleApplication::PyIdleWaitFrames(uint32_t frames)
{
struct Ticker : public AZ::TickBus::Handler
{
Ticker(QEventLoop* loop, uint32_t targetFrames) : m_loop(loop), m_targetFrames(targetFrames)
{
AZ::TickBus::Handler::BusConnect();
}
~Ticker()
{
AZ::TickBus::Handler::BusDisconnect();
}
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override
{
AZ_UNUSED(deltaTime);
AZ_UNUSED(time);
if (++m_elapsedFrames == m_targetFrames)
{
m_loop->quit();
}
}
QEventLoop* m_loop = nullptr;
uint32_t m_elapsedFrames = 0;
uint32_t m_targetFrames = 0;
};
QEventLoop loop;
Ticker ticker(&loop, frames);
loop.exec();
}
} // namespace ShaderManagementConsole
@@ -8,63 +8,32 @@
#pragma once
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/UserSettings/UserSettingsProvider.h>
#include <AzCore/Debug/TraceMessageBus.h>
#include <AzFramework/Application/Application.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzToolsFramework/API/AssetDatabaseBus.h>
#include <AzToolsFramework/API/EditorPythonConsoleBus.h>
#include <AzToolsFramework/Logger/TraceLogger.h>
#include <Atom/Document/ShaderManagementConsoleDocumentSystemRequestBus.h>
#include <Atom/Window/ShaderManagementConsoleWindowNotificationBus.h>
#include <AzQtComponents/Application/AzQtApplication.h>
#include <AtomToolsFramework/Application/AtomToolsApplication.h>
#include <QTimer>
namespace ShaderManagementConsole
{
class ShaderManagementConsoleApplication
: public AzFramework::Application
, public AzQtComponents::AzQtApplication
, private AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler
: public AtomToolsFramework::AtomToolsApplication
, private ShaderManagementConsoleWindowNotificationBus::Handler
, private AzFramework::AssetSystemStatusBus::Handler
, private AZ::UserSettingsOwnerRequestBus::Handler
, private AZ::Debug::TraceMessageBus::Handler
, private AzToolsFramework::EditorPythonConsoleNotificationBus::Handler
{
public:
AZ_TYPE_INFO(ShaderManagementConsole::ShaderManagementConsoleApplication, "{30F90CA5-1253-49B5-8143-19CEE37E22BB}");
AZ_TYPE_INFO(ShaderManagementConsole::ShaderManagementConsoleApplication, "{A31B1AEB-4DA3-49CD-884A-CC998FF7546F}");
using Base = AzFramework::Application;
using Base = AtomToolsFramework::AtomToolsApplication;
ShaderManagementConsoleApplication(int* argc, char*** argv);
virtual ~ShaderManagementConsoleApplication() = default;
//////////////////////////////////////////////////////////////////////////
// AzFramework::Application
void CreateReflectionManager() override;
void Reflect(AZ::ReflectContext* context) override;
void RegisterCoreComponents() override;
AZ::ComponentTypeList GetRequiredSystemComponents() const override;
void CreateStaticModules(AZStd::vector<AZ::Module*>& outModules) override;
const char* GetCurrentConfigurationName() const override;
void StartCommon(AZ::Entity* systemEntity) override;
void Tick(float deltaOverride = -1.f) override;
void Stop() override;
private:
//////////////////////////////////////////////////////////////////////////
// AssetDatabaseRequestsBus::Handler overrides...
bool GetAssetDatabaseLocation(AZStd::string& result) override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// ShaderManagementConsoleWindowNotificationBus::Handler overrides...
void OnShaderManagementConsoleWindowClosing() override;
@@ -75,57 +44,9 @@ namespace ShaderManagementConsole
void Destroy() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzFramework::ApplicationRequests::Bus overrides...
void QueryApplicationType(AZ::ApplicationTypeQuery& appType) const override;
//////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////
// EditorPythonConsoleNotificationBus::Handler overrides...
void OnTraceMessage(AZStd::string_view message) override;
void OnErrorMessage(AZStd::string_view message) override;
void OnExceptionMessage(AZStd::string_view message) override;
////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AzFramework::AssetSystemStatusBus::Handler overrides...
void AssetSystemAvailable() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::UserSettingsOwnerRequestBus::Handler overrides...
void SaveSettings() override;
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// AZ::Debug::TraceMessageBus::Handler overrides...
bool OnPrintf(const char* window, const char* message) override;
//////////////////////////////////////////////////////////////////////////
void CompileCriticalAssets();
void ProcessCommandLine();
void LoadSettings();
void UnloadSettings();
bool LaunchDiscoveryService();
void StartInternal();
static void PyIdleWaitFrames(uint32_t frames);
AzToolsFramework::TraceLogger m_traceLogger;
//! Local user settings are used to store asset browser tree expansion state
AZ::UserSettingsProvider m_localUserSettings;
//! Are local settings loaded
bool m_activatedLocalUserSettings = false;
QTimer m_timer;
bool m_started = false;
bool m_closing = false;
void StartInternal() override;
AZStd::string GetBuildTargetName() const override;
AZStd::vector<AZStd::string> GetCriticalAssetFilters() const override;
};
} // namespace ShaderManagementConsole
@@ -24,37 +24,64 @@ namespace AZ
namespace Render
{
struct ThreadRegionEntry
//! Stores all the data associated with a row in the table.
struct TableRow
{
AZStd::thread_id m_threadId;
AZStd::sys_time_t m_startTick = 0;
AZStd::sys_time_t m_endTick = 0;
template <typename T>
struct TableRowCompareFunctor
{
TableRowCompareFunctor(T memberPointer, bool isAscending) : m_memberPointer(memberPointer), m_ascending(isAscending){};
bool operator()(const TableRow* lhs, const TableRow* rhs)
{
return m_ascending ? lhs->*m_memberPointer < rhs->*m_memberPointer : lhs->*m_memberPointer > rhs->*m_memberPointer;
}
T m_memberPointer;
bool m_ascending;
};
// Update running statistics with new region data
void RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId);
void ResetPerFrameStatistics();
// Get a string of all threads that this region executed in during the last frame
AZStd::string GetExecutingThreadsLabel() const;
AZStd::string m_groupName;
AZStd::string m_regionName;
// --- Per frame statistics ---
u64 m_invocationsLastFrame = 0;
// NOTE: set over unordered_set so the threads can be shown in increasing order in tooltip.
AZStd::set<AZStd::thread_id> m_executingThreads;
AZStd::sys_time_t m_lastFrameTotalTicks = 0;
// Maximum execution time of a region in the last frame.
AZStd::sys_time_t m_maxTicks = 0;
// --- Aggregate statistics ---
u64 m_invocationsTotal = 0;
// Running average of Mean Time Per Call
AZStd::sys_time_t m_runningAverageTicks = 0;
};
// Stores data about a region that is agreggated from all collected frames
// Data collection can be toggled on and off through m_record.
struct RegionStatistics
{
float CalcAverageTimeMs() const;
void RecordRegion(const AZ::RHI::CachedTimeRegion& region);
bool m_draw = false;
bool m_record = true;
u64 m_invocations = 0;
AZStd::sys_time_t m_totalTicks = 0;
};
//! Visual profiler for Cpu statistics.
//! It uses ImGui as the library for displaying the Attachments and Heaps.
//! It shows all heaps that are being used by the RHI and how the
//! resources are allocated in each heap.
//! ImGui widget for examining Atom CPU Profiling instrumentation.
//! Offers both a statistical view (with sorting and searching capability) and a visualizer
//! similar to RAD and other profiling tools.
class ImGuiCpuProfiler
: SystemTickBus::Handler
{
// Region Name -> Array of ThreadRegion entries
using RegionEntryMap = AZStd::map<AZStd::string, AZStd::vector<ThreadRegionEntry>>;
// Group Name -> RegionEntryMap
using GroupRegionMap = AZStd::map<AZStd::string, RegionEntryMap>;
// Region Name -> statistical view row data
using RegionRowMap = AZStd::map<AZStd::string, TableRow>;
// Group Name -> RegionRowMap
using GroupRegionMap = AZStd::map<AZStd::string, RegionRowMap>;
using TimeRegion = AZ::RHI::CachedTimeRegion;
using GroupRegionName = AZ::RHI::CachedTimeRegion::GroupRegionName;
@@ -66,42 +93,26 @@ namespace AZ
//! Draws the overall CPU profiling window, defaults to the statistical view
void Draw(bool& keepDrawing, const AZ::RHI::CpuTimingStatistics& cpuTimingStatistics);
//! Draws the statistical view of the CPU profiling data
void DrawStatisticsView();
//! Draws the CPU profiling visualizer in a new window.
void DrawVisualizer();
private:
static constexpr float RowHeight = 50.0;
static constexpr int DefaultFramesToCollect = 50;
static constexpr float MediumFrameTimeLimit = 16.6; // 60 fps
static constexpr float HighFrameTimeLimit = 33.3; // 30 fps
// Draw the shared header between the two windows
//! Draws the statistical view of the CPU profiling data.
void DrawStatisticsView();
//! Draws the CPU profiling visualizer.
void DrawVisualizer();
// Draw the shared header between the two windows.
void DrawCommonHeader();
// ImGui filter used to filter TimedRegions.
ImGuiTextFilter m_timedRegionFilter;
// Draw the region statistics table in the order specified by the pointers in m_tableData.
void DrawTable();
// Saves statistical view data organized by group name -> region name -> regions
GroupRegionMap m_groupRegionMap;
// Pause cpu profiling. The profiler will show the statistics of the last frame before pause
bool m_paused = false;
// Export the profiling data from a single frame to a local file
bool m_captureToFile = false;
// Toggle between the normal statistical view and the visual profiling view
bool m_enableVisualizer = false;
// Total frames need to be saved
int m_captureFrameCount = 1;
AZ::RHI::CpuTimingStatistics m_cpuTimingStatisticsWhenPause;
AZStd::string m_lastCapturedFilePath;
// Visualizer methods
// Sort the table by a given column, rearranges the pointers in m_tableData.
void SortTable(ImGuiTableSortSpecs* sortSpecs);
// Get the profiling data from the last frame, only called when the profiler is not paused.
void CollectFrameData();
@@ -109,7 +120,7 @@ namespace AZ
// Cull old data from internal storage, only called when profiler is not paused.
void CullFrameData(const AZ::RHI::CpuTimingStatistics& currentCpuTimingStatistics);
// Draws a single block onto the timeline
// Draws a single block onto the timeline into the specified row
void DrawBlock(const TimeRegion& block, u64 targetRow);
// Draw horizontal lines between threads in the timeline
@@ -118,28 +129,28 @@ namespace AZ
// Draw the "Thread XXXXX" label onto the viewport
void DrawThreadLabel(u64 baseRow, AZStd::thread_id threadId);
// Draws all active function statistics windows
void DrawRegionStatistics();
// Draw the vertical lines separating frames in the timeline
void DrawFrameBoundaries();
// Draw the ruler with frame time labels
void DrawRuler();
// Draw the frame time histogram
void DrawFrameTimeHistogram();
// Converts raw ticks to a pixel value suitable to give to ImDrawList, handles window scrolling
float ConvertTickToPixelSpace(AZStd::sys_time_t tick) const;
float ConvertTickToPixelSpace(AZStd::sys_time_t tick, AZStd::sys_time_t leftBound, AZStd::sys_time_t rightBound) const;
AZStd::sys_time_t GetViewportTickWidth() const;
// Gets the color for a block using the GroupRegionName as a key into the cache
// Generates a random ImU32 if the block does not yet have a color
// Gets the color for a block using the GroupRegionName as a key into the cache.
// Generates a random ImU32 if the block does not yet have a color.
ImU32 GetBlockColor(const TimeRegion& block);
// System tick bus overrides
virtual void OnSystemTick() override;
// Visualizer state
// --- Visualizer Members ---
int m_framesToCollect = DefaultFramesToCollect;
@@ -159,10 +170,34 @@ namespace AZ
// Tracks the frame boundaries
AZStd::vector<AZStd::sys_time_t> m_frameEndTicks = { INT64_MIN };
// Main data structure for storing function statistics to be shown in the popup windows.
// For now we default allocate for all regions on the first render frame and then use RegionStatistics.m_draw to determine
// if we should draw the window or not. FIXME(ATOM-15948) this should be changed once RegionStatistics gets heavier.
AZStd::unordered_map<const GroupRegionName*, RegionStatistics> m_regionStatisticsMap;
// Filter for highlighting regions on the visualizer
ImGuiTextFilter m_visualizerHighlightFilter;
// --- Tabular view members ---
// ImGui filter used to filter TimedRegions.
ImGuiTextFilter m_timedRegionFilter;
// Saves statistical view data organized by group name -> region name -> row data
GroupRegionMap m_groupRegionMap;
// Saves pointers to objects in m_groupRegionMap, order reflects table ordering.
// Non-owning, will be cleared when m_groupRegionMap is cleared.
AZStd::vector<TableRow*> m_tableData;
// Pause cpu profiling. The profiler will show the statistics of the last frame before pause.
bool m_paused = false;
// Export the profiling data from a single frame to a local file.
bool m_captureToFile = false;
// Toggle between the normal statistical view and the visual profiling view.
bool m_enableVisualizer = false;
// Last captured CPU timing statistics
AZ::RHI::CpuTimingStatistics m_cpuTimingStatisticsWhenPause;
AZStd::string m_lastCapturedFilePath;
};
} // namespace Render
} // namespace AZ
@@ -14,6 +14,7 @@
#include <AzCore/IO/Path/Path_fwd.h>
#include <AzCore/std/containers/map.h>
#include <AzCore/std/containers/set.h>
#include <AzCore/std/limits.h>
#include <AzCore/std/sort.h>
#include <AzCore/std/time.h>
@@ -36,6 +37,7 @@ namespace AZ
{
return AZStd::string::format("Thread: %zu", static_cast<size_t>(threadId));
}
inline float TicksToMs(AZStd::sys_time_t ticks)
{
// Note: converting to microseconds integer before converting to milliseconds float
@@ -134,6 +136,99 @@ namespace AZ
}
}
inline void ImGuiCpuProfiler::DrawTable()
{
const auto flags =
ImGuiTableFlags_Borders | ImGuiTableFlags_Sortable | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable;
if (ImGui::BeginTable("FunctionStatisticsTable", 6, flags))
{
// Table header setup
ImGui::TableSetupColumn("Group");
ImGui::TableSetupColumn("Region");
ImGui::TableSetupColumn("MTPC (ms)");
ImGui::TableSetupColumn("Max (ms)");
ImGui::TableSetupColumn("Invocations");
ImGui::TableSetupColumn("Total (ms)");
ImGui::TableHeadersRow();
ImGui::TableNextColumn();
ImGuiTableSortSpecs* sortSpecs = ImGui::TableGetSortSpecs();
if (sortSpecs && sortSpecs->SpecsDirty)
{
SortTable(sortSpecs);
}
// Draw all of the rows held in the GroupRegionMap
for (const auto* statistics : m_tableData)
{
if (!m_timedRegionFilter.PassFilter(statistics->m_groupName.c_str())
&& !m_timedRegionFilter.PassFilter(statistics->m_regionName.c_str()))
{
continue;
}
ImGui::Text(statistics->m_groupName.c_str());
const ImVec2 topLeftBound = ImGui::GetItemRectMin();
ImGui::TableNextColumn();
ImGui::Text(statistics->m_regionName.c_str());
ImGui::TableNextColumn();
ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_runningAverageTicks));
ImGui::TableNextColumn();
ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_maxTicks));
ImGui::TableNextColumn();
ImGui::Text("%llu", statistics->m_invocationsLastFrame);
ImGui::TableNextColumn();
ImGui::Text("%.2f", CpuProfilerImGuiHelper::TicksToMs(statistics->m_lastFrameTotalTicks));
const ImVec2 botRightBound = ImGui::GetItemRectMax();
ImGui::TableNextColumn();
// NOTE: we are manually checking the bounds rather than using ImGui::IsItemHovered + Begin/EndGroup because
// ImGui reports incorrect bounds when using Begin/End group in the Tables API.
if (ImGui::IsWindowHovered() && ImGui::IsMouseHoveringRect(topLeftBound, botRightBound, false))
{
ImGui::BeginTooltip();
ImGui::Text(statistics->GetExecutingThreadsLabel().c_str());
ImGui::EndTooltip();
}
}
}
ImGui::EndTable();
}
inline void ImGuiCpuProfiler::SortTable(ImGuiTableSortSpecs* sortSpecs)
{
const bool ascending = sortSpecs->Specs->SortDirection == ImGuiSortDirection_Ascending;
const ImS16 columnToSort = sortSpecs->Specs->ColumnIndex;
switch (columnToSort)
{
case (0): // Sort by group name
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_groupName, ascending));
break;
case (1): // Sort by region name
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_regionName, ascending));
break;
case (2): // Sort by average time
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_runningAverageTicks, ascending));
break;
case (3): // Sort by max time
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_maxTicks, ascending));
break;
case (4): // Sort by invocations
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_invocationsLastFrame, ascending));
break;
case (5): // Sort by total time
AZStd::sort(m_tableData.begin(), m_tableData.end(), TableRow::TableRowCompareFunctor(&TableRow::m_lastFrameTotalTicks, ascending));
break;
}
sortSpecs->SpecsDirty = false;
}
inline void ImGuiCpuProfiler::DrawStatisticsView()
{
DrawCommonHeader();
@@ -156,62 +251,6 @@ namespace AZ
ImGui::NextColumn();
};
const auto DrawRegionHoverMarker = [this, &ShowTimeInMs](AZStd::vector<ThreadRegionEntry>& entries)
{
if (ImGui::IsItemHovered())
{
ImGui::BeginTooltip();
ImGui::PushTextWrapPos(ImGui::GetFontSize() * 60.0f);
for (ThreadRegionEntry& entry : entries)
{
ImGui::Text(CpuProfilerImGuiHelper::TextThreadId(entry.m_threadId.m_id).c_str());
const AZStd::sys_time_t elapsed = entry.m_endTick - entry.m_startTick;
ShowTimeInMs(elapsed);
ImGui::Separator();
}
ImGui::PopTextWrapPos();
ImGui::EndTooltip();
}
};
const auto ShowRegionRow =
[ticksPerSecond, &DrawRegionHoverMarker,
&ShowTimeInMs](const char* regionLabel, AZStd::vector<ThreadRegionEntry> regions, AZStd::sys_time_t duration)
{
// Draw the region label
ImGui::Text(regionLabel);
ImGui::NextColumn();
// Draw the thread count label
AZStd::sys_time_t totalTime = 0;
AZStd::set<AZStd::thread_id> threads;
for (ThreadRegionEntry& entry : regions) // Find the thread count and total execution time for all threads
{
threads.insert(entry.m_threadId);
totalTime += entry.m_endTick - entry.m_startTick;
}
const AZStd::string threadLabel = AZStd::string::format("Threads: %u", static_cast<uint32_t>(threads.size()));
ImGui::Text(threadLabel.c_str());
DrawRegionHoverMarker(regions);
ImGui::NextColumn();
// Draw the overall invocation count
const AZStd::string invocationLabel = AZStd::string::format("Total calls: %u", static_cast<uint32_t>(regions.size()));
ImGui::Text(invocationLabel.c_str());
DrawRegionHoverMarker(regions);
ImGui::NextColumn();
// Draw the time labels (max and then total)
const AZStd::string timeLabel = AZStd::string::format(
"%.2f ms max, %.2f ms total", CpuProfilerImGuiHelper::TicksToMs(duration),
CpuProfilerImGuiHelper::TicksToMs(totalTime));
ImGui::Text(timeLabel.c_str());
ImGui::NextColumn();
};
if (ImGui::BeginChild("Statistics View", { 0, 0 }, true))
{
// Set column settings.
@@ -229,44 +268,20 @@ namespace AZ
ImGui::Separator();
ImGui::Columns(1, "view", false);
m_timedRegionFilter.Draw("TimedRegion Filter");
// Draw the timed regions
if (ImGui::BeginChild("TimedRegions"))
m_timedRegionFilter.Draw("Filter");
ImGui::SameLine();
if (ImGui::Button("Clear Filter"))
{
for (auto& timeRegionMapEntry : m_groupRegionMap)
{
// Draw the regions
if (ImGui::TreeNodeEx(timeRegionMapEntry.first.c_str(), ImGuiTreeNodeFlags_DefaultOpen))
{
ImGui::Columns(4, "view", false);
ImGui::SetColumnWidth(0, 400.0f);
ImGui::SetColumnWidth(1, 100.0f);
ImGui::SetColumnWidth(2, 150.0f);
ImGui::SetColumnWidth(3, 240.0f);
for (auto& region : timeRegionMapEntry.second)
{
// Calculate the region with the longest execution time
AZStd::sys_time_t threadExecutionElapsed = 0;
for (ThreadRegionEntry& entry : region.second)
{
const AZStd::sys_time_t elapsed = entry.m_endTick - entry.m_startTick;
threadExecutionElapsed = AZStd::max(threadExecutionElapsed, elapsed);
}
// Only draw the TimedRegion rows when it passes the filter
if (m_timedRegionFilter.PassFilter(region.first.c_str()))
{
ShowRegionRow(region.first.c_str(), region.second, threadExecutionElapsed);
}
}
ImGui::Columns(1, "view", false);
ImGui::TreePop();
}
}
ImGui::EndChild();
m_timedRegionFilter.Clear();
}
ImGui::SameLine();
if (ImGui::Button("Reset Table"))
{
m_tableData.clear();
m_groupRegionMap.clear();
}
DrawTable();
}
}
@@ -279,8 +294,8 @@ namespace AZ
if (ImGui::BeginChild("Options and Statistics", { 0, 0 }, true))
{
ImGui::Columns(3, "Options", true);
ImGui::Text("Frames To Collect:");
ImGui::SliderInt("", &m_framesToCollect, 10, 100, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic);
ImGui::SliderInt("Saved Frames", &m_framesToCollect, 10, 10000, "%d", ImGuiSliderFlags_AlwaysClamp | ImGuiSliderFlags_Logarithmic);
m_visualizerHighlightFilter.Draw("Find Region");
ImGui::NextColumn();
@@ -295,6 +310,13 @@ namespace AZ
"Hold the right mouse button to move around. Zoom by scrolling the mouse wheel while holding <ctrl>.");
}
ImGui::Columns(1, "FrameTimeColumn", true);
if (ImGui::BeginChild("FrameTimeHistogram", { 0, 50 }, true, ImGuiWindowFlags_NoScrollbar))
{
DrawFrameTimeHistogram();
}
ImGui::EndChild();
ImGui::Columns(1, "RulerColumn", true);
@@ -365,7 +387,6 @@ namespace AZ
baseRow += maxDepth + 1; // Next draw loop should start one row down
}
DrawRegionStatistics();
DrawFrameBoundaries();
// Draw an invisible button to capture inputs
@@ -425,14 +446,11 @@ namespace AZ
// view is only holding data from the last frame, the memory overhead is minimal and gives us a faster redraw
// compared to if we needed to transform the visualizer's data into the statistical format every frame.
// Clear the statistical view's cached entries
m_groupRegionMap.clear();
// Get the latest TimeRegionMap
const RHI::CpuProfiler::TimeRegionMap& timeRegionMap = RHI::CpuProfiler::Get()->GetTimeRegionMap();
m_viewportStartTick = INT64_MAX;
m_viewportEndTick = INT64_MIN;
m_viewportStartTick = AZStd::numeric_limits<s64>::max();
m_viewportEndTick = AZStd::numeric_limits<s64>::lowest();
// Iterate through the entire TimeRegionMap and copy the data since it will get deleted on the next frame
for (const auto& [threadId, singleThreadRegionMap] : timeRegionMap)
@@ -454,14 +472,15 @@ namespace AZ
// Also update the statistical view's data
const AZStd::string& groupName = region.m_groupRegionName->m_groupName;
m_groupRegionMap[groupName][regionName].push_back(
{ threadId, region.m_startTick, region.m_endTick });
// Update running statistics if we want to record this region's data
if (m_regionStatisticsMap[region.m_groupRegionName].m_record)
if (!m_groupRegionMap[groupName].contains(regionName))
{
m_regionStatisticsMap[region.m_groupRegionName].RecordRegion(region);
m_groupRegionMap[groupName][regionName].m_groupName = groupName;
m_groupRegionMap[groupName][regionName].m_regionName = regionName;
m_tableData.push_back(&m_groupRegionMap[groupName][regionName]);
}
m_groupRegionMap[groupName][regionName].RecordRegion(region, threadId);
}
}
@@ -515,12 +534,18 @@ namespace AZ
inline void ImGuiCpuProfiler::DrawBlock(const TimeRegion& block, u64 targetRow)
{
// Don't draw anything if the user is searching for regions and this block doesn't pass the filter
if (!m_visualizerHighlightFilter.PassFilter(block.m_groupRegionName->m_regionName))
{
return;
}
float wy = ImGui::GetWindowPos().y - ImGui::GetScrollY();
ImDrawList* drawList = ImGui::GetWindowDrawList();
const float startPixel = ConvertTickToPixelSpace(block.m_startTick);
const float endPixel = ConvertTickToPixelSpace(block.m_endTick);
const float startPixel = ConvertTickToPixelSpace(block.m_startTick, m_viewportStartTick, m_viewportEndTick);
const float endPixel = ConvertTickToPixelSpace(block.m_endTick, m_viewportStartTick, m_viewportEndTick);
const ImVec2 startPoint = { startPixel, wy + targetRow * RowHeight };
const ImVec2 endPoint = { endPixel, wy + targetRow * RowHeight + 40 };
@@ -560,13 +585,14 @@ namespace AZ
// Tooltip and block highlighting
if (ImGui::IsMouseHoveringRect(startPoint, endPoint) && ImGui::IsWindowHovered())
{
// Open function statistics map on click
// Go to the statistics view when a region is clicked
if (ImGui::IsMouseClicked(ImGuiMouseButton_Left))
{
const GroupRegionName* key = block.m_groupRegionName;
m_regionStatisticsMap[key].m_draw = true;
m_enableVisualizer = false;
const auto newFilter = AZStd::string(block.m_groupRegionName->m_regionName);
m_timedRegionFilter = ImGuiTextFilter(newFilter.c_str());
m_timedRegionFilter.Build();
}
// Hovering outline
drawList->AddRect(startPoint, endPoint, ImGui::GetColorU32({ 1, 1, 1, 1 }), 0.0, 0, 1.5);
@@ -618,31 +644,6 @@ namespace AZ
ImGui::GetWindowDrawList()->AddText({ wx + 10, wy + baseRow * RowHeight + 5 }, IM_COL32_WHITE, threadIdText.c_str());
}
inline void ImGuiCpuProfiler::DrawRegionStatistics()
{
for (auto& [groupRegionName, stat] : m_regionStatisticsMap)
{
if (stat.m_draw)
{
ImGui::SetNextWindowSize({300, 340}, ImGuiCond_FirstUseEver);
ImGui::Begin(groupRegionName->m_regionName, &stat.m_draw, 0);
if (ImGui::Button(stat.m_record ? "Pause" : "Resume"))
{
stat.m_record = !stat.m_record;
}
ImGui::Text("Invocations: %llu", stat.m_invocations);
ImGui::Text("Average time: %.3f ms", stat.CalcAverageTimeMs());
ImGui::Separator();
ImGui::ColorPicker4("Region color", &m_regionColorMap[groupRegionName].x);
ImGui::End();
}
}
}
inline void ImGuiCpuProfiler::DrawFrameBoundaries()
{
ImDrawList* drawList = ImGui::GetWindowDrawList();
@@ -656,7 +657,7 @@ namespace AZ
while (endTickItr != m_frameEndTicks.end() && *endTickItr < m_viewportEndTick)
{
const float horizontalPixel = ConvertTickToPixelSpace(*endTickItr);
const float horizontalPixel = ConvertTickToPixelSpace(*endTickItr, m_viewportStartTick, m_viewportEndTick);
drawList->AddLine({ horizontalPixel, wy }, { horizontalPixel, wy + windowHeight }, red);
++endTickItr;
}
@@ -684,8 +685,8 @@ namespace AZ
break;
}
const float lastFrameBoundaryPixel = ConvertTickToPixelSpace(lastFrameBoundaryTick);
const float nextFrameBoundaryPixel = ConvertTickToPixelSpace(nextFrameBoundaryTick);
const float lastFrameBoundaryPixel = ConvertTickToPixelSpace(lastFrameBoundaryTick, m_viewportStartTick, m_viewportEndTick);
const float nextFrameBoundaryPixel = ConvertTickToPixelSpace(nextFrameBoundaryTick, m_viewportStartTick, m_viewportEndTick);
const AZStd::string label =
AZStd::string::format("%.2f ms", CpuProfilerImGuiHelper::TicksToMs(nextFrameBoundaryTick - lastFrameBoundaryTick));
@@ -738,16 +739,98 @@ namespace AZ
}
}
inline void ImGuiCpuProfiler::DrawFrameTimeHistogram()
{
ImDrawList* drawList = ImGui::GetWindowDrawList();
const auto [wx, wy] = ImGui::GetWindowPos();
const ImU32 orange = ImGui::GetColorU32({ 1, .7, 0, 1 });
const ImU32 red = ImGui::GetColorU32({ 1, 0, 0, 1 });
const AZStd::sys_time_t ticksPerSecond = AZStd::GetTimeTicksPerSecond();
const AZStd::sys_time_t viewportCenter = m_viewportEndTick - (m_viewportEndTick - m_viewportStartTick) / 2;
const AZStd::sys_time_t leftHistogramBound = viewportCenter - ticksPerSecond;
const AZStd::sys_time_t rightHistogramBound = viewportCenter + ticksPerSecond;
// Draw frame limit lines
drawList->AddLine(
{ wx, wy + ImGui::GetWindowHeight() - MediumFrameTimeLimit },
{ wx + ImGui::GetWindowWidth(), wy + ImGui::GetWindowHeight() - MediumFrameTimeLimit },
orange);
drawList->AddLine(
{ wx, wy + ImGui::GetWindowHeight() - HighFrameTimeLimit },
{ wx + ImGui::GetWindowWidth(), wy + ImGui::GetWindowHeight() - HighFrameTimeLimit },
red);
// Draw viewport bound rectangle
const float leftViewportPixel = ConvertTickToPixelSpace(m_viewportStartTick, leftHistogramBound, rightHistogramBound);
const float rightViewportPixel = ConvertTickToPixelSpace(m_viewportEndTick, leftHistogramBound, rightHistogramBound);
const ImVec2 topLeftPos = { leftViewportPixel, wy };
const ImVec2 botRightPos = { rightViewportPixel, wy + ImGui::GetWindowHeight() };
const ImU32 gray = ImGui::GetColorU32({ 1, 1, 1, .3 });
drawList->AddRectFilled(topLeftPos, botRightPos, gray);
// Find the first onscreen frame execution time
auto frameEndTickItr = AZStd::lower_bound(m_frameEndTicks.begin(), m_frameEndTicks.end(), leftHistogramBound);
if (frameEndTickItr != m_frameEndTicks.begin())
{
--frameEndTickItr;
}
// Since we only store the frame end ticks, we must calculate the execution times on the fly by comparing pairs of elements.
AZStd::sys_time_t lastFrameEndTick = *frameEndTickItr;
while (*frameEndTickItr < rightHistogramBound && ++frameEndTickItr != m_frameEndTicks.end())
{
const AZStd::sys_time_t frameEndTick = *frameEndTickItr;
const float framePixelPos = ConvertTickToPixelSpace(frameEndTick, leftHistogramBound, rightHistogramBound);
const float frameTimeMs = CpuProfilerImGuiHelper::TicksToMs(frameEndTick - lastFrameEndTick);
const ImVec2 lineBottom = { framePixelPos, ImGui::GetWindowHeight() + wy };
const ImVec2 lineTop = { framePixelPos, ImGui::GetWindowHeight() + wy - frameTimeMs };
ImU32 lineColor = ImGui::GetColorU32({ .3, .3, .3, 1 }); // Gray
if (frameTimeMs > HighFrameTimeLimit)
{
lineColor = ImGui::GetColorU32({1, 0, 0, 1}); // Red
}
else if (frameTimeMs > MediumFrameTimeLimit)
{
lineColor = ImGui::GetColorU32({1, .7, 0, 1}); // Orange
}
drawList->AddLine(lineBottom, lineTop, lineColor, 3.0);
lastFrameEndTick = frameEndTick;
}
// Handle input
ImGui::InvisibleButton("HistogramInputCapture", { ImGui::GetWindowWidth(), ImGui::GetWindowHeight() });
ImGuiIO& io = ImGui::GetIO();
if (ImGui::IsItemClicked(ImGuiMouseButton_Left))
{
const float mousePixelX = io.MousePos.x;
const float percentWindow = (mousePixelX - wx) / ImGui::GetWindowWidth();
const AZStd::sys_time_t newViewportCenterTick = leftHistogramBound +
aznumeric_cast<AZStd::sys_time_t>((rightHistogramBound - leftHistogramBound) * percentWindow);
const AZStd::sys_time_t viewportWidth = GetViewportTickWidth();
m_viewportEndTick = newViewportCenterTick + viewportWidth / 2;
m_viewportStartTick = newViewportCenterTick - viewportWidth / 2;
}
}
inline AZStd::sys_time_t ImGuiCpuProfiler::GetViewportTickWidth() const
{
return m_viewportEndTick - m_viewportStartTick;
}
inline float ImGuiCpuProfiler::ConvertTickToPixelSpace(AZStd::sys_time_t tick) const
inline float ImGuiCpuProfiler::ConvertTickToPixelSpace(AZStd::sys_time_t tick, AZStd::sys_time_t leftBound, AZStd::sys_time_t rightBound) const
{
const float wx = ImGui::GetWindowPos().x;
const float tickSpaceShifted = aznumeric_cast<float>(tick - m_viewportStartTick); // This will be close to zero, so FP inaccuracy should not be too bad
const float tickSpaceNormalized = tickSpaceShifted / GetViewportTickWidth();
const float tickSpaceShifted = aznumeric_cast<float>(tick - leftBound); // This will be close to zero, so FP inaccuracy should not be too bad
const float tickSpaceNormalized = tickSpaceShifted / (rightBound - leftBound);
const float pixelSpace = tickSpaceNormalized * ImGui::GetWindowWidth() + wx;
return pixelSpace;
}
@@ -762,25 +845,51 @@ namespace AZ
else
{
m_frameEndTicks.push_back(AZStd::GetTimeNowTicks());
for (auto& [groupName, regionMap] : m_groupRegionMap)
{
for (auto& [regionName, row] : regionMap)
{
row.ResetPerFrameStatistics();
}
}
}
}
// ----- RegionStatistics implementation -----
inline float RegionStatistics::CalcAverageTimeMs() const
// ---- TableRow impl ----
inline void TableRow::RecordRegion(const AZ::RHI::CachedTimeRegion& region, AZStd::thread_id threadId)
{
if (m_invocations == 0)
const AZStd::sys_time_t deltaTime = region.m_endTick - region.m_startTick;
// Update per frame statistics
++m_invocationsLastFrame;
m_executingThreads.insert(threadId);
m_lastFrameTotalTicks += deltaTime;
m_maxTicks = AZStd::max(m_maxTicks, deltaTime);
// Update aggregate statistics
m_runningAverageTicks =
aznumeric_cast<AZStd::sys_time_t>((1.0 * (deltaTime + m_invocationsTotal * m_runningAverageTicks)) / (m_invocationsTotal + 1));
++m_invocationsTotal;
}
inline void TableRow::ResetPerFrameStatistics()
{
m_invocationsLastFrame = 0;
m_executingThreads.clear();
m_lastFrameTotalTicks = 0;
m_maxTicks = 0;
}
inline AZStd::string TableRow::GetExecutingThreadsLabel() const
{
auto threadString = AZStd::string::format("Executed in %zu threads\n", m_executingThreads.size());
for (const auto& threadId : m_executingThreads)
{
return 0.0;
threadString.append(CpuProfilerImGuiHelper::TextThreadId(threadId.m_id) + "\n");
}
const double averageTicks = aznumeric_cast<double>(m_totalTicks) / m_invocations;
return CpuProfilerImGuiHelper::TicksToMs(aznumeric_cast<AZStd::sys_time_t>(averageTicks));
}
inline void RegionStatistics::RecordRegion(const AZ::RHI::CachedTimeRegion& region)
{
m_invocations++;
m_totalTicks += region.m_endTick - region.m_startTick;
return threadString;
}
} // namespace Render
} // namespace AZ
@@ -21,6 +21,8 @@ namespace AZ
public:
//! Get all material assignments that can be overridden
virtual MaterialAssignmentMap GetOriginalMaterialAssignments() const = 0;
//! Get material assignment id matching lod and label substring
virtual MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0;
//! Set material overrides
virtual void SetMaterialOverrides(const MaterialAssignmentMap& materials) = 0;
//! Get material overrides
@@ -69,6 +71,9 @@ namespace AZ
: public ComponentBus
{
public:
//! Get material assignment id matching lod and label substring
virtual MaterialAssignmentId FindMaterialAssignmentId(
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const = 0;
virtual MaterialAssignmentMap GetMaterialAssignments() const = 0;
virtual AZStd::unordered_set<AZ::Name> GetModelUvNames() const = 0;
};
@@ -33,6 +33,7 @@ namespace AZ
->Attribute(AZ::Script::Attributes::Category, "render")
->Attribute(AZ::Script::Attributes::Module, "render")
->Event("GetOriginalMaterialAssignments", &MaterialComponentRequestBus::Events::GetOriginalMaterialAssignments)
->Event("FindMaterialAssignmentId", &MaterialComponentRequestBus::Events::FindMaterialAssignmentId)
->Event("SetMaterialOverrides", &MaterialComponentRequestBus::Events::SetMaterialOverrides)
->Event("GetMaterialOverrides", &MaterialComponentRequestBus::Events::GetMaterialOverrides)
->Event("ClearAllMaterialOverrides", &MaterialComponentRequestBus::Events::ClearAllMaterialOverrides)
@@ -249,10 +250,20 @@ namespace AZ
MaterialAssignmentMap MaterialComponentController::GetOriginalMaterialAssignments() const
{
MaterialAssignmentMap materialAssignmentMap;
MaterialReceiverRequestBus::EventResult(materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
MaterialReceiverRequestBus::EventResult(
materialAssignmentMap, m_entityId, &MaterialReceiverRequestBus::Events::GetMaterialAssignments);
return materialAssignmentMap;
}
MaterialAssignmentId MaterialComponentController::FindMaterialAssignmentId(
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
{
MaterialAssignmentId materialAssignmentId;
MaterialReceiverRequestBus::EventResult(
materialAssignmentId, m_entityId, &MaterialReceiverRequestBus::Events::FindMaterialAssignmentId, lod, label);
return materialAssignmentId;
}
void MaterialComponentController::SetMaterialOverrides(const MaterialAssignmentMap& materials)
{
// this function is called twice once material asset is changed, a temp variable is
@@ -46,6 +46,7 @@ namespace AZ
//! MaterialComponentRequestBus overrides...
MaterialAssignmentMap GetOriginalMaterialAssignments() const override;
MaterialAssignmentId FindMaterialAssignmentId(const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
void SetMaterialOverrides(const MaterialAssignmentMap& materials) override;
const MaterialAssignmentMap& GetMaterialOverrides() const override;
void ClearAllMaterialOverrides() override;
@@ -252,6 +252,12 @@ namespace AZ
}
}
MaterialAssignmentId MeshComponentController::FindMaterialAssignmentId(
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
{
return FindMaterialAssignmentIdInModel(GetModel(), lod, label);
}
MaterialAssignmentMap MeshComponentController::GetMaterialAssignments() const
{
return GetMaterialAssignmentsFromModel(GetModel());
@@ -111,6 +111,8 @@ namespace AZ
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// MaterialReceiverRequestBus::Handler overrides ...
virtual MaterialAssignmentId FindMaterialAssignmentId(
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
MaterialAssignmentMap GetMaterialAssignments() const override;
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
@@ -308,6 +308,17 @@ namespace AZ
m_skinnedMeshFeatureProcessor = nullptr;
}
MaterialAssignmentId AtomActorInstance::FindMaterialAssignmentId(
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const
{
if (m_skinnedMeshInstance && m_skinnedMeshInstance->m_model)
{
return FindMaterialAssignmentIdInModel(m_skinnedMeshInstance->m_model, lod, label);
}
return MaterialAssignmentId();
}
MaterialAssignmentMap AtomActorInstance::GetMaterialAssignments() const
{
if (m_skinnedMeshInstance && m_skinnedMeshInstance->m_model)
@@ -120,6 +120,8 @@ namespace AZ
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// MaterialReceiverRequestBus::Handler overrides...
virtual MaterialAssignmentId FindMaterialAssignmentId(
const MaterialAssignmentLodIndex lod, const AZStd::string& label) const override;
MaterialAssignmentMap GetMaterialAssignments() const override;
AZStd::unordered_set<AZ::Name> GetModelUvNames() const override;
@@ -14,7 +14,7 @@
#include <AudioControlsLoader.h>
#include <AudioControlsWriter.h>
#include <Include/IResourceSelectorHost.h>
#include <AudioResourceSelectors.h>
#include <IAudioSystem.h>
#include <IAudioSystemEditor.h>
@@ -39,7 +39,7 @@ CAudioControlsEditorPlugin::CAudioControlsEditorPlugin(IEditor* editor)
QtViewOptions options;
options.canHaveMultipleInstances = true;
RegisterQtViewPane<CAudioControlsEditorWindow>(editor, LyViewPane::AudioControlsEditor, LyViewPane::CategoryOther, options);
RegisterModuleResourceSelectors(GetIEditor()->GetResourceSelectorHost());
RegisterAudioControlsResourceSelectors();
Audio::AudioSystemRequestBus::BroadcastResult(ms_pIAudioProxy, &Audio::AudioSystemRequestBus::Events::GetFreeAudioProxy);
@@ -7,14 +7,13 @@
*/
#include <AudioResourceSelectors.h>
#include <ATLControlsResourceDialog.h>
#include <AudioControlsEditorPlugin.h>
#include <Include/IResourceSelectorHost.h>
#include <QAudioControlEditorIcons.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
using namespace AudioControls;
namespace AudioControls
{
//-------------------------------------------------------------------------------------------//
@@ -67,10 +66,32 @@ namespace AudioControls
}
//-------------------------------------------------------------------------------------------//
REGISTER_RESOURCE_SELECTOR("AudioTrigger", AudioTriggerSelector, ":/AudioControlsEditor/Icons/Trigger_Icon.png");
REGISTER_RESOURCE_SELECTOR("AudioSwitch", AudioSwitchSelector, ":/AudioControlsEditor/Icons/Switch_Icon.png");
REGISTER_RESOURCE_SELECTOR("AudioSwitchState", AudioSwitchStateSelector, ":/AudioControlsEditor/Icons/State_Icon.png");
REGISTER_RESOURCE_SELECTOR("AudioRTPC", AudioRTPCSelector, ":/AudioControlsEditor/Icons/RTPC_Icon.png");
REGISTER_RESOURCE_SELECTOR("AudioEnvironment", AudioEnvironmentSelector, ":/AudioControlsEditor/Icons/Environment_Icon.png");
REGISTER_RESOURCE_SELECTOR("AudioPreloadRequest", AudioPreloadRequestSelector, ":/AudioControlsEditor/Icons/Bank_Icon.png");
static SStaticResourceSelectorEntry audioTriggerSelector(
"AudioTrigger", AudioTriggerSelector, ":/Icons/Trigger_Icon.svg");
static SStaticResourceSelectorEntry audioSwitchSelector(
"AudioSwitch", AudioSwitchSelector, ":/Icons/Switch_Icon.svg");
static SStaticResourceSelectorEntry audioStateSelector(
"AudioSwitchState", AudioSwitchStateSelector, ":/Icons/Property_Icon.png");
static SStaticResourceSelectorEntry audioRtpcSelector(
"AudioRTPC", AudioRTPCSelector, ":/Icons/RTPC_Icon.svg");
static SStaticResourceSelectorEntry audioEnvironmentSelector(
"AudioEnvironment", AudioEnvironmentSelector, ":/Icons/Environment_Icon.svg");
static SStaticResourceSelectorEntry audioPreloadSelector(
"AudioPreloadRequest", AudioPreloadRequestSelector, ":/Icons/Bank_Icon.png");
//-------------------------------------------------------------------------------------------//
void RegisterAudioControlsResourceSelectors()
{
if (IResourceSelectorHost* host = GetIEditor()->GetResourceSelectorHost();
host != nullptr)
{
host->RegisterResourceSelector(&audioTriggerSelector);
host->RegisterResourceSelector(&audioSwitchSelector);
host->RegisterResourceSelector(&audioStateSelector);
host->RegisterResourceSelector(&audioRtpcSelector);
host->RegisterResourceSelector(&audioEnvironmentSelector);
host->RegisterResourceSelector(&audioPreloadSelector);
}
}
} // namespace AudioControls
@@ -0,0 +1,14 @@
/*
* 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
namespace AudioControls
{
void RegisterAudioControlsResourceSelectors();
}
@@ -54,6 +54,7 @@ set(FILES
Source/Editor/AudioControlsEditorWindow.h
Source/Editor/AudioControlsLoader.h
Source/Editor/AudioControlsWriter.h
Source/Editor/AudioResourceSelectors.h
Source/Editor/AudioSystemPanel.h
Source/Editor/ImplementationManager.h
Source/Editor/InspectorPanel.h
@@ -7,6 +7,7 @@
*/
#include "OrthographicCamera.h"
#include <MCore/Source/AABB.h>
#include <MCore/Source/Compare.h>
#include <MCore/Source/Distance.h>
#include <EMotionFX/Source/EMotionFXManager.h>
@@ -8,7 +8,7 @@
#include "RotateManipulator.h"
#include <MCore/Source/AzCoreConversions.h>
#include <MCore/Source/PlaneEq.h>
namespace MCommon
{
@@ -7,7 +7,7 @@
*/
#include "ScaleManipulator.h"
#include <MCore/Source/PlaneEq.h>
namespace MCommon
{
@@ -7,7 +7,7 @@
*/
#include "TranslateManipulator.h"
#include <MCore/Source/PlaneEq.h>
namespace MCommon
{
@@ -7,6 +7,7 @@
*/
#include <MCore/Source/Config.h>
#include <MCore/Source/LogManager.h>
#include "GLSLShader.h"
#include "GraphicsManager.h"
#include <QFile>
@@ -41,6 +41,7 @@
#include <MCore/Source/IDGenerator.h>
#include <MCore/Source/Compare.h>
#include <MCore/Source/LogManager.h>
#include <MCore/Source/OBB.h>
#include <Atom/RPI.Reflect/Model/MorphTargetDelta.h>
@@ -395,7 +395,7 @@ namespace EMotionFX
}
// If new parameter matches the last deleted parameter, we add it back to the parameter mask.
if (!m_deletedParameterNames.empty() && newParameterName == m_deletedParameterNames.back())
if (!m_deletedParameterNames.empty() && newParameterName == m_deletedParameterNames.top())
{
m_parameterNames.push_back(newParameterName);
SortAndRemoveDuplicates(GetAnimGraph(), m_parameterNames); // make sure the mask is sorted correctly.
@@ -24,6 +24,7 @@
#include <MCore/Source/AzCoreConversions.h>
#include <MCore/Source/Distance.h>
#include <MCore/Source/File.h>
#include <MCore/Source/LogManager.h>
#include <MCore/Source/ReflectionSerializer.h>
#include <MCore/Source/StringConversions.h>
#include <MCore/Source/MCoreSystem.h>
@@ -10,6 +10,7 @@
#define __EMSTUDIO_LOGWINDOWPLUGIN_H
#if !defined(Q_MOC_RUN)
#include <MCore/Source/LogManager.h>
#include "../StandardPluginsConfig.h"
#include "../../../../EMStudioSDK/Source/DockWidgetPlugin.h"
#endif
@@ -17,7 +17,7 @@
#include <AzCore/Math/PackedVector3.h>
#include <MCore/Source/Algorithms.h>
#include <MCore/Source/Color.h>
#include <MCore/Source/Quaternion.h>
#include <MCore/Source/Vector.h>
#include <EMotionFX/Source/Transform.h>
// This file is "glue" code to convert math back-forward between MCore and AZ. It also has functions that MCore used to
@@ -37,18 +37,6 @@ namespace MCore
return RGBAColor(static_cast<float>(azColor.GetR()), static_cast<float>(azColor.GetG()), static_cast<float>(azColor.GetB()), static_cast<float>(azColor.GetA()));
}
// Deprecated
AZ_FORCE_INLINE AZ::Quaternion EmfxQuatToAzQuat(const MCore::Quaternion& emfxQuat)
{
return AZ::Quaternion(emfxQuat.x, emfxQuat.y, emfxQuat.z, emfxQuat.w);
}
// Deprecated
AZ_FORCE_INLINE MCore::Quaternion AzQuatToEmfxQuat(const AZ::Quaternion& azQuat)
{
return MCore::Quaternion(azQuat.GetX(), azQuat.GetY(), azQuat.GetZ(), azQuat.GetW());
}
AZ_FORCE_INLINE AZ::Transform EmfxTransformToAzTransform(const EMotionFX::Transform& emfxTransform)
{
AZ::Transform transform = AZ::Transform::CreateFromQuaternionAndTranslation(emfxTransform.mRotation, emfxTransform.mPosition);
@@ -530,91 +518,4 @@ namespace MCore
AZ::Vector3ToVector4(m33.GetRow(2), translation.GetZ()),
mat.GetRow(3));
}
// Deprecated. Please use AZ::Transform instead of MCore::Matrix.
MCORE_INLINE AZ::Quaternion MCoreMatrixToQuaternion(const MCore::Matrix& m)
{
const float trace = MMAT(m, 0, 0) + MMAT(m, 1, 1) + MMAT(m, 2, 2);
if (trace > 0.0f /*Math::epsilon*/)
{
const float s = 0.5f / Math::Sqrt(trace + 1.0f);
return AZ::Quaternion((MMAT(m, 1, 2) - MMAT(m, 2, 1)) * s,
(MMAT(m, 2, 0) - MMAT(m, 0, 2)) * s,
(MMAT(m, 0, 1) - MMAT(m, 1, 0)) * s,
0.25f / s);
}
else
{
if (MMAT(m, 0, 0) > MMAT(m, 1, 1) && MMAT(m, 0, 0) > MMAT(m, 2, 2))
{
const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 0, 0) - MMAT(m, 1, 1) - MMAT(m, 2, 2));
const float oneOverS = 1.0f / s;
return AZ::Quaternion(0.25f * s,
(MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS,
(MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS,
(MMAT(m, 1, 2) - MMAT(m, 2, 1)) * oneOverS);
}
else if (MMAT(m, 1, 1) > MMAT(m, 2, 2))
{
const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 1, 1) - MMAT(m, 0, 0) - MMAT(m, 2, 2));
const float oneOverS = 1.0f / s;
return AZ::Quaternion((MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS,
0.25f * s,
(MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS,
(MMAT(m, 2, 0) - MMAT(m, 0, 2)) * oneOverS);
}
else
{
const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 2, 2) - MMAT(m, 0, 0) - MMAT(m, 1, 1));
const float oneOverS = 1.0f / s;
return AZ::Quaternion((MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS,
(MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS,
0.25f * s,
(MMAT(m, 0, 1) - MMAT(m, 1, 0)) * oneOverS);
}
}
/*
const float trace = MMAT(m,0,0) + MMAT(m,1,1) + MMAT(m,2,2) + 1.0f;
if (trace > Math::epsilon)
{
const float s = 0.5f / Math::Sqrt(trace);
result.w = 0.25f / s;
result.x = ( MMAT(m,1,2) - MMAT(m,2,1) ) * s;
result.y = ( MMAT(m,2,0) - MMAT(m,0,2) ) * s;
result.z = ( MMAT(m,0,1) - MMAT(m,1,0) ) * s;
}
else
{
if (MMAT(m,0,0) > MMAT(m,1,1) && MMAT(m,0,0) > MMAT(m,2,2))
{
const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,0,0) - MMAT(m,1,1) - MMAT(m,2,2));
const float oneOverS = 1.0f / s;
result.x = 0.25f * s;
result.y = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS;
result.z = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS;
result.w = (MMAT(m,2,1) - MMAT(m,1,2) ) * oneOverS;
}
else
if (MMAT(m,1,1) > MMAT(m,2,2))
{
const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,1,1) - MMAT(m,0,0) - MMAT(m,2,2));
const float oneOverS = 1.0f / s;
result.x = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS;
result.y = 0.25f * s;
result.z = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS;
result.w = (MMAT(m,2,0) - MMAT(m,0,2) ) * oneOverS;
}
else
{
const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,2,2) - MMAT(m,0,0) - MMAT(m,1,1) );
const float oneOverS = 1.0f / s;
result.x = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS;
result.y = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS;
result.z = 0.25f * s;
result.w = (MMAT(m,1,0) - MMAT(m,0,1) ) * oneOverS;
}
}
*/
}
} // namespace MCore
@@ -2248,27 +2248,6 @@ namespace MCore
}
// simple decompose a matrix into translation and rotation
void Matrix::Decompose(AZ::Vector3* outTranslation, AZ::Quaternion* outRotation) const
{
// make a copy of the matrix
Matrix mat(*this);
// normalize the basis vectors
mat.SetRight(SafeNormalize(mat.GetRight()));
mat.SetUp(SafeNormalize(mat.GetUp()));
mat.SetForward(SafeNormalize(mat.GetForward()));
// extract the translation from the matrix
*outTranslation = mat.GetTranslation();
// convert the normalized 3x3 rotation part into a AZ::Quaternion
*outRotation = MCore::MCoreMatrixToQuaternion(*this);
}
// calculate a rotation matrix from two vectors
void Matrix::SetRotationMatrixTwoVectors(const AZ::Vector3& from, const AZ::Vector3& to)
{
@@ -2365,30 +2344,6 @@ namespace MCore
}
//
void Matrix::DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale, AZ::Vector3& shear) const
{
Matrix rotMatrix;
DecomposeQRGramSchmidt(translation, rotMatrix, scale, shear);
rot = MCore::MCoreMatrixToQuaternion(*this);
}
//
void Matrix::DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale) const
{
Matrix rotMatrix;
DecomposeQRGramSchmidt(translation, rotMatrix, scale);
rot = MCore::MCoreMatrixToQuaternion(rotMatrix);
}
//
void Matrix::DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot) const
{
Matrix rotMatrix;
DecomposeQRGramSchmidt(translation, rotMatrix);
rot = MCore::MCoreMatrixToQuaternion(rotMatrix);
}
//
@@ -685,26 +685,11 @@ namespace MCore
*/
void Frustum(float left, float right, float top, float bottom, float znear, float zfar);
/**
* Decompose a transformation matrix into translation and rotation components.
* The translation part is just the translation part of the matrix.
* The rotation AZ::Quaternion is calculated by normalizing the basis vectors and converting the
* 3x3 rotation part of the matrix to a AZ::Quaternion.
* It is allowed for the matrix to contain scaling.
* The matrix where you call Decompose on remains unchanged.
* @param outTranslation A pointer to a vector where the translation will be written to.
* @param outRotation A pointer to a AZ::Quaternion where the rotation will be written to.
* @note Please keep in mind that nullptr values for the parameters are NOT allowed.
*/
void Decompose(AZ::Vector3* outTranslation, AZ::Quaternion* outRotation) const;
// QR Gram-Schmidt decomposition
void DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot) const;
void DecomposeQRGramSchmidt(AZ::Vector3& translation, Matrix& rot) const;
void DecomposeQRGramSchmidt(AZ::Vector3& translation, Matrix& rot, AZ::Vector3& scale) const;
void DecomposeQRGramSchmidt(AZ::Vector3& translation, Matrix& rot, AZ::Vector3& scale, AZ::Vector3& shear) const;
void DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale, AZ::Vector3& shear) const;
void DecomposeQRGramSchmidt(AZ::Vector3& translation, AZ::Quaternion& rot, AZ::Vector3& scale) const;
static Matrix OuterProduct(const AZ::Vector4& column, const AZ::Vector4& row);
@@ -1,604 +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 required headers
#include "Quaternion.h"
#include <AzCore/std/typetraits/aligned_storage.h>
namespace MCore
{
// spherical quadratic interpolation
Quaternion Quaternion::Squad(const Quaternion& p, const Quaternion& a, const Quaternion& b, const Quaternion& q, float t)
{
Quaternion q0(p.Slerp(q, t));
Quaternion q1(a.Slerp(b, t));
return q0.Slerp(q1, 2.0f * t * (1.0f - t));
}
// returns the approximately normalized linear interpolated result [t must be between 0..1]
Quaternion Quaternion::NLerp(const Quaternion& to, float t) const
{
AZ_Assert(t > -MCore::Math::epsilon && t < (1 + MCore::Math::epsilon), "Expected t to be between 0..1");
static const float weightCloseToOne = 1.0f - MCore::Math::epsilon;
// Early out for boundaries (common cases)
if (t < MCore::Math::epsilon)
{
return *this;
}
else if (t > weightCloseToOne)
{
return to;
}
#if AZ_TRAIT_USE_PLATFORM_SIMD_SSE
__m128 num1, num2, num3, num4, fromVec, toVec;
const float omt = 1.0f - t;
float dot;
// perform dot product between this quat and the 'to' quat
num4 = _mm_setzero_ps(); // sets sum to zero
fromVec = _mm_loadu_ps(&x); //
toVec = _mm_loadu_ps(&to.x); //
num3 = _mm_mul_ps(fromVec, toVec); // performs multiplication num3 = a[3]*b[3] a[2]*b[2] a[1]*b[1] a[0]*b[0]
num3 = _mm_hadd_ps(num3, num3); // performs horizontal addition - num3= a[3]*b[3]+ a[2]*b[2] a[1]*b[1]+a[0]*b[0] a[3]*b[3]+ a[2]*b[2] a[1]*b[1]+a[0]*b[0]
num4 = _mm_add_ps(num4, num3); // performs vertical addition
num4 = _mm_hadd_ps(num4, num4);
_mm_store_ss(&dot, num4); // store the dot result
if (dot < 0.0f)
{
t = -t;
}
// calculate interpolated value
num2 = _mm_load_ps1(&omt);
num3 = _mm_load_ps1(&t);
num4 = _mm_mul_ps(fromVec, num2); // omt * xyzw
num1 = _mm_mul_ps(toVec, num3); // t * to.xyzw
num2 = _mm_add_ps(num1, num4); // interpolated value
// calculate the square length
num4 = _mm_setzero_ps();
num3 = _mm_mul_ps(num2, num2); // square length
num1 = _mm_hadd_ps(num3, num3);
num4 = _mm_add_ps(num4, num1);
num3 = _mm_hadd_ps(num4, num4);
//num4 = _mm_rsqrt_ps( num3 ); // length (argh, too inaccurate on some models)
AZStd::aligned_storage<sizeof(float) * 4, 16>::type numFloatStorage;
float* numFloat = reinterpret_cast<float*>(&numFloatStorage);
_mm_store_ps(numFloat, num3);
const float invLen = Math::InvSqrt(numFloat[0]);
num4 = _mm_load_ps1(&invLen);
// calc inverse length, which normalizes everything
num1 = _mm_mul_ps(num2, num4);
_mm_store_ps(numFloat, num1);
return Quaternion(numFloat[0], numFloat[1], numFloat[2], numFloat[3]);
#else
const float omt = 1.0f - t;
const float dot = x * to.x + y * to.y + z * to.z + w * to.w;
if (dot < 0.0f)
{
t = -t;
}
// calculate the interpolated values
const float newX = (omt * x + t * to.x);
const float newY = (omt * y + t * to.y);
const float newZ = (omt * z + t * to.z);
const float newW = (omt * w + t * to.w);
// calculate the inverse length
// const float invLen = 1.0f / Math::FastSqrt( newX*newX + newY*newY + newZ*newZ + newW*newW );
// const float invLen = Math::FastInvSqrt( newX*newX + newY*newY + newZ*newZ + newW*newW );
const float invLen = Math::InvSqrt(newX * newX + newY * newY + newZ * newZ + newW * newW);
// return the normalized linear interpolation
return Quaternion(newX * invLen,
newY * invLen,
newZ * invLen,
newW * invLen);
#endif
}
// returns the linear interpolated result [t must be between 0..1]
Quaternion Quaternion::Lerp(const Quaternion& to, float t) const
{
const float omt = 1.0f - t;
const float cosom = x * to.x + y * to.y + z * to.z + w * to.w;
if (cosom < 0.0f)
{
t = -t;
}
// return the linear interpolation
return Quaternion(omt * x + t * to.x,
omt * y + t * to.y,
omt * z + t * to.z,
omt * w + t * to.w);
}
// quaternion from an axis and angle
Quaternion::Quaternion(const AZ::Vector3& axis, float angle)
{
const float squaredLength = axis.GetLengthSq();
if (squaredLength > 0.0f)
{
const float halfAngle = angle * 0.5f;
const float sinScale = Math::Sin(halfAngle) / Math::Sqrt(squaredLength);
x = axis.GetX() * sinScale;
y = axis.GetY() * sinScale;
z = axis.GetZ() * sinScale;
w = Math::Cos(halfAngle);
}
else
{
x = y = z = 0.0f;
w = 1.0f;
}
}
// quaternion from a spherical rotation
Quaternion::Quaternion(const AZ::Vector2& spherical, float angle)
{
const float latitude = spherical.GetX();
const float longitude = spherical.GetY();
const float s = Math::Sin(angle / 2.0f);
const float c = Math::Cos(angle / 2.0f);
const float sin_lat = Math::Sin(latitude);
const float cos_lat = Math::Cos(latitude);
const float sin_lon = Math::Sin(longitude);
const float cos_lon = Math::Cos(longitude);
x = s * cos_lat * sin_lon;
y = s * sin_lat;
z = s * sin_lat * cos_lon;
w = c;
}
// convert to an axis and angle
void Quaternion::ToAxisAngle(AZ::Vector3* axis, float* angle) const
{
*angle = 2.0f * Math::ACos(w);
const float sinHalfAngle = Math::Sin(*angle * 0.5f);
if (sinHalfAngle > 0.0f)
{
const float invS = 1.0f / sinHalfAngle;
axis->Set(x * invS, y * invS, z * invS);
}
else
{
axis->Set(0.0f, 1.0f, 0.0f);
*angle = 0.0f;
}
}
// converts from unit quaternion to spherical rotation angles
void Quaternion::ToSpherical(AZ::Vector2* spherical, float* angle) const
{
AZ::Vector3 axis;
ToAxisAngle(&axis, angle);
float longitude;
if (axis.GetX() * axis.GetX() + axis.GetZ() * axis.GetZ() < 0.0001f)
{
longitude = 0.0f;
}
else
{
longitude = Math::ATan2(axis.GetX(), axis.GetZ());
if (longitude < 0.0f)
{
longitude += Math::twoPi;
}
}
spherical->SetX(-Math::ASin(axis.GetY()));
spherical->SetY(longitude);
}
// setup the quaternion from a roll, pitch and yaw
Quaternion& Quaternion::SetEuler(float pitch, float yaw, float roll)
{
// METHOD #1:
const float halfYaw = yaw * 0.5f;
const float halfPitch = pitch * 0.5f;
const float halfRoll = roll * 0.5f;
const float cY = Math::Cos(halfYaw);
const float sY = Math::Sin(halfYaw);
const float cP = Math::Cos(halfPitch);
const float sP = Math::Sin(halfPitch);
const float cR = Math::Cos(halfRoll);
const float sR = Math::Sin(halfRoll);
x = cY * sP * cR - sY * cP * sR;
y = cY * sP * sR + sY * cP * cR;
z = cY * cP * sR - sY * sP * cR;
w = cY * cP * cR + sY * sP * sR;
// Normalize(); // we might be able to leave the normalize away, but better safe than not, this is more robust :)
return *this;
/*
// METHOD #2:
Quaternion Qx(Vector3(sP, 0, 0), cP);
Quaternion Qy(Vector3(0, sY, 0), cY);
Quaternion Qz(Vector3(0, 0, sR), cR);
Quaternion result = Qx * Qy * Qz;
x = result.x;
y = result.y;
z = result.z;
w = result.w;
return *this;
*/
}
// convert the quaternion to a matrix
Matrix Quaternion::ToMatrix() const
{
Matrix m;
const float xx = x * x;
const float xy = x * y, yy = y * y;
const float xz = x * z, yz = y * z, zz = z * z;
const float xw = x * w, yw = y * w, zw = z * w, ww = w * w;
MMAT(m, 0, 0) = +xx - yy - zz + ww;
MMAT(m, 0, 1) = +xy + zw + xy + zw;
MMAT(m, 0, 2) = +xz - yw + xz - yw;
MMAT(m, 0, 3) = 0.0f;
MMAT(m, 1, 0) = +xy - zw + xy - zw;
MMAT(m, 1, 1) = -xx + yy - zz + ww;
MMAT(m, 1, 2) = +yz + xw + yz + xw;
MMAT(m, 1, 3) = 0.0f;
MMAT(m, 2, 0) = +xz + yw + xz + yw;
MMAT(m, 2, 1) = +yz - xw + yz - xw;
MMAT(m, 2, 2) = -xx - yy + zz + ww;
MMAT(m, 2, 3) = 0.0f;
MMAT(m, 3, 0) = 0.0f;
MMAT(m, 3, 1) = 0.0f;
MMAT(m, 3, 2) = 0.0f;
MMAT(m, 3, 3) = 1.0f;
return m;
}
// construct the quaternion from a given rotation matrix
Quaternion Quaternion::ConvertFromMatrix(const Matrix& m)
{
Quaternion result;
const float trace = MMAT(m, 0, 0) + MMAT(m, 1, 1) + MMAT(m, 2, 2);
if (trace > 0.0f /*Math::epsilon*/)
{
const float s = 0.5f / Math::Sqrt(trace + 1.0f);
result.w = 0.25f / s;
result.x = (MMAT(m, 1, 2) - MMAT(m, 2, 1)) * s;
result.y = (MMAT(m, 2, 0) - MMAT(m, 0, 2)) * s;
result.z = (MMAT(m, 0, 1) - MMAT(m, 1, 0)) * s;
}
else
{
if (MMAT(m, 0, 0) > MMAT(m, 1, 1) && MMAT(m, 0, 0) > MMAT(m, 2, 2))
{
const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 0, 0) - MMAT(m, 1, 1) - MMAT(m, 2, 2));
const float oneOverS = 1.0f / s;
result.x = 0.25f * s;
result.y = (MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS;
result.z = (MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS;
result.w = (MMAT(m, 1, 2) - MMAT(m, 2, 1)) * oneOverS;
}
else
if (MMAT(m, 1, 1) > MMAT(m, 2, 2))
{
const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 1, 1) - MMAT(m, 0, 0) - MMAT(m, 2, 2));
const float oneOverS = 1.0f / s;
result.x = (MMAT(m, 1, 0) + MMAT(m, 0, 1)) * oneOverS;
result.y = 0.25f * s;
result.z = (MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS;
result.w = (MMAT(m, 2, 0) - MMAT(m, 0, 2)) * oneOverS;
}
else
{
const float s = 2.0f * Math::Sqrt(1.0f + MMAT(m, 2, 2) - MMAT(m, 0, 0) - MMAT(m, 1, 1));
const float oneOverS = 1.0f / s;
result.x = (MMAT(m, 2, 0) + MMAT(m, 0, 2)) * oneOverS;
result.y = (MMAT(m, 2, 1) + MMAT(m, 1, 2)) * oneOverS;
result.z = 0.25f * s;
result.w = (MMAT(m, 0, 1) - MMAT(m, 1, 0)) * oneOverS;
}
}
/*
const float trace = MMAT(m,0,0) + MMAT(m,1,1) + MMAT(m,2,2) + 1.0f;
if (trace > Math::epsilon)
{
const float s = 0.5f / Math::Sqrt(trace);
result.w = 0.25f / s;
result.x = ( MMAT(m,1,2) - MMAT(m,2,1) ) * s;
result.y = ( MMAT(m,2,0) - MMAT(m,0,2) ) * s;
result.z = ( MMAT(m,0,1) - MMAT(m,1,0) ) * s;
}
else
{
if (MMAT(m,0,0) > MMAT(m,1,1) && MMAT(m,0,0) > MMAT(m,2,2))
{
const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,0,0) - MMAT(m,1,1) - MMAT(m,2,2));
const float oneOverS = 1.0f / s;
result.x = 0.25f * s;
result.y = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS;
result.z = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS;
result.w = (MMAT(m,2,1) - MMAT(m,1,2) ) * oneOverS;
}
else
if (MMAT(m,1,1) > MMAT(m,2,2))
{
const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,1,1) - MMAT(m,0,0) - MMAT(m,2,2));
const float oneOverS = 1.0f / s;
result.x = (MMAT(m,1,0) + MMAT(m,0,1) ) * oneOverS;
result.y = 0.25f * s;
result.z = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS;
result.w = (MMAT(m,2,0) - MMAT(m,0,2) ) * oneOverS;
}
else
{
const float s = 2.0f * Math::Sqrt( 1.0f + MMAT(m,2,2) - MMAT(m,0,0) - MMAT(m,1,1) );
const float oneOverS = 1.0f / s;
result.x = (MMAT(m,2,0) + MMAT(m,0,2) ) * oneOverS;
result.y = (MMAT(m,2,1) + MMAT(m,1,2) ) * oneOverS;
result.z = 0.25f * s;
result.w = (MMAT(m,1,0) - MMAT(m,0,1) ) * oneOverS;
}
}
*/
return result;
}
// convert a quaternion to euler angles (in degrees)
AZ::Vector3 Quaternion::ToEuler() const
{
/*
// METHOD #1:
Vector3 euler;
float matrix[3][3];
float cx,sx;
float cy,sy,yr;
float cz,sz;
matrix[0][0] = 1.0 - (2.0 * y * y) - (2.0 * z * z);
matrix[1][0] = (2.0 * x * y) + (2.0 * w * z);
matrix[2][0] = (2.0 * x * z) - (2.0 * w * y);
matrix[2][1] = (2.0 * y * z) + (2.0 * w * x);
matrix[2][2] = 1.0 - (2.0 * x * x) - (2.0 * y * y);
sy = -matrix[2][0];
cy = Math::Sqrt(1 - (sy * sy));
yr = Math::ATan2(sy,cy);
euler.y = yr;
// avoid divide by zero only where y ~90 or ~270
if (sy != 1.0 && sy != -1.0)
{
cx = matrix[2][2] / cy;
sx = matrix[2][1] / cy;
euler.x = Math::ATan2(sx,cx);
cz = matrix[0][0] / cy;
sz = matrix[1][0] / cy;
euler.z = Math::ATan2(sz,cz);
}
else
{
matrix[1][1] = 1.0 - (2.0 * x * x) - (2.0 * z * z);
matrix[1][2] = (2.0 * y * z) - (2.0 * w * x);
cx = matrix[1][1];
sx = -matrix[1][2];
euler.x = Math::ATan2(sx,cx);
cz = 1.0;
sz = 0.0;
euler.z = Math::ATan2(sz,cz);
}
return euler;
*/
/*
// METHOD #2:
Matrix mat = ToMatrix();
//
float cy = Math::Sqrt(mat.m44[0][0]*mat.m44[0][0] + mat.m44[0][1]*mat.m44[0][1]);
if (cy > 16.0*Math::epsilon)
{
result.x = -atan2(mat.m44[1][2], mat.m44[2][2]);
result.y = -atan2(-mat.m44[0][2], cy);
result.z = -atan2(mat.m44[0][1], mat.m44[0][0]);
}
else
{
result.x = -atan2(-mat.m44[2][1], mat.m44[1][1]);
result.y = -atan2(-mat.m44[0][2], cy);
result.z = 0.0;
}
return result;
*/
// METHOD #3 (without conversion to matrix first):
// TODO: safety checks?
float m00 = 1.0f - (2.0f * ((y * y) + z * z));
float m01 = 2.0f * (x * y + w * z);
AZ::Vector3 result(
Math::ATan2(2.0f * (y * z + w * x), 1.0f - (2.0f * ((x * x) + (y * y)))),
Math::ATan2(-2.0f * (x * z - w * y), Math::Sqrt((m00 * m00) + (m01 * m01))),
Math::ATan2(m01, m00)
);
return result;
}
float Quaternion::GetEulerZ() const
{
float m00 = 1.0f - (2.0f * ((y * y) + z * z));
float m01 = 2.0f * (x * y + w * z);
return Math::ATan2(m01, m00);
}
// returns the spherical interpolated result [t must be between 0..1]
Quaternion Quaternion::Slerp(const Quaternion& to, float t) const
{
float cosom = (x * to.x) + (y * to.y) + (z * to.z) + (w * to.w);
float scale0, scale1, scale1sign = 1.0f;
if (cosom < 0.0f)
{
scale1sign = -1.0f;
cosom *= -1.0f;
}
if ((1.0 - cosom) > Math::epsilon)
{
const float omega = Math::ACos(cosom);
const float sinOmega = Math::Sin(omega);
const float oosinom = 1.0f / sinOmega;
scale0 = Math::Sin((1.0f - t) * omega) * oosinom;
scale1 = Math::Sin(t * omega) * oosinom;
}
else
{
scale0 = 1.0f - t;
scale1 = t;
}
scale1 *= scale1sign;
return Quaternion(scale0 * x + scale1 * to.x,
scale0 * y + scale1 * to.y,
scale0 * z + scale1 * to.z,
scale0 * w + scale1 * to.w);
}
// set as delta rotation
Quaternion Quaternion::CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector)
{
Quaternion q;
q.SetAsDeltaRotation(fromVector, toVector);
return q;
}
// set as delta rotation but limited
Quaternion Quaternion::CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians)
{
Quaternion q;
q.SetAsDeltaRotation(fromVector, toVector, maxAngleRadians);
return q;
}
// set as delta rotation
void Quaternion::SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector)
{
// check if we are in parallel or not
const float dot = fromVector.Dot(toVector);
if (dot < 0.99999f) // we have rotated compared to the forward direction
{
const float angleRadians = Math::ACos(dot);
const AZ::Vector3 rotAxis = fromVector.Cross(toVector);
*this = Quaternion(rotAxis, angleRadians);
}
else
{
Identity();
}
}
// set as delta rotation, but limited
void Quaternion::SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians)
{
// check if we are in parallel or not
const float dot = fromVector.Dot(toVector);
if (dot < 0.99999f) // we have rotated compared to the forward direction
{
const float angleRadians = Math::ACos(dot);
const float rotAngle = Min(angleRadians, maxAngleRadians);
const AZ::Vector3 rotAxis = fromVector.Cross(toVector);
*this = Quaternion(rotAxis, rotAngle);
}
else
{
Identity();
}
}
/*
Decompose the rotation on to 2 parts.
1. Twist - rotation around the "direction" vector
2. Swing - rotation around axis that is perpendicular to "direction" vector
The rotation can be composed back by
rotation = swing * twist
has singularity in case of swing_rotation close to 180 degrees rotation.
if the input quaternion is of non-unit length, the outputs are non-unit as well
otherwise, outputs are both unit
*/
void Quaternion::DecomposeSwingTwist(const AZ::Vector3& direction, Quaternion* outSwing, Quaternion* outTwist) const
{
AZ::Vector3 rotAxis(x, y, z);
AZ::Vector3 p = Projected(rotAxis, direction); // return projection v1 on to v2 (parallel component)
outTwist->Set(p.GetX(), p.GetY(), p.GetZ(), w);
outTwist->Normalize();
*outSwing = *this * outTwist->Conjugated();
}
// rotate the current quaternion and renormalize it
void Quaternion::RotateFromTo(const AZ::Vector3& fromVector, const AZ::Vector3& toVector)
{
*this = CreateDeltaRotation(fromVector, toVector) * *this;
Normalize();
}
} // namespace MCore
@@ -1,386 +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 required headers
#include <AzCore/RTTI/TypeInfo.h>
#include <AzCore/Math/Vector2.h>
#include "StandardHeaders.h"
#include "FastMath.h"
#include "Vector.h"
#include "Matrix4.h"
#include "Algorithms.h"
namespace MCore
{
/**
* Depracated. Please use AZ::Quaternion instead.
* The quaternion class in MCore.
* Quaternions are mostly used to represent rotations in 3D applications.
* The advantages of quaternions over matrices are that they take up less space and that interpolation between
* two quaternions is easier to perform. Instead of a 3x3 rotation matrix, which is 9 floats or doubles, a quaternion
* only uses 4 floats or doubles. This template/class provides you with methods to perform all kind of operations on
* these quaternions, from interpolation to conversion to matrices and other rotation representations.
*/
class MCORE_API Quaternion
{
public:
AZ_TYPE_INFO(MCore::Quaternion, "{1807CD22-EBB5-45E8-8113-3B1DABB53F12}")
/**
* Default constructor. Sets x, y and z to 0 and w to 1.
*/
MCORE_INLINE Quaternion()
: x(0.0f)
, y(0.0f)
, z(0.0f)
, w(1.0f) {}
/**
* Constructor which sets the x, y, z and w.
* @param xVal The value of x.
* @param yVal The value of y.
* @param zVal The value of z.
* @param wVal The value of w.
*/
MCORE_INLINE Quaternion(float xVal, float yVal, float zVal, float wVal)
: x(xVal)
, y(yVal)
, z(zVal)
, w(wVal) {}
/**
* Copy constructor. Copies the x, y, z, w values from the other quaternion.
* @param other The quaternion to copy the attributes from.
*/
MCORE_INLINE Quaternion(const Quaternion& other)
: x(other.x)
, y(other.y)
, z(other.z)
, w(other.w) {}
/**
* Constructor which creates a quaternion from a pitch, yaw and roll.
* @param pitch Rotation around the x-axis, in radians.
* @param yaw Rotation around the y-axis, in radians.
* @param roll Rotation around the z-axis, in radians.
*/
MCORE_INLINE Quaternion(float pitch, float yaw, float roll) { SetEuler(pitch, yaw, roll); }
/**
* Constructor which takes a matrix as input parameter.
* This converts the rotation of the specified matrix into a quaternion. Please keep in mind that the matrix may NOT contain
* any scaling, so if it does, please normalize your matrix first!
* @param matrix The matrix to initialize the quaternion from.
*/
MCORE_INLINE Quaternion(const Matrix& matrix) { FromMatrix(matrix); }
/**
* Constructor which creates a quaternion from a spherical rotation.
* @param spherical The spherical coordinates in radians, which creates an axis to rotate around.
* @param angle The angle to rotate around this axis.
*/
Quaternion(const AZ::Vector2& spherical, float angle);
/**
* Constructor which creates a quaternion from an axis and angle.
* @param axis The axis to rotate around.
* @param angle The angle in radians to rotate around the given axis.
*/
Quaternion(const AZ::Vector3& axis, float angle);
/**
* Set the quaternion x/y/z/w component values.
* @param vx The value of x.
* @param vy The value of y.
* @param vz The value of z.
* @param vw The value of w.
*/
MCORE_INLINE void Set(float vx, float vy, float vz, float vw) { x = vx; y = vy; z = vz; w = vw; }
/**
* Calculates the square length of the quaternion.
* @result The square length (length*length).
*/
MCORE_INLINE float SquareLength() const { return (x * x + y * y + z * z + w * w); }
/**
* Calculates the length of the quaternion.
* It's safe, since it prevents a division by 0.
* @result The length of the quaternion.
*/
MCORE_INLINE float Length() const;
/**
* Performs a dot product on the quaternions.
* @param q The quaternion to multiply (dot product) this quaternion with.
* @result The quaternion which is the result of the dot product.
*/
MCORE_INLINE float Dot(const Quaternion& q) const { return (x * q.x + y * q.y + z * q.z + w * q.w); }
/**
* Normalize the quaternion.
* @result The normalized quaternion. It modifies itself, so no new quaternion is returned.
*/
MCORE_INLINE Quaternion& Normalize();
/**
* Sets the quaternion to identity. Where x, y and z are set to 0 and w is set to 1.
* @result The quaternion, now set to identity.
*/
MCORE_INLINE Quaternion& Identity() { x = 0.0f; y = 0.0f; z = 0.0f; w = 1.0f; return *this; }
/**
* Calculate the inversed version of this quaternion.
* @result The inversed version of this quaternion.
*/
MCORE_INLINE Quaternion& Inverse() { const float len = 1.0f / SquareLength(); x = -x * len; y = -y * len; z = -z * len; w = w * len; return *this; }
/**
* Conjugate this quaternion.
* @result Returns itself Conjugated.
*/
MCORE_INLINE Quaternion& Conjugate() { x = -x; y = -y; z = -z; return *this; }
/**
* Calculate the inversed version of this quaternion.
* @result The inversed version of this quaternion.
*/
MCORE_INLINE Quaternion Inversed() const { const float len = 1.0f / SquareLength(); return Quaternion(-x * len, -y * len, -z * len, w * len); }
/**
* Returns the normalized version of this quaternion.
* @result The normalized version of this quaternion.
*/
MCORE_INLINE Quaternion Normalized() const { Quaternion result(*this); result.Normalize(); return result; }
/**
* Return the conjugated version of this quaternion.
* @result The conjugated version of this quaternion.
*/
MCORE_INLINE Quaternion Conjugated() const { return Quaternion(-x, -y, -z, w); }
/**
* Calculate the exponent of this quaternion.
* @result The resulting quaternion of the exp.
*/
MCORE_INLINE Quaternion Exp() const { const float r = Math::Sqrt(x * x + y * y + z * z); const float expW = Math::Exp(w); const float s = (r >= 0.00001f) ? expW* Math::Sin(r) / r : 0.0f; return Quaternion(s * x, s * y, s * z, expW * Math::Cos(r)); }
/**
* Calculate the log of the quaternion.
* @result The resulting quaternion of the log.
*/
MCORE_INLINE Quaternion LogN() const { const float r = Math::Sqrt(x * x + y * y + z * z); float t = (r > 0.00001f) ? Math::ATan2(r, w) / r : 0.0f; return Quaternion(t * x, t * y, t * z, 0.5f * Math::Log(SquareLength())); }
/**
* Calculate and get the right basis vector.
* @result The basis vector pointing to the right. This assumes x+ points to the right.
*/
MCORE_INLINE AZ::Vector3 CalcRightAxis() const;
/**
* Calculate and get the up basis vector.
* @result The basis vector pointing upwards. This assumes z+ points up.
*/
MCORE_INLINE AZ::Vector3 CalcUpAxis() const;
/**
* Calculate and get the forward basis vector.
* @result The basis vector pointing forward. This assumes y+ points forward, into the depth.
*/
MCORE_INLINE AZ::Vector3 CalcForwardAxis() const;
/**
* Initialize the current quaternion from a specified matrix.
* Please note that the matrix may not contain any scaling!
* So make sure the matrix has been normalized before, if it contains any scale.
* @param m The matrix to initialize the quaternion from.
*/
MCORE_INLINE void FromMatrix(const Matrix& m) { *this = Quaternion::ConvertFromMatrix(m); }
/**
* Setup the quaternion from a pitch, yaw and roll.
* @param pitch The rotation around the x-axis, in radians.
* @param yaw The rotation around the y-axis, in radians.
* @param roll The rotation around the z-axis in radians.
* @result The quaternion, now initialized with the given pitch, yaw, roll rotation.
*/
Quaternion& SetEuler(float pitch, float yaw, float roll);
/**
* Convert the quaternion to an axis and angle. Which represents a rotation of the resulting angle around the resulting axis.
* @param axis Pointer to the vector to store the axis in.
* @param angle Pointer to the variable to store the angle in (will be in radians).
*/
void ToAxisAngle(AZ::Vector3* axis, float* angle) const;
/**
* Convert the quaternion to a spherical rotation.
* @param spherical A pointer to the 2D vector to store the spherical coordinates in radians, which build the axis.
* @param angle The pointer to the variable to store the angle around this axis in radians.
*/
void ToSpherical(AZ::Vector2* spherical, float* angle) const;
/**
* Extract the euler angles in radians.
* The x component of the resulting vector represents the rotation around the x-axis (pitch).
* The y component results the rotation around the y-axis (yaw) and the z component represents
* the rotation around the z-axis (roll).
* @result The 3D vector containing the euler angles in radians, around each axis.
*/
AZ::Vector3 ToEuler() const;
/**
* Returns the angle of rotation about the z axis. This is same as
* the z component of the vector returned by the ToEuler method. It
* is just more efficient to call this when one is interested only in rotation about the z axis.
* @result The angle of rotation about z axis in radians.
*/
float GetEulerZ() const;
/**
* Convert this quaternion into a matrix.
* @result The matrix representing the rotation of this quaternion.
*/
Matrix ToMatrix() const;
/**
* Convert a matrix into a quaternion.
* Please keep in mind that the specified matrix may NOT contain any scaling!
* So make sure the matrix has been normalized before, if it contains any scale.
* @param m The matrix to extract the rotation from.
* @result The quaternion, now containing the rotation of the given matrix, in quaternion form.
*/
static Quaternion ConvertFromMatrix(const Matrix& m);
/**
* Create a delta rotation that rotates one vector onto another vector.
* @param fromVector The normalized vector to start from. This must be normalized!
* @param toVector The normalized vector to rotate towards. This must be normalized as well!
* @result The delta rotation quaternion.
*/
static Quaternion CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector);
/**
* Create a delta rotation that rotates one vector onto another vector.
* If the angle is bigger than the max allowed angle that is specified it will rotate with an angle of the maximum specified angle.
* So if the angle between the vectors is 40 degrees and you maxAngleRadians equals 10 degrees (in radians) it will only rotate 10 degrees.
* @param fromVector The normalized vector to start from. This must be normalized!
* @param toVector The normalized vector to rotate towards. This must be normalized as well!
* @param maxAngleRadians The maximum rotation angle on the plane defined by the two vectors. This cannot be more than Math::pi (180 degrees).
* @result The delta rotation quaternion.
*/
static Quaternion CreateDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians);
/**
* Init this quaternion as a delta rotation that rotates one vector onto another vector.
* @param fromVector The normalized vector to start from. This must be normalized!
* @param toVector The normalized vector to rotate towards. This must be normalized as well!
*/
void SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector);
/**
* Init this quaternion as a delta rotation that rotates one vector onto another vector.
* If the angle is bigger than the max allowed angle that is specified it will rotate with an angle of the maximum specified angle.
* So if the angle between the vectors is 40 degrees and you maxAngleRadians equals 10 degrees (in radians) it will only rotate 10 degrees.
* @param fromVector The normalized vector to start from. This must be normalized!
* @param toVector The normalized vector to rotate towards. This must be normalized as well!
* @param maxAngleRadians The maximum rotation angle on the plane defined by the two vectors. This cannot be more than Math::pi (180 degrees).
*/
void SetAsDeltaRotation(const AZ::Vector3& fromVector, const AZ::Vector3& toVector, float maxAngleRadians);
/**
* Rotate this current quaternion using a given delta that is calculated from two vectors.
* The rotation axis used is the cross product between the from and to vector. The rotation angle is the angle between these two vectors.
* @param fromVector The current direction vector, must be normalized.
* @param toVector The desired new direction vector, must be normalized.
*/
void RotateFromTo(const AZ::Vector3& fromVector, const AZ::Vector3& toVector);
/**
* Decompose into swing and twist.
* The original rotation quat can be reassembled by doing swing * twist.
* @param direction The direction vector to get the twist from.
* @param outSwing This will contain the swing quaternion.
* @param outTwist This will contain the twist quaternion.
*/
void DecomposeSwingTwist(const AZ::Vector3& direction, Quaternion* outSwing, Quaternion* outTwist) const;
/**
* Linear interpolate between this and another quaternion.
* @param to The quaternion to interpolate towards.
* @param t The time value, between 0 and 1.
* @result The quaternion at the given time in the interpolation process.
*/
Quaternion Lerp(const Quaternion& to, float t) const;
/**
* Linear interpolate between this and another quaternion, and normalize afterwards.
* @param to The quaternion to interpolate towards.
* @param t The time value, between 0 and 1.
* @result The normalized quaternion at the given time in the interpolation process.
*/
Quaternion NLerp(const Quaternion& to, float t) const;
/**
* Spherical Linear interpolate between this and another quaternion.
* @param to The quaternion to interpolate towards.
* @param t The time value, between 0 and 1.
* @result The quaternion at the given time in the interpolation process.
*/
Quaternion Slerp(const Quaternion& to, float t) const;
/**
* Spherical cubic interpolate.
* @param p The first quaternion.
* @param a The second quaternion.
* @param b The third quaternion.
* @param q The fourth quaternion.
* @param t The time value, between 0 and 1.
* @result The quaternion at the given time in the interpolation process.
*/
static Quaternion Squad(const Quaternion& p, const Quaternion& a, const Quaternion& b, const Quaternion& q, float t);
// operators
MCORE_INLINE const Quaternion& operator=(const Matrix& m) { FromMatrix(m); return *this; }
MCORE_INLINE const Quaternion& operator=(const Quaternion& other) { x = other.x; y = other.y; z = other.z; w = other.w; return *this; }
MCORE_INLINE Quaternion operator-() const { return Quaternion(-x, -y, -z, -w); }
MCORE_INLINE const Quaternion& operator+=(const Quaternion& q) { x += q.x; y += q.y; z += q.z; w += q.w; return *this; }
MCORE_INLINE const Quaternion& operator-=(const Quaternion& q) { x -= q.x; y -= q.y; z -= q.z; w -= q.w; return *this; }
MCORE_INLINE const Quaternion& operator*=(const Quaternion& q);
MCORE_INLINE const Quaternion& operator*=(float f) { x *= f; y *= f; z *= f; w *= f; return *this; }
//MCORE_INLINE const Quaternion& operator*=(double f) { x*=f; y*=f; z*=f; w*=f; return *this; }
MCORE_INLINE bool operator==(const Quaternion& q) const { return ((q.x == x) && (q.y == y) && (q.z == z) && (q.w == w)); }
MCORE_INLINE bool operator!=(const Quaternion& q) const { return ((q.x != x) || (q.y != y) || (q.z != z) || (q.w != w)); }
//MCORE_INLINE float& operator[](int32 row) { return ((float*)&x)[row]; }
MCORE_INLINE operator float*() { return (float*)&x; }
MCORE_INLINE operator const float*() const { return (const float*)&x; }
MCORE_INLINE AZ::Vector3 operator*(const AZ::Vector3& p) const; // multiply a vector by a quaternion
MCORE_INLINE Quaternion operator/(const Quaternion& q) const; // returns the ratio of two quaternions
// attributes
float x, y, z, w;
};
// operators
MCORE_INLINE Quaternion operator*(const Quaternion& a, float f) { return Quaternion(a.x * f, a.y * f, a.z * f, a.w * f); }
MCORE_INLINE Quaternion operator*(float f, const Quaternion& b) { return Quaternion(f * b.x, f * b.y, f * b.z, f * b.w); }
//MCORE_INLINE Quaternion operator*(const Quaternion& a, double f) { return Quaternion(a.x*f, a.y*f, a.z*f, a.w*f); }
//MCORE_INLINE Quaternion operator*(double f, const Quaternion& b) { return Quaternion(f*b.x, f*b.y, f*b.z, f*b.w); }
MCORE_INLINE Quaternion operator+(const Quaternion& a, const Quaternion& b) { return Quaternion(a.x + b.x, a.y + b.y, a.z + b.z, a.w + b.w); }
MCORE_INLINE Quaternion operator-(const Quaternion& a, const Quaternion& b) { return Quaternion(a.x - b.x, a.y - b.y, a.z - b.z, a.w - b.w); }
MCORE_INLINE Quaternion operator*(const Quaternion& a, const Quaternion& b) { return Quaternion(a.w * b.x + a.x * b.w + a.y * b.z - a.z * b.y, a.w * b.y + a.y * b.w + a.z * b.x - a.x * b.z, a.w * b.z + a.z * b.w + a.x * b.y - a.y * b.x, a.w * b.w - a.x * b.x - a.y * b.y - a.z * b.z); }
// include the inline code
#include "Quaternion.inl"
} // namespace MCore
@@ -1,96 +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
*
*/
// multiply a vector by a quaternion
MCORE_INLINE AZ::Vector3 Quaternion::operator * (const AZ::Vector3& p) const
{
Quaternion v(p.GetX(), p.GetY(), p.GetZ(), 0.0f);
v = *this* v* this->Conjugated();
return AZ::Vector3(v.x, v.y, v.z);
}
// returns the ratio of two quaternions
MCORE_INLINE Quaternion Quaternion::operator / (const Quaternion& q) const
{
Quaternion t((*this) * -q);
Quaternion s((-q) * (-q));
t *= (1.0f / s.w);
return t;
}
// calculates the length of the quaternion
MCORE_INLINE float Quaternion::Length() const
{
const float sqLen = SquareLength();
return Math::SafeSqrt(sqLen);
}
// normalizes the quaternion using approximation
MCORE_INLINE Quaternion& Quaternion::Normalize()
{
// calculate 1.0 / length
// const float ooLen = 1.0f / Math::FastSqrt(x*x + y*y + z*z + w*w);
// const float ooLen = Math::FastInvSqrt(x*x + y*y + z*z + w*w);
const float squareValue = x * x + y * y + z * z + w * w;
const float ooLen = Math::InvSqrt(squareValue);
x *= ooLen;
y *= ooLen;
z *= ooLen;
w *= ooLen;
return *this;
}
// get the right axis
MCORE_INLINE AZ::Vector3 Quaternion::CalcRightAxis() const
{
return AZ::Vector3(1.0f - 2.0f * y * y - 2.0f * z * z,
2.0f * x * y + 2.0f * z * w,
2.0f * x * z - 2.0f * y * w);
}
// get the forward axis
MCORE_INLINE AZ::Vector3 Quaternion::CalcForwardAxis() const
{
return AZ::Vector3(2.0f * x * y - 2.0f * z * w,
1.0f - 2.0f * x * x - 2.0f * z * z,
2.0f * y * z + 2.0f * x * w);
}
// get the up axis
MCORE_INLINE AZ::Vector3 Quaternion::CalcUpAxis() const
{
return AZ::Vector3(2.0f * x * z + 2.0f * y * w,
2.0f * y * z - 2.0f * x * w,
1.0f - 2.0f * x * x - 2.0f * y * y);
}
// multiply by a quaternion
MCORE_INLINE const Quaternion& Quaternion::operator*=(const Quaternion& q)
{
const float vx = w * q.x + x * q.w + y * q.z - z * q.y;
const float vy = w * q.y + y * q.w + z * q.x - x * q.z;
const float vz = w * q.z + z * q.w + x * q.y - y * q.x;
const float vw = w * q.w - x * q.x - y * q.y - z * q.z;
x = vx;
y = vy;
z = vz;
w = vw;
return *this;
}
@@ -107,9 +107,6 @@ set(FILES
Source/PlaneEq.cpp
Source/PlaneEq.h
Source/PlaneEq.inl
Source/Quaternion.cpp
Source/Quaternion.h
Source/Quaternion.inl
Source/Random.cpp
Source/Random.h
Source/Ray.cpp
@@ -12,7 +12,6 @@
#include <AzCore/Math/Matrix4x4.h>
#include <AzCore/Math/MathUtils.h>
#include <MCore/Source/Quaternion.h>
#include <MCore/Source/Vector.h>
#include <MCore/Source/AzCoreConversions.h>
@@ -25,7 +24,6 @@ protected:
{
m_azNormalizedVector3_a = AZ::Vector3(s_x1, s_y1, s_z1);
m_azNormalizedVector3_a.Normalize();
m_emQuaternion_a = MCore::Quaternion(m_azNormalizedVector3_a, s_angle_a);
m_azQuaternion_a = AZ::Quaternion::CreateFromAxisAngle(m_azNormalizedVector3_a, s_angle_a);
}
@@ -55,26 +53,6 @@ protected:
return true;
}
bool EmfxQuaternionCompareExact(MCore::Quaternion& quaternion, float x, float y, float z, float w)
{
if (quaternion.x != x)
{
return false;
}
if (quaternion.y != y)
{
return false;
}
if (quaternion.z != z)
{
return false;
}
if (quaternion.w != w)
{
return false;
}
return true;
}
bool AZQuaternionCompareClose(AZ::Quaternion& quaternion, float x, float y, float z, float w, float tolerance)
{
@@ -131,26 +109,6 @@ protected:
return true;
}
bool AZEMQuaternionsAreEqual(AZ::Quaternion& azQuaternion, const MCore::Quaternion& emQuaternion)
{
if (AZQuaternionCompareExact(azQuaternion, emQuaternion.x, emQuaternion.y,
emQuaternion.z, emQuaternion.w))
{
return true;
}
return false;
}
bool AZEMQuaternionsAreClose(AZ::Quaternion& azQuaternion, const MCore::Quaternion& emQuaternion, const float tolerance)
{
if (AZQuaternionCompareClose(azQuaternion, emQuaternion.x, emQuaternion.y,
emQuaternion.z, emQuaternion.w, tolerance))
{
return true;
}
return false;
}
static const float s_toleranceHigh;
static const float s_toleranceMedium;
static const float s_toleranceLow;
@@ -161,7 +119,6 @@ protected:
static const float s_angle_a;
AZ::Vector3 m_azNormalizedVector3_a;
AZ::Quaternion m_azQuaternion_a;
MCore::Quaternion m_emQuaternion_a;
};
const float EmotionFXMathLibTests::s_toleranceHigh = 0.00001f;
@@ -174,18 +131,6 @@ const float EmotionFXMathLibTests::s_y1 = 0.3f;
const float EmotionFXMathLibTests::s_z1 = 0.4f;
const float EmotionFXMathLibTests::s_angle_a = 0.5f;
///////////////////////////////////////////////////////////////////////////////
// MCore::Quaternion: Test identity values
TEST_F(EmotionFXMathLibTests, QuaternionIdentity_Identity_Success)
{
MCore::Quaternion test(0.1f, 0.2f, 0.3f, 0.4f);
test.Identity();
ASSERT_TRUE(test == MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
}
//////////////////////////////////////////////////////////////////
//Getting and setting of Quaternions
//////////////////////////////////////////////////////////////////
@@ -196,52 +141,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternionGet_Elements_Success)
ASSERT_TRUE(AZQuaternionCompareExact(test, 0.1f, 0.2f, 0.3f, 0.4f));
}
// Compare equivalent normalized quaternions between systems
TEST_F(EmotionFXMathLibTests, AZEMQuaternionNormalizeEquivalent_Success)
{
AZ::Quaternion azTest(0.1f, 0.2f, 0.3f, 0.4f);
MCore::Quaternion emTest(0.1f, 0.2f, 0.3f, 0.4f);
azTest.Normalize();
emTest.Normalize();
ASSERT_TRUE(AZQuaternionCompareClose(azTest, emTest.x, emTest.y, emTest.z, emTest.w, s_toleranceMedium));
}
///////////////////////////////////////////////////////////////////////////////
// Axis Angle
///////////////////////////////////////////////////////////////////////////////
// Compare setting a quaternion using axis and angle
TEST_F(EmotionFXMathLibTests, AZEMQuaternionConversion_SetToAxisAngleEquivalent_Success)
{
MCore::Quaternion emQuaternion(m_azNormalizedVector3_a, s_angle_a);
AZ::Quaternion azQuaternion = AZ::Quaternion::CreateFromAxisAngle(m_azNormalizedVector3_a, s_angle_a);
ASSERT_TRUE(AZQuaternionCompareClose(azQuaternion, emQuaternion.x, emQuaternion.y, emQuaternion.z, emQuaternion.w, s_toleranceLow));
}
// Compare equivalent conversions quaternions -> (axis, angle) between systems
TEST_F(EmotionFXMathLibTests, AZEMQuaternionConversion_ToAxisAngleEquivalent_Success)
{
//populate Quaternions with same data
MCore::Quaternion emTest = m_emQuaternion_a;
AZ::Quaternion azTest(emTest.x, emTest.y, emTest.z, emTest.w);
AZ::Vector3 emAxis;
float emAngle;
emTest.ToAxisAngle(&emAxis, &emAngle);
AZ::Vector3 azAxis;
float azAngle;
AZ::ConvertQuaternionToAxisAngle(azTest, azAxis, azAngle);
bool same = AZ::IsClose(azAngle, emAngle, s_toleranceLow) &&
AZVector3CompareClose(azAxis, emAxis, s_toleranceLow);
ASSERT_TRUE(same);
}
///////////////////////////////////////////////////////////////////////////////
//Basic rotations
///////////////////////////////////////////////////////////////////////////////
@@ -420,18 +319,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternion_EulerGetSet3ComponentAxisCompareTrans
ASSERT_TRUE(same);
}
// EM Quaternion to Euler test
TEST_F(EmotionFXMathLibTests, EMQuaternionConversion_ToEulerEquivalent_Success)
{
AZ::Vector3 eulerIn(0.1f, 0.2f, 0.3f);
MCore::Quaternion test;
test.SetEuler(eulerIn.GetX(), eulerIn.GetY(), eulerIn.GetZ());
AZ::Vector3 eulerOut = test.ToEuler();
ASSERT_TRUE(AZVector3CompareClose(eulerOut, 0.1f, 0.2f, 0.3f, s_toleranceHigh));
}
// AZ Quaternion to Euler test
//only way to test Quaternions sameness is to apply it to a vector and measure result
TEST_F(EmotionFXMathLibTests, AZQuaternionConversion_ToEulerEquivalent_Success)
@@ -456,41 +343,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternionConversion_ToEulerEquivalent_Success)
ASSERT_TRUE(AZVector3CompareClose(eulerOut1, eulerOut2, s_toleranceReallyLow));
}
///////////////////////////////////////////////////////////////////////////////
//Quaternion order test
//determines that ordering is same between systems.
///////////////////////////////////////////////////////////////////////////////
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_OrderTest_Success)
{
AZ::Vector3 axis = AZ::Vector3(1.0f, 0.7f, 0.3f);
axis.Normalize();
AZ::Quaternion azQuaternion1 = AZ::Quaternion::CreateFromAxisAngle(axis, AZ::Constants::HalfPi);
AZ::Vector3 axis2 = AZ::Vector3(0.2f, 0.5f, 0.9f);
axis2.Normalize();
AZ::Quaternion azQuaternion2 = AZ::Quaternion::CreateFromAxisAngle(axis2, AZ::Constants::HalfPi);
MCore::Quaternion emQuaternion1(azQuaternion1.GetX(), azQuaternion1.GetY(), azQuaternion1.GetZ(), azQuaternion1.GetW());
MCore::Quaternion emQuaternion2(azQuaternion2.GetX(), azQuaternion2.GetY(), azQuaternion2.GetZ(), azQuaternion2.GetW());
AZ::Quaternion azQuaterionOut = azQuaternion1 * azQuaternion2;
AZ::Quaternion azQuaterionOut2 = azQuaternion2 * azQuaternion1;
MCore::Quaternion emQuaterionOut = emQuaternion1 * emQuaternion2;
AZ::Vector3 azVertexIn(0.1f, 0.2f, 0.3f);
AZ::Vector3 azVertexOut, azVertexOut2;
AZ::Vector3 emVertexOut;
azVertexOut = azQuaterionOut.TransformVector(azVertexIn);
azVertexOut2 = azQuaterionOut2.TransformVector(azVertexIn);
emVertexOut = emQuaterionOut * azVertexIn;
bool same = AZVector3CompareClose(emVertexOut, azVertexOut.GetX(), azVertexOut.GetY(), azVertexOut.GetZ(), s_toleranceMedium);
ASSERT_TRUE(same);
}
///////////////////////////////////////////////////////////////////////////////
// Quaternion Matrix
///////////////////////////////////////////////////////////////////////////////
@@ -616,225 +468,6 @@ TEST_F(EmotionFXMathLibTests, AZQuaternionConversion_ToMatrix_Success)
ASSERT_TRUE(AZ::IsClose(azMatrix.GetElement(3, 3), 1.0f, s_toleranceReallyLow));
}
///////////////////////////////////////////////////////////////////////////////
// AZEMQuaternion Compare Output tests
// Determines the AZ and MCore quaternion outputs are same/close after same math operations.
///////////////////////////////////////////////////////////////////////////////
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_CompareOperatorAddEquivalent_Success)
{
// Quaternion test: operator '+' and operator '+='
AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f);
azQuaternion.Normalize();
azQuaternion2.Normalize();
azQuaternion = azQuaternion + azQuaternion2;
azQuaternion2 += azQuaternion;
MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f);
emQuaternion.Normalize();
emQuaternion2.Normalize();
emQuaternion = emQuaternion + emQuaternion2;
emQuaternion2 += emQuaternion;
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '+'";
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '+='";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_CompareOperatorSubtractEquivalent_Success)
{
// Quaternion test: operator '-' and operator '-='
AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f);
azQuaternion.Normalize();
azQuaternion2.Normalize();
azQuaternion = azQuaternion - azQuaternion2;
azQuaternion2 -= azQuaternion;
MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f);
emQuaternion.Normalize();
emQuaternion2.Normalize();
emQuaternion = emQuaternion - emQuaternion2;
emQuaternion2 -= emQuaternion;
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '-'";
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '-='";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_CompareOperatorMultiplyHasSimilarOutput_Success)
{
// Quaternion test: operator '*' and operator '*=' with another quaternion, vector3 and float
AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f);
AZ::Quaternion azQuaternion3 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
azQuaternion.Normalize();
azQuaternion2.Normalize();
azQuaternion3.Normalize();
azQuaternion = azQuaternion * azQuaternion2;
azQuaternion2 *= azQuaternion;
azQuaternion3 *= 0.5f;
AZ::Vector3 aztestVec3 = azQuaternion2.TransformVector(m_azNormalizedVector3_a);
MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f);
MCore::Quaternion emQuaternion3 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f);
emQuaternion.Normalize();
emQuaternion2.Normalize();
emQuaternion3.Normalize();
emQuaternion = emQuaternion * emQuaternion2;
emQuaternion2 *= emQuaternion;
emQuaternion3 *= 0.5f;
AZ::Vector3 emtestVec3 = emQuaternion2 * m_azNormalizedVector3_a;
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*' with another quaternion";
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*=' with another quaternion";
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion3, emQuaternion3, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*=' with a float value";
EXPECT_TRUE(AZVector3CompareClose(aztestVec3, emtestVec3, s_toleranceLow)) << "AZ/MCore Quaternions should have similar output with operator '*' with a vector3";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_EquivalentOperatorsHasSameOutput_Success)
{
// Testing Quaternion == Quaternion and operator!=
bool azCheck = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() == AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized();
bool azCheck2 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() == AZ::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).GetNormalized();
bool azCheck3 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() != AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized();
bool azCheck4 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized() != AZ::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).GetNormalized();
bool emCheck = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() == MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized();
bool emCheck2 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() == MCore::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).Normalized();
bool emCheck3 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() != MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized();
bool emCheck4 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized() != MCore::Quaternion(0.1000001f, 0.2000001f, 0.3000001f, 1.0f).Normalized();
EXPECT_TRUE(azCheck == emCheck) << "AZ/MCore Quaternions should have same output of 'true' with operator '=='";
EXPECT_TRUE(azCheck2 == emCheck2) << "AZ/MCore Quaternions should have same output of 'false' with operator '=='";
EXPECT_TRUE(azCheck3 == emCheck3) << "AZ/MCore Quaternions should have same output of 'false' with operator '!='";
EXPECT_TRUE(azCheck4 == emCheck4) << "AZ/MCore Quaternions should have same output of 'true' with operator '!='";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_InverseHasSimilarOutput_Success)
{
// Test quaternions inverse method
AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetInverseFull();
AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetInverseFull();
MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Inverse();
MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Inverse();
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Inverse output";
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar Inverse output";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_ConjugateHasSimilarOutput_Success)
{
// Test quaternion conjugate method
AZ::Quaternion azQuaternion = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetConjugate();
AZ::Quaternion azQuaternion2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetConjugate();
MCore::Quaternion emQuaternion = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Conjugate();
MCore::Quaternion emQuaternion2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Conjugate();
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion, emQuaternion, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Conjugate output";
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternion2, emQuaternion2, s_toleranceLow)) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar Conjugate output";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSameSquareLengthOutput_Success)
{
// Test AZ and MCore quaternions to have similar square length
float azTest = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetLengthSq();
float azTest2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetLengthSq();
float emTest = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().SquareLength();
float emTest2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().SquareLength();
EXPECT_TRUE(AZ::GetAbs(azTest - emTest) < s_toleranceLow) << "AZ/MCore Quaternions should have similar square length output";
EXPECT_TRUE(AZ::GetAbs(azTest2 - emTest2) < s_toleranceLow) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar square length output";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSameLengthOutput_Success)
{
// Test AZ and MCore quaternions to have similar length
// AZ GetLength, GetLengthApprox, GetLength all returns sqrtf(Dot(*this))
float azTest = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().GetLength();
float azTest2 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().GetLength();
float emTest = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Length();
float emTest2 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Length();
EXPECT_TRUE(AZ::GetAbs(azTest - emTest) < s_toleranceLow) << "AZ/MCore Quaternions should have similar length output";
EXPECT_TRUE(AZ::GetAbs(azTest2 - emTest2) < s_toleranceLow) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar length output";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSameDotProductOutput_Success)
{
// Test AZ and MCore quaternions to have similar dot product
float azDotTest = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().Dot(AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f));
float azDotTest2 = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized().Dot(AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
float azDotTest3 = AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).GetNormalized().Dot(AZ::Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
float emDotTest = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Dot(MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f));
float emDotTest2 = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized().Dot(MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
float emDotTest3 = MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f).Normalized().Dot(MCore::Quaternion(0.0f, 0.0f, 0.0f, 1.0f));
EXPECT_TRUE(AZ::GetAbs(azDotTest - emDotTest) < s_toleranceLow) << "AZ/MCore Quaternions should have similar dot product output";
EXPECT_TRUE(AZ::GetAbs(azDotTest2 - emDotTest2) < s_toleranceLow) << "AZ/MCore Quaternions should have similar dot product output";
EXPECT_TRUE(AZ::GetAbs(azDotTest3 - emDotTest3) < s_toleranceLow) << "AZ/MCore Quaternion(0.0f, 0.0f, 0.0f, 1.0f) should have similar dot product output";
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSimilarLerpOutput_Success)
{
// Test AZ and MCore quaternions to have similar Linear Interpolated quaternions
float testCases[6] = { 0.0f, 0.1f, 0.25f, 0.5f, 0.8f, 1.0f };
for (float testVal : testCases)
{
AZ::Quaternion azQuaternionA = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized();
AZ::Quaternion azQuaternionB = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).GetNormalized();
AZ::Quaternion azQuaternionC = azQuaternionA.Lerp(azQuaternionB, testVal);
MCore::Quaternion emQuaternionA = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized();
MCore::Quaternion emQuaternionB = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).Normalized();
MCore::Quaternion emQuaternionC = emQuaternionA.Lerp(emQuaternionB, testVal);
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternionA, emQuaternionA, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Lerp output with given float: " << testVal;
}
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSimilarNLerpOutput_Success)
{
// Test AZ and MCore quaternions to have similar Linear Interpolated and then normalized quaternions
float testCases[6] = {0.0f, 0.1f, 0.25f, 0.5f, 0.8f, 1.0f};
for (float testVal : testCases)
{
AZ::Quaternion azQuaternionA = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized();
AZ::Quaternion azQuaternionB = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).GetNormalized();
AZ::Quaternion azQuaternionC = azQuaternionA.NLerp(azQuaternionB, testVal);
MCore::Quaternion emQuaternionA = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized();
MCore::Quaternion emQuaternionB = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).Normalized();
MCore::Quaternion emQuaternionC = emQuaternionA.NLerp(emQuaternionB, testVal);
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternionA, emQuaternionA, s_toleranceLow)) << "AZ/MCore Quaternions should have similar NLerp output with given float: " << testVal;
}
}
TEST_F(EmotionFXMathLibTests, AZEMQuaternion_HasSimilarSLerpOutput_Success)
{
// Test AZ and MCore quaternions to have similar spherical Linear Interpolated quaternions
float testCases[6] = { 0.0f, 0.1f, 0.25f, 0.5f, 0.8f, 1.0f };
for (float testVal : testCases)
{
AZ::Quaternion azQuaternionA = AZ::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).GetNormalized();
AZ::Quaternion azQuaternionB = AZ::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).GetNormalized();
AZ::Quaternion azQuaternionC = azQuaternionA.Slerp(azQuaternionB, testVal);
MCore::Quaternion emQuaternionA = MCore::Quaternion(0.1f, 0.2f, 0.3f, 1.0f).Normalized();
MCore::Quaternion emQuaternionB = MCore::Quaternion(0.8f, 0.7f, 0.6f, 1.0f).Normalized();
MCore::Quaternion emQuaternionC = emQuaternionA.Slerp(emQuaternionB, testVal);
EXPECT_TRUE(AZEMQuaternionsAreClose(azQuaternionA, emQuaternionA, s_toleranceLow)) << "AZ/MCore Quaternions should have similar Slerp output with given float: " << testVal;
}
}
//////////////////////////////////////////////////////////////////
// Skinning
//////////////////////////////////////////////////////////////////
+1 -32
View File
@@ -13,8 +13,8 @@
#include <AzCore/Math/Quaternion.h>
#include <EMotionFX/Source/EMotionFXConfig.h>
#include <EMotionFX/Source/Transform.h>
#include <MCore/Source/Quaternion.h>
#include <MCore/Source/Compare.h>
#include <MCore/Source/Matrix4.h>
#include <Tests/Printers.h>
#include <AzCore/std/string/string.h>
@@ -76,37 +76,6 @@ inline bool IsCloseMatcherP<AZ::Quaternion>::gmock_Impl<const AZ::Quaternion&>::
return false;
}
template<>
template<>
inline bool IsCloseMatcherP<MCore::Quaternion>::gmock_Impl<const MCore::Quaternion&>::MatchAndExplain(const MCore::Quaternion& arg, ::testing::MatchResultListener* result_listener) const
{
const MCore::Quaternion compareQuat = (expected.Dot(arg) < 0.0f) ? -arg : arg;
const AZ::Vector4 compareVec4(compareQuat.x, compareQuat.y, compareQuat.z, compareQuat.w);
if (::testing::ExplainMatchResult(IsClose(AZ::Vector4(expected.x, expected.y, expected.z, expected.w)), compareVec4, result_listener))
{
return true;
}
AZ::Vector3 gotAxis;
AZ::Vector3 expectedAxis;
float gotAngle;
float expectedAngle;
// convert to an axis and angle representation
expected.ToAxisAngle(&expectedAxis, &expectedAngle);
compareQuat.ToAxisAngle(&gotAxis, &gotAngle);
*result_listener << "\n Got Axis: ";
PrintTo(gotAxis, result_listener->stream());
*result_listener << ", Got Angle: " << gotAngle << "\n";
*result_listener << "Expected Axis: ";
PrintTo(expectedAxis, result_listener->stream());
*result_listener << ", Expected Angle: " << expectedAngle;
return false;
}
template<>
template<>
inline bool IsCloseMatcherP<EMotionFX::Transform>::gmock_Impl<const EMotionFX::Transform&>::MatchAndExplain(const EMotionFX::Transform& arg, ::testing::MatchResultListener* result_listener) const
-12
View File
@@ -34,18 +34,6 @@ namespace AZStd
}
} // namespace AZStd
namespace MCore
{
void PrintTo(const Quaternion& quaternion, ::std::ostream* os)
{
*os << "(x: " << quaternion.x
<< ", y: " << quaternion.y
<< ", z: " << quaternion.z
<< ", w: " << quaternion.w
<< ")";
}
} // namespace MCore
namespace EMotionFX
{
void PrintTo(const Transform& transform, ::std::ostream* os)
-6
View File
@@ -11,7 +11,6 @@
#include <AzCore/std/string/string.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Quaternion.h>
#include <MCore/Source/Quaternion.h>
#include <EMotionFX/Source/Transform.h>
namespace AZ
@@ -25,11 +24,6 @@ namespace AZStd
void PrintTo(const string& string, ::std::ostream* os);
} // namespace AZStd
namespace MCore
{
void PrintTo(const Quaternion& quaternion, ::std::ostream* os);
} // namespace MCore
namespace EMotionFX
{
void PrintTo(const Transform& transform, ::std::ostream* os);
@@ -191,7 +191,6 @@ namespace GraphCanvas
void GraphCanvasSystemComponent::Activate()
{
RegisterAssetHandler();
RegisterTranslationBuilder();
AzFramework::AssetCatalogEventBus::Handler::BusConnect();
@@ -386,34 +385,6 @@ namespace GraphCanvas
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::EnumerateAssets, nullptr, collectAssetsCb, postEnumerateCb);
}
void GraphCanvasSystemComponent::RegisterAssetHandler()
{
AZ::Data::AssetType assetType(azrtti_typeid<TranslationAsset>());
if (AZ::Data::AssetManager::Instance().GetHandler(assetType))
{
return; // Asset Type already handled
}
auto* catalogBus = AZ::Data::AssetCatalogRequestBus::FindFirstHandler();
if (catalogBus)
{
// Register asset types the asset DB should query our catalog for.
catalogBus->AddAssetType(assetType);
// Build the catalog (scan).
catalogBus->AddExtension(".names");
}
m_assetHandler = AZStd::make_unique<TranslationAssetHandler>();
AZ::Data::AssetManager::Instance().RegisterHandler(m_assetHandler.get(), assetType);
// Use AssetCatalog service to register ScriptEvent asset type and extension
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddAssetType, assetType);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::EnableCatalogForAsset, assetType);
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequests::AddExtension, TranslationAsset::GetFileFilter());
}
void GraphCanvasSystemComponent::UnregisterAssetHandler()
{
if (m_assetHandler)
@@ -82,8 +82,6 @@ namespace GraphCanvas
AZStd::unique_ptr<TranslationAssetHandler> m_assetHandler;
void RegisterTranslationBuilder();
void RegisterAssetHandler();
void UnregisterAssetHandler();
TranslationAssetWorker m_translationAssetWorker;
AZStd::vector<AZ::Data::AssetId> m_translationAssets;
+8 -12
View File
@@ -452,7 +452,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
const InputDeviceId& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
// Handle Keyboard Hotkeys
if (inputDeviceId == InputDeviceKeyboard::Id && inputChannel.IsStateBegan())
if (InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId) && inputChannel.IsStateBegan())
{
// Cycle through ImGui Menu Bar States on Home button press
if (inputChannelId == InputDeviceKeyboard::Key::NavigationHome)
@@ -477,7 +477,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
}
// Handle Keyboard Modifier Keys
if (inputDeviceId == InputDeviceKeyboard::Id)
if (InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
{
if (inputChannelId == InputDeviceKeyboard::Key::ModifierShiftL
|| inputChannelId == InputDeviceKeyboard::Key::ModifierShiftR)
@@ -506,14 +506,10 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
// Handle Controller Inputs
int inputControllerIndex = -1;
bool controllerInput = false;
for (int i = 0; i < MaxControllerNumber; ++i)
if (InputDeviceGamepad::IsGamepadDevice(inputDeviceId))
{
//Allow only one controller navigating ImGui at the same time. After menu bar dismissed, other controllers could take over
if (inputDeviceId == InputDeviceGamepad::IdForIndexN(i))
{
inputControllerIndex = i;
controllerInput = true;
}
inputControllerIndex = inputDeviceId.GetIndex();
controllerInput = true;
}
@@ -570,7 +566,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
}
// Handle Mouse Inputs
if (inputDeviceId == InputDeviceMouse::Id)
if (InputDeviceMouse::IsMouseDevice(inputDeviceId))
{
const int mouseButtonIndex = GetAzMouseButtonIndex(inputChannelId);
if (0 <= mouseButtonIndex && mouseButtonIndex < AZ_ARRAY_SIZE(io.MouseDown))
@@ -584,7 +580,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
}
// Handle Touch Inputs
if (inputDeviceId == InputDeviceTouch::Id)
if (InputDeviceTouch::IsTouchDevice(inputDeviceId))
{
const int touchIndex = GetAzTouchIndex(inputChannelId);
if (0 <= touchIndex && touchIndex < AZ_ARRAY_SIZE(io.MouseDown))
@@ -605,7 +601,7 @@ bool ImGuiManager::OnInputChannelEventFiltered(const InputChannel& inputChannel)
}
// Handle Virtual Keyboard Inputs
if (inputDeviceId == InputDeviceVirtualKeyboard::Id)
if (InputDeviceVirtualKeyboard::IsVirtualKeyboardDevice(inputDeviceId))
{
if (inputChannelId == AzFramework::InputDeviceVirtualKeyboard::Command::EditEnter)
{
@@ -98,7 +98,7 @@ namespace LmbrCentral
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->EBus<PolygonPrismShapeComponentRequestBus>("PolygonPrismShapeComponentRequestBus")
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Common)
->Attribute(AZ::Edit::Attributes::Category, "Shape")
->Attribute(AZ::Script::Attributes::Module, "shape")
->Event("GetPolygonPrism", &PolygonPrismShapeComponentRequestBus::Events::GetPolygonPrism)
@@ -323,10 +323,12 @@ namespace {{ Component.attrib['Namespace'] }}
/// Place in your .cpp
#include <{{ Component.attrib['OverrideInclude'] }}>
#include <AzCore/Serialization/SerializeContext.h>
namespace {{ Component.attrib['Namespace'] }}
{
{% if ComponentDerived %}
void {{ ComponentName }}::{{ ComponentName }}::Reflect(AZ::ReflectContext* context)
void {{ ComponentName }}::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
@@ -970,7 +970,7 @@ enum class NetworkProperties
{% macro DefineComponentServiceProxyGrabs(Component, ClassType, ComponentType) %}
{% for Service in Component.iter('ComponentRelation') %}
{% if Service.attrib['Constraint'] != 'Incompatible' %}
m_{{ LowerFirst(Service.attrib['Name']) }} = FindComponent<{{ UpperFirst(Service.attrib['Namespace']) }}::{{ UpperFirst(Service.attrib['Name']) }}>();
m_{{ LowerFirst(Service.attrib['Name']) }} = FindComponent<{{ Service.attrib['Namespace'] }}::{{ UpperFirst(Service.attrib['Name']) }}>();
{% endif %}
{% endfor %}
{% endmacro %}
@@ -1709,12 +1709,12 @@ namespace {{ Component.attrib['Namespace'] }}
{% for Service in Component.iter('ComponentRelation') %}
{% if Service.attrib['Constraint'] != 'Incompatible' %}
const {{ UpperFirst(Service.attrib['Namespace']) }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}() const
const {{ Service.attrib['Namespace'] }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}() const
{
return m_{{ LowerFirst(Service.attrib['Name']) }};
}
{{ UpperFirst(Service.attrib['Namespace']) }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}()
{{ Service.attrib['Namespace'] }}::{{ UpperFirst(Service.attrib['Name']) }}* {{ ComponentBaseName }}::Get{{ UpperFirst(Service.attrib['Name']) }}()
{
return m_{{ LowerFirst(Service.attrib['Name']) }};
}
@@ -69,19 +69,24 @@ namespace Multiplayer
{
using namespace AzNetworking;
AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port");
AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The address of the remote server or host to connect to");
AZ_CVAR(AZ::CVarFixedString, cl_serverpassword, "", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Optional server password");
AZ_CVAR(uint16_t, cl_clientport, 0, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The port to bind to for game traffic when connecting to a remote host, a value of 0 will select any available port");
AZ_CVAR(AZ::CVarFixedString, cl_serveraddr, AZ::CVarFixedString(LocalHost), nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The address of the remote server or host to connect to");
AZ_CVAR(uint16_t, cl_serverport, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port of the remote host to connect to for game traffic");
AZ_CVAR(uint16_t, sv_port, DefaultServerPort, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The port that this multiplayer gem will bind to for game traffic");
AZ_CVAR(AZ::CVarFixedString, sv_map, "nolevel", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The map the server should load");
AZ_CVAR(AZ::CVarFixedString, sv_gamerules, "norules", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "GameRules server works with");
AZ_CVAR(ProtocolType, sv_protocol, ProtocolType::Udp, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "This flag controls whether we use TCP or UDP for game networking");
AZ_CVAR(bool, sv_isDedicated, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether the host command creates an independent or client hosted server");
AZ_CVAR(bool, sv_isTransient, true, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Whether a dedicated server shuts down if all existing connections disconnect.");
AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "Max Ms to use to activate entities coming from the network, 0 means instantiate everything");
AZ_CVAR(AZ::TimeMs, cl_defaultNetworkEntityActivationTimeSliceMs, AZ::TimeMs{ 0 }, nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"Max Ms to use to activate entities coming from the network, 0 means instantiate everything");
AZ_CVAR(AZ::TimeMs, sv_serverSendRateMs, AZ::TimeMs{ 50 }, nullptr, AZ::ConsoleFunctorFlags::Null, "Minimum number of milliseconds between each network update");
AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate, "The default spawnable to use when a new player connects");
AZ_CVAR(AZ::CVarFixedString, sv_defaultPlayerSpawnAsset, "prefabs/player.network.spawnable", nullptr, AZ::ConsoleFunctorFlags::DontReplicate,
"The default spawnable to use when a new player connects");
AZ_CVAR(float, cl_renderTickBlendBase, 0.15f, nullptr, AZ::ConsoleFunctorFlags::Null,
"The base used for blending between network updates, 0.1 will be quite linear, 0.2 or 0.3 will "
"slow down quicker and may be better suited to connections with highly variable latency");
void MultiplayerSystemComponent::Reflect(AZ::ReflectContext* context)
{
@@ -546,7 +551,7 @@ namespace Multiplayer
if ((GetAgentType() == MultiplayerAgentType::Client) && (packet.GetHostFrameId() > m_lastReplicatedHostFrameId))
{
// Update client to latest server time
m_renderBlendFactor = 0.0f;
m_tickFactor = 0.0f;
m_lastReplicatedHostTimeMs = packet.GetHostTimeMs();
m_lastReplicatedHostFrameId = packet.GetHostFrameId();
m_networkTime.AlterTime(m_lastReplicatedHostFrameId, m_lastReplicatedHostTimeMs, AzNetworking::InvalidConnectionId);
@@ -849,10 +854,18 @@ namespace Multiplayer
void MultiplayerSystemComponent::TickVisibleNetworkEntities(float deltaTime, float serverRateSeconds)
{
m_tickFactor += deltaTime / serverRateSeconds;
// Linear close to the origin, but asymptote at y = 1
const float targetAdjustBlend = AZStd::clamp(deltaTime / serverRateSeconds, 0.0f, 1.0f);
m_renderBlendFactor = 1.0f - (std::pow(0.2f, m_renderBlendFactor + targetAdjustBlend));
AZLOG(NET_Blending, "Computed blend factor of %0.2f using a frametime of %0.2f and a serverTickRate of %0.2f", m_renderBlendFactor, deltaTime, serverRateSeconds);
const float renderBlendFactor = AZStd::clamp(1.0f - (std::pow(cl_renderTickBlendBase, m_tickFactor)), 0.0f, 1.0f);
AZLOG
(
NET_Blending,
"Computed blend factor of %0.3f using a tick factor of %0.3f, a frametime of %0.3f and a serverTickRate of %0.3f",
renderBlendFactor,
m_tickFactor,
deltaTime,
serverRateSeconds
);
if (Camera::ActiveCameraRequestBus::HasHandlers())
{
@@ -895,7 +908,7 @@ namespace Multiplayer
for (NetBindComponent* netBindComponent : gatheredEntities)
{
netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor);
netBindComponent->NotifyPreRender(deltaTime, renderBlendFactor);
}
}
else
@@ -907,7 +920,7 @@ namespace Multiplayer
NetBindComponent* netBindComponent = entity->FindComponent<NetBindComponent>();
if (netBindComponent != nullptr)
{
netBindComponent->NotifyPreRender(deltaTime, m_renderBlendFactor);
netBindComponent->NotifyPreRender(deltaTime, renderBlendFactor);
}
}
}
@@ -155,7 +155,7 @@ namespace Multiplayer
HostFrameId m_lastReplicatedHostFrameId = HostFrameId(0);
double m_serverSendAccumulator = 0.0;
float m_renderBlendFactor = 0.0f;
float m_tickFactor = 0.0f;
#if !defined(AZ_RELEASE_BUILD)
MultiplayerEditorConnection m_editorConnectionListener;
@@ -107,8 +107,10 @@ namespace Multiplayer
void ServerToClientReplicationWindow::UpdateWindow()
{
// clear the candidate queue, we're going to rebuild it
ReplicationCandidateQueue clearQueue;
clearQueue.get_container().reserve(sv_MaxEntitiesToTrackReplication);
ReplicationCandidateQueue::container_type clearQueueContainer;
clearQueueContainer.reserve(sv_MaxEntitiesToTrackReplication);
// Move the clearQueueContainer into the ReplicationCandidateQueue to maintain the reserved memory
ReplicationCandidateQueue clearQueue(ReplicationCandidateQueue::value_compare{}, AZStd::move(clearQueueContainer));
m_candidateQueue.swap(clearQueue);
m_replicationSet.clear();
@@ -578,7 +578,7 @@ namespace NvCloth
const AZ::Vector3& renderTangent = renderTangents[renderVertexIndex];
destTangentsBuffer[index].Set(
renderTangent,
1.0f);
-1.0f); // Shader function ConstructTBN inverts w to change bitangent sign, but the bitangents passed are already corrected, so passing -1.0 to counteract.
}
if (destBitangentsBuffer)
@@ -12,7 +12,7 @@ namespace NvCloth
{
namespace
{
const float Tolerance = 0.0001f;
const float Tolerance = 1e-7f;
}
bool TangentSpaceHelper::CalculateNormals(
@@ -34,7 +34,8 @@ namespace NvCloth
const size_t vertexCount = vertices.size();
// Reset results
outNormals.resize(vertexCount, AZ::Vector3::CreateZero());
outNormals.resize(vertexCount);
AZStd::fill(outNormals.begin(), outNormals.end(), AZ::Vector3::CreateZero());
// calculate the normals per triangle
for (size_t i = 0; i < triangleCount; ++i)
@@ -115,8 +116,10 @@ namespace NvCloth
const size_t vertexCount = vertices.size();
// Reset results
outTangents.resize(vertexCount, AZ::Vector3::CreateZero());
outBitangents.resize(vertexCount, AZ::Vector3::CreateZero());
outTangents.resize(vertexCount);
outBitangents.resize(vertexCount);
AZStd::fill(outTangents.begin(), outTangents.end(), AZ::Vector3::CreateZero());
AZStd::fill(outBitangents.begin(), outBitangents.end(), AZ::Vector3::CreateZero());
// calculate the base vectors per triangle
for (size_t i = 0; i < triangleCount; ++i)
@@ -193,9 +196,12 @@ namespace NvCloth
const size_t vertexCount = vertices.size();
// Reset results
outTangents.resize(vertexCount, AZ::Vector3::CreateZero());
outBitangents.resize(vertexCount, AZ::Vector3::CreateZero());
outNormals.resize(vertexCount, AZ::Vector3::CreateZero());
outTangents.resize(vertexCount);
outBitangents.resize(vertexCount);
outNormals.resize(vertexCount);
AZStd::fill(outTangents.begin(), outTangents.end(), AZ::Vector3::CreateZero());
AZStd::fill(outBitangents.begin(), outBitangents.end(), AZ::Vector3::CreateZero());
AZStd::fill(outNormals.begin(), outNormals.end(), AZ::Vector3::CreateZero());
// calculate the base vectors per triangle
for (size_t i = 0; i < triangleCount; ++i)
@@ -125,6 +125,9 @@ namespace UnitTest
const AZStd::vector<AZ::Vector4>& motionConstraints = clothConstraints->GetMotionConstraints();
EXPECT_TRUE(motionConstraints.size() == SimulationParticles.size());
EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(SimulationParticles[0].GetAsVector3(), Tolerance));
EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(SimulationParticles[1].GetAsVector3(), Tolerance));
EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(SimulationParticles[2].GetAsVector3(), Tolerance));
EXPECT_NEAR(motionConstraints[0].GetW(), 6.0f, Tolerance);
EXPECT_NEAR(motionConstraints[1].GetW(), 0.0f, Tolerance);
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
@@ -278,6 +281,9 @@ namespace UnitTest
const AZStd::vector<AZ::Vector4>& separationConstraints = clothConstraints->GetSeparationConstraints();
EXPECT_TRUE(motionConstraints.size() == newParticles.size());
EXPECT_THAT(motionConstraints[0].GetAsVector3(), IsCloseTolerance(newParticles[0].GetAsVector3(), Tolerance));
EXPECT_THAT(motionConstraints[1].GetAsVector3(), IsCloseTolerance(newParticles[1].GetAsVector3(), Tolerance));
EXPECT_THAT(motionConstraints[2].GetAsVector3(), IsCloseTolerance(newParticles[2].GetAsVector3(), Tolerance));
EXPECT_NEAR(motionConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(motionConstraints[1].GetW(), 1.5f, Tolerance);
EXPECT_NEAR(motionConstraints[2].GetW(), 0.0f, Tolerance);
@@ -286,8 +292,8 @@ namespace UnitTest
EXPECT_NEAR(separationConstraints[0].GetW(), 3.0f, Tolerance);
EXPECT_NEAR(separationConstraints[1].GetW(), 1.5f, Tolerance);
EXPECT_NEAR(separationConstraints[2].GetW(), 0.3f, Tolerance);
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-3.03902f, 2.80752f, 3.80752f), Tolerance));
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(-1.41659f, 0.651243f, -0.348757f), Tolerance));
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(6.15313f, -0.876132f, 0.123868f), Tolerance));
EXPECT_THAT(separationConstraints[0].GetAsVector3(), IsCloseTolerance(AZ::Vector3(0.0f, 3.53553f, 4.53553f), Tolerance));
EXPECT_THAT(separationConstraints[1].GetAsVector3(), IsCloseTolerance(AZ::Vector3(0.0f, 2.06066f, 1.06066f), Tolerance));
EXPECT_THAT(separationConstraints[2].GetAsVector3(), IsCloseTolerance(AZ::Vector3(1.0f, -3.74767f, -2.74767f), Tolerance));
}
} // namespace UnitTest
@@ -156,6 +156,33 @@ namespace PhysX
->DataElement(AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_kinematic,
"Kinematic", "Rigid body is kinematic")
->Attribute(AZ::Edit::Attributes::Visibility, &AzPhysics::RigidBodyConfiguration::GetKinematicVisibility)
// Linear axis locking properties
->ClassElement(AZ::Edit::ClassElements::Group, "Linear Axis Locking")
->Attribute(AZ::Edit::Attributes::AutoExpand, false)
->DataElement(
AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockLinearX, "Lock X",
"Lock motion along X direction")
->DataElement(
AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockLinearY, "Lock Y",
"Lock motion along Y direction")
->DataElement(
AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockLinearZ, "Lock Z",
"Lock motion along Z direction")
// Angular axis locking properties
->ClassElement(AZ::Edit::ClassElements::Group, "Angular Axis Locking")
->Attribute(AZ::Edit::Attributes::AutoExpand, false)
->DataElement(
AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockAngularX, "Lock X",
"Lock rotation around X direction")
->DataElement(
AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockAngularY, "Lock Y",
"Lock rotation around Y direction")
->DataElement(
AZ::Edit::UIHandlers::Default, &AzPhysics::RigidBodyConfiguration::m_lockAngularZ, "Lock Z",
"Lock rotation around Z direction")
->ClassElement(AZ::Edit::ClassElements::Group, "Continuous Collision Detection")
->Attribute(AZ::Edit::Attributes::AutoExpand, true)
->Attribute(AZ::Edit::Attributes::Visibility, &AzPhysics::RigidBodyConfiguration::GetCCDVisibility)
+5 -3
View File
@@ -95,6 +95,7 @@ namespace PhysX
{
serialize->Class<SystemComponent, AZ::Component>()
->Version(1)
->Attribute(AZ::Edit::Attributes::SystemComponentTags, AZStd::vector<AZ::Crc32>({ AZ_CRC_CE("AssetBuilder") }))
->Field("Enabled", &SystemComponent::m_enabled)
;
@@ -122,13 +123,14 @@ namespace PhysX
incompatible.push_back(AZ_CRC("PhysXService", 0x75beae2d));
}
void SystemComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
void SystemComponent::GetRequiredServices([[maybe_unused]] AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC("AssetDatabaseService", 0x3abf5601));
}
void SystemComponent::GetDependentServices([[maybe_unused]]AZ::ComponentDescriptor::DependencyArrayType& dependent)
void SystemComponent::GetDependentServices(AZ::ComponentDescriptor::DependencyArrayType& dependent)
{
dependent.push_back(AZ_CRC_CE("AssetDatabaseService"));
dependent.push_back(AZ_CRC_CE("AssetCatalogService"));
}
SystemComponent::SystemComponent()
+8
View File
@@ -1466,6 +1466,14 @@ namespace PhysX
rigidDynamic->setRigidBodyFlag(physx::PxRigidBodyFlag::eKINEMATIC, configuration.m_kinematic);
rigidDynamic->setMaxAngularVelocity(configuration.m_maxAngularVelocity);
// Set axis locks.
rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_X, configuration.m_lockLinearX);
rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Y, configuration.m_lockLinearY);
rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_LINEAR_Z, configuration.m_lockLinearZ);
rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_X, configuration.m_lockAngularX);
rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Y, configuration.m_lockAngularY);
rigidDynamic->setRigidDynamicLockFlag(physx::PxRigidDynamicLockFlag::eLOCK_ANGULAR_Z, configuration.m_lockAngularZ);
return rigidDynamic;
}

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