Merge branch 'development' of https://github.com/o3de/o3de into cgalvan/RemovedGradientImageBuilder

monroegm-disable-blank-issue-2
Chris Galvan 4 years ago
commit 283fe216dc

@ -12,7 +12,7 @@ import pytest
import editor_python_test_tools.hydra_test_utils as hydra
import ly_test_tools.environment.file_system as file_system
from ly_test_tools.benchmark.data_aggregator import BenchmarkDataAggregator
from ly_test_tools.o3de.editor_test import EditorSharedTest, EditorTestSuite
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorTestSuite
from Atom.atom_utils.atom_component_helper import compare_screenshot_to_golden_image, golden_images_directory
DEFAULT_SUBFOLDER_PATH = 'user/PythonTests/Automated/Screenshots'
@ -23,89 +23,137 @@ logger = logging.getLogger(__name__)
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
class TestAutomation(EditorTestSuite):
# Remove -autotest_mode from global_extra_cmdline_args since we need rendering for these tests.
global_extra_cmdline_args = ["-BatchMode"] # Default is ["-BatchMode", "-autotest_mode"]
global_extra_cmdline_args = ["-autotest_mode"] # Default is ["-BatchMode", "-autotest_mode"]
use_null_renderer = False # Default is True
enable_prefab_system = False
@pytest.mark.test_case_id("C34603773")
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_name = "AtomBasicLevelSetup.ppm"
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
test_screenshots = [os.path.join(screenshot_directory, self.screenshot_name)]
file_system.delete(test_screenshots, True, True)
golden_images = [os.path.join(golden_images_directory(), screenshot_name)]
from Atom.tests import hydra_AtomGPU_BasicLevelSetup as test_module
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
@pytest.mark.test_case_id("C34525095")
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
for screenshot in self.screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
self.test_screenshots.append(screenshot_path)
file_system.delete(self.test_screenshots, True, True)
@staticmethod
def screenshot_setup(screenshot_directory, screenshot_names):
"""
:param screenshot_names: list of screenshot file names with extensions
:return: tuple test_screenshots, golden_images each a list of full file paths
"""
test_screenshots = []
golden_images = []
for screenshot in screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
test_screenshots.append(screenshot_path)
file_system.delete(test_screenshots, True, True)
for golden_image in screenshot_names:
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
return test_screenshots, golden_images
@pytest.mark.test_case_id("C34525095")
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages_DX12(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
extra_cmdline_args = ["-rhi=dx12"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
"SpotLight_4.ppm",
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
test_screenshots = [] # Gets set by setup()
screenshot_directory = "" # Gets set by setup()
@pytest.mark.test_case_id("C34525095")
class AtomGPU_LightComponent_AreaLightScreenshotsMatchGoldenImages_Vulkan(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_AreaLightScreenshotTest as test_module
# Clear existing test screenshots before starting test.
def setup(self, workspace):
screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
for screenshot in self.screenshot_names:
screenshot_path = os.path.join(screenshot_directory, screenshot)
self.test_screenshots.append(screenshot_path)
file_system.delete(self.test_screenshots, True, True)
extra_cmdline_args = ["-rhi=vulkan"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"AreaLight_1.ppm",
"AreaLight_2.ppm",
"AreaLight_3.ppm",
"AreaLight_4.ppm",
"AreaLight_5.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
golden_images = []
for golden_image in screenshot_names:
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages_DX12(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module
extra_cmdline_args = ["-rhi=dx12"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
"SpotLight_4.ppm",
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
@pytest.mark.test_case_id("C34525110")
class AtomGPU_LightComponent_SpotLightScreenshotsMatchGoldenImages_Vulkan(EditorSingleTest):
from Atom.tests import hydra_AtomGPU_SpotLightScreenshotTest as test_module
assert compare_screenshot_to_golden_image(screenshot_directory, test_screenshots, golden_images, 0.99) is True
extra_cmdline_args = ["-rhi=vulkan"]
# Custom setup/teardown to remove old screenshots and establish paths to golden images
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
self.screenshot_directory = os.path.join(workspace.paths.project(), DEFAULT_SUBFOLDER_PATH)
self.screenshot_names = [
"SpotLight_1.ppm",
"SpotLight_2.ppm",
"SpotLight_3.ppm",
"SpotLight_4.ppm",
"SpotLight_5.ppm",
"SpotLight_6.ppm",
]
self.test_screenshots, self.golden_images = TestAutomation.screenshot_setup(
screenshot_directory=self.screenshot_directory,
screenshot_names=self.screenshot_names)
def wrap_run(self, request, workspace, editor, editor_test_results, launcher_platform):
yield
assert compare_screenshot_to_golden_image(self.screenshot_directory,
self.test_screenshots,
self.golden_images,
similarity_threshold=0.96) is True
@pytest.mark.parametrize('rhi', ['dx12', 'vulkan'])

@ -86,9 +86,8 @@ def compare_screenshot_similarity(
if create_zip_archive:
create_screenshots_archive(screenshot_directory)
result = (
f"When comparing the test_screenshot: '{test_screenshot}' "
f"to golden_image: '{golden_image}' the mean similarity of '{mean_similarity}' "
f"was lower than the similarity threshold of '{similarity_threshold}'. ")
f"When comparing the test_screenshot: '{test_screenshot}' to golden_image: '{golden_image}'.\n"
f"The mean similarity ({mean_similarity}) was lower than the similarity threshold ({similarity_threshold})")
return result
@ -123,7 +122,9 @@ def initial_viewport_setup(screen_width=1280, screen_height=720):
import azlmbr.legacy.general as general
general.set_viewport_size(screen_width, screen_height)
general.idle_wait_frames(1)
general.update_viewport()
general.idle_wait_frames(1)
def enter_exit_game_mode_take_screenshot(screenshot_name, enter_game_tuple, exit_game_tuple, timeout_in_seconds=4):
@ -137,13 +138,18 @@ def enter_exit_game_mode_take_screenshot(screenshot_name, enter_game_tuple, exit
"""
import azlmbr.legacy.general as general
from editor_python_test_tools.utils import TestHelper
from editor_python_test_tools.utils import TestHelper, Report
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
screenshot_helper = ScreenshotHelper(general.idle_wait_frames)
TestHelper.enter_game_mode(enter_game_tuple)
TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(screenshot_name)
screenshot_helper.prepare_viewport_for_screenshot(1920, 1080)
success_screenshot = TestHelper.wait_for_condition(
function=lambda: screenshot_helper.capture_screenshot_blocking(screenshot_name),
timeout_in_seconds=timeout_in_seconds)
Report.result(("Screenshot taken", "Screenshot failed to be taken"), success_screenshot)
TestHelper.exit_game_mode(exit_game_tuple)
TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=timeout_in_seconds)

@ -107,10 +107,8 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
18. Add Mesh component to Sphere Entity and set the Mesh Asset property for the Mesh component.
19. Create a Camera Entity as a child entity of the Default Level Entity then add a Camera component.
20. Set the Camera Entity rotation value and set the Camera component Field of View value.
21. Enter game mode.
22. Take screenshot.
23. Exit game mode.
24. Look for errors.
21. Enter/Exit game mode taking screenshot.
22. Look for errors.
:return: None
"""
@ -127,7 +125,7 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
from Atom.atom_utils.atom_constants import AtomComponentProperties
from Atom.atom_utils.atom_component_helper import initial_viewport_setup
from Atom.atom_utils.screenshot_utils import ScreenshotHelper
from Atom.atom_utils.atom_component_helper import enter_exit_game_mode_take_screenshot
DEGREE_RADIAN_FACTOR = 0.0174533
SCREENSHOT_NAME = "AtomBasicLevelSetup"
@ -300,18 +298,10 @@ def AtomGPU_BasicLevelSetup_SetsUpLevel():
Report.result(Tests.camera_fov_set, camera_component.get_component_property_value(
AtomComponentProperties.camera('Field of view')) == camera_fov_value)
# 21. Enter game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
TestHelper.wait_for_condition(function=lambda: general.is_in_game_mode(), timeout_in_seconds=4.0)
# 21. Enter/Exit game mode taking screenshot.
enter_exit_game_mode_take_screenshot(f"{SCREENSHOT_NAME}.ppm", Tests.enter_game_mode, Tests.exit_game_mode)
# 22. Take screenshot.
ScreenshotHelper(general.idle_wait_frames).capture_screenshot_blocking(f"{SCREENSHOT_NAME}.ppm")
# 23. Exit game mode.
TestHelper.exit_game_mode(Tests.exit_game_mode)
TestHelper.wait_for_condition(function=lambda: not general.is_in_game_mode(), timeout_in_seconds=4.0)
# 24. Look for errors.
# 22. Look for errors.
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
for error_info in error_tracer.errors:
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")

@ -360,18 +360,6 @@ void ReflectedPropertyControl::CreateItems(XmlNodeRef node, CVarBlockPtr& outBlo
textureVar->Set(textureName);
}
}
else if (!azstricmp(type, "color"))
{
CSmartVariable<Vec3> colorVar;
AddVariable(group, colorVar, child->getTag(), humanReadableName.toUtf8().data(), strDescription.toUtf8().data(), func, pUserData, IVariable::DT_COLOR);
ColorB color;
if (child->getAttr("value", color))
{
ColorF colorLinear = ColorGammaToLinear(QColor(color.r, color.g, color.b));
Vec3 colorVec3(colorLinear.r, colorLinear.g, colorLinear.b);
colorVar->Set(colorVec3);
}
}
}
}

@ -243,9 +243,6 @@ void ReflectedPropertyItem::SetVariable(IVariable *var)
case ePropertySelection:
m_reflectedVarAdapter = new ReflectedVarEnumAdapter;
break;
case ePropertyColor:
m_reflectedVarAdapter = new ReflectedVarColorAdapter;
break;
case ePropertyUser:
m_reflectedVarAdapter = new ReflectedVarUserAdapter;
break;

@ -312,50 +312,6 @@ void ReflectedVarVector4Adapter::SyncIVarToReflectedVar(IVariable *pVariable)
pVariable->Set(Vec4(m_reflectedVar->m_value.GetX(), m_reflectedVar->m_value.GetY(), m_reflectedVar->m_value.GetZ(), m_reflectedVar->m_value.GetW()));
}
void ReflectedVarColorAdapter::SetVariable(IVariable *pVariable)
{
m_reflectedVar.reset(new CReflectedVarColor(pVariable->GetHumanName().toUtf8().data()));
m_reflectedVar->m_description = pVariable->GetDescription().toUtf8().data();
}
void ReflectedVarColorAdapter::SyncReflectedVarToIVar(IVariable *pVariable)
{
if (pVariable->GetType() == IVariable::VECTOR)
{
Vec3 v(0, 0, 0);
pVariable->Get(v);
const QColor col = ColorLinearToGamma(ColorF(v.x, v.y, v.z));
m_reflectedVar->m_color.Set(static_cast<float>(col.redF()), static_cast<float>(col.greenF()), static_cast<float>(col.blueF()));
}
else
{
int col(0);
pVariable->Get(col);
const QColor qcolor = ColorToQColor((uint32)col);
m_reflectedVar->m_color.Set(static_cast<float>(qcolor.redF()), static_cast<float>(qcolor.greenF()), static_cast<float>(qcolor.blueF()));
}
}
void ReflectedVarColorAdapter::SyncIVarToReflectedVar(IVariable *pVariable)
{
if (pVariable->GetType() == IVariable::VECTOR)
{
ColorF colLin = ColorGammaToLinear(QColor::fromRgbF(m_reflectedVar->m_color.GetX(), m_reflectedVar->m_color.GetY(), m_reflectedVar->m_color.GetZ()));
pVariable->Set(Vec3(colLin.r, colLin.g, colLin.b));
}
else
{
int ir = static_cast<int>(m_reflectedVar->m_color.GetX() * 255.0f);
int ig = static_cast<int>(m_reflectedVar->m_color.GetY() * 255.0f);
int ib = static_cast<int>(m_reflectedVar->m_color.GetZ() * 255.0f);
pVariable->Set(static_cast<int>(RGB(ir, ig, ib)));
}
}
void ReflectedVarResourceAdapter::SetVariable(IVariable *pVariable)
{
m_reflectedVar.reset(new CReflectedVarResource(pVariable->GetHumanName().toUtf8().data()));

@ -186,21 +186,6 @@ AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
class EDITOR_CORE_API ReflectedVarColorAdapter
: public ReflectedVarAdapter
{
public:
void SetVariable(IVariable* pVariable) override;
void SyncReflectedVarToIVar(IVariable* pVariable) override;
void SyncIVarToReflectedVar(IVariable* pVariable) override;
CReflectedVar* GetReflectedVar() override { return m_reflectedVar.data(); }
private:
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
QScopedPointer<CReflectedVarColor > m_reflectedVar;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
class EDITOR_CORE_API ReflectedVarResourceAdapter
: public ReflectedVarAdapter
{

@ -1,46 +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
*
*/
// Qt
#include <QColor>
#include <CryCommon/Cry_Color.h>
//////////////////////////////////////////////////////////////////////////
QColor ColorLinearToGamma(ColorF col)
{
float r = clamp_tpl(col.r, 0.0f, 1.0f);
float g = clamp_tpl(col.g, 0.0f, 1.0f);
float b = clamp_tpl(col.b, 0.0f, 1.0f);
float a = clamp_tpl(col.a, 0.0f, 1.0f);
r = (float)(r <= 0.0031308 ? (12.92 * r) : (1.055 * pow((double)r, 1.0 / 2.4) - 0.055));
g = (float)(g <= 0.0031308 ? (12.92 * g) : (1.055 * pow((double)g, 1.0 / 2.4) - 0.055));
b = (float)(b <= 0.0031308 ? (12.92 * b) : (1.055 * pow((double)b, 1.0 / 2.4) - 0.055));
return QColor(int(r * 255.0f), int(g * 255.0f), int(b * 255.0f), int(a * 255.0f));
}
//////////////////////////////////////////////////////////////////////////
ColorF ColorGammaToLinear(const QColor& col)
{
float r = (float)col.red() / 255.0f;
float g = (float)col.green() / 255.0f;
float b = (float)col.blue() / 255.0f;
float a = (float)col.alpha() / 255.0f;
return ColorF((float)(r <= 0.04045 ? (r / 12.92) : pow(((double)r + 0.055) / 1.055, 2.4)),
(float)(g <= 0.04045 ? (g / 12.92) : pow(((double)g + 0.055) / 1.055, 2.4)),
(float)(b <= 0.04045 ? (b / 12.92) : pow(((double)b + 0.055) / 1.055, 2.4)), a);
}
QColor ColorToQColor(uint32 color)
{
return QColor::fromRgbF((float)GetRValue(color) / 255.0f,
(float)GetGValue(color) / 255.0f,
(float)GetBValue(color) / 255.0f);
}

@ -6,6 +6,7 @@
*
*/
#include <AzCore/Math/Color.h>
#include "EditorDefs.h"
@ -184,9 +185,9 @@ QColor ColorLinearToGamma(ColorF col)
float b = clamp_tpl(col.b, 0.0f, 1.0f);
float a = clamp_tpl(col.a, 0.0f, 1.0f);
r = (float)(r <= 0.0031308 ? (12.92 * r) : (1.055 * pow((double)r, 1.0 / 2.4) - 0.055));
g = (float)(g <= 0.0031308 ? (12.92 * g) : (1.055 * pow((double)g, 1.0 / 2.4) - 0.055));
b = (float)(b <= 0.0031308 ? (12.92 * b) : (1.055 * pow((double)b, 1.0 / 2.4) - 0.055));
r = AZ::Color::ConvertSrgbLinearToGamma(r);
g = AZ::Color::ConvertSrgbLinearToGamma(g);
b = AZ::Color::ConvertSrgbLinearToGamma(b);
return QColor(int(r * 255.0f), int(g * 255.0f), int(b * 255.0f), int(a * 255.0f));
}
@ -199,9 +200,9 @@ ColorF ColorGammaToLinear(const QColor& col)
float b = (float)col.blue() / 255.0f;
float a = (float)col.alpha() / 255.0f;
return ColorF((float)(r <= 0.04045 ? (r / 12.92) : pow(((double)r + 0.055) / 1.055, 2.4)),
(float)(g <= 0.04045 ? (g / 12.92) : pow(((double)g + 0.055) / 1.055, 2.4)),
(float)(b <= 0.04045 ? (b / 12.92) : pow(((double)b + 0.055) / 1.055, 2.4)), a);
return ColorF(AZ::Color::ConvertSrgbGammaToLinear(r),
AZ::Color::ConvertSrgbGammaToLinear(g),
AZ::Color::ConvertSrgbGammaToLinear(b), a);
}
QColor ColorToQColor(uint32 color)

@ -48,7 +48,6 @@ set(FILES
Util/Image.cpp
Util/ImageHistogram.h
Util/Image.h
Util/ColorUtils.cpp
Undo/Undo.cpp
Undo/IUndoManagerListener.h
Undo/IUndoObject.h

@ -134,6 +134,12 @@ namespace AZ
//! Color from u32 => 0xAABBGGRR, RGB convert from Gamma corrected to Linear values.
void FromU32GammaToLinear(u32 c);
//! Convert SRGB gamma space to linear space
static float ConvertSrgbGammaToLinear(float x);
//! Convert SRGB linear space to gamma space
static float ConvertSrgbLinearToGamma(float x);
//! Convert color from linear to gamma corrected space.
Color LinearToGamma() const;

@ -370,6 +370,15 @@ namespace AZ
*this = GammaToLinear();
}
AZ_MATH_INLINE float Color::ConvertSrgbGammaToLinear(float x)
{
return x <= 0.04045 ? (x / 12.92f) : static_cast<float>(pow((static_cast<double>(x) + 0.055) / 1.055, 2.4));
}
AZ_MATH_INLINE float Color::ConvertSrgbLinearToGamma(float x)
{
return x <= 0.0031308 ? 12.92f * x : static_cast<float>(1.055 * pow(static_cast<double>(x), 1.0 / 2.4) - 0.055);
}
AZ_MATH_INLINE Color Color::LinearToGamma() const
{
@ -377,9 +386,9 @@ namespace AZ
float g = GetG();
float b = GetB();
r = (r <= 0.0031308 ? 12.92f * r : static_cast<float>(1.055 * pow(static_cast<double>(r), 1.0 / 2.4) - 0.055));
g = (g <= 0.0031308 ? 12.92f * g : static_cast<float>(1.055 * pow(static_cast<double>(g), 1.0 / 2.4) - 0.055));
b = (b <= 0.0031308 ? 12.92f * b : static_cast<float>(1.055 * pow(static_cast<double>(b), 1.0 / 2.4) - 0.055));
r = ConvertSrgbLinearToGamma(r);
g = ConvertSrgbLinearToGamma(g);
b = ConvertSrgbLinearToGamma(b);
return Color(r,g,b,GetA());
}
@ -391,9 +400,9 @@ namespace AZ
float g = GetG();
float b = GetB();
return Color(r <= 0.04045 ? (r / 12.92f) : static_cast<float>(pow((static_cast<double>(r) + 0.055) / 1.055, 2.4)),
g <= 0.04045 ? (g / 12.92f) : static_cast<float>(pow((static_cast<double>(g) + 0.055) / 1.055, 2.4)),
b <= 0.04045 ? (b / 12.92f) : static_cast<float>(pow((static_cast<double>(b) + 0.055) / 1.055, 2.4)), GetA());
return Color(ConvertSrgbGammaToLinear(r),
ConvertSrgbGammaToLinear(g),
ConvertSrgbGammaToLinear(b), GetA());
}

@ -22,6 +22,7 @@
#include <AzCore/Serialization/Json/UnorderedSetSerializer.h>
#include <AzCore/Serialization/Json/UnsupportedTypesSerializer.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/ConfigurableStack.h>
#include <AzCore/std/any.h>
#include <AzCore/std/optional.h>
#include <AzCore/std/tuple.h>
@ -50,6 +51,8 @@ namespace AZ
void JsonSystemComponent::Reflect(ReflectContext* reflectContext)
{
JsonConfigurableStackSerializer::Reflect(reflectContext);
if (JsonRegistrationContext* jsonContext = azrtti_cast<JsonRegistrationContext*>(reflectContext))
{
jsonContext->Serializer<JsonBoolSerializer>()->HandlesType<bool>();

@ -8,11 +8,12 @@
#pragma once
#include <AzCore/Math/Uuid.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/RTTI/ReflectContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/Math/Uuid.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
namespace AZ
@ -22,6 +23,7 @@ namespace AZ
{
public:
AZ_RTTI(JsonRegistrationContext, "{5A763774-CA8B-4245-A897-A03C503DCD60}", ReflectContext);
AZ_CLASS_ALLOCATOR(JsonRegistrationContext, SystemAllocator, 0);
class SerializerBuilder;
using SerializerMap = AZStd::unordered_map<Uuid, AZStd::unique_ptr<BaseJsonSerializer>, AZStd::hash<Uuid>>;

@ -0,0 +1,172 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Settings/ConfigurableStack.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/std/containers/queue.h>
#include <AzCore/std/tuple.h>
namespace AZ
{
AZ_CLASS_ALLOCATOR_IMPL(JsonConfigurableStackSerializer, AZ::SystemAllocator, 0);
JsonSerializationResult::Result JsonConfigurableStackSerializer::Load(
void* outputValue,
[[maybe_unused]] const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
switch (inputValue.GetType())
{
case rapidjson::kArrayType:
return LoadFromArray(outputValue, inputValue, context);
case rapidjson::kObjectType:
return LoadFromObject(outputValue, inputValue, context);
case rapidjson::kNullType:
[[fallthrough]];
case rapidjson::kFalseType:
[[fallthrough]];
case rapidjson::kTrueType:
[[fallthrough]];
case rapidjson::kStringType:
[[fallthrough]];
case rapidjson::kNumberType:
return context.Report(
JSR::Tasks::ReadField, JSR::Outcomes::Unsupported,
"Unsupported type. Configurable stack values can only be read from arrays or objects.");
default:
return context.Report(JSR::Tasks::ReadField, JSR::Outcomes::Unknown, "Unknown json type encountered for string value.");
}
}
JsonSerializationResult::Result JsonConfigurableStackSerializer::Store(
[[maybe_unused]] rapidjson::Value& outputValue,
[[maybe_unused]] const void* inputValue,
[[maybe_unused]] const void* defaultValue,
[[maybe_unused]] const Uuid& valueTypeId,
JsonSerializerContext& context)
{
return context.Report(
JsonSerializationResult::Tasks::WriteValue, JsonSerializationResult::Outcomes::Unsupported,
"Configuration stacks can not be written out.");
}
void JsonConfigurableStackSerializer::Reflect(ReflectContext* context)
{
if (JsonRegistrationContext* jsonContext = azrtti_cast<JsonRegistrationContext*>(context))
{
jsonContext->Serializer<JsonConfigurableStackSerializer>()->HandlesType<ConfigurableStack>();
}
}
JsonSerializationResult::Result JsonConfigurableStackSerializer::LoadFromArray(
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
auto stack = reinterpret_cast<ConfigurableStackInterface*>(outputValue);
const Uuid& nodeValueType = stack->GetNodeType();
JSR::ResultCode result(JSR::Tasks::ReadField);
uint32_t counter = 0;
for (auto& it : inputValue.GetArray())
{
ScopedContextPath subPath(context, counter);
void* value = stack->AddNode(AZStd::to_string(counter));
result.Combine(ContinueLoading(value, nodeValueType, it, context));
counter++;
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Loaded configurable stack from array."
: "Failed to load configurable stack from array.");
}
JsonSerializationResult::Result JsonConfigurableStackSerializer::LoadFromObject(
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context)
{
namespace JSR = JsonSerializationResult; // Used remove name conflicts in AzCore in uber builds.
auto stack = reinterpret_cast<ConfigurableStackInterface*>(outputValue);
const Uuid& nodeValueType = stack->GetNodeType();
AZStd::queue<AZStd::tuple<ConfigurableStackInterface::InsertPosition, AZStd::string_view, rapidjson::Value::ConstMemberIterator>>
delayedEntries;
JSR::ResultCode result(JSR::Tasks::ReadField);
auto add = [&](ConfigurableStackInterface::InsertPosition position, rapidjson::Value::ConstMemberIterator target,
rapidjson::Value::ConstMemberIterator it)
{
if (target->value.IsString())
{
delayedEntries.emplace(position, AZStd::string_view(target->value.GetString(), target->value.GetStringLength()), it);
}
else
{
result.Combine(context.Report(
JSR::Tasks::ReadField, JSR::Outcomes::Skipped,
"Skipped value for the Configurable Stack because the target wasn't a string."));
}
};
// Load all the regular entries into the stack and store any with a before or after binding for
// later inserting.
for (auto it = inputValue.MemberBegin(); it != inputValue.MemberEnd(); ++it)
{
AZStd::string_view name(it->name.GetString(), it->name.GetStringLength());
ScopedContextPath subPath(context, name);
if (it->value.IsObject())
{
if (auto target = it->value.FindMember(StackBefore); target != it->value.MemberEnd())
{
add(ConfigurableStackInterface::InsertPosition::Before, target, it);
continue;
}
if (auto target = it->value.FindMember(StackAfter); target != it->value.MemberEnd())
{
add(ConfigurableStackInterface::InsertPosition::After, target, it);
continue;
}
}
void* value = stack->AddNode(name);
result.Combine(ContinueLoading(value, nodeValueType, it->value, context));
}
// Insert the entries that have been delayed.
while (!delayedEntries.empty())
{
auto&& [insertPosition, target, valueStore] = delayedEntries.front();
AZStd::string_view name(valueStore->name.GetString(), valueStore->name.GetStringLength());
ScopedContextPath subPath(context, name);
void* value = stack->AddNode(name, target, insertPosition);
if (value != nullptr)
{
result.Combine(ContinueLoading(value, nodeValueType, valueStore->value, context));
}
else
{
result.Combine(context.Report(
JSR::Tasks::ReadField, JSR::Outcomes::Skipped,
AZStd::string::format(
"Skipped value for the Configurable Stack because the target '%.*s' couldn't be found.", AZ_STRING_ARG(name))));
}
delayedEntries.pop();
}
return context.Report(
result,
result.GetProcessing() != JSR::Processing::Halted ? "Loaded configurable stack from array."
: "Failed to load configurable stack from array.");
}
} // namespace AZ

@ -0,0 +1,187 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/base.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzCore/std/string/string.h>
#include <AzCore/std/utils.h>
namespace AZ
{
//! The ConfigurableStack makes configuring stacks and arrays through the Settings Registry easier than using direct serialization
//! to a container like AZStd::vector. It does this by using JSON Objects rather than JSON arrays, although arrays are supported
//! for backwards compatibility. Two key words were added:
//! - $stack_before : Insert the new entry before the referenced entry. Referencing is done by name.
//! - $stack_after : Insert the new entry after the referenced entry. Referencing is done by name.
//! to allow inserting new entries at specific locations. An example of a .setreg file at updates existing settings would be:
//! // Original settings
//! {
//! "Settings in a stack":
//! {
//! "AnOriginalEntry":
//! {
//! "MyValue": "hello",
//! "ExampleValue": 84
//! },
//! "TheSecondEntry":
//! {
//! "MyValue": "world"
//! }
//! }
//! }
//!
//! // Customized settings.
//! {
//! "Settings in a stack":
//! {
//! // Add a new entry before "AnOriginalEntry" in the original document.
//! "NewEntry":
//! {
//! "$stack_before": "AnOriginalEntry",
//! "MyValue": 42
//! },
//! // Add a second entry after "AnOriginalEntry" in the original document.
//! "SecondNewEntry":
//! {
//! "$stack_after": "AnOriginalEntry",
//! "MyValue": "FortyTwo".
//! },
//! // Update a value in "AnOriginalEntry".
//! "AnOriginalEntry":
//! {
//! "ExampleValue": 42
//! },
//! // Delete the "TheSecondEntry" from the settings.
//! "TheSecondEntry" : null,
//! }
//! }
//!
//! The ConfigurableStack uses an AZStd::shared_ptr to store the values. This supports settings up a base class and specifying
//! derived classes in the settings, but requires that the base and derived classes all have a memory allocator associated with
//! them (i.e. by using the "AZ_CLASS_ALLOCATOR" macro) and that the relation of the classes is reflected. Loading a
//! ConfigurableStack can be done using the GetObject call on the SettingsRegistryInterface.
class ReflectContext;
class ConfigurableStackInterface
{
public:
friend class JsonConfigurableStackSerializer;
virtual ~ConfigurableStackInterface() = default;
virtual const TypeId& GetNodeType() const = 0;
protected:
enum class InsertPosition
{
Before,
After
};
virtual void* AddNode(AZStd::string name) = 0;
virtual void* AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) = 0;
};
template<typename StackBaseType>
class ConfigurableStack final : public ConfigurableStackInterface
{
public:
using NodeValue = AZStd::shared_ptr<StackBaseType>;
using Node = AZStd::pair<AZStd::string, NodeValue>;
using NodeContainer = AZStd::vector<Node>;
using Iterator = typename NodeContainer::iterator;
using ConstIterator = typename NodeContainer::const_iterator;
~ConfigurableStack() override = default;
const TypeId& GetNodeType() const override;
Iterator begin();
Iterator end();
ConstIterator begin() const;
ConstIterator end() const;
size_t size() const;
protected:
void* AddNode(AZStd::string name) override;
void* AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position) override;
private:
NodeContainer m_nodes;
};
AZ_TYPE_INFO_TEMPLATE(ConfigurableStack, "{0A3D2038-6E6A-4EFD-A1B4-F30D947E21B1}", AZ_TYPE_INFO_TYPENAME);
template<typename StackBaseType>
struct SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>
{
using ConfigurableStackType = ConfigurableStack<StackBaseType>;
class GenericConfigurableStackInfo : public GenericClassInfo
{
public:
AZ_TYPE_INFO(GenericConfigurableStackInfo, "{FC5A9353-D0DE-48F6-81B5-1CB2985C5F65}");
GenericConfigurableStackInfo();
SerializeContext::ClassData* GetClassData() override;
size_t GetNumTemplatedArguments() override;
const Uuid& GetTemplatedTypeId([[maybe_unused]] size_t element) override;
const Uuid& GetSpecializedTypeId() const override;
const Uuid& GetGenericTypeId() const override;
void Reflect(SerializeContext* serializeContext) override;
SerializeContext::ClassData m_classData;
};
using ClassInfoType = GenericConfigurableStackInfo;
static ClassInfoType* GetGenericInfo();
static const Uuid& GetClassTypeId();
};
class JsonConfigurableStackSerializer : public BaseJsonSerializer
{
public:
AZ_RTTI(JsonConfigurableStackSerializer, "{45A31805-9058-41A9-B1A3-71E2CB4D9237}", BaseJsonSerializer);
AZ_CLASS_ALLOCATOR_DECL;
JsonSerializationResult::Result Load(
void* outputValue,
const Uuid& outputValueTypeId,
const rapidjson::Value& inputValue,
JsonDeserializerContext& context) override;
JsonSerializationResult::Result Store(
rapidjson::Value& outputValue,
const void* inputValue,
const void* defaultValue,
const Uuid& valueTypeId,
JsonSerializerContext& context) override;
static void Reflect(ReflectContext* context);
private:
JsonSerializationResult::Result LoadFromArray(
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context);
JsonSerializationResult::Result LoadFromObject(
void* outputValue, const rapidjson::Value& inputValue, JsonDeserializerContext& context);
static constexpr const char* StackBefore = "$stack_before";
static constexpr const char* StackAfter = "$stack_after";
};
} // namespace AZ
#include <AzCore/Settings/ConfigurableStack.inl>

@ -0,0 +1,149 @@
/*
* 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 AZ
{
//
// ConfigurableStack
//
template<typename StackBaseType>
const TypeId& ConfigurableStack<StackBaseType>::GetNodeType() const
{
return azrtti_typeid<NodeValue>();
}
template<typename StackBaseType>
auto ConfigurableStack<StackBaseType>::begin() -> Iterator
{
return m_nodes.begin();
}
template<typename StackBaseType>
auto ConfigurableStack<StackBaseType>::end() -> Iterator
{
return m_nodes.end();
}
template<typename StackBaseType>
auto ConfigurableStack<StackBaseType>::begin() const -> ConstIterator
{
return m_nodes.begin();
}
template<typename StackBaseType>
auto ConfigurableStack<StackBaseType>::end() const -> ConstIterator
{
return m_nodes.end();
}
template<typename StackBaseType>
size_t ConfigurableStack<StackBaseType>::size() const
{
return m_nodes.size();
}
template<typename StackBaseType>
void* ConfigurableStack<StackBaseType>::AddNode(AZStd::string name)
{
Node& result = m_nodes.emplace_back(AZStd::move(name));
return &result.second;
}
template<typename StackBaseType>
void* ConfigurableStack<StackBaseType>::AddNode(AZStd::string name, AZStd::string_view target, InsertPosition position)
{
auto end = m_nodes.end();
for (auto it = m_nodes.begin(); it != end; ++it)
{
if (it->first == target)
{
if (position == InsertPosition::After)
{
++it;
}
auto result = m_nodes.insert(it, Node(AZStd::move(name), {}));
return &result->second;
}
}
return nullptr;
}
//
// SerializeGenericTypeInfo
//
template<typename StackBaseType>
SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GenericConfigurableStackInfo()
: m_classData{ SerializeContext::ClassData::Create<ConfigurableStackType>(
"AZ::ConfigurableStack", GetSpecializedTypeId(), Internal::NullFactory::GetInstance(), nullptr, nullptr) }
{
}
template<typename StackBaseType>
SerializeContext::ClassData* SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetClassData()
{
return &m_classData;
}
template<typename StackBaseType>
size_t SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetNumTemplatedArguments()
{
return 1;
}
template<typename StackBaseType>
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetTemplatedTypeId(
[[maybe_unused]] size_t element)
{
return SerializeGenericTypeInfo<StackBaseType>::GetClassTypeId();
}
template<typename StackBaseType>
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetSpecializedTypeId() const
{
return azrtti_typeid<ConfigurableStackType>();
}
template<typename StackBaseType>
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::GetGenericTypeId() const
{
return TYPEINFO_Uuid();
}
template<typename StackBaseType>
void SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GenericConfigurableStackInfo::Reflect(SerializeContext* serializeContext)
{
if (serializeContext)
{
serializeContext->RegisterGenericClassInfo(GetSpecializedTypeId(), this, &AnyTypeInfoConcept<ConfigurableStackType>::CreateAny);
if (serializeContext->FindClassData(azrtti_typeid<AZStd::shared_ptr<StackBaseType>>()) == nullptr)
{
serializeContext->RegisterGenericType<AZStd::shared_ptr<StackBaseType>>();
}
}
}
template<typename StackBaseType>
auto SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GetGenericInfo() -> ClassInfoType*
{
return GetCurrentSerializeContextModule().CreateGenericClassInfo<ConfigurableStackType>();
}
template<typename StackBaseType>
const Uuid& SerializeGenericTypeInfo<ConfigurableStack<StackBaseType>>::GetClassTypeId()
{
return GetGenericInfo()->GetClassData()->m_typeId;
}
} // namespace AZ

@ -560,6 +560,9 @@ set(FILES
Serialization/std/VariantReflection.inl
Settings/CommandLine.cpp
Settings/CommandLine.h
Settings/ConfigurableStack.cpp
Settings/ConfigurableStack.inl
Settings/ConfigurableStack.h
Settings/SettingsRegistry.cpp
Settings/SettingsRegistry.h
Settings/SettingsRegistryConsoleUtils.cpp

@ -0,0 +1,281 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/JSON/document.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/Settings/ConfigurableStack.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/RegistrationContext.h>
#include <AzCore/Serialization/Json/JsonSystemComponent.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/UnitTest/TestTypes.h>
namespace UnitTest
{
struct ConfigInt
{
AZ_TYPE_INFO(UnitTest::ConfigInt, "{1FAF6E55-7FA4-4FFA-8C41-34F422B8E8AB}");
AZ_CLASS_ALLOCATOR(ConfigInt, AZ::SystemAllocator, 0);
int m_value;
static void Reflect(AZ::ReflectContext* context)
{
if (auto sc = azrtti_cast<AZ::SerializeContext*>(context))
{
sc->Class<ConfigInt>()->Field("Value", &ConfigInt::m_value);
}
}
};
struct ConfigurableStackTests : public ScopedAllocatorSetupFixture
{
void Reflect(AZ::ReflectContext* context)
{
if (auto sc = azrtti_cast<AZ::SerializeContext*>(context))
{
AZ::JsonSystemComponent::Reflect(sc);
ConfigInt::Reflect(sc);
sc->RegisterGenericType<AZ::ConfigurableStack<ConfigInt>>();
}
else if (auto jrc = azrtti_cast<AZ::JsonRegistrationContext*>(context))
{
AZ::JsonSystemComponent::Reflect(jrc);
}
}
void SetUp() override
{
ScopedAllocatorSetupFixture::SetUp();
m_serializeContext = AZStd::make_unique<AZ::SerializeContext>();
m_jsonRegistrationContext = AZStd::make_unique<AZ::JsonRegistrationContext>();
Reflect(m_serializeContext.get());
Reflect(m_jsonRegistrationContext.get());
m_deserializationSettings.m_registrationContext = m_jsonRegistrationContext.get();
m_deserializationSettings.m_serializeContext = m_serializeContext.get();
}
void TearDown()
{
m_jsonRegistrationContext->EnableRemoveReflection();
Reflect(m_jsonRegistrationContext.get());
m_jsonRegistrationContext->DisableRemoveReflection();
m_serializeContext->EnableRemoveReflection();
Reflect(m_serializeContext.get());
m_serializeContext->DisableRemoveReflection();
ScopedAllocatorSetupFixture::TearDown();
}
void ObjectTest(AZStd::string_view jsonText)
{
AZ::ConfigurableStack<ConfigInt> stack;
rapidjson::Document document;
document.Parse(jsonText.data(), jsonText.length());
ASSERT_FALSE(document.HasParseError());
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
ASSERT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
ASSERT_EQ(4, stack.size());
int numberCounter = 0;
int valueCounter = 42;
for (auto& [name, value] : stack)
{
EXPECT_STREQ(AZStd::string::format("Value%i", numberCounter).c_str(), name.c_str());
EXPECT_EQ(valueCounter, value->m_value);
numberCounter++;
valueCounter++;
}
}
AZStd::unique_ptr<AZ::SerializeContext> m_serializeContext;
AZStd::unique_ptr<AZ::JsonRegistrationContext> m_jsonRegistrationContext;
AZ::JsonDeserializerSettings m_deserializationSettings;
};
TEST_F(ConfigurableStackTests, DeserializeArray)
{
AZ::ConfigurableStack<ConfigInt> stack;
rapidjson::Document document;
document.Parse(
R"([
{ "Value": 42 },
{ "Value": 43 },
{ "Value": 44 },
{ "Value": 45 }
])");
ASSERT_FALSE(document.HasParseError());
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
ASSERT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
ASSERT_EQ(4, stack.size());
int numberCounter = 0;
int valueCounter = 42;
for (auto& [name, value] : stack)
{
EXPECT_STREQ(AZStd::to_string(numberCounter).c_str(), name.c_str());
EXPECT_EQ(valueCounter, value->m_value);
numberCounter++;
valueCounter++;
}
}
TEST_F(ConfigurableStackTests, DeserializeObject)
{
ObjectTest(
R"({
"Value0": { "Value": 42 },
"Value1": { "Value": 43 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 }
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithLateBefore)
{
ObjectTest(
R"({
"Value0": { "Value": 42 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 },
"Value1":
{
"$stack_before": "Value2",
"Value": 43
}
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithEarlyBefore)
{
ObjectTest(
R"({
"Value1":
{
"$stack_before": "Value2",
"Value": 43
},
"Value0": { "Value": 42 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 }
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithLateAfter)
{
ObjectTest(
R"({
"Value0": { "Value": 42 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 },
"Value1":
{
"$stack_after": "Value0",
"Value": 43
}
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithEarlyAfter)
{
ObjectTest(
R"({
"Value1":
{
"$stack_after": "Value0",
"Value": 43
},
"Value0": { "Value": 42 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 }
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithBeforeFirst)
{
ObjectTest(
R"({
"Value1": { "Value": 43 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 },
"Value0":
{
"$stack_before": "Value1",
"Value": 42
}
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithInsertAfterLast)
{
ObjectTest(
R"({
"Value3":
{
"$stack_after": "Value2",
"Value": 45
},
"Value0": { "Value": 42 },
"Value1": { "Value": 43 },
"Value2": { "Value": 44 }
})");
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithInvalidTarget)
{
AZ::ConfigurableStack<ConfigInt> stack;
rapidjson::Document document;
document.Parse(
R"({
"Value1":
{
"$stack_after": "airplane",
"Value": 43
},
"Value0": { "Value": 42 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 }
})");
ASSERT_FALSE(document.HasParseError());
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialSkip, result.GetOutcome());
EXPECT_EQ(3, stack.size());
}
TEST_F(ConfigurableStackTests, DeserializeObjectWithInvalidTargetType)
{
AZ::ConfigurableStack<ConfigInt> stack;
rapidjson::Document document;
document.Parse(
R"({
"Value1":
{
"$stack_after": 42,
"Value": 43
},
"Value0": { "Value": 42 },
"Value2": { "Value": 44 },
"Value3": { "Value": 45 }
})");
ASSERT_FALSE(document.HasParseError());
AZ::JsonSerializationResult::ResultCode result = AZ::JsonSerialization::Load(stack, document, m_deserializationSettings);
EXPECT_EQ(AZ::JsonSerializationResult::Processing::Completed, result.GetProcessing());
EXPECT_EQ(AZ::JsonSerializationResult::Outcomes::PartialSkip, result.GetOutcome());
EXPECT_EQ(3, stack.size());
}
} // namespace UnitTest

@ -76,6 +76,7 @@ set(FILES
Name/NameTests.cpp
RTTI/TypeSafeIntegralTests.cpp
Settings/CommandLineTests.cpp
Settings/ConfigurableStackTests.cpp
Settings/SettingsRegistryTests.cpp
Settings/SettingsRegistryConsoleUtilsTests.cpp
Settings/SettingsRegistryMergeUtilsTests.cpp

@ -140,28 +140,28 @@ namespace AzFramework
//! Given a list of XY coordinates, call the provided callback function with surface data corresponding to each
//! XY coordinate in the list.
virtual void ProcessHeightsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessHeightsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessNormalsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessNormalsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessSurfaceWeightsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessSurfaceWeightsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessSurfacePointsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessSurfacePointsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessHeightsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessHeightsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessNormalsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessNormalsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessSurfaceWeightsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessSurfaceWeightsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
virtual void ProcessSurfacePointsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessSurfacePointsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const = 0;
@ -272,42 +272,42 @@ namespace AzFramework
//! Asynchronous versions of the various 'Process*' API functions declared above.
//! It's the responsibility of the caller to ensure all callbacks are threadsafe.
virtual AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;
virtual AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const = 0;

@ -77,21 +77,21 @@ namespace UnitTest
MOCK_CONST_METHOD5(
GetSurfacePointFromFloats, void(float, float, AzFramework::SurfaceData::SurfacePoint&, Sampler, bool*));
MOCK_CONST_METHOD3(
ProcessHeightsFromList, void(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessHeightsFromList, void(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessNormalsFromList, void(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessNormalsFromList, void(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessSurfaceWeightsFromList, void(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessSurfaceWeightsFromList, void(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessSurfacePointsFromList, void(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessSurfacePointsFromList, void(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessHeightsFromListOfVector2, void(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessHeightsFromListOfVector2, void(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessNormalsFromListOfVector2, void(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessNormalsFromListOfVector2, void(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessSurfaceWeightsFromListOfVector2, void(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessSurfaceWeightsFromListOfVector2, void(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD3(
ProcessSurfacePointsFromListOfVector2, void(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
ProcessSurfacePointsFromListOfVector2, void(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler));
MOCK_CONST_METHOD2(
GetNumSamplesFromRegion, AZStd::pair<size_t, size_t>(const AZ::Aabb&, const AZ::Vector2&));
MOCK_CONST_METHOD4(
@ -107,21 +107,21 @@ namespace UnitTest
MOCK_CONST_METHOD1(
GetClosestIntersection, AzFramework::RenderGeometry::RayResult(const AzFramework::RenderGeometry::RayRequest&));
MOCK_CONST_METHOD4(
ProcessHeightsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessHeightsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessNormalsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessNormalsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessSurfaceWeightsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessSurfaceWeightsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessSurfacePointsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessSurfacePointsFromListAsync, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector3>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessHeightsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessHeightsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessNormalsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessNormalsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessSurfaceWeightsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessSurfaceWeightsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD4(
ProcessSurfacePointsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
ProcessSurfacePointsFromListOfVector2Async, AZStd::shared_ptr<TerrainJobContext>(const AZStd::span<const AZ::Vector2>&, AzFramework::Terrain::SurfacePointListFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD5(
ProcessHeightsFromRegionAsync, AZStd::shared_ptr<TerrainJobContext>(const AZ::Aabb&, const AZ::Vector2&, AzFramework::Terrain::SurfacePointRegionFillCallback, Sampler, AZStd::shared_ptr<ProcessAsyncParams>));
MOCK_CONST_METHOD5(

@ -11,6 +11,7 @@
#include <Processing/PixelFormatInfo.h>
#include <Processing/ImageFlags.h>
#include <Atom/ImageProcessing/PixelFormats.h>
#include <AzCore/Math/Color.h>
#include <Converters/FIR-Weights.h>
#include <Converters/PixelOperation.h>
@ -118,19 +119,8 @@ namespace ImageProcessingAtom
float m_fMaxDiff = 0.0f;
};
static float GammaToLinear(float x)
{
return (x <= 0.04045f) ? x / 12.92f : powf((x + 0.055f) / 1.055f, 2.4f);
}
static float LinearToGamma(float x)
{
return (x <= 0.0031308f) ? x * 12.92f : 1.055f * powf(x, 1.0f / 2.4f) - 0.055f;
}
static FunctionLookupTable<1024> s_lutGammaToLinear(GammaToLinear, 0.04045f, 0.00001f);
static FunctionLookupTable<1024> s_lutLinearToGamma(LinearToGamma, 0.05f, 0.00001f);
static FunctionLookupTable<1024> s_lutGammaToLinear(AZ::Color::ConvertSrgbGammaToLinear, 0.04045f, 0.00001f);
static FunctionLookupTable<1024> s_lutLinearToGamma(AZ::Color::ConvertSrgbLinearToGamma, 0.05f, 0.00001f);
///////////////////////////////////////////////////////////////////////////////////

@ -14,6 +14,8 @@
#include <Atom/RPI.Public/RPIUtils.h>
#include <Atom/RPI.Public/Shader/Shader.h>
#include <AzCore/Math/Color.h>
#include <AzCore/std/containers/array.h>
#include <AzFramework/Asset/AssetSystemBus.h>
namespace AZ
@ -105,6 +107,25 @@ namespace AZ
return ((value - origMin) / (origMax - origMin)) * (scaledMax - scaledMin) + scaledMin;
}
// Pre-compute a lookup table for converting SRGB gamma to linear
// by specifying the AZ::u8 so we don't have to do the computation
// when retrieving pixels
using ConversionLookupTable = AZStd::array<float, 256>;
ConversionLookupTable CreateSrgbGammaToLinearLookupTable()
{
ConversionLookupTable lookupTable;
for (size_t i = 0; i < lookupTable.array_size; ++i)
{
float srgbValue = i / static_cast<float>(std::numeric_limits<AZ::u8>::max());
lookupTable[i] = AZ::Color::ConvertSrgbGammaToLinear(srgbValue);
}
return lookupTable;
}
static ConversionLookupTable s_SrgbGammaToLinearLookupTable = CreateSrgbGammaToLinearLookupTable();
float RetrieveFloatValue(const AZ::u8* mem, size_t index, AZ::RHI::Format format)
{
switch (format)
@ -113,12 +134,23 @@ namespace AZ
case AZ::RHI::Format::A8_UNORM:
case AZ::RHI::Format::R8G8_UNORM:
case AZ::RHI::Format::R8G8B8A8_UNORM:
case AZ::RHI::Format::A8B8G8R8_UNORM:
{
return mem[index] / static_cast<float>(std::numeric_limits<AZ::u8>::max());
}
case AZ::RHI::Format::R8_UNORM_SRGB:
case AZ::RHI::Format::R8G8_UNORM_SRGB:
case AZ::RHI::Format::R8G8B8A8_UNORM_SRGB:
case AZ::RHI::Format::A8B8G8R8_UNORM_SRGB:
{
// Use a lookup table that takes an AZ::u8 instead of a float
// for better performance
return s_SrgbGammaToLinearLookupTable[mem[index]];
}
case AZ::RHI::Format::R8_SNORM:
case AZ::RHI::Format::R8G8_SNORM:
case AZ::RHI::Format::R8G8B8A8_SNORM:
case AZ::RHI::Format::A8B8G8R8_SNORM:
{
// Scale the value from AZ::s8 min/max to -1 to 1
// We need to treat -128 and -127 the same, so that we get a symmetric
@ -452,9 +484,15 @@ namespace AZ
case AZ::RHI::Format::A8_UNORM:
case AZ::RHI::Format::R8G8_UNORM:
case AZ::RHI::Format::R8G8B8A8_UNORM:
case AZ::RHI::Format::A8B8G8R8_UNORM:
case AZ::RHI::Format::R8_UNORM_SRGB:
case AZ::RHI::Format::R8G8_UNORM_SRGB:
case AZ::RHI::Format::R8G8B8A8_UNORM_SRGB:
case AZ::RHI::Format::A8B8G8R8_UNORM_SRGB:
case AZ::RHI::Format::R8_SNORM:
case AZ::RHI::Format::R8G8_SNORM:
case AZ::RHI::Format::R8G8B8A8_SNORM:
case AZ::RHI::Format::A8B8G8R8_SNORM:
case AZ::RHI::Format::D16_UNORM:
case AZ::RHI::Format::R16_UNORM:
case AZ::RHI::Format::R16G16_UNORM:

@ -144,44 +144,46 @@ namespace SurfaceData
return false;
}
bool SurfaceDataMeshComponent::DoRayTrace(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const
void SurfaceDataMeshComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);
GetSurfacePointsFromList(AZStd::span<const AZ::Vector3>(&inPosition, 1), surfacePointList);
}
// test AABB as first pass to claim the point
const AZ::Vector3 testPosition = AZ::Vector3(
inPosition.GetX(),
inPosition.GetY(),
(m_meshBounds.GetMax().GetZ() + m_meshBounds.GetMin().GetZ()) * 0.5f);
void SurfaceDataMeshComponent::GetSurfacePointsFromList(
AZStd::span<const AZ::Vector3> inPositions, SurfacePointList& surfacePointList) const
{
AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
if (!m_meshBounds.Contains(testPosition))
{
return false;
}
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);
AZ::RPI::ModelAsset* mesh = m_meshAssetData.GetAs<AZ::RPI::ModelAsset>();
if (!mesh)
{
return false;
return;
}
const AZ::Vector3 rayStart = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_meshBounds.GetMax().GetZ() + s_rayAABBHeightPadding);
const AZ::Vector3 rayEnd = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_meshBounds.GetMin().GetZ() - s_rayAABBHeightPadding);
return GetMeshRayIntersection(
*mesh, m_meshWorldTM, m_meshWorldTMInverse, m_meshNonUniformScale, rayStart, rayEnd, outPosition, outNormal);
}
void SurfaceDataMeshComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
if (DoRayTrace(inPosition, hitPosition, hitNormal))
for (auto& inPosition : inPositions)
{
surfacePointList.AddSurfacePoint(GetEntityId(), inPosition, hitPosition, hitNormal, m_newPointWeights);
// test AABB as first pass to claim the point
if (SurfaceData::AabbContains2D(m_meshBounds, inPosition))
{
const AZ::Vector3 rayStart =
AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_meshBounds.GetMax().GetZ() + s_rayAABBHeightPadding);
const AZ::Vector3 rayEnd =
AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_meshBounds.GetMin().GetZ() - s_rayAABBHeightPadding);
bool rayHit = GetMeshRayIntersection(
*mesh, m_meshWorldTM, m_meshWorldTMInverse, m_meshNonUniformScale, rayStart, rayEnd, hitPosition, hitNormal);
if (rayHit)
{
surfacePointList.AddSurfacePoint(GetEntityId(), inPosition, hitPosition, hitNormal, m_newPointWeights);
}
}
}
}
AZ::Aabb SurfaceDataMeshComponent::GetSurfaceAabb() const
{
return m_meshBounds;

@ -80,9 +80,9 @@ namespace SurfaceData
////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const override;
void GetSurfacePointsFromList(AZStd::span<const AZ::Vector3> inPositions, SurfacePointList& surfacePointList) const override;
private:
bool DoRayTrace(const AZ::Vector3& inPosition, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const;
void UpdateMeshData();
void OnCompositionChanged();

@ -302,25 +302,28 @@ namespace EMStudio
}
}
// Check if motion event with this configuration exists and return color.
AZ::u32 MotionEventPresetManager::GetEventColor(const EMotionFX::EventDataSet& eventDatas) const
{
for (const MotionEventPreset* preset : m_eventPresets)
{
EMotionFX::EventDataSet commonDatas;
const EMotionFX::EventDataSet& presetDatas = preset->GetEventDatas();
const bool allMatch = AZStd::all_of(presetDatas.cbegin(), presetDatas.cend(), [eventDatas](const EMotionFX::EventDataPtr& presetData)
const size_t numEventDatas = eventDatas.size();
if (numEventDatas == presetDatas.size())
{
const auto thisPresetDataHasMatch = AZStd::find_if(eventDatas.cbegin(), eventDatas.cend(), [presetData](const EMotionFX::EventDataPtr& eventData)
for (size_t i = 0; i < numEventDatas; ++i)
{
return ((presetData && eventData && *presetData == *eventData) || (!presetData && !eventData));
});
return thisPresetDataHasMatch != eventDatas.cend();
});
if (allMatch)
{
return preset->GetEventColor();
const EMotionFX::EventDataPtr& eventData = eventDatas[i];
const EMotionFX::EventDataPtr& presetData = presetDatas[i];
if (eventData && presetData &&
eventData->RTTI_GetType() == presetData->RTTI_GetType() &&
*eventData == *presetData)
{
return preset->GetEventColor();
}
}
}
}

@ -108,29 +108,9 @@ namespace GradientSignal
void AddTag(AZStd::string tag) override;
private:
static float CalculateAltitudeRatio(const SurfaceData::SurfacePointList& points, size_t inPositionIndex, float altitudeMin, float altitudeMax)
{
if (points.IsEmpty(inPositionIndex))
{
return 0.0f;
}
// GetSurfacePoints (which was used to populate the points list) always returns points in decreasing height order, so the
// first point in the list contains the highest altitude.
const float highestAltitude = points.GetHighestSurfacePoint(inPositionIndex).m_position.GetZ();
// Turn the absolute altitude value into a 0-1 value by returning the % of the given altitude range that it falls at.
return GetRatio(altitudeMin, altitudeMax, highestAltitude);
}
mutable AZStd::shared_mutex m_cacheMutex;
SurfaceAltitudeGradientConfig m_configuration;
LmbrCentral::DependencyMonitor m_dependencyMonitor;
AZStd::atomic_bool m_dirty{ false };
// m_surfacePointList exists here because it will more efficiently reuse memory across GetValue() calls if it stays constructed.
// The mutex exists to ensure that we don't try to use it from GetValue() in multiple threads at once.
mutable AZStd::recursive_mutex m_surfacePointListMutex;
mutable SurfaceData::SurfacePointList m_surfacePointList;
};
}

@ -82,25 +82,6 @@ namespace GradientSignal
void AddTag(AZStd::string tag) override;
private:
static float GetMaxSurfaceWeight(const SurfaceData::SurfacePointList& points)
{
float result = 0.0f;
points.EnumeratePoints([&result](
[[maybe_unused]] size_t inPositionIndex, [[maybe_unused]] const AZ::Vector3& position,
[[maybe_unused]] const AZ::Vector3& normal, const SurfaceData::SurfaceTagWeights& masks) -> bool
{
masks.EnumerateWeights(
[&result]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool
{
result = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), result);
return true;
});
return true;
});
return result;
}
SurfaceMaskGradientConfig m_configuration;
LmbrCentral::DependencyMonitor m_dependencyMonitor;
};

@ -124,39 +124,6 @@ namespace GradientSignal
void SetFallOffMidpoint(float midpoint) override;
private:
float GetSlopeRatio(const SurfaceData::SurfacePointList& points, float angleMin, float angleMax) const
{
constexpr size_t inPositionIndex = 0;
if (points.IsEmpty(inPositionIndex))
{
return 0.0f;
}
// Assuming our surface normal vector is actually normalized, we can get the slope
// by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()).
auto highestSurfacePoint = points.GetHighestSurfacePoint(inPositionIndex);
AZ_Assert(
highestSurfacePoint.m_normal.GetNormalized().IsClose(highestSurfacePoint.m_normal),
"Surface normals are expected to be normalized");
const float slope = highestSurfacePoint.m_normal.GetZ();
// Convert slope back to an angle so that we can lerp in "angular space", not "slope value space".
// (We want our 0-1 range to be linear across the range of angles)
const float slopeAngle = acosf(slope);
switch (m_configuration.m_rampType)
{
case SurfaceSlopeGradientConfig::RampType::SMOOTH_STEP:
return m_configuration.m_smoothStep.GetSmoothedValue(GetRatio(angleMin, angleMax, slopeAngle));
case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_UP:
// For ramp up, linearly interpolate from min to max.
return GetRatio(angleMin, angleMax, slopeAngle);
case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_DOWN:
default:
// For ramp down, linearly interpolate from max to min.
return GetRatio(angleMax, angleMin, slopeAngle);
}
}
SurfaceSlopeGradientConfig m_configuration;
};
}

@ -202,18 +202,9 @@ namespace GradientSignal
float SurfaceAltitudeGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
{
// For GetValue(), we reuse our SurfacePointList for the GetSurfacePoints query to avoid the repeated cost of memory allocation
// and deallocation across GetValue() calls. However, this also means we can only use it from one thread at a time, so lock a
// mutex to ensure no other threads call GetValue() at the same time.
AZStd::unique_lock<decltype(m_surfacePointListMutex)> surfacePointLock(m_surfacePointListMutex);
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints,
sampleParams.m_position, m_configuration.m_surfaceTagsToSample, m_surfacePointList);
constexpr size_t inPositionIndex = 0;
return CalculateAltitudeRatio(m_surfacePointList, inPositionIndex, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax);
float result = 0.0f;
GetValues(AZStd::span<const AZ::Vector3>(&sampleParams.m_position, 1), AZStd::span<float>(&result, 1));
return result;
}
void SurfaceAltitudeGradientComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
@ -231,17 +222,20 @@ namespace GradientSignal
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList, positions, m_configuration.m_surfaceTagsToSample,
points);
if (points.IsEmpty())
// For each position, turn the height into a 0-1 value based on our min/max altitudes.
for (size_t index = 0; index < positions.size(); index++)
{
// No surface data, so no output values.
AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
}
else
{
// For each position, turn the height into a 0-1 value based on our min/max altitudes.
for (size_t index = 0; index < positions.size(); index++)
if (!points.IsEmpty(index))
{
// Get the point with the highest Z value and use that for the altitude.
const float highestAltitude = points.GetHighestSurfacePoint(index).m_position.GetZ();
// Turn the absolute altitude value into a 0-1 value by returning the % of the given altitude range that it falls at.
outValues[index] = GetRatio(m_configuration.m_altitudeMin, m_configuration.m_altitudeMax, highestAltitude);
}
else
{
outValues[index] = CalculateAltitudeRatio(points, index, m_configuration.m_altitudeMin, m_configuration.m_altitudeMax);
outValues[index] = 0.0f;
}
}
}

@ -162,16 +162,7 @@ namespace GradientSignal
float SurfaceMaskGradientComponent::GetValue(const GradientSampleParams& params) const
{
float result = 0.0f;
if (!m_configuration.m_surfaceTagList.empty())
{
SurfaceData::SurfacePointList points;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints,
params.m_position, m_configuration.m_surfaceTagList, points);
result = GetMaxSurfaceWeight(points);
}
GetValues(AZStd::span<const AZ::Vector3>(&params.m_position, 1), AZStd::span<float>(&result, 1));
return result;
}
@ -183,34 +174,31 @@ namespace GradientSignal
return;
}
bool valuesFound = false;
// Initialize all our output values to 0.
AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
if (!m_configuration.m_surfaceTagList.empty())
{
// Rather than calling GetSurfacePoints on the EBus repeatedly in a loop, we instead pass a lambda into the EBus that contains
// the loop within it so that we can avoid the repeated EBus-calling overhead.
SurfaceData::SurfacePointList points;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
[this, positions, &outValues, &valuesFound](SurfaceData::SurfaceDataSystemRequestBus::Events* surfaceDataRequests)
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList, positions, m_configuration.m_surfaceTagList,
points);
// For each position, get the max surface weight that matches our filter and that appears at that position.
points.EnumeratePoints(
[&outValues](
size_t inPositionIndex, [[maybe_unused]] const AZ::Vector3& position, [[maybe_unused]] const AZ::Vector3& normal,
const SurfaceData::SurfaceTagWeights& masks) -> bool
{
// It's possible that there's nothing connected to the EBus, so keep track of the fact that we have valid results.
valuesFound = true;
SurfaceData::SurfacePointList points;
for (size_t index = 0; index < positions.size(); index++)
{
points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagList, points);
outValues[index] = GetMaxSurfaceWeight(points);
}
masks.EnumerateWeights(
[inPositionIndex, &outValues]([[maybe_unused]] AZ::Crc32 surfaceType, float weight) -> bool
{
outValues[inPositionIndex] = AZ::GetMax(AZ::GetClamp(weight, 0.0f, 1.0f), outValues[inPositionIndex]);
return true;
});
return true;
});
}
if (!valuesFound)
{
// No surface tags, so no output values.
AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
}
}
size_t SurfaceMaskGradientComponent::GetNumTags() const

@ -205,14 +205,9 @@ namespace GradientSignal
float SurfaceSlopeGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
{
SurfaceData::SurfacePointList points;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePoints,
sampleParams.m_position, m_configuration.m_surfaceTagsToSample, points);
const float angleMin = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMin, 0.0f, 90.0f));
const float angleMax = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMax, 0.0f, 90.0f));
return GetSlopeRatio(points, angleMin, angleMax);
float result = 0.0f;
GetValues(AZStd::span<const AZ::Vector3>(&sampleParams.m_position, 1), AZStd::span<float>(&result, 1));
return result;
}
void SurfaceSlopeGradientComponent::GetValues(AZStd::span<const AZ::Vector3> positions, AZStd::span<float> outValues) const
@ -223,32 +218,49 @@ namespace GradientSignal
return;
}
bool valuesFound = false;
// Rather than calling GetSurfacePoints on the EBus repeatedly in a loop, we instead pass a lambda into the EBus that contains
// the loop within it so that we can avoid the repeated EBus-calling overhead.
SurfaceData::SurfacePointList points;
SurfaceData::SurfaceDataSystemRequestBus::Broadcast(
[this, positions, &outValues, &valuesFound](SurfaceData::SurfaceDataSystemRequestBus::Events* surfaceDataRequests)
{
// It's possible that there's nothing connected to the EBus, so keep track of the fact that we have valid results.
valuesFound = true;
SurfaceData::SurfacePointList points;
&SurfaceData::SurfaceDataSystemRequestBus::Events::GetSurfacePointsFromList, positions, m_configuration.m_surfaceTagsToSample,
points);
const float angleMin = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMin, 0.0f, 90.0f));
const float angleMax = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMax, 0.0f, 90.0f));
const float angleMin = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMin, 0.0f, 90.0f));
const float angleMax = AZ::DegToRad(AZ::GetClamp(m_configuration.m_slopeMax, 0.0f, 90.0f));
for (size_t index = 0; index < positions.size(); index++)
for (size_t index = 0; index < positions.size(); index++)
{
if (points.IsEmpty(index))
{
outValues[index] = 0.0f;
}
else
{
// Assuming our surface normal vector is actually normalized, we can get the slope
// by just grabbing the Z value. It's the same thing as normal.Dot(AZ::Vector3::CreateAxisZ()).
auto highestSurfacePoint = points.GetHighestSurfacePoint(index);
AZ_Assert(
highestSurfacePoint.m_normal.GetNormalized().IsClose(highestSurfacePoint.m_normal),
"Surface normals are expected to be normalized");
const float slope = highestSurfacePoint.m_normal.GetZ();
// Convert slope back to an angle so that we can lerp in "angular space", not "slope value space".
// (We want our 0-1 range to be linear across the range of angles)
const float slopeAngle = acosf(slope);
switch (m_configuration.m_rampType)
{
points.Clear();
surfaceDataRequests->GetSurfacePoints(positions[index], m_configuration.m_surfaceTagsToSample, points);
outValues[index] = GetSlopeRatio(points, angleMin, angleMax);
case SurfaceSlopeGradientConfig::RampType::SMOOTH_STEP:
outValues[index] = m_configuration.m_smoothStep.GetSmoothedValue(GetRatio(angleMin, angleMax, slopeAngle));
break;
case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_UP:
// For ramp up, linearly interpolate from min to max.
outValues[index] = GetRatio(angleMin, angleMax, slopeAngle);
break;
case SurfaceSlopeGradientConfig::RampType::LINEAR_RAMP_DOWN:
default:
// For ramp down, linearly interpolate from max to min.
outValues[index] = GetRatio(angleMax, angleMin, slopeAngle);
break;
}
});
if (!valuesFound)
{
// No surface tags, so no output values.
AZStd::fill(outValues.begin(), outValues.end(), 0.0f);
}
}
}

@ -108,6 +108,7 @@ namespace GradientSignal
// Create a fake surface point with the position we're sampling.
AzFramework::SurfaceData::SurfacePoint point;
point.m_position = params.m_position;
point.m_normal = AZ::Vector3::CreateAxisZ();
SurfaceData::SurfacePointList pointList = AZStd::span<const AzFramework::SurfaceData::SurfacePoint>(&point, 1);
// Send it into the component, see what emerges

@ -78,6 +78,7 @@ namespace UnitTest
for (int x = 0; x < dataSize; x++)
{
float angle = AZ::DegToRad(inputAngles[(y * dataSize) + x]);
point.m_position = AZ::Vector3(aznumeric_cast<float>(x), aznumeric_cast<float>(y), 0.0f);
point.m_normal = AZ::Vector3(sinf(angle), 0.0f, cosf(angle));
mockSurface->m_surfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] =
AZStd::span<const AzFramework::SurfaceData::SurfacePoint>(&point, 1);
@ -549,10 +550,10 @@ namespace UnitTest
mockSurface->m_bounds = mockShapeComponentHandler.m_GetEncompassingAabb;
AzFramework::SurfaceData::SurfacePoint mockOutputs[] =
{
{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateAxisZ() },
};
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 0.0f)] =
@ -597,10 +598,10 @@ namespace UnitTest
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(1.0f));
AzFramework::SurfaceData::SurfacePoint mockOutputs[] = {
{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 0.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 2.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 5.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 10.0f), AZ::Vector3::CreateAxisZ() },
};
mockSurface->m_surfacePoints[AZStd::make_pair(0.0f, 0.0f)] =
@ -667,10 +668,10 @@ namespace UnitTest
auto mockSurface = surfaceEntity->CreateComponent<MockSurfaceProviderComponent>();
mockSurface->m_bounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(0.0f), AZ::Vector3(1.0f));
AzFramework::SurfaceData::SurfacePoint mockOutputs[] = {
{ AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateZero() },
{ AZ::Vector3(0.0f, 0.0f, -10.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, -5.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 15.0f), AZ::Vector3::CreateAxisZ() },
{ AZ::Vector3(0.0f, 0.0f, 20.0f), AZ::Vector3::CreateAxisZ() },
};
// Altitude value below min - should result in 0.0f.
@ -724,6 +725,8 @@ namespace UnitTest
{
for (int x = 0; x < dataSize; x++)
{
point.m_position = AZ::Vector3(aznumeric_cast<float>(x), aznumeric_cast<float>(y), 0.0f);
point.m_normal = AZ::Vector3::CreateAxisZ();
point.m_surfaceTags.clear();
point.m_surfaceTags.emplace_back(AZ_CRC_CE("test_mask"), expectedOutput[(y * dataSize) + x]);
mockSurface->m_surfacePoints[AZStd::make_pair(static_cast<float>(x), static_cast<float>(y))] =

@ -9,6 +9,8 @@
//#include "CustomizeKeyboardPage.h"
#include <AzCore/Math/Color.h>
#include <Util/EditorUtils.h>
#include <Editor/Resource.h>
@ -99,9 +101,9 @@ QColor ColorLinearToGamma(ColorF col)
float g = clamp_tpl(col.g, 0.0f, 1.0f);
float b = clamp_tpl(col.b, 0.0f, 1.0f);
r = (float)(r <= 0.0031308 ? (12.92 * r) : (1.055 * pow((double)r, 1.0 / 2.4) - 0.055));
g = (float)(g <= 0.0031308 ? (12.92 * g) : (1.055 * pow((double)g, 1.0 / 2.4) - 0.055));
b = (float)(b <= 0.0031308 ? (12.92 * b) : (1.055 * pow((double)b, 1.0 / 2.4) - 0.055));
r = AZ::Color::ConvertSrgbLinearToGamma(r);
g = AZ::Color::ConvertSrgbLinearToGamma(g);
b = AZ::Color::ConvertSrgbLinearToGamma(b);
return QColor(int(r * 255.0f), int(g * 255.0f), int(b * 255.0f));
}
@ -113,7 +115,7 @@ ColorF ColorGammaToLinear(const QColor& col)
float g = (float)col.green() / 255.0f;
float b = (float)col.blue() / 255.0f;
return ColorF((float)(r <= 0.04045 ? (r / 12.92) : pow(((double)r + 0.055) / 1.055, 2.4)),
(float)(g <= 0.04045 ? (g / 12.92) : pow(((double)g + 0.055) / 1.055, 2.4)),
(float)(b <= 0.04045 ? (b / 12.92) : pow(((double)b + 0.055) / 1.055, 2.4)));
return ColorF(AZ::Color::ConvertSrgbGammaToLinear(r),
AZ::Color::ConvertSrgbGammaToLinear(g),
AZ::Color::ConvertSrgbGammaToLinear(b));
}

@ -82,6 +82,7 @@ namespace SurfaceData
////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const override;
void GetSurfacePointsFromList(AZStd::span<const AZ::Vector3> inPositions, SurfacePointList& surfacePointList) const override;
//////////////////////////////////////////////////////////////////////////
// SurfaceDataModifierRequestBus

@ -66,6 +66,7 @@ namespace SurfaceData
//////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const override;
void GetSurfacePointsFromList(AZStd::span<const AZ::Vector3> inPositions, SurfacePointList& surfacePointList) const override;
//////////////////////////////////////////////////////////////////////////
// SurfaceDataModifierRequestBus

@ -185,8 +185,6 @@ namespace SurfaceData
bool SurfaceDataColliderComponent::DoRayTrace(const AZ::Vector3& inPosition, bool queryPointOnly, AZ::Vector3& outPosition, AZ::Vector3& outNormal) const
{
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);
// test AABB as first pass to claim the point
const AZ::Vector3 testPosition = AZ::Vector3(
inPosition.GetX(),
@ -229,18 +227,45 @@ namespace SurfaceData
void SurfaceDataColliderComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
AZ::Vector3 hitPosition;
AZ::Vector3 hitNormal;
GetSurfacePointsFromList(AZStd::span<const AZ::Vector3>(&inPosition, 1), surfacePointList);
}
// We want a full raycast, so don't just query the start point.
constexpr bool queryPointOnly = false;
void SurfaceDataColliderComponent::GetSurfacePointsFromList(
AZStd::span<const AZ::Vector3> inPositions, SurfacePointList& surfacePointList) const
{
AzPhysics::SimulatedBodyComponentRequestsBus::Event(
GetEntityId(),
[this, inPositions, &surfacePointList](AzPhysics::SimulatedBodyComponentRequestsBus::Events* simBody)
{
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);
if (DoRayTrace(inPosition, queryPointOnly, hitPosition, hitNormal))
{
surfacePointList.AddSurfacePoint(GetEntityId(), inPosition, hitPosition, hitNormal, m_newPointWeights);
}
AzPhysics::RayCastRequest request;
request.m_direction = -AZ::Vector3::CreateAxisZ();
for (auto& inPosition : inPositions)
{
// test AABB as first pass to claim the point
if (SurfaceData::AabbContains2D(m_colliderBounds, inPosition))
{
// We're casting the ray to look for a collision, so start at the top of the collider and cast downwards
// the full height of the collider.
request.m_start = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_colliderBounds.GetMax().GetZ());
request.m_distance = m_colliderBounds.GetExtents().GetZ();
AzPhysics::SceneQueryHit result = simBody->RayCast(request);
if (result)
{
surfacePointList.AddSurfacePoint(
GetEntityId(), inPosition, result.m_position, result.m_normal, m_newPointWeights);
}
}
}
});
}
void SurfaceDataColliderComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const
{
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);

@ -143,24 +143,48 @@ namespace SurfaceData
}
void SurfaceDataShapeComponent::GetSurfacePoints(const AZ::Vector3& inPosition, SurfacePointList& surfacePointList) const
{
GetSurfacePointsFromList(AZStd::span<const AZ::Vector3>(&inPosition, 1), surfacePointList);
}
void SurfaceDataShapeComponent::GetSurfacePointsFromList(
AZStd::span<const AZ::Vector3> inPositions, SurfacePointList& surfacePointList) const
{
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);
if (m_shapeBoundsIsValid && SurfaceData::AabbContains2D(m_shapeBounds, inPosition))
if (!m_shapeBoundsIsValid)
{
const AZ::Vector3 rayOrigin = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_shapeBounds.GetMax().GetZ());
const AZ::Vector3 rayDirection = -AZ::Vector3::CreateAxisZ();
float intersectionDistance = 0.0f;
bool hitShape = false;
LmbrCentral::ShapeComponentRequestsBus::EventResult(hitShape, GetEntityId(), &LmbrCentral::ShapeComponentRequestsBus::Events::IntersectRay, rayOrigin, rayDirection, intersectionDistance);
if (hitShape)
{
AZ::Vector3 position = rayOrigin + intersectionDistance * rayDirection;
surfacePointList.AddSurfacePoint(GetEntityId(), inPosition, position, AZ::Vector3::CreateAxisZ(), m_newPointWeights);
}
return;
}
LmbrCentral::ShapeComponentRequestsBus::Event(
GetEntityId(),
[this, inPositions, &surfacePointList](LmbrCentral::ShapeComponentRequestsBus::Events* shape)
{
const AZ::Vector3 rayDirection = -AZ::Vector3::CreateAxisZ();
// Shapes don't currently have a way to query normals at a point intersection, so we'll just return a Z-up normal
// until they get support for it.
const AZ::Vector3 surfacePointNormal = AZ::Vector3::CreateAxisZ();
for (auto& inPosition : inPositions)
{
if (SurfaceData::AabbContains2D(m_shapeBounds, inPosition))
{
const AZ::Vector3 rayOrigin = AZ::Vector3(inPosition.GetX(), inPosition.GetY(), m_shapeBounds.GetMax().GetZ());
float intersectionDistance = 0.0f;
bool hitShape = shape->IntersectRay(rayOrigin, rayDirection, intersectionDistance);
if (hitShape)
{
AZ::Vector3 position = rayOrigin + intersectionDistance * rayDirection;
surfacePointList.AddSurfacePoint(GetEntityId(), inPosition, position, surfacePointNormal, m_newPointWeights);
}
}
}
});
}
void SurfaceDataShapeComponent::ModifySurfacePoints(SurfacePointList& surfacePointList) const
{
AZStd::shared_lock<decltype(m_cacheMutex)> lock(m_cacheMutex);

@ -32,7 +32,11 @@ namespace SurfaceData
inPositionIndex = (inPositionIndex + 1) % m_inputPositions.size();
}
AZ_Assert(foundMatch, "Couldn't find input position!");
AZ_Assert(
foundMatch,
"Couldn't find input position: (%0.7f, %0.7f, %0.7f), m_lastInputPositionIndex = %zu, m_inputPositions.size() = %zu",
inPosition.GetX(), inPosition.GetY(), inPosition.GetZ(), m_lastInputPositionIndex, m_inputPositions.size());
m_lastInputPositionIndex = inPositionIndex;
return inPositionIndex;
}

@ -145,29 +145,42 @@ namespace Terrain
void TerrainSurfaceDataSystemComponent::GetSurfacePoints(
const AZ::Vector3& inPosition, SurfaceData::SurfacePointList& surfacePointList) const
{
GetSurfacePointsFromList(AZStd::span<const AZ::Vector3>(&inPosition, 1), surfacePointList);
}
void TerrainSurfaceDataSystemComponent::GetSurfacePointsFromList(
AZStd::span<const AZ::Vector3> inPositions, SurfaceData::SurfacePointList& surfacePointList) const
{
if (!m_terrainBoundsIsValid)
{
return;
}
bool isTerrainValidAtPoint = false;
AzFramework::SurfaceData::SurfacePoint terrainSurfacePoint;
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(&AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfacePoint,
inPosition, terrainSurfacePoint, AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR,
&isTerrainValidAtPoint);
size_t inPositionIndex = 0;
const bool isHole = !isTerrainValidAtPoint;
AzFramework::Terrain::TerrainDataRequestBus::Broadcast(
&AzFramework::Terrain::TerrainDataRequestBus::Events::ProcessSurfacePointsFromList, inPositions,
[this, inPositions, &inPositionIndex, &surfacePointList]
(const AzFramework::SurfaceData::SurfacePoint& surfacePoint, bool terrainExists)
{
AZ_Assert(inPositionIndex < inPositions.size(), "Too many points returned from ProcessSurfacePointsFromList");
SurfaceData::SurfaceTagWeights weights(surfacePoint.m_surfaceTags);
SurfaceData::SurfaceTagWeights weights(terrainSurfacePoint.m_surfaceTags);
// Always add a "terrain" or "terrainHole" tag.
const AZ::Crc32 terrainTag = terrainExists ? Constants::s_terrainTagCrc : Constants::s_terrainHoleTagCrc;
weights.AddSurfaceTagWeight(terrainTag, 1.0f);
// Always add a "terrain" or "terrainHole" tag.
const AZ::Crc32 terrainTag = isHole ? Constants::s_terrainHoleTagCrc : Constants::s_terrainTagCrc;
weights.AddSurfaceTagWeight(terrainTag, 1.0f);
surfacePointList.AddSurfacePoint(
GetEntityId(), inPositions[inPositionIndex], surfacePoint.m_position, surfacePoint.m_normal, weights);
surfacePointList.AddSurfacePoint(GetEntityId(), inPosition, terrainSurfacePoint.m_position, terrainSurfacePoint.m_normal, weights);
inPositionIndex++;
},
AzFramework::Terrain::TerrainDataRequests::Sampler::BILINEAR);
}
AZ::Aabb TerrainSurfaceDataSystemComponent::GetSurfaceAabb() const
{
auto terrain = AzFramework::Terrain::TerrainDataRequestBus::FindFirstHandler();

@ -59,6 +59,8 @@ namespace Terrain
//////////////////////////////////////////////////////////////////////////
// SurfaceDataProviderRequestBus
void GetSurfacePoints(const AZ::Vector3& inPosition, SurfaceData::SurfacePointList& surfacePointList) const override;
void GetSurfacePointsFromList(
AZStd::span<const AZ::Vector3> inPositions, SurfaceData::SurfacePointList& surfacePointList) const override;
//////////////////////////////////////////////////////////////////////////
// AzFramework::Terrain::TerrainDataNotificationBus

@ -192,7 +192,7 @@ bool TerrainSystem::InWorldBounds(float x, float y) const
}
// Generate positions to be queried based on the sampler type.
void TerrainSystem::GenerateQueryPositions(const AZStd::span<AZ::Vector3>& inPositions,
void TerrainSystem::GenerateQueryPositions(const AZStd::span<const AZ::Vector3>& inPositions,
AZStd::vector<AZ::Vector3>& outPositions,
Sampler sampler) const
{
@ -253,7 +253,7 @@ AZStd::vector<AZ::Vector3> TerrainSystem::GenerateInputPositionsFromRegion(
}
void TerrainSystem::MakeBulkQueries(
const AZStd::span<AZ::Vector3> inPositions,
const AZStd::span<const AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
@ -296,7 +296,7 @@ void TerrainSystem::MakeBulkQueries(
if (prevAreaId != AZ::EntityId())
{
size_t spanLength = (windowEnd - windowStart) + 1;
queryCallback(AZStd::span<AZ::Vector3>(inPositions.begin() + windowStart, spanLength),
queryCallback(AZStd::span<const AZ::Vector3>(inPositions.begin() + windowStart, spanLength),
AZStd::span<AZ::Vector3>(outPositions.begin() + windowStart, spanLength),
AZStd::span<bool>(outTerrainExists.begin() + windowStart, spanLength),
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList>(outSurfaceWeights.begin() + windowStart, spanLength),
@ -311,7 +311,7 @@ void TerrainSystem::MakeBulkQueries(
}
}
void TerrainSystem::GetHeightsSynchronous(const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
void TerrainSystem::GetHeightsSynchronous(const AZStd::span<const AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<float> heights, AZStd::span<bool> terrainExists) const
{
AZStd::shared_lock<AZStd::shared_mutex> lock(m_areaMutex);
@ -328,7 +328,7 @@ void TerrainSystem::GetHeightsSynchronous(const AZStd::span<AZ::Vector3>& inPosi
GenerateQueryPositions(inPositions, outPositions, sampler);
auto callback = []([[maybe_unused]] const AZStd::span<AZ::Vector3> inPositions,
auto callback = []([[maybe_unused]] const AZStd::span<const AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
[[maybe_unused]] AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
@ -516,7 +516,7 @@ bool TerrainSystem::GetIsHoleFromFloats(float x, float y, Sampler sampler) const
return !terrainExists;
}
void TerrainSystem::GetNormalsSynchronous(const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
void TerrainSystem::GetNormalsSynchronous(const AZStd::span<const AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<AZ::Vector3> normals, AZStd::span<bool> terrainExists) const
{
AZStd::vector<AZ::Vector3> directionVectors;
@ -685,7 +685,7 @@ AzFramework::RenderGeometry::RayResult TerrainSystem::GetClosestIntersection(
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -695,7 +695,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -705,7 +705,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -715,7 +715,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -725,7 +725,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessHeightsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -735,7 +735,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessNormalsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -745,7 +745,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfaceWeightsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -755,7 +755,7 @@ AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext>
}
AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessSurfacePointsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -830,7 +830,7 @@ AZ::EntityId TerrainSystem::FindBestAreaEntityAtPosition(float x, float y, AZ::A
}
void TerrainSystem::GetOrderedSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
[[maybe_unused]] Sampler sampler,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList,
AZStd::span<bool> terrainExists) const
@ -841,7 +841,7 @@ void TerrainSystem::GetOrderedSurfaceWeightsFromList(
GetHeightsSynchronous(inPositions, AzFramework::Terrain::TerrainDataRequests::Sampler::EXACT, heights, terrainExists);
}
auto callback = [](const AZStd::span<AZ::Vector3> inPositions,
auto callback = [](const AZStd::span<const AZ::Vector3> inPositions,
[[maybe_unused]] AZStd::span<AZ::Vector3> outPositions,
[[maybe_unused]] AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
@ -929,7 +929,7 @@ const char* TerrainSystem::GetMaxSurfaceName(
}
void TerrainSystem::ProcessHeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -953,7 +953,7 @@ void TerrainSystem::ProcessHeightsFromList(
}
void TerrainSystem::ProcessNormalsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -977,7 +977,7 @@ void TerrainSystem::ProcessNormalsFromList(
}
void TerrainSystem::ProcessSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -1001,7 +1001,7 @@ void TerrainSystem::ProcessSurfaceWeightsFromList(
}
void TerrainSystem::ProcessSurfacePointsFromList(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -1034,7 +1034,7 @@ void TerrainSystem::ProcessSurfacePointsFromList(
}
void TerrainSystem::ProcessHeightsFromListOfVector2(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -1054,7 +1054,7 @@ void TerrainSystem::ProcessHeightsFromListOfVector2(
}
void TerrainSystem::ProcessNormalsFromListOfVector2(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -1074,7 +1074,7 @@ void TerrainSystem::ProcessNormalsFromListOfVector2(
}
void TerrainSystem::ProcessSurfaceWeightsFromListOfVector2(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{
@ -1094,7 +1094,7 @@ void TerrainSystem::ProcessSurfaceWeightsFromListOfVector2(
}
void TerrainSystem::ProcessSurfacePointsFromListOfVector2(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter) const
{

@ -140,28 +140,28 @@ namespace Terrain
//! Given a list of XY coordinates, call the provided callback function with surface data corresponding to each
//! XY coordinate in the list.
virtual void ProcessHeightsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessHeightsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessNormalsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessNormalsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessSurfaceWeightsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessSurfaceWeightsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessSurfacePointsFromList(const AZStd::span<AZ::Vector3>& inPositions,
virtual void ProcessSurfacePointsFromList(const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessHeightsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessHeightsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessNormalsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessNormalsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessSurfaceWeightsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessSurfaceWeightsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
virtual void ProcessSurfacePointsFromListOfVector2(const AZStd::span<AZ::Vector2>& inPositions,
virtual void ProcessSurfacePointsFromListOfVector2(const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT) const override;
@ -194,42 +194,42 @@ namespace Terrain
const AzFramework::RenderGeometry::RayRequest& ray) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListAsync(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessHeightsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessNormalsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessSurfaceWeightsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
AZStd::shared_ptr<TerrainJobContext> ProcessSurfacePointsFromListOfVector2Async(
const AZStd::span<AZ::Vector2>& inPositions,
const AZStd::span<const AZ::Vector2>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const override;
@ -262,7 +262,7 @@ namespace Terrain
template<typename SynchronousFunctionType, typename VectorType>
AZStd::shared_ptr<TerrainJobContext> ProcessFromListAsync(
SynchronousFunctionType synchronousFunction,
const AZStd::span<VectorType>& inPositions,
const AZStd::span<const VectorType>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter = Sampler::DEFAULT,
AZStd::shared_ptr<ProcessAsyncParams> params = nullptr) const;
@ -291,31 +291,31 @@ namespace Terrain
AZ::Vector3 GetNormalSynchronous(float x, float y, Sampler sampler, bool* terrainExistsPtr) const;
typedef AZStd::function<void(
const AZStd::span<AZ::Vector3> inPositions,
const AZStd::span<const AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeights,
AZ::EntityId areaId)> BulkQueriesCallback;
void GetHeightsSynchronous(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
Sampler sampler, AZStd::span<float> heights,
AZStd::span<bool> terrainExists) const;
void GetNormalsSynchronous(
const AZStd::span<AZ::Vector3>& inPositions,
const AZStd::span<const AZ::Vector3>& inPositions,
Sampler sampler, AZStd::span<AZ::Vector3> normals,
AZStd::span<bool> terrainExists) const;
void GetOrderedSurfaceWeightsFromList(
const AZStd::span<AZ::Vector3>& inPositions, Sampler sampler,
const AZStd::span<const AZ::Vector3>& inPositions, Sampler sampler,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWeightsList,
AZStd::span<bool> terrainExists) const;
void MakeBulkQueries(
const AZStd::span<AZ::Vector3> inPositions,
const AZStd::span<const AZ::Vector3> inPositions,
AZStd::span<AZ::Vector3> outPositions,
AZStd::span<bool> outTerrainExists,
AZStd::span<AzFramework::SurfaceData::SurfaceTagWeightList> outSurfaceWieghts,
BulkQueriesCallback queryCallback) const;
void GenerateQueryPositions(const AZStd::span<AZ::Vector3>& inPositions,
void GenerateQueryPositions(const AZStd::span<const AZ::Vector3>& inPositions,
AZStd::vector<AZ::Vector3>& outPositions,
Sampler sampler) const;
AZStd::vector<AZ::Vector3> GenerateInputPositionsFromRegion(
@ -361,7 +361,7 @@ namespace Terrain
template<typename SynchronousFunctionType, typename VectorType>
inline AZStd::shared_ptr<AzFramework::Terrain::TerrainDataRequests::TerrainJobContext> TerrainSystem::ProcessFromListAsync(
SynchronousFunctionType synchronousFunction,
const AZStd::span<VectorType>& inPositions,
const AZStd::span<const VectorType>& inPositions,
AzFramework::Terrain::SurfacePointListFillCallback perPositionCallback,
Sampler sampleFilter,
AZStd::shared_ptr<ProcessAsyncParams> params) const
@ -397,7 +397,7 @@ namespace Terrain
const size_t subSpanCount = (i < numJobs - 1) ? numPositionsPerJob : AZStd::dynamic_extent;
// Define the job function using the sub span of positions to process.
const AZStd::span<VectorType>& positionsToProcess = inPositions.subspan(subSpanOffset, subSpanCount);
const AZStd::span<const VectorType>& positionsToProcess = inPositions.subspan(subSpanOffset, subSpanCount);
auto jobFunction = [this, synchronousFunction, positionsToProcess, perPositionCallback, sampleFilter, jobContext, params]()
{
// Process the sub span of positions, unless the associated job context has been cancelled.

Loading…
Cancel
Save