Merge branch 'development' into jillich/EmfxAabbImprovements
This commit is contained in:
+102
@@ -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)
|
||||
|
||||
+3
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+6
@@ -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;
|
||||
|
||||
|
||||
+1
-1
@@ -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
|
||||
|
||||
+12
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+115
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
+28
-365
@@ -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
|
||||
|
||||
+7
-86
@@ -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
|
||||
|
||||
+5
@@ -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;
|
||||
};
|
||||
|
||||
+12
-1
@@ -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
|
||||
|
||||
+1
@@ -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>
|
||||
|
||||
+1
@@ -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
|
||||
//////////////////////////////////////////////////////////////////
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user