Merge branch 'development' into issues/exception_handling

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-10-14 12:41:43 -07:00
222 changed files with 5742 additions and 1124 deletions
@@ -23,20 +23,20 @@ TEST_DIRECTORY = os.path.join(os.path.dirname(__file__), "tests")
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
@pytest.mark.test_case_id("C32078118") # Decal
@pytest.mark.test_case_id("C32078119") # DepthOfField
@pytest.mark.test_case_id("C32078120") # Directional Light
@pytest.mark.test_case_id("C32078121") # Exposure Control
@pytest.mark.test_case_id("C32078115") # Global Skylight (IBL)
@pytest.mark.test_case_id("C32078125") # Physical Sky
@pytest.mark.test_case_id("C32078127") # PostFX Layer
@pytest.mark.test_case_id("C32078131") # PostFX Radius Weight Modifier
@pytest.mark.test_case_id("C32078117") # Light
@pytest.mark.test_case_id("C36525660") # Display Mapper
def test_AtomEditorComponents_AddedToEntity(self, request, editor, level, workspace, project, launcher_platform):
"""
Please review the hydra script run by this test for more specific test info.
Tests the following Atom components and verifies all "expected_lines" appear in Editor.log:
1. Display Mapper
2. Light
3. PostFX Radius Weight Modifier
4. PostFX Layer
5. Physical Sky
6. Global Skylight (IBL)
7. Exposure Control
8. Directional Light
9. DepthOfField
10. Decal
Tests the Atom components & verifies all "expected_lines" appear in Editor.log
"""
cfg_args = [level]
@@ -69,6 +69,18 @@ class TestAtomEditorComponentsMain(object):
"DepthOfField_test: Entity deleted: True",
"DepthOfField_test: UNDO entity deletion works: True",
"DepthOfField_test: REDO entity deletion works: True",
# Directional Light Component
"Directional Light Entity successfully created",
"Directional Light_test: Component added to the entity: True",
"Directional Light_test: Component removed after UNDO: True",
"Directional Light_test: Component added after REDO: True",
"Directional Light_test: Entered game mode: True",
"Directional Light_test: Exit game mode: True",
"Directional Light_test: Entity is hidden: True",
"Directional Light_test: Entity is shown: True",
"Directional Light_test: Entity deleted: True",
"Directional Light_test: UNDO entity deletion works: True",
"Directional Light_test: REDO entity deletion works: True",
# Exposure Control Component
"Exposure Control Entity successfully created",
"Exposure Control_test: Component added to the entity: True",
@@ -180,6 +192,7 @@ class TestAtomEditorComponentsMain(object):
cfg_args=cfg_args,
)
@pytest.mark.test_case_id("C34525095")
def test_AtomEditorComponents_LightComponent(
self, request, editor, workspace, project, launcher_platform, level):
"""
@@ -266,6 +279,15 @@ class TestMaterialEditorBasicTests(object):
request.addfinalizer(teardown)
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
@pytest.mark.test_case_id("C34448118") # Saving Material.
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
def test_MaterialEditorBasicTests(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
@@ -73,6 +73,7 @@ def create_screenshots_archive(screenshot_path):
class TestAllComponentsIndepthTests(object):
@pytest.mark.parametrize("screenshot_name", ["AtomBasicLevelSetup.ppm"])
@pytest.mark.test_case_id("C34603773")
def test_BasicLevelSetup_SetsUpLevel(
self, request, editor, workspace, project, launcher_platform, level, screenshot_name):
"""
@@ -115,6 +116,7 @@ class TestAllComponentsIndepthTests(object):
create_screenshots_archive(screenshot_directory)
@pytest.mark.test_case_id("C34525095")
def test_LightComponent_ScreenshotMatchesGoldenImage(
self, request, editor, workspace, project, launcher_platform, level):
"""
@@ -146,7 +148,7 @@ class TestAllComponentsIndepthTests(object):
golden_image_path = os.path.join(golden_images_directory(), golden_image)
golden_images.append(golden_image_path)
expected_lines = ["Light component tests completed."]
expected_lines = ["spot_light Controller|Configuration|Shadows|Shadowmap size: SUCCESS"]
unexpected_lines = [
"Trace::Assert",
"Trace::Error",
@@ -225,6 +227,8 @@ class TestMaterialEditor(object):
pytest.param("-rhi=Vulkan", ["Registering vulkan RHI"])
])
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
@pytest.mark.test_case_id("C30973986") # Material Editor Launching in Dx12
@pytest.mark.test_case_id("C30973987") # Material Editor Launching in Vulkan
def test_MaterialEditorLaunch_AllRHIOptionsSucceed(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name, cfg_args,
expected_lines):
@@ -23,6 +23,7 @@ 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"]
@pytest.mark.test_case_id("C34603773")
class AtomGPU_BasicLevelSetup_SetsUpLevel(EditorSharedTest):
use_null_renderer = False # Default is True
screenshot_name = "AtomBasicLevelSetup.ppm"
@@ -67,5 +67,9 @@ class TestAutomation(EditorTestSuite):
class AtomEditorComponents_PostFXLayerAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFXLayerAdded as test_module
@pytest.mark.test_case_id("C36525665")
class AtomEditorComponents_PostFXShapeWeightModifierAdded(EditorSharedTest):
from Atom.tests import hydra_AtomEditorComponents_PostFxShapeWeightModifierAdded as test_module
class ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges(EditorSharedTest):
from Atom.tests import hydra_ShaderAssetBuilder_RecompilesShaderAsChainOfDependenciesChanges as test_module
@@ -30,6 +30,7 @@ class TestAtomEditorComponentsSandbox(object):
class TestAtomEditorComponentsMain(object):
"""Holds tests for Atom components."""
@pytest.mark.test_case_id("C32078128")
def test_AtomEditorComponents_ReflectionProbeAddedToEntity(
self, request, editor, level, workspace, project, launcher_platform):
"""
@@ -0,0 +1,207 @@
"""
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
"""
class Tests:
creation_undo = (
"UNDO Entity creation success",
"UNDO Entity creation failed")
creation_redo = (
"REDO Entity creation success",
"REDO Entity creation failed")
postfx_shape_weight_creation = (
"PostFx Shape Weight Modifier Entity successfully created",
"PostFx Shape Weight Modifier Entity failed to be created")
postfx_shape_weight_component = (
"Entity has a PostFx Shape Weight Modifier component",
"Entity failed to find PostFx Shape Weight Modifier component")
postfx_shape_weight_disabled = (
"PostFx Shape Weight Modifier component disabled",
"PostFx Shape Weight Modifier component was not disabled.")
postfx_layer_component = (
"Entity has a PostFX Layer component",
"Entity did not have an PostFX Layer component")
tube_shape_component = (
"Entity has a Tube Shape component",
"Entity did not have a Tube Shape component")
postfx_shape_weight_enabled = (
"PostFx Shape Weight Modifier component enabled",
"PostFx Shape Weight Modifier component was not enabled.")
enter_game_mode = (
"Entered game mode",
"Failed to enter game mode")
exit_game_mode = (
"Exited game mode",
"Couldn't exit game mode")
is_visible = (
"Entity is visible",
"Entity was not visible")
is_hidden = (
"Entity is hidden",
"Entity was not hidden")
entity_deleted = (
"Entity deleted",
"Entity was not deleted")
deletion_undo = (
"UNDO deletion success",
"UNDO deletion failed")
deletion_redo = (
"REDO deletion success",
"REDO deletion failed")
def AtomEditorComponents_postfx_shape_weight_AddedToEntity():
"""
Summary:
Tests the PostFx Shape Weight Modifier component can be added to an entity and has the expected functionality.
Test setup:
- Wait for Editor idle loop.
- Open the "Base" level.
Expected Behavior:
The component can be added, used in game mode, hidden/shown, deleted, and has accurate required components.
Creation and deletion undo/redo should also work.
Test Steps:
1) Create a PostFx Shape Weight Modifier entity with no components.
2) Add a PostFx Shape Weight Modifier component to PostFx Shape Weight Modifier entity.
3) UNDO the entity creation and component addition.
4) REDO the entity creation and component addition.
5) Verify PostFx Shape Weight Modifier component not enabled.
6) Add PostFX Layer component since it is required by the PostFx Shape Weight Modifier component.
7) Verify PostFx Shape Weight Modifier component is NOT enabled since it also requires a shape.
8) Add a required shape looping over a list and checking if it enables PostFX Shape Weight Modifier.
9) Undo to remove each added shape and verify PostFX Shape Weight Modifier is not enabled.
10) Verify PostFx Shape Weight Modifier component is enabled by adding Spline and Tube Shape component.
11) Enter/Exit game mode.
12) Test IsHidden.
13) Test IsVisible.
14) Delete PostFx Shape Weight Modifier entity.
15) UNDO deletion.
16) REDO deletion.
17) Look for errors.
:return: None
"""
import azlmbr.legacy.general as general
from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report, Tracer, TestHelper
with Tracer() as error_tracer:
# Test setup begins.
# Setup: Wait for Editor idle loop before executing Python hydra scripts then open "Base" level.
TestHelper.init_idle()
TestHelper.open_level("", "Base")
# Test steps begin.
# 1. Create a PostFx Shape Weight Modifier entity with no components.
postfx_shape_weight_name = "PostFX Shape Weight Modifier"
postfx_shape_weight_entity = EditorEntity.create_editor_entity(postfx_shape_weight_name)
Report.critical_result(Tests.postfx_shape_weight_creation, postfx_shape_weight_entity.exists())
# 2. Add a PostFx Shape Weight Modifier component to PostFx Shape Weight Modifier entity.
postfx_shape_weight_component = postfx_shape_weight_entity.add_component(postfx_shape_weight_name)
Report.critical_result(
Tests.postfx_shape_weight_component,
postfx_shape_weight_entity.has_component(postfx_shape_weight_name))
# 3. UNDO the entity creation and component addition.
# -> UNDO component addition.
general.undo()
# -> UNDO naming entity.
general.undo()
# -> UNDO selecting entity.
general.undo()
# -> UNDO entity creation.
general.undo()
general.idle_wait_frames(1)
Report.result(Tests.creation_undo, not postfx_shape_weight_entity.exists())
# 4. REDO the entity creation and component addition.
# -> REDO entity creation.
general.redo()
# -> REDO selecting entity.
general.redo()
# -> REDO naming entity.
general.redo()
# -> REDO component addition.
general.redo()
general.idle_wait_frames(1)
Report.result(Tests.creation_redo, postfx_shape_weight_entity.exists())
# 5. Verify PostFx Shape Weight Modifier component not enabled.
Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled())
# 6. Add PostFX Layer component since it is required by the PostFx Shape Weight Modifier component.
postfx_layer_name = "PostFX Layer"
postfx_shape_weight_entity.add_component(postfx_layer_name)
Report.result(Tests.postfx_layer_component, postfx_shape_weight_entity.has_component(postfx_layer_name))
# 7. Verify PostFx Shape Weight Modifier component is NOT enabled since it also requires a shape.
Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled())
# 8. Add a required shape looping over a list and checking if it enables PostFX Shape Weight Modifier.
for shape in ['Axis Aligned Box Shape', 'Box Shape', 'Capsule Shape', 'Compound Shape', 'Cylinder Shape',
'Disk Shape', 'Polygon Prism Shape', 'Quad Shape', 'Sphere Shape', 'Vegetation Reference Shape']:
postfx_shape_weight_entity.add_component(shape)
test_shape = (
f"Entity has a {shape} component",
f"Entity did not have a {shape} component")
Report.result(test_shape, postfx_shape_weight_entity.has_component(shape))
# Check if required shape allows PostFX Shape Weight Modifier to be enabled
Report.result(Tests.postfx_shape_weight_enabled, postfx_shape_weight_component.is_enabled())
# 9. Undo to remove each added shape and verify PostFX Shape Weight Modifier is not enabled.
general.undo()
TestHelper.wait_for_condition(lambda: not postfx_shape_weight_entity.has_component(shape), 1.0)
Report.result(Tests.postfx_shape_weight_disabled, not postfx_shape_weight_component.is_enabled())
# 10. Verify PostFx Shape Weight Modifier component is enabled by adding Spline and Tube Shape component.
postfx_shape_weight_entity.add_components(['Spline', 'Tube Shape'])
Report.result(Tests.tube_shape_component, postfx_shape_weight_entity.has_component('Tube Shape'))
Report.result(Tests.postfx_shape_weight_enabled, postfx_shape_weight_component.is_enabled())
# 11. Enter/Exit game mode.
TestHelper.enter_game_mode(Tests.enter_game_mode)
general.idle_wait_frames(1)
TestHelper.exit_game_mode(Tests.exit_game_mode)
# 12. Test IsHidden.
postfx_shape_weight_entity.set_visibility_state(False)
Report.result(Tests.is_hidden, postfx_shape_weight_entity.is_hidden() is True)
# 13. Test IsVisible.
postfx_shape_weight_entity.set_visibility_state(True)
general.idle_wait_frames(1)
Report.result(Tests.is_visible, postfx_shape_weight_entity.is_visible() is True)
# 14. Delete PostFx Shape Weight Modifier entity.
postfx_shape_weight_entity.delete()
Report.result(Tests.entity_deleted, not postfx_shape_weight_entity.exists())
# 15. UNDO deletion.
general.undo()
Report.result(Tests.deletion_undo, postfx_shape_weight_entity.exists())
# 16. REDO deletion.
general.redo()
Report.result(Tests.deletion_redo, not postfx_shape_weight_entity.exists())
# 17. Look for errors or asserts.
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}")
for assert_info in error_tracer.asserts:
Report.info(f"Assert: {assert_info.filename} {assert_info.function} | {assert_info.message}")
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(AtomEditorComponents_postfx_shape_weight_AddedToEntity)
@@ -0,0 +1,13 @@
{
"description": "",
"materialType": "Materials/Types/Skin.materialtype",
"parentMaterial": "",
"propertyLayoutVersion": 3,
"properties": {
"wrinkleLayers": {
"count": 3,
"enable": true,
"showBlendValues": true
}
}
}
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:53e17ec8155911c8b42e85436130f600bd6dddd8931a8ccb1b2f8a9f8674cc85
size 45104
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:0da56a05daa0ec1c476cfe25ca6d3b65267c98886cf33408f6e852fb325a8e2c
size 198084
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:e3537fbe9205731a242251c525a67bbb5f3b8f5307537f1dc0c318b5b885ce52
size 198112
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:bd794d5dd4b749c3275bfab79b9b5ae3f8e007d3e6741c0566c9c2d3931123bf
size 198112
@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:45ded862987a64061deffd8e4c9aa1dff4eec3bcff5f7b505679f1959e8ae137
size 51440
+1 -1
View File
@@ -102,7 +102,7 @@ ly_add_target(
3rdParty::Qt::Gui
3rdParty::Qt::Widgets
3rdParty::Qt::Concurrent
3rdParty::tiff
3rdParty::TIFF
3rdParty::squish-ccr
3rdParty::AWSNativeSDK::STS
Legacy::CryCommon
+10
View File
@@ -57,6 +57,7 @@ AZ_POP_DISABLE_WARNING
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/Terrain/TerrainDataRequestBus.h>
#include <AzFramework/ProjectManager/ProjectManager.h>
#include <AzFramework/Spawnable/RootSpawnableInterface.h>
// AzToolsFramework
#include <AzToolsFramework/Component/EditorComponentAPIBus.h>
@@ -3021,6 +3022,15 @@ CCryEditApp::ECreateLevelResult CCryEditApp::CreateLevel(const QString& levelNam
bool bIsDocModified = GetIEditor()->GetDocument()->IsModified();
OnSwitchPhysics();
GetIEditor()->GetDocument()->SetModifiedFlag(bIsDocModified);
if (usePrefabSystemForLevels)
{
auto* rootSpawnableInterface = AzFramework::RootSpawnableInterface::Get();
if (rootSpawnableInterface)
{
rootSpawnableInterface->ProcessSpawnableQueue();
}
}
}
const QScopedValueRollback<bool> rollback(m_creatingNewLevel);
@@ -15,7 +15,7 @@ using namespace Intersect;
// IntersectSegmentTriangleCCW
// [10/21/2009]
//=========================================================================
int Intersect::IntersectSegmentTriangleCCW(
bool Intersect::IntersectSegmentTriangleCCW(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t)
{
@@ -34,7 +34,7 @@ int Intersect::IntersectSegmentTriangleCCW(
float d = qp.Dot(normal);
if (d <= 0.0f)
{
return 0;
return false;
}
// Compute intersection t value of pq with plane of triangle. A ray
@@ -46,7 +46,7 @@ int Intersect::IntersectSegmentTriangleCCW(
// range segment check t[0,1] (it this case [0,d])
if (t < 0.0f || t > d)
{
return 0;
return false;
}
// Compute barycentric coordinate components and test if within bounds
@@ -54,12 +54,12 @@ int Intersect::IntersectSegmentTriangleCCW(
v = ac.Dot(e);
if (v < 0.0f || v > d)
{
return 0;
return false;
}
w = -ab.Dot(e);
if (w < 0.0f || v + w > d)
{
return 0;
return false;
}
// Segment/ray intersects triangle. Perform delayed division and
@@ -72,14 +72,14 @@ int Intersect::IntersectSegmentTriangleCCW(
normal.Normalize();
return 1;
return true;
}
//=========================================================================
// IntersectSegmentTriangle
// [10/21/2009]
//=========================================================================
int
bool
Intersect::IntersectSegmentTriangle(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t)
@@ -111,7 +111,7 @@ Intersect::IntersectSegmentTriangle(
// so either have a parallel ray or our normal is flipped
if (d >= -Constants::FloatEpsilon)
{
return 0; // parallel
return false; // parallel
}
d = -d;
e = ap.Cross(qp);
@@ -125,19 +125,19 @@ Intersect::IntersectSegmentTriangle(
// range segment check t[0,1] (it this case [0,d])
if (t < 0.0f || t > d)
{
return 0;
return false;
}
// Compute barycentric coordinate components and test if within bounds
v = ac.Dot(e);
if (v < 0.0f || v > d)
{
return 0;
return false;
}
w = -ab.Dot(e);
if (w < 0.0f || v + w > d)
{
return 0;
return false;
}
// Segment/ray intersects the triangle. Perform delayed division and
@@ -150,14 +150,14 @@ Intersect::IntersectSegmentTriangle(
normal.Normalize();
return 1;
return true;
}
//=========================================================================
// TestSegmentAABBOrigin
// [10/21/2009]
//=========================================================================
int
bool
AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& halfVector, const Vector3& aabbExtends)
{
const Vector3 EPSILON(0.001f); // \todo this is slow load move to a const
@@ -168,7 +168,7 @@ AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& hal
// Try world coordinate axes as separating axes
if (!absMidpoint.IsLessEqualThan(absHalfMidpoint))
{
return 0;
return false;
}
// Add in an epsilon term to counteract arithmetic errors when segment is
@@ -188,11 +188,11 @@ AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& hal
Vector3 ead(ey * adz + ez * ady, ex * adz + ez * adx, ex * ady + ey * adx);
if (!absMDCross.IsLessEqualThan(ead))
{
return 0;
return false;
}
// No separating axis found; segment must be overlapping AABB
return 1;
return true;
}
@@ -200,7 +200,7 @@ AZ::Intersect::TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& hal
// IntersectRayAABB
// [10/21/2009]
//=========================================================================
int
RayAABBIsectTypes
AZ::Intersect::IntersectRayAABB(
const Vector3& rayStart, const Vector3& dir, const Vector3& dirRCP, const Aabb& aabb,
float& tStart, float& tEnd, Vector3& startNormal /*, Vector3& inter*/)
@@ -356,7 +356,7 @@ AZ::Intersect::IntersectRayAABB(
// IntersectRayAABB2
// [2/18/2011]
//=========================================================================
int
RayAABBIsectTypes
AZ::Intersect::IntersectRayAABB2(const Vector3& rayStart, const Vector3& dirRCP, const Aabb& aabb, float& start, float& end)
{
float tmin, tmax, tymin, tymax, tzmin, tzmax;
@@ -408,7 +408,7 @@ AZ::Intersect::IntersectRayAABB2(const Vector3& rayStart, const Vector3& dirRCP,
return ISECT_RAY_AABB_ISECT;
}
int AZ::Intersect::IntersectRayDisk(
bool AZ::Intersect::IntersectRayDisk(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& diskCenter, const float diskRadius, const Vector3& diskNormal, float& t)
{
// First intersect with the plane of the disk
@@ -421,10 +421,10 @@ int AZ::Intersect::IntersectRayDisk(
if (pointOnPlane.GetDistance(diskCenter) < diskRadius)
{
t = planeIntersectionDistance;
return 1;
return true;
}
}
return 0;
return false;
}
// Reference: Real-Time Collision Detection - 5.3.7 Intersecting Ray or Segment Against Cylinder, and the book's errata.
@@ -1012,7 +1012,7 @@ int AZ::Intersect::IntersectRayQuad(
}
// reference: Real-Time Collision Detection, 5.3.3 Intersecting Ray or Segment Against Box
int AZ::Intersect::IntersectRayBox(
bool AZ::Intersect::IntersectRayBox(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& boxCenter, const Vector3& boxAxis1,
const Vector3& boxAxis2, const Vector3& boxAxis3, float boxHalfExtent1, float boxHalfExtent2, float boxHalfExtent3, float& t)
{
@@ -1044,7 +1044,7 @@ int AZ::Intersect::IntersectRayBox(
// If the ray is parallel to the slab and the ray origin is outside, return no intersection.
if (tp < 0.0f || tn < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1065,7 +1065,7 @@ int AZ::Intersect::IntersectRayBox(
tmax = AZ::GetMin(tmax, t2);
if (tmin > tmax)
{
return 0;
return false;
}
}
@@ -1085,7 +1085,7 @@ int AZ::Intersect::IntersectRayBox(
// If the ray is parallel to the slab and the ray origin is outside, return no intersection.
if (tp < 0.0f || tn < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1106,7 +1106,7 @@ int AZ::Intersect::IntersectRayBox(
tmax = AZ::GetMin(tmax, t2);
if (tmin > tmax)
{
return 0;
return false;
}
}
@@ -1126,7 +1126,7 @@ int AZ::Intersect::IntersectRayBox(
// If the ray is parallel to the slab and the ray origin is outside, return no intersection.
if (tp < 0.0f || tn < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1147,15 +1147,15 @@ int AZ::Intersect::IntersectRayBox(
tmax = AZ::GetMin(tmax, t2);
if (tmin > tmax)
{
return 0;
return false;
}
}
t = (isRayOriginInsideBox ? tmax : tmin);
return 1;
return true;
}
int AZ::Intersect::IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t)
bool AZ::Intersect::IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t)
{
return AZ::Intersect::IntersectRayBox(rayOrigin, rayDir, obb.GetPosition(),
obb.GetAxisX(), obb.GetAxisY(), obb.GetAxisZ(),
@@ -1166,7 +1166,7 @@ int AZ::Intersect::IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayD
// IntersectSegmentCylinder
// [10/21/2009]
//=========================================================================
int
CylinderIsectTypes
AZ::Intersect::IntersectSegmentCylinder(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t)
{
@@ -1225,7 +1225,7 @@ AZ::Intersect::IntersectSegmentCylinder(
return RR_ISECT_RAY_CYL_NONE; // No real roots; no intersection
}
t = (-b - Sqrt(discr)) / a;
int result = RR_ISECT_RAY_CYL_PQ; // default along the PQ segment
CylinderIsectTypes result = RR_ISECT_RAY_CYL_PQ; // default along the PQ segment
if (md + t * nd < 0.0f)
{
@@ -1294,7 +1294,7 @@ AZ::Intersect::IntersectSegmentCylinder(
// IntersectSegmentCapsule
// [10/21/2009]
//=========================================================================
int
CapsuleIsectTypes
AZ::Intersect::IntersectSegmentCapsule(const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t)
{
int result = IntersectSegmentCylinder(sa, dir, p, q, r, t);
@@ -1361,13 +1361,13 @@ AZ::Intersect::IntersectSegmentCapsule(const Vector3& sa, const Vector3& dir, co
// IntersectSegmentPolyhedron
// [10/21/2009]
//=========================================================================
int
bool
AZ::Intersect::IntersectSegmentPolyhedron(
const Vector3& sa, const Vector3& sBA, const Plane p[], int numPlanes,
const Vector3& sa, const Vector3& dir, const Plane p[], int numPlanes,
float& tfirst, float& tlast, int& iFirstPlane, int& iLastPlane)
{
// Compute direction vector for the segment
Vector3 d = /*b - a*/ sBA;
Vector3 d = /*b - a*/ dir;
// Set initial interval to being the whole segment. For a ray, tlast should be
// set to +RR_FLT_MAX. For a line, additionally tfirst should be set to -RR_FLT_MAX
tfirst = 0.0f;
@@ -1388,7 +1388,7 @@ AZ::Intersect::IntersectSegmentPolyhedron(
// If so, return "no intersection" if segment lies outside plane
if (dist < 0.0f)
{
return 0;
return false;
}
}
else
@@ -1417,7 +1417,7 @@ AZ::Intersect::IntersectSegmentPolyhedron(
// Exit with "no intersection" if intersection becomes empty
if (tfirst > tlast)
{
return 0;
return false;
}
}
}
@@ -1425,11 +1425,11 @@ AZ::Intersect::IntersectSegmentPolyhedron(
//DBG_Assert(iFirstPlane!=-1&&iLastPlane!=-1,("We have some bad border case to have only one plane, fix this function!"));
if (iFirstPlane == -1 && iLastPlane == -1)
{
return 0;
return false;
}
// A nonzero logical intersection, so the segment intersects the polyhedron
return 1;
return true;
}
//=========================================================================
@@ -1442,7 +1442,7 @@ AZ::Intersect::ClosestSegmentSegment(
const Vector3& segment2Start, const Vector3& segment2End,
float& segment1Proportion, float& segment2Proportion,
Vector3& closestPointSegment1, Vector3& closestPointSegment2,
float epsilon /*= 1e-4f*/ )
float epsilon)
{
const Vector3 segment1 = segment1End - segment1Start;
const Vector3 segment2 = segment2End - segment2Start;
@@ -5,363 +5,398 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef AZCORE_MATH_SEGMENT_INTERSECTION_H
#define AZCORE_MATH_SEGMENT_INTERSECTION_H
#pragma once
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Obb.h>
#include <AzCore/Math/Plane.h>
/// \file isect_segment.h
#include <AzCore/Math/Vector3.h>
namespace AZ
{
namespace Intersect
{
//! LineToPointDistanceTime computes the time of the shortest distance from point 'p' to segment (s1,s2).
//! To calculate the point of intersection:
//! P = s1 + u (s2 - s1)
//! @param s1 segment start point
//! @param s2 segment end point
//! @param p point to find the closest time to.
//! @return time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
inline float LineToPointDistanceTime(const Vector3& s1, const Vector3& s21, const Vector3& p)
{
// so u = (p.x - s1.x)*(s2.x - s1.x) + (p.y - s1.y)*(s2.y - s1.y) + (p.z-s1.z)*(s2.z-s1.z) / |s2-s1|^2
return s21.Dot(p - s1) / s21.Dot(s21);
}
//! To calculate the point of intersection: P = s1 + u (s2 - s1)
//! @param s1 Segment start point.
//! @param s2 Segment end point.
//! @param p Point to find the closest time to.
//! @return Time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
float LineToPointDistanceTime(const Vector3& s1, const Vector3& s21, const Vector3& p);
//! LineToPointDistance computes the closest point to 'p' from a segment (s1,s2).
//! @param s1 segment start point
//! @param s2 segment end point
//! @param p point to find the closest time to.
//! @param u time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
//! @return the closest point
inline Vector3 LineToPointDistance(const Vector3& s1, const Vector3& s2, const Vector3& p, float& u)
{
const Vector3 s21 = s2 - s1;
// we assume seg1 and seg2 are NOT coincident
AZ_MATH_ASSERT(!s21.IsClose(Vector3(0.0f), 1e-4f), "OK we agreed that we will pass valid segments! (s1 != s2)");
u = LineToPointDistanceTime(s1, s21, p);
return s1 + u * s21;
}
//! @param s1 Segment start point
//! @param s2 Segment end point
//! @param p Point to find the closest time to.
//! @param u Time (on the segment) for the shortest distance from 'p' to (s1,s2) [0.0f (s1),1.0f (s2)]
//! @return The closest point
Vector3 LineToPointDistance(const Vector3& s1, const Vector3& s2, const Vector3& p, float& u);
//! Given segment pq and triangle abc (CCW), returns whether segment intersects
//! triangle and if so, also returns the barycentric coordinates (u,v,w)
//! of the intersection point.
//! @param p segment start point
//! @param q segment end point
//! @param a triangle point 1
//! @param b triangle point 2
//! @param c triangle point 3
//! @param normal at the intersection point.
//! @param t time of intersection along the segment [0.0 (p), 1.0 (q)]
//! @return 1 if the segment intersects the triangle otherwise 0
int IntersectSegmentTriangleCCW(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t);
//! @param p Segment start point.
//! @param q Segment end point.
//! @param a Triangle point 1.
//! @param b Triangle point 2.
//! @param c Triangle point 3.
//! @param normal At the intersection point.
//! @param t Time of intersection along the segment [0.0 (p), 1.0 (q)].
//! @return true if the segments intersects the triangle otherwise false.
bool IntersectSegmentTriangleCCW(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c, Vector3& normal, float& t);
//! Same as \ref IntersectSegmentTriangleCCW without respecting the triangle (a,b,c) vertex order (double sided).
int IntersectSegmentTriangle(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c,
/*float &u, float &v, float &w,*/ Vector3& normal, float& t);
//! @param p Segment start point.
//! @param q Segment end point.
//! @param a Triangle point 1.
//! @param b Triangle point 2.
//! @param c Triangle point 3.
//! @param normal At the intersection point.
//! @param t Time of intersection along the segment [0.0 (p), 1.0 (q)].
//! @return True if the segments intersects the triangle otherwise false.
bool IntersectSegmentTriangle(
const Vector3& p, const Vector3& q, const Vector3& a, const Vector3& b, const Vector3& c, Vector3& normal, float& t);
//! Ray aabb intersection result types.
enum RayAABBIsectTypes
enum RayAABBIsectTypes : AZ::s32
{
ISECT_RAY_AABB_NONE = 0, ///< no intersection
ISECT_RAY_AABB_SA_INSIDE, ///< the ray starts inside the aabb
ISECT_RAY_AABB_ISECT, ///< intersects along the PQ segment
ISECT_RAY_AABB_NONE = 0, ///< no intersection
ISECT_RAY_AABB_SA_INSIDE, ///< the ray starts inside the aabb
ISECT_RAY_AABB_ISECT, ///< intersects along the PQ segment
};
//! Intersect ray R(t) = rayStart + t*d against AABB a. When intersecting,
//! return intersection distance tmin and point q of intersection.
//! @param rayStart ray starting point
//! @param dir ray direction and length (dir = rayEnd - rayStart)
//! @param dirRCP 1/dir (reciprocal direction - we cache this result very often so we don't need to compute it multiple times, otherwise just use dir.GetReciprocal())
//! @param rayStart Ray starting point
//! @param dir Ray direction and length (dir = rayEnd - rayStart)
//! @param dirRCP 1/dir (reciprocal direction - we cache this result very often so we don't need to compute it multiple times,
//! otherwise just use dir.GetReciprocal())
//! @param aabb Axis aligned bounding box to intersect against
//! @param tStart time on ray of the first intersection [0,1] or 0 if the ray starts inside the aabb - check the return value
//! @param tEnd time of the of the second intersection [0,1] (it can be > 1 if intersects after the rayEnd)
//! @param startNormal normal at the start point.
//! @param tStart Time on ray of the first intersection [0,1] or 0 if the ray starts inside the aabb - check the return value
//! @param tEnd Time of the of the second intersection [0,1] (it can be > 1 if intersects after the rayEnd)
//! @param startNormal Normal at the start point.
//! @return \ref RayAABBIsectTypes
int IntersectRayAABB(
const Vector3& rayStart, const Vector3& dir, const Vector3& dirRCP, const Aabb& aabb,
float& tStart, float& tEnd, Vector3& startNormal /*, Vector3& inter*/);
RayAABBIsectTypes IntersectRayAABB(
const Vector3& rayStart,
const Vector3& dir,
const Vector3& dirRCP,
const Aabb& aabb,
float& tStart,
float& tEnd,
Vector3& startNormal);
//! Intersect ray against AABB.
//! @param rayStart ray starting point.
//! @param dir ray reciprocal direction.
//! @param rayStart Ray starting point.
//! @param dir Ray reciprocal direction.
//! @param aabb Axis aligned bounding box to intersect against.
//! @param start length on ray of the first intersection.
//! @param end length of the of the second intersection.
//! @return \ref RayAABBIsectTypes In this faster version than IntersectRayAABB we return only ISECT_RAY_AABB_NONE and ISECT_RAY_AABB_ISECT.
//! You can check yourself for that case.
int IntersectRayAABB2(
const Vector3& rayStart, const Vector3& dirRCP, const Aabb& aabb,
float& start, float& end);
//! @param start Length on ray of the first intersection.
//! @param end Length of the of the second intersection.
//! @return \ref RayAABBIsectTypes In this faster version than IntersectRayAABB we return only ISECT_RAY_AABB_NONE and
//! ISECT_RAY_AABB_ISECT. You can check yourself for that case.
RayAABBIsectTypes IntersectRayAABB2(const Vector3& rayStart, const Vector3& dirRCP, const Aabb& aabb, float& start, float& end);
//! Clip a ray to an aabb. return true if ray was clipped. The ray
//! can be inside so don't use the result if the ray intersect the box.
inline int ClipRayWithAabb(
const Aabb& aabb, Vector3& rayStart, Vector3& rayEnd, float& tClipStart, float& tClipEnd)
{
Vector3 startNormal;
float tStart, tEnd;
Vector3 dirLen = rayEnd - rayStart;
if (IntersectRayAABB(rayStart, dirLen, dirLen.GetReciprocal(), aabb, tStart, tEnd, startNormal) != ISECT_RAY_AABB_NONE)
{
// clip the ray with the box
if (tStart > 0.0f)
{
rayStart = rayStart + tStart * dirLen;
tClipStart = tStart;
}
if (tEnd < 1.0f)
{
rayEnd = rayStart + tEnd * dirLen;
tClipEnd = tEnd;
}
return 1;
}
return 0;
}
//! @param aabb Bounds to test against.
//! @param rayStart The start of the ray.
//! @param rayEnd The end of the ray.
//! @param[out] tClipStart The proportion where the ray enters the \ref Aabb.
//! @param[out] tClipEnd The proportion where the ray exits the \ref Aabb.
//! @return True if the ray was clipped, otherwise false.
bool ClipRayWithAabb(const Aabb& aabb, Vector3& rayStart, Vector3& rayEnd, float& tClipStart, float& tClipEnd);
//! Test segment and aabb where the segment is defined by midpoint
//! midPoint = (p1-p0) * 0.5f and half vector halfVector = p1 - midPoint.
//! the aabb is at the origin and defined by half extents only.
//! @return 1 if the intersect, otherwise 0.
int TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& halfVector, const Vector3& aabbExtends);
//! @param midPoint Midpoint of a line segment.
//! @param halfVector Half vector of an aabb.
//! @param aabbExtends The extends of a bounded box.
//! @return True if the segment and AABB intersect, otherwise false
bool TestSegmentAABBOrigin(const Vector3& midPoint, const Vector3& halfVector, const Vector3& aabbExtends);
//! Test if segment specified by points p0 and p1 intersects AABB. \ref TestSegmentAABBOrigin
//! @return 1 if the segment and AABB intersect, otherwise 0.
inline int TestSegmentAABB(const Vector3& p0, const Vector3& p1, const Aabb& aabb)
{
Vector3 e = aabb.GetExtents();
Vector3 d = p1 - p0;
Vector3 m = p0 + p1 - aabb.GetMin() - aabb.GetMax();
return TestSegmentAABBOrigin(m, d, e);
}
//! Test if segment specified by points p0 and p1 intersects AABB. \ref TestSegmentAABBOrigin.
//! @param p0 Segment start point.
//! @param p1 Segment end point.
//! @param aabb Bounded box to test against.
//! @return True if the segment and AABB intersect, otherwise false.
bool TestSegmentAABB(const Vector3& p0, const Vector3& p1, const Aabb& aabb);
//! Ray sphere intersection result types.
enum SphereIsectTypes
enum SphereIsectTypes : AZ::s32
{
ISECT_RAY_SPHERE_SA_INSIDE = -1, // the ray starts inside the cylinder
ISECT_RAY_SPHERE_NONE, // no intersection
ISECT_RAY_SPHERE_ISECT, // along the PQ segment
ISECT_RAY_SPHERE_SA_INSIDE = -1, //!< The ray starts inside the cylinder
ISECT_RAY_SPHERE_NONE, //!< No intersection
ISECT_RAY_SPHERE_ISECT, //!< Along the PQ segment
};
//! IntersectRaySphereOrigin
//! return time t>=0 but not limited, so if you check a segment make sure
//! t <= segmentLen
//! @param rayStart ray start point
//! t <= segmentLen.
//! @param rayStart ray start point.
//! @param rayDirNormalized ray direction normalized.
//! @param shereRadius sphere radius
//! @param shereRadius Radius of sphere at origin.
//! @param time of closest intersection [0,+INF] in relation to the normalized direction.
//! @return \ref SphereIsectTypes
AZ_INLINE int IntersectRaySphereOrigin(
const Vector3& rayStart, const Vector3& rayDirNormalized,
const float sphereRadius, float& t)
{
Vector3 m = rayStart;
float b = m.Dot(rayDirNormalized);
float c = m.Dot(m) - sphereRadius * sphereRadius;
// Exit if r's origin outside s (c > 0)and r pointing away from s (b > 0)
if (c > 0.0f && b > 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
float discr = b * b - c;
// A negative discriminant corresponds to ray missing sphere
if (discr < 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
// Ray now found to intersect sphere, compute smallest t value of intersection
t = -b - Sqrt(discr);
// If t is negative, ray started inside sphere so clamp t to zero
if (t < 0.0f)
{
// t = 0.0f;
return ISECT_RAY_SPHERE_SA_INSIDE; // no hit if inside
}
//q = p + t * d;
return ISECT_RAY_SPHERE_ISECT;
}
//! @return \ref SphereIsectTypes.
SphereIsectTypes IntersectRaySphereOrigin(
const Vector3& rayStart, const Vector3& rayDirNormalized, const float sphereRadius, float& t);
//! Intersect ray (rayStart,rayDirNormalized) and sphere (sphereCenter,sphereRadius) \ref IntersectRaySphereOrigin
inline int IntersectRaySphere(
const Vector3& rayStart, const Vector3& rayDirNormalized, const Vector3& sphereCenter, const float sphereRadius, float& t)
{
return IntersectRaySphereOrigin(rayStart - sphereCenter, rayDirNormalized, sphereRadius, t);
}
//! @param rayStart The start of the ray.
//! @param rayDirNormalized The direction of the ray normalized.
//! @param sphereCenter The center of the sphere.
//! @param sphereRadius Radius of the sphere.
//! @param[out] t Coefficient in the ray's explicit equation from which an
//! intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @return SphereIsectTypes
SphereIsectTypes IntersectRaySphere(
const Vector3& rayStart, const Vector3& rayDirNormalized, const Vector3& sphereCenter, const float sphereRadius, float& t);
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param diskCenter Center point of the disk
//! @param diskRadius Radius of the disk
//! @param diskNormal A normal perpendicular to the disk
//! @param[out] t If returning 1 (indicating a hit), this contains distance from rayOrigin along the normalized rayDir that the hit occured at.
//! @return The number of intersecting points.
int IntersectRayDisk(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& diskCenter, const float diskRadius, const AZ::Vector3& diskNormal, float& t);
//! Intersect ray (rayStarty, rayDirNormalized) and disk (center, radius, normal)
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param diskCenter Center point of the disk.
//! @param diskRadius Radius of the disk.
//! @param diskNormal A normal perpendicular to the disk.
//! @param[out] t If returning 1 (indicating a hit), this contains distance from rayOrigin along the normalized rayDir
//! that the hit occured at.
//! @return False if not interesecting and true if intersecting
bool IntersectRayDisk(
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& diskCenter,
const float diskRadius,
const AZ::Vector3& diskNormal,
float& t);
//! If there is only one intersecting point, the coefficient is stored in \ref t1.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param cylinderEnd1 The center of the circle on one end of the cylinder.
//! @param cylinderDir The direction pointing from \ref cylinderEnd1 to the other end of the cylinder. It has to be unit length.
//! @param cylinderHeight The distance between two centers of the circles on two ends of the cylinder respectively.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param cylinderEnd1 The center of the circle on one end of the cylinder.
//! @param cylinderDir The direction pointing from \ref cylinderEnd1 to the other end of the cylinder. It has to be unit length.
//! @param cylinderHeight The distance between two centers of the circles on two ends of the cylinder respectively.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
int IntersectRayCappedCylinder(
const Vector3& rayOrigin, const Vector3& rayDir,
const Vector3& cylinderEnd1, const Vector3& cylinderDir, float cylinderHeight, float cylinderRadius,
float& t1, float& t2);
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& cylinderEnd1,
const Vector3& cylinderDir,
float cylinderHeight,
float cylinderRadius,
float& t1,
float& t2);
//! If there is only one intersecting point, the coefficient is stored in \ref t1.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param coneApex The apex of the cone.
//! @param coneDir The unit-length direction from the apex to the base.
//! @param coneHeight The height of the cone, from the apex to the base.
//! @param coneBaseRadius The radius of the cone base circle.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
//! @param rayOrigin The origin of the ray to test.
//! @param rayDir The direction of the ray to test. It has to be unit length.
//! @param coneApex The apex of the cone.
//! @param coneDir The unit-length direction from the apex to the base.
//! @param coneHeight The height of the cone, from the apex to the base.
//! @param coneBaseRadius The radius of the cone base circle.
//! @param[out] t1 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t1 * rayDir".
//! @param[out] t2 A possible coefficient in the ray's explicit equation from which an intersecting point is calculated as "rayOrigin + t2 * rayDir".
//! @return The number of intersecting points.
int IntersectRayCone(
const Vector3& rayOrigin, const Vector3& rayDir,
const Vector3& coneApex, const Vector3& coneDir, float coneHeight, float coneBaseRadius,
float& t1, float& t2);
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& coneApex,
const Vector3& coneDir,
float coneHeight,
float coneBaseRadius,
float& t1,
float& t2);
//! Test intersection between a ray and a plane in 3D.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param planePos A point on the plane to test intersection with.
//! @param planeNormal The normal of the plane to test intersection with.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param planePos A point on the plane to test intersection with.
//! @param planeNormal The normal of the plane to test intersection with.
//! @param[out] t The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
int IntersectRayPlane(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& planePos,
const Vector3& planeNormal, float& t);
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& planePos, const Vector3& planeNormal, float& t);
//! Test intersection between a ray and a two-sided quadrilateral defined by four points in 3D.
//! The four points that define the quadrilateral could be passed in with either counter clock-wise
//! The four points that define the quadrilateral could be passed in with either counter clock-wise
//! winding or clock-wise winding.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param vertexA One of the four points that define the quadrilateral.
//! @param vertexB One of the four points that define the quadrilateral.
//! @param vertexC One of the four points that define the quadrilateral.
//! @param vertexD One of the four points that define the quadrilateral.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param vertexA One of the four points that define the quadrilateral.
//! @param vertexB One of the four points that define the quadrilateral.
//! @param vertexC One of the four points that define the quadrilateral.
//! @param vertexD One of the four points that define the quadrilateral.
//! @param[out] t The coefficient in the ray's explicit equation from which the
//! intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return The number of intersection point.
int IntersectRayQuad(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& vertexA,
const Vector3& vertexB, const Vector3& vertexC, const Vector3& vertexD, float& t);
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& vertexA,
const Vector3& vertexB,
const Vector3& vertexC,
const Vector3& vertexD,
float& t);
//! Test intersection between a ray and an oriented box in 3D.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param boxCenter The position of the center of the box.
//! @param boxAxis1 An axis along one dimension of the oriented box.
//! @param boxAxis2 An axis along one dimension of the oriented box.
//! @param boxAxis3 An axis along one dimension of the oriented box.
//! @param boxHalfExtent1 The half extent of the box on the dimension of \ref boxAxis1.
//! @param boxHalfExtent2 The half extent of the box on the dimension of \ref boxAxis2.
//! @param boxHalfExtent3 The half extent of the box on the dimension of \ref boxAxis3.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return 1 if there is an intersection, 0 otherwise.
int IntersectRayBox(
const Vector3& rayOrigin, const Vector3& rayDir, const Vector3& boxCenter, const Vector3& boxAxis1,
const Vector3& boxAxis2, const Vector3& boxAxis3, float boxHalfExtent1, float boxHalfExtent2, float boxHalfExtent3,
//! Test intersection between a ray and an oriented box in 3D.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param boxCenter The position of the center of the box.
//! @param boxAxis1 An axis along one dimension of the oriented box.
//! @param boxAxis2 An axis along one dimension of the oriented box.
//! @param boxAxis3 An axis along one dimension of the oriented box.
//! @param boxHalfExtent1 The half extent of the box on the dimension of \ref boxAxis1.
//! @param boxHalfExtent2 The half extent of the box on the dimension of \ref boxAxis2.
//! @param boxHalfExtent3 The half extent of the box on the dimension of \ref boxAxis3.
//! @param[out] t The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return true if there is an intersection, false otherwise.
bool IntersectRayBox(
const Vector3& rayOrigin,
const Vector3& rayDir,
const Vector3& boxCenter,
const Vector3& boxAxis1,
const Vector3& boxAxis2,
const Vector3& boxAxis3,
float boxHalfExtent1,
float boxHalfExtent2,
float boxHalfExtent3,
float& t);
//! Test intersection between a ray and an OBB.
//! @param rayOrigin The origin of the ray to test intersection with.
//! @param rayDir The direction of the ray to test intersection with.
//! @param obb The OBB to test for intersection with the ray.
//! @param t[out] The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return 1 if there is an intersection, 0 otherwise.
int IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t);
//! @param[out] t The coefficient in the ray's explicit equation from which the intersecting point is calculated as "rayOrigin + t * rayDirection".
//! @return True if there is an intersection, false otherwise.
bool IntersectRayObb(const Vector3& rayOrigin, const Vector3& rayDir, const Obb& obb, float& t);
//! Ray cylinder intersection types.
enum CylinderIsectTypes
enum CylinderIsectTypes : AZ::s32
{
RR_ISECT_RAY_CYL_SA_INSIDE = -1, // the ray starts inside the cylinder
RR_ISECT_RAY_CYL_NONE, // no intersection
RR_ISECT_RAY_CYL_PQ, // along the PQ segment
RR_ISECT_RAY_CYL_P_SIDE, // on the P side
RR_ISECT_RAY_CYL_Q_SIDE, // on the Q side
RR_ISECT_RAY_CYL_SA_INSIDE = -1, //!< the ray starts inside the cylinder
RR_ISECT_RAY_CYL_NONE, //!< no intersection
RR_ISECT_RAY_CYL_PQ, //!< along the PQ segment
RR_ISECT_RAY_CYL_P_SIDE, //!< on the P side
RR_ISECT_RAY_CYL_Q_SIDE, //!< on the Q side
};
//! Reference: Real-Time Collision Detection - 5.3.7 Intersecting Ray or Segment Against Cylinder
//! Intersect segment S(t)=sa+t(dir), 0<=t<=1 against cylinder specified by p, q and r.
int IntersectSegmentCylinder(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q,
const float r, float& t);
//! @param sa The initial point.
//! @param dir Magnitude and direction for sa.
//! @param p Center point of side 1 cylinder.
//! @param q Center point of side 2 cylinder.
//! @param r Radius of cylinder.
//! @param[out] t Proporition along line segment.
//! @return CylinderIsectTypes
CylinderIsectTypes IntersectSegmentCylinder(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t);
//! Capsule ray intersect types.
enum CapsuleIsectTypes
{
ISECT_RAY_CAPSULE_SA_INSIDE = -1, // the ray starts inside the cylinder
ISECT_RAY_CAPSULE_NONE, // no intersection
ISECT_RAY_CAPSULE_PQ, // along the PQ segment
ISECT_RAY_CAPSULE_P_SIDE, // on the P side
ISECT_RAY_CAPSULE_Q_SIDE, // on the Q side
ISECT_RAY_CAPSULE_SA_INSIDE = -1, //!< The ray starts inside the cylinder
ISECT_RAY_CAPSULE_NONE, //!< No intersection
ISECT_RAY_CAPSULE_PQ, //!< Along the PQ segment
ISECT_RAY_CAPSULE_P_SIDE, //!< On the P side
ISECT_RAY_CAPSULE_Q_SIDE, //!< On the Q side
};
//! This is a quick implementation of segment capsule based on segment cylinder \ref IntersectSegmentCylinder
//! segment sphere intersection. We can optimize it a lot once we fix the ray
//! cylinder intersection.
int IntersectSegmentCapsule(
const Vector3& sa, const Vector3& dir, const Vector3& p,
const Vector3& q, const float r, float& t);
//! @param sa The beginning of the line segment.
//! @param dir The direction and length of the segment.
//! @param p Center point of side 1 capsule.
//! @param q Center point of side 1 capsule.
//! @param r The radius of the capsule.
//! @param[out] t Proporition along line segment.
//! @return CapsuleIsectTypes
CapsuleIsectTypes IntersectSegmentCapsule(
const Vector3& sa, const Vector3& dir, const Vector3& p, const Vector3& q, const float r, float& t);
//! Intersect segment S(t)=A+t(B-A), 0<=t<=1 against convex polyhedron specified
//! by the n halfspaces defined by the planes p[]. On exit tfirst and tlast
//! define the intersection, if any.
int IntersectSegmentPolyhedron(
const Vector3& sa, const Vector3& sBA, const Plane p[], int numPlanes,
float& tfirst, float& tlast, int& iFirstPlane, int& iLastPlane);
//! @param sa The beggining of the line segment.
//! @param dir The direction and length of the segment.
//! @param p Planes that compose a convex ponvex polyhedron.
//! @param numPlanes number of planes.
//! @param[out] tfirst Proportion along the line segment where the line enters.
//! @param[out] tlast Proportion along the line segment where the line exits.
//! @param[out] iFirstPlane The plane where the line enters.
//! @param[out] iLastPlane The plane where the line exits.
//! @return True if intersects else false.
bool IntersectSegmentPolyhedron(
const Vector3& sa,
const Vector3& dir,
const Plane p[],
int numPlanes,
float& tfirst,
float& tlast,
int& iFirstPlane,
int& iLastPlane);
//! Calculate the line segment closestPointSegment1<->closestPointSegment2 that is the shortest route between
//! two segments segment1Start<->segment1End and segment2Start<->segment2End. Also calculate the values of segment1Proportion and segment2Proportion where
//! closestPointSegment1 = segment1Start + (segment1Proportion * (segment1End - segment1Start))
//! two segments segment1Start<->segment1End and segment2Start<->segment2End. Also calculate the values of segment1Proportion and
//! segment2Proportion where closestPointSegment1 = segment1Start + (segment1Proportion * (segment1End - segment1Start))
//! closestPointSegment2 = segment2Start + (segment2Proportion * (segment2End - segment2Start))
//! If segments are parallel returns a solution.
//! @param segment1Start Start of segment 1.
//! @param segment1End End of segment 1.
//! @param segment2Start Start of segment 2.
//! @param segment2End End of segment 2.
//! @param[out] segment1Proportion The proporition along segment 1 [0..1]
//! @param[out] segment2Proportion The proporition along segment 2 [0..1]
//! @param[out] closestPointSegment1 Closest point on segment 1.
//! @param[out] closestPointSegment2 Closest point on segment 2.
//! @param epsilon The minimum square distance where a line segment can be treated as a single point.
void ClosestSegmentSegment(
const Vector3& segment1Start, const Vector3& segment1End,
const Vector3& segment2Start, const Vector3& segment2End,
float& segment1Proportion, float& segment2Proportion,
Vector3& closestPointSegment1, Vector3& closestPointSegment2,
const Vector3& segment1Start,
const Vector3& segment1End,
const Vector3& segment2Start,
const Vector3& segment2End,
float& segment1Proportion,
float& segment2Proportion,
Vector3& closestPointSegment1,
Vector3& closestPointSegment2,
float epsilon = 1e-4f);
//! Calculate the line segment closestPointSegment1<->closestPointSegment2 that is the shortest route between
//! two segments segment1Start<->segment1End and segment2Start<->segment2End.
//! If segments are parallel returns a solution.
//! @param segment1Start Start of segment 1.
//! @param segment1End End of segment 1.
//! @param segment2Start Start of segment 2.
//! @param segment2End End of segment 2.
//! @param[out] closestPointSegment1 Closest point on segment 1.
//! @param[out] closestPointSegment2 Closest point on segment 2.
//! @param epsilon The minimum square distance where a line segment can be treated as a single point.
void ClosestSegmentSegment(
const Vector3& segment1Start, const Vector3& segment1End,
const Vector3& segment2Start, const Vector3& segment2End,
Vector3& closestPointSegment1, Vector3& closestPointSegment2,
const Vector3& segment1Start,
const Vector3& segment1End,
const Vector3& segment2Start,
const Vector3& segment2End,
Vector3& closestPointSegment1,
Vector3& closestPointSegment2,
float epsilon = 1e-4f);
//! Calculate the point (closestPointOnSegment) that is the closest point on
//! segment segmentStart/segmentEnd to point. Also calculate the value of proportion where
//! closestPointOnSegment = segmentStart + (proportion * (segmentEnd - segmentStart))
//! @param point The point to test
//! @param segmentStart The start of the segment
//! @param segmentEnd The end of the segment
//! @param[out] proportion The proportion of the segment L(t) = (end - start) * t
//! @param[out] closestPointOnSegment The point along the line segment
void ClosestPointSegment(
const Vector3& point, const Vector3& segmentStart, const Vector3& segmentEnd,
float& proportion, Vector3& closestPointOnSegment);
}
}
const Vector3& point,
const Vector3& segmentStart,
const Vector3& segmentEnd,
float& proportion,
Vector3& closestPointOnSegment);
} // namespace Intersect
} // namespace AZ
#endif // AZCORE_MATH_SEGMENT_INTERSECTION_H
#pragma once
#include <AzCore/Math/IntersectSegment.inl>
@@ -0,0 +1,101 @@
/*
* 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
*
*/
namespace AZ
{
namespace Intersect
{
AZ_MATH_INLINE bool ClipRayWithAabb(const Aabb& aabb, Vector3& rayStart, Vector3& rayEnd, float& tClipStart, float& tClipEnd)
{
Vector3 startNormal;
float tStart, tEnd;
Vector3 dirLen = rayEnd - rayStart;
if (IntersectRayAABB(rayStart, dirLen, dirLen.GetReciprocal(), aabb, tStart, tEnd, startNormal) != ISECT_RAY_AABB_NONE)
{
// clip the ray with the box
if (tStart > 0.0f)
{
rayStart = rayStart + tStart * dirLen;
tClipStart = tStart;
}
if (tEnd < 1.0f)
{
rayEnd = rayStart + tEnd * dirLen;
tClipEnd = tEnd;
}
return true;
}
return false;
}
AZ_MATH_INLINE SphereIsectTypes
IntersectRaySphereOrigin(const Vector3& rayStart, const Vector3& rayDirNormalized, const float sphereRadius, float& t)
{
Vector3 m = rayStart;
float b = m.Dot(rayDirNormalized);
float c = m.Dot(m) - sphereRadius * sphereRadius;
// Exit if r's origin outside s (c > 0)and r pointing away from s (b > 0)
if (c > 0.0f && b > 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
float discr = b * b - c;
// A negative discriminant corresponds to ray missing sphere
if (discr < 0.0f)
{
return ISECT_RAY_SPHERE_NONE;
}
// Ray now found to intersect sphere, compute smallest t value of intersection
t = -b - Sqrt(discr);
// If t is negative, ray started inside sphere so clamp t to zero
if (t < 0.0f)
{
// t = 0.0f;
return ISECT_RAY_SPHERE_SA_INSIDE; // no hit if inside
}
// q = p + t * d;
return ISECT_RAY_SPHERE_ISECT;
}
AZ_MATH_INLINE SphereIsectTypes IntersectRaySphere(const Vector3& rayStart, const Vector3& rayDirNormalized, const Vector3& sphereCenter, const float sphereRadius, float& t)
{
return IntersectRaySphereOrigin(rayStart - sphereCenter, rayDirNormalized, sphereRadius, t);
}
AZ_MATH_INLINE Vector3 LineToPointDistance(const Vector3& s1, const Vector3& s2, const Vector3& p, float& u)
{
const Vector3 s21 = s2 - s1;
// we assume seg1 and seg2 are NOT coincident
AZ_MATH_ASSERT(!s21.IsClose(Vector3(0.0f), 1e-4f), "OK we agreed that we will pass valid segments! (s1 != s2)");
u = LineToPointDistanceTime(s1, s21, p);
return s1 + u * s21;
}
AZ_MATH_INLINE float LineToPointDistanceTime(const Vector3& s1, const Vector3& s21, const Vector3& p)
{
// so u = (p.x - s1.x)*(s2.x - s1.x) + (p.y - s1.y)*(s2.y - s1.y) + (p.z-s1.z)*(s2.z-s1.z) / |s2-s1|^2
return s21.Dot(p - s1) / s21.Dot(s21);
}
AZ_MATH_INLINE bool TestSegmentAABB(const Vector3& p0, const Vector3& p1, const Aabb& aabb)
{
Vector3 e = aabb.GetExtents();
Vector3 d = p1 - p0;
Vector3 m = p0 + p1 - aabb.GetMin() - aabb.GetMax();
return TestSegmentAABBOrigin(m, d, e);
}
} // namespace Intersect
} // namespace AZ
@@ -282,6 +282,7 @@ set(FILES
Math/Internal/VertexContainer.inl
Math/InterpolationSample.h
Math/IntersectPoint.h
Math/IntersectSegment.inl
Math/IntersectSegment.cpp
Math/IntersectSegment.h
Math/MathIntrinsics.h
@@ -29,6 +29,10 @@ namespace AzFramework
AZStd::vector<AZ::IO::Path> m_absoluteSourcePaths; //!< Where the gem's source path folder are located(as an absolute path)
static constexpr const char* GetGemAssetFolder() { return "Assets"; }
static constexpr const char* GetGemRegistryFolder()
{
return "Registry";
}
};
//! Returns a list of GemInfo of all the gems that are active for the for the specified game project.
@@ -20,6 +20,7 @@
#include <AzCore/RTTI/BehaviorContext.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzCore/Settings/SettingsRegistry.h>
////////////////////////////////////////////////////////////////////////////////////////////////////
namespace AzFramework
@@ -190,6 +191,25 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////////////////////////////
void InputSystemComponent::Activate()
{
const auto* settingsRegistry = AZ::SettingsRegistry::Get();
if (settingsRegistry)
{
AZ::u64 value = 0;
if (settingsRegistry->Get(value, "/O3DE/InputSystem/MouseMovementSampleRateHertz"))
{
m_mouseMovementSampleRateHertz = aznumeric_caster(value);
}
if (settingsRegistry->Get(value, "/O3DE/InputSystem/GamepadsEnabled"))
{
m_gamepadsEnabled = aznumeric_caster(value);
}
settingsRegistry->Get(m_keyboardEnabled, "/O3DE/InputSystem/KeyboardEnabled");
settingsRegistry->Get(m_motionEnabled, "/O3DE/InputSystem/MotionEnabled");
settingsRegistry->Get(m_mouseEnabled, "/O3DE/InputSystem/MouseEnabled");
settingsRegistry->Get(m_touchEnabled, "/O3DE/InputSystem/TouchEnabled");
settingsRegistry->Get(m_virtualKeyboardEnabled, "/O3DE/InputSystem/VirtualKeyboardEnabled");
}
// Create all enabled input devices
CreateEnabledInputDevices();
@@ -22,6 +22,7 @@ namespace AzFramework
->Field("terminationTime", &SessionConfig::m_terminationTime)
->Field("creatorId", &SessionConfig::m_creatorId)
->Field("sessionProperties", &SessionConfig::m_sessionProperties)
->Field("matchmakingData", &SessionConfig::m_matchmakingData)
->Field("sessionId", &SessionConfig::m_sessionId)
->Field("sessionName", &SessionConfig::m_sessionName)
->Field("dnsName", &SessionConfig::m_dnsName)
@@ -46,6 +47,8 @@ namespace AzFramework
"CreatorId", "A unique identifier for a player or entity creating the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionProperties,
"SessionProperties", "A collection of custom properties for a session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_matchmakingData,
"MatchmakingData", "The matchmaking process information that was used to create the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionId,
"SessionId", "A unique identifier for the session.")
->DataElement(AZ::Edit::UIHandlers::Default, &AzFramework::SessionConfig::m_sessionName,
@@ -35,6 +35,9 @@ namespace AzFramework
// A collection of custom properties for a session.
AZStd::unordered_map<AZStd::string, AZStd::string> m_sessionProperties;
// The matchmaking process information that was used to create the session.
AZStd::string m_matchmakingData;
// A unique identifier for the session.
AZStd::string m_sessionId;
@@ -41,6 +41,11 @@ namespace AzFramework
// OnDestroySessionBegin is fired at the beginning of session termination
// @return The result of all OnDestroySessionBegin notifications
virtual bool OnDestroySessionBegin() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update
// @param sessionConfig The properties to describe a session
// @param updateReason The reason for session update
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
};
using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework
@@ -61,6 +61,10 @@ namespace AzFramework
//! be deleted and the spawnable asset to be released. This call is automatically done when
//! AssignRootSpawnable is called while a root spawnable is assigned.
virtual void ReleaseRootSpawnable() = 0;
//! Force processing all SpawnableEntitiesManager requests immediately
//! This is useful when loading a different level while SpawnableEntitiesManager still has
//! pending requests
virtual void ProcessSpawnableQueue() = 0;
};
using RootSpawnableInterface = AZ::Interface<RootSpawnableDefinition>;
@@ -45,8 +45,7 @@ namespace AzFramework
void SpawnableSystemComponent::OnTick(float /*deltaTime*/, AZ::ScriptTimePoint /*time*/)
{
m_entitiesManager.ProcessQueue(
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
ProcessSpawnableQueue();
RootSpawnableNotificationBus::ExecuteQueuedEvents();
}
@@ -121,6 +120,12 @@ namespace AzFramework
m_rootSpawnableId = AZ::Data::AssetId();
}
void SpawnableSystemComponent::ProcessSpawnableQueue()
{
m_entitiesManager.ProcessQueue(
SpawnableEntitiesManager::CommandQueuePriority::High | SpawnableEntitiesManager::CommandQueuePriority::Regular);
}
void SpawnableSystemComponent::OnRootSpawnableAssigned([[maybe_unused]] AZ::Data::Asset<Spawnable> rootSpawnable,
[[maybe_unused]] uint32_t generation)
{
@@ -161,6 +166,8 @@ namespace AzFramework
void SpawnableSystemComponent::Deactivate()
{
ProcessSpawnableQueue();
m_registryChangeHandler.Disconnect();
AZ::TickBus::Handler::BusDisconnect();
@@ -75,6 +75,7 @@ namespace AzFramework
uint64_t AssignRootSpawnable(AZ::Data::Asset<Spawnable> rootSpawnable) override;
void ReleaseRootSpawnable() override;
void ProcessSpawnableQueue() override;
//
// RootSpawnbleNotificationBus
@@ -10,6 +10,7 @@
#include <AzFramework/Windowing/NativeWindow.h>
#include <AzFramework/XcbNativeWindow.h>
#include <AzFramework/XcbConnectionManager.h>
#include <AzFramework/XcbInterface.h>
#include <xcb/xcb.h>
@@ -12,7 +12,6 @@
#include <xcb/xcb.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/XcbApplication.h>
#include <AzFramework/XcbInputDeviceKeyboard.h>
#include <AzFramework/Input/Buses/Notifications/InputTextNotificationBus.h>
@@ -20,6 +19,7 @@
#include "Matchers.h"
#include "Actions.h"
#include "XcbBaseTestFixture.h"
#include "XcbTestApplication.h"
template<typename T>
xcb_generic_event_t MakeEvent(T event)
@@ -33,6 +33,7 @@ namespace AzFramework
class XcbInputDeviceKeyboardTests
: public XcbBaseTestFixture
{
public:
void SetUp() override
{
using testing::Return;
@@ -123,6 +124,15 @@ namespace AzFramework
static constexpr xcb_keycode_t s_keycodeForAKey{38};
static constexpr xcb_keycode_t s_keycodeForShiftLKey{50};
XcbTestApplication m_application{
/*enabledGamepadsCount=*/0,
/*keyboardEnabled=*/true,
/*motionEnabled=*/false,
/*mouseEnabled=*/false,
/*touchEnabled=*/false,
/*virtualKeyboardEnabled=*/false
};
};
class InputTextNotificationListener
@@ -195,27 +205,23 @@ namespace AzFramework
EXPECT_CALL(m_interface, xkb_state_key_get_one_sym(&m_xkbState, s_keycodeForAKey))
.Times(2);
Application application;
application.Start({}, {});
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_application.Start();
const InputChannel* inputChannel = InputChannelRequests::FindInputChannel(InputDeviceKeyboard::Key::AlphanumericA);
ASSERT_TRUE(inputChannel);
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Idle));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Began));
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
EXPECT_THAT(inputChannel->GetState(), Eq(InputChannel::State::Ended));
application.Stop();
}
TEST_F(XcbInputDeviceKeyboardTests, TextEnteredFromXcbKeyPressEvents)
@@ -420,17 +426,13 @@ namespace AzFramework
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("a"), _)).Times(1);
EXPECT_CALL(textListener, OnInputTextEvent(StrEq("A"), _)).Times(1);
Application application;
application.Start({}, {});
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
m_application.Start();
for (int i = 0; i < 4; ++i)
{
application.PumpSystemEventLoopUntilEmpty();
application.TickSystem();
application.Tick();
m_application.PumpSystemEventLoopUntilEmpty();
m_application.TickSystem();
m_application.Tick();
}
application.Stop();
}
} // namespace AzFramework
@@ -0,0 +1,38 @@
/*
* 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/Settings/SettingsRegistry.h>
#include <AzCore/UserSettings/UserSettingsComponent.h>
#include <AzFramework/Application/Application.h>
namespace AzFramework
{
class XcbTestApplication
: public Application
{
public:
XcbTestApplication(AZ::u64 enabledGamepadsCount, bool keyboardEnabled, bool motionEnabled, bool mouseEnabled, bool touchEnabled, bool virtualKeyboardEnabled)
{
auto* settingsRegistry = AZ::SettingsRegistry::Get();
settingsRegistry->Set("/O3DE/InputSystem/GamepadsEnabled", enabledGamepadsCount);
settingsRegistry->Set("/O3DE/InputSystem/KeyboardEnabled", keyboardEnabled);
settingsRegistry->Set("/O3DE/InputSystem/MotionEnabled", motionEnabled);
settingsRegistry->Set("/O3DE/InputSystem/MouseEnabled", mouseEnabled);
settingsRegistry->Set("/O3DE/InputSystem/TouchEnabled", touchEnabled);
settingsRegistry->Set("/O3DE/InputSystem/VirtualKeyboardEnabled", virtualKeyboardEnabled);
}
void Start(const Descriptor& descriptor = {}, const StartupParameters& startupParameters = {}) override
{
Application::Start(descriptor, startupParameters);
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
}
};
} // namespace AzFramework
@@ -17,4 +17,5 @@ set(FILES
XcbBaseTestFixture.cpp
XcbBaseTestFixture.h
XcbInputDeviceKeyboardTests.cpp
XcbTestApplication.h
)
@@ -96,6 +96,8 @@ namespace AzGameFramework
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_ProjectUserRegistry(registry, AZ_TRAIT_OS_PLATFORM_CODENAME, specializations, &scratchBuffer);
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, true);
#else
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(registry, m_commandLine, false);
#endif
// Update the Runtime file paths in case the "{BootstrapSettingsRootKey}/assets" key was overriden by a setting registry
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(registry);
@@ -12,6 +12,7 @@ set(FILES
../../Utilities/QtWindowUtilities_linux.cpp
../../Utilities/ScreenGrabber_linux.cpp
../../../Platform/Linux/AzQtComponents/Components/StyledDockWidget_Linux.cpp
../../../Platform/Linux/AzQtComponents/Utilities/DesktopUtilities_Linux.cpp
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Linux.h
../../../Platform/Linux/AzQtComponents/AzQtComponents_Traits_Platform.h
)
@@ -12,6 +12,7 @@ set(FILES
../../Utilities/QtWindowUtilities_mac.mm
../../Utilities/ScreenGrabber_mac.mm
../../../Platform/Mac/AzQtComponents/Components/StyledDockWidget_Mac.cpp
../../../Platform/Mac/AzQtComponents/Utilities/DesktopUtilities_Mac.cpp
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Mac.h
../../../Platform/Mac/AzQtComponents/AzQtComponents_Traits_Platform.h
)
@@ -9,6 +9,7 @@
set(FILES
../../natvis/qt.natvis
../../../Platform/Windows/AzQtComponents/Utilities/HandleDpiAwareness_Windows.cpp
../../../Platform/Windows/AzQtComponents/Utilities/DesktopUtilities_Windows.cpp
../../Utilities/MouseHider_win.cpp
../../Utilities/QtWindowUtilities_win.cpp
../../Utilities/ScreenGrabber_win.cpp
@@ -271,7 +271,6 @@ set(FILES
Utilities/ColorUtilities.h
Utilities/Conversions.h
Utilities/Conversions.cpp
Utilities/DesktopUtilities.cpp
Utilities/DesktopUtilities.h
Utilities/HandleDpiAwareness.cpp
Utilities/HandleDpiAwareness.h
@@ -0,0 +1,49 @@
/*
* 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 <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QDir>
#include <QProcess>
namespace AzQtComponents
{
void ShowFileOnDesktop(const QString& path)
{
const char* defaultNautilusPath = "/usr/bin/nautilus";
const char* defaultXdgOpenPath = "/usr/bin/xdg-open";
// Determine if Nautilus (for Gnome Desktops) is available because it supports opening the file manager
// and selecting a specific file
bool nautilusAvailable = QFileInfo(defaultNautilusPath).exists();
QFileInfo pathInfo(path);
if (pathInfo.isDir())
{
QProcess::startDetached(defaultXdgOpenPath, { path });
}
else
{
if (nautilusAvailable)
{
QProcess::startDetached(defaultNautilusPath, { "--select", path });
}
else
{
QDir parentDir { pathInfo.dir() };
QProcess::startDetached(defaultXdgOpenPath, { parentDir.path() });
}
}
}
QString fileBrowserActionName()
{
const char* exploreActionName = "Open in file browser";
return QObject::tr(exploreActionName);
}
}
@@ -15,21 +15,6 @@ namespace AzQtComponents
{
void ShowFileOnDesktop(const QString& path)
{
#if defined(AZ_PLATFORM_WINDOWS)
// Launch explorer at the path provided
QStringList args;
if (!QFileInfo(path).isDir())
{
// Folders are just opened, files are selected
args << "/select,";
}
args << QDir::toNativeSeparators(path);
QProcess::startDetached("explorer", args);
#else
if (QFileInfo(path).isDir())
{
QProcess::startDetached("/usr/bin/osascript", { "-e",
@@ -43,19 +28,11 @@ namespace AzQtComponents
QProcess::startDetached("/usr/bin/osascript", { "-e",
QStringLiteral("tell application \"Finder\" to activate") });
#endif
}
QString fileBrowserActionName()
{
#ifdef AZ_PLATFORM_WINDOWS
const char* exploreActionName = "Open in Explorer";
#elif defined(AZ_PLATFORM_MAC)
const char* exploreActionName = "Open in Finder";
#else
const char* exploreActionName = "Open in file browser";
#endif
return QObject::tr(exploreActionName);
}
}
@@ -0,0 +1,35 @@
/*
* 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 <AzQtComponents/Utilities/DesktopUtilities.h>
#include <QDir>
#include <QProcess>
namespace AzQtComponents
{
void ShowFileOnDesktop(const QString& path)
{
// Launch explorer at the path provided
QStringList args;
if (!QFileInfo(path).isDir())
{
// Folders are just opened, files are selected
args << "/select,";
}
args << QDir::toNativeSeparators(path);
QProcess::startDetached("explorer", args);
}
QString fileBrowserActionName()
{
const char* exploreActionName = "Open in Explorer";
return QObject::tr(exploreActionName);
}
}
@@ -28,8 +28,10 @@ namespace AzToolsFramework
//////////////////////////////////////////////////////////////////////////
//! Triggered when the editor focus is changed to a different entity.
//! @param entityId The entity the focus has been moved to.
virtual void OnEditorFocusChanged(AZ::EntityId entityId) = 0;
//! @param previousFocusEntityId The entity the focus has been moved from.
//! @param newFocusEntityId The entity the focus has been moved to.
virtual void OnEditorFocusChanged(
[[maybe_unused]] AZ::EntityId previousFocusEntityId, [[maybe_unused]] AZ::EntityId newFocusEntityId) {}
protected:
~FocusModeNotifications() = default;
@@ -71,8 +71,9 @@ namespace AzToolsFramework
return;
}
AZ::EntityId previousFocusEntityId = m_focusRoot;
m_focusRoot = entityId;
FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, m_focusRoot);
FocusModeNotificationBus::Broadcast(&FocusModeNotifications::OnEditorFocusChanged, previousFocusEntityId, m_focusRoot);
if (auto tracker = AZ::Interface<ViewportEditorModeTrackerInterface>::Get();
tracker != nullptr)
@@ -116,7 +116,7 @@ namespace AzToolsFramework
{
return AZ::Intersect::IntersectRayBox(
rayOrigin, rayDirection, m_center, m_axis1, m_axis2, m_axis3, m_halfExtents.GetX(), m_halfExtents.GetY(),
m_halfExtents.GetZ(), rayIntersectionDistance) > 0;
m_halfExtents.GetZ(), rayIntersectionDistance);
}
void ManipulatorBoundBox::SetShapeData(const BoundRequestShapeBase& shapeData)
@@ -262,7 +262,7 @@ namespace AzToolsFramework
if (assetId.IsValid())
{
asset.Create(assetId, true);
asset.Create(assetId, false);
}
}
};
@@ -8,12 +8,14 @@
#include <AzToolsFramework/Prefab/PrefabFocusHandler.h>
#include <AzToolsFramework/Commands/SelectionCommand.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusNotificationBus.h>
#include <AzToolsFramework/Prefab/PrefabFocusUndo.h>
namespace AzToolsFramework::Prefab
{
@@ -28,10 +30,12 @@ namespace AzToolsFramework::Prefab
EditorEntityContextNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabFocusInterface>::Register(this);
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
}
PrefabFocusHandler::~PrefabFocusHandler()
{
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
AZ::Interface<PrefabFocusInterface>::Unregister(this);
EditorEntityContextNotificationBus::Handler::BusDisconnect();
}
@@ -61,6 +65,44 @@ namespace AzToolsFramework::Prefab
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
{
// Initialize Undo Batch object
ScopedUndoBatch undoBatch("Edit Prefab");
// Clear selection
{
const EntityIdList selectedEntities = EntityIdList{};
auto selectionUndo = aznew SelectionCommand(selectedEntities, "Clear Selection");
selectionUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::SetSelectedEntities, selectedEntities);
}
// Edit Prefab
{
auto editUndo = aznew PrefabFocusUndo("Edit Prefab");
editUndo->Capture(entityId);
editUndo->SetParent(undoBatch.GetUndoBatch());
ToolsApplicationRequestBus::Broadcast(&ToolsApplicationRequestBus::Events::RunRedoSeparately, editUndo);
}
return AZ::Success();
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{
if (index < 0 || index >= m_instanceFocusVector.size())
{
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
}
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
return AZ::Success();
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId)
{
InstanceOptionalReference focusedInstance;
@@ -85,18 +127,6 @@ namespace AzToolsFramework::Prefab
return FocusOnPrefabInstance(focusedInstance);
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{
if (index < 0 || index >= m_instanceFocusVector.size())
{
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
}
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index];
return FocusOnPrefabInstance(focusedInstance);
}
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPrefabInstance(InstanceOptionalReference focusedInstance)
{
if (!focusedInstance.has_value())
@@ -122,17 +152,10 @@ namespace AzToolsFramework::Prefab
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{
containerEntityId = focusedInstance->get().GetContainerEntityId();
// Select the container entity
AzToolsFramework::SelectEntity(containerEntityId);
}
else
{
containerEntityId = AZ::EntityId();
// Clear the selection
AzToolsFramework::SelectEntities({});
}
// Focus on the descendants of the container entity
@@ -161,6 +184,17 @@ namespace AzToolsFramework::Prefab
return m_focusedInstance;
}
AZ::EntityId PrefabFocusHandler::GetFocusedPrefabContainerEntityId([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{
if (!m_focusedInstance.has_value())
{
// PrefabFocusHandler has not been initialized yet.
return AZ::EntityId();
}
return m_focusedInstance->get().GetContainerEntityId();
}
bool PrefabFocusHandler::IsOwningPrefabBeingFocused(AZ::EntityId entityId) const
{
if (!m_focusedInstance.has_value())
@@ -200,7 +234,7 @@ namespace AzToolsFramework::Prefab
m_instanceFocusVector.clear();
// Focus on the root prefab (AZ::EntityId() will default to it)
FocusOnOwningPrefab(AZ::EntityId());
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
}
void PrefabFocusHandler::RefreshInstanceFocusList()
@@ -13,6 +13,7 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework
@@ -28,6 +29,7 @@ namespace AzToolsFramework::Prefab
//! Handles Prefab Focus mode, determining which prefab file entity changes will target.
class PrefabFocusHandler final
: private PrefabFocusInterface
, private PrefabFocusPublicInterface
, private EditorEntityContextNotificationBus::Handler
{
public:
@@ -39,10 +41,14 @@ namespace AzToolsFramework::Prefab
void Initialize();
// PrefabFocusInterface overrides ...
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
// PrefabFocusPublicInterface overrides ...
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const override;
bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const override;
const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const override;
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
@@ -20,7 +20,7 @@ namespace AzToolsFramework::Prefab
{
using PrefabFocusOperationResult = AZ::Outcome<void, AZStd::string>;
//! Interface to handle operations related to the Prefab Focus system.
//! Interface to handle internal operations related to the Prefab Focus system.
class PrefabFocusInterface
{
public:
@@ -28,29 +28,13 @@ namespace AzToolsFramework::Prefab
//! Set the focused prefab instance to the owning instance of the entityId provided.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
//! Set the focused prefab instance to the instance at position index of the current path.
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;
//! Returns the template id of the instance the prefab system is focusing on.
virtual TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns a reference to the instance the prefab system is focusing on.
virtual InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants.
//! @param entityId The entityId of the queried entity.
//! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise.
virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0;
//! Returns the path from the root instance to the currently focused instance.
//! @return A path composed from the names of the container entities for the instance path.
virtual const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns the size of the path to the currently focused instance.
virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0;
};
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,53 @@
/*
* 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/Interface/Interface.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <AzFramework/Entity/EntityContext.h>
#include <AzToolsFramework/Prefab/Instance/Instance.h>
#include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework::Prefab
{
using PrefabFocusOperationResult = AZ::Outcome<void, AZStd::string>;
//! Public Interface for external systems to utilize the Prefab Focus system.
class PrefabFocusPublicInterface
{
public:
AZ_RTTI(PrefabFocusPublicInterface, "{53EE1D18-A41F-4DB1-9B73-9448F425722E}");
//! Set the focused prefab instance to the owning instance of the entityId provided. Supports undo/redo.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) = 0;
//! Set the focused prefab instance to the instance at position index of the current path. Supports undo/redo.
//! @param index The index of the instance in the current path that we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) = 0;
//! Returns the entity id of the container entity for the instance the prefab system is focusing on.
virtual AZ::EntityId GetFocusedPrefabContainerEntityId(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns whether the entity belongs to the instance that is being focused on, or one of its descendants.
//! @param entityId The entityId of the queried entity.
//! @return true if the entity belongs to the focused instance or one of its descendants, false otherwise.
virtual bool IsOwningPrefabBeingFocused(AZ::EntityId entityId) const = 0;
//! Returns the path from the root instance to the currently focused instance.
//! @return A path composed from the names of the container entities for the instance path.
virtual const AZ::IO::Path& GetPrefabFocusPath(AzFramework::EntityContextId entityContextId) const = 0;
//! Returns the size of the path to the currently focused instance.
virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0;
};
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,52 @@
/*
* 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 <AzToolsFramework/Prefab/PrefabFocusUndo.h>
#include <AzCore/Interface/Interface.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
namespace AzToolsFramework::Prefab
{
PrefabFocusUndo::PrefabFocusUndo(const AZStd::string& undoOperationName)
: UndoSystem::URSequencePoint(undoOperationName)
{
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
AZ_Assert(m_prefabFocusInterface, "PrefabFocusUndo - Failed to grab prefab focus interface");
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
AZ_Assert(m_prefabFocusPublicInterface, "PrefabFocusUndo - Failed to grab prefab focus public interface");
}
bool PrefabFocusUndo::Changed() const
{
return true;
}
void PrefabFocusUndo::Capture(AZ::EntityId entityId)
{
auto entityContextId = AzFramework::EntityContextId::CreateNull();
EditorEntityContextRequestBus::BroadcastResult(entityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
m_beforeEntityId = m_prefabFocusPublicInterface->GetFocusedPrefabContainerEntityId(entityContextId);
m_afterEntityId = entityId;
}
void PrefabFocusUndo::Undo()
{
m_prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(m_beforeEntityId);
}
void PrefabFocusUndo::Redo()
{
m_prefabFocusInterface->FocusOnPrefabInstanceOwningEntityId(m_afterEntityId);
}
} // namespace AzToolsFramework::Prefab
@@ -0,0 +1,39 @@
/*
* 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/Component/EntityId.h>
#include <AzToolsFramework/Undo/UndoSystem.h>
namespace AzToolsFramework::Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
//! Undo node for prefab focus change operations.
class PrefabFocusUndo
: public UndoSystem::URSequencePoint
{
public:
explicit PrefabFocusUndo(const AZStd::string& undoOperationName);
bool Changed() const override;
void Capture(AZ::EntityId entityId);
void Undo() override;
void Redo() override;
protected:
PrefabFocusInterface* m_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
AZ::EntityId m_beforeEntityId;
AZ::EntityId m_afterEntityId;
};
} // namespace AzToolsFramework::Prefab
@@ -313,7 +313,8 @@ namespace AzToolsFramework
StyledTreeView::StartCustomDrag(indexListSorted, supportedActions);
}
void EntityOutlinerTreeView::OnEditorFocusChanged([[maybe_unused]] AZ::EntityId entityId)
void EntityOutlinerTreeView::OnEditorFocusChanged(
[[maybe_unused]] AZ::EntityId previousFocusEntityId, [[maybe_unused]] AZ::EntityId newFocusEntityId)
{
viewport()->repaint();
}
@@ -64,7 +64,7 @@ namespace AzToolsFramework
void leaveEvent(QEvent* event) override;
// FocusModeNotificationBus overrides ...
void OnEditorFocusChanged(AZ::EntityId entityId) override;
void OnEditorFocusChanged(AZ::EntityId previousFocusEntityId, AZ::EntityId newFocusEntityId) override;
//! Renders the left side of the item: appropriate background, branch lines, icons.
void drawBranches(QPainter* painter, const QRect& rect, const QModelIndex& index) const override;
@@ -324,7 +324,8 @@ namespace AzToolsFramework
// Currently, the first behavior is implemented.
void EntityOutlinerWidget::OnSelectionChanged(const QItemSelection& selected, const QItemSelection& deselected)
{
if (m_selectionChangeInProgress || !m_enableSelectionUpdates)
if (m_selectionChangeInProgress || !m_enableSelectionUpdates
|| (selected.empty() && deselected.empty()))
{
return;
}
@@ -552,6 +553,13 @@ namespace AzToolsFramework
return;
}
// Do not display the context menu if the item under the mouse cursor is not selectable.
if (const QModelIndex& index = m_gui->m_objectTree->indexAt(pos); index.isValid()
&& (index.flags() & Qt::ItemIsSelectable) == 0)
{
return;
}
QMenu* contextMenu = new QMenu(this);
// Populate global context menu.
@@ -24,11 +24,11 @@
#include <AzToolsFramework/AssetBrowser/AssetSelectionModel.h>
#include <AzToolsFramework/AssetBrowser/Entries/SourceAssetBrowserEntry.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabLoaderInterface.h>
#include <AzToolsFramework/Prefab/Procedural/ProceduralPrefabAsset.h>
#include <AzToolsFramework/Prefab/Instance/InstanceEntityMapperInterface.h>
#include <AzToolsFramework/Prefab/EditorPrefabComponent.h>
#include <AzToolsFramework/ToolsComponents/EditorLayerComponentBus.h>
#include <AzToolsFramework/UI/EditorEntityUi/EditorEntityUiInterface.h>
#include <AzToolsFramework/UI/Prefab/PrefabIntegrationInterface.h>
@@ -39,7 +39,6 @@
#include <AzQtComponents/Components/StyleManager.h>
#include <AzQtComponents/Components/Widgets/CardHeader.h>
#include <QApplication>
#include <QCheckBox>
#include <QDialog>
@@ -56,14 +55,13 @@
#include <QVBoxLayout>
#include <QWidget>
namespace AzToolsFramework
{
namespace Prefab
{
ContainerEntityInterface* PrefabIntegrationManager::s_containerEntityInterface = nullptr;
EditorEntityUiInterface* PrefabIntegrationManager::s_editorEntityUiInterface = nullptr;
PrefabFocusInterface* PrefabIntegrationManager::s_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* PrefabIntegrationManager::s_prefabFocusPublicInterface = nullptr;
PrefabLoaderInterface* PrefabIntegrationManager::s_prefabLoaderInterface = nullptr;
PrefabPublicInterface* PrefabIntegrationManager::s_prefabPublicInterface = nullptr;
PrefabSystemComponentInterface* PrefabIntegrationManager::s_prefabSystemComponentInterface = nullptr;
@@ -129,10 +127,10 @@ namespace AzToolsFramework
return;
}
s_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
if (s_prefabFocusInterface == nullptr)
s_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
if (s_prefabFocusPublicInterface == nullptr)
{
AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabIntegrationManager construction.");
AZ_Assert(false, "Prefab - could not get PrefabFocusPublicInterface on PrefabIntegrationManager construction.");
return;
}
@@ -247,12 +245,8 @@ namespace AzToolsFramework
if (s_prefabPublicInterface->IsInstanceContainerEntity(selectedEntity))
{
// Edit Prefab
if (prefabWipFeaturesEnabled)
if (prefabWipFeaturesEnabled && !s_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(selectedEntity))
{
bool beingEdited = s_prefabFocusInterface->IsOwningPrefabBeingFocused(selectedEntity);
if (!beingEdited)
{
QAction* editAction = menu->addAction(QObject::tr("Edit Prefab"));
editAction->setToolTip(QObject::tr("Edit the prefab in focus mode."));
@@ -261,7 +255,6 @@ namespace AzToolsFramework
});
itemWasShown = true;
}
}
// Save Prefab
@@ -317,7 +310,7 @@ namespace AzToolsFramework
void PrefabIntegrationManager::OnEscape()
{
s_prefabFocusInterface->FocusOnOwningPrefab(AZ::EntityId());
s_prefabFocusPublicInterface->FocusOnOwningPrefab(AZ::EntityId());
}
void PrefabIntegrationManager::HandleSourceFileType(AZStd::string_view sourceFilePath, AZ::EntityId parentId, AZ::Vector3 position) const
@@ -490,7 +483,7 @@ namespace AzToolsFramework
void PrefabIntegrationManager::ContextMenu_EditPrefab(AZ::EntityId containerEntity)
{
s_prefabFocusInterface->FocusOnOwningPrefab(containerEntity);
s_prefabFocusPublicInterface->FocusOnOwningPrefab(containerEntity);
}
void PrefabIntegrationManager::ContextMenu_SavePrefab(AZ::EntityId containerEntity)
@@ -30,7 +30,7 @@ namespace AzToolsFramework
namespace Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
class PrefabLoaderInterface;
//! Structure for saving/retrieving user settings related to prefab workflows.
@@ -144,7 +144,7 @@ namespace AzToolsFramework
static ContainerEntityInterface* s_containerEntityInterface;
static EditorEntityUiInterface* s_editorEntityUiInterface;
static PrefabFocusInterface* s_prefabFocusInterface;
static PrefabFocusPublicInterface* s_prefabFocusPublicInterface;
static PrefabLoaderInterface* s_prefabLoaderInterface;
static PrefabPublicInterface* s_prefabPublicInterface;
static PrefabSystemComponentInterface* s_prefabSystemComponentInterface;
@@ -10,7 +10,7 @@
#include <AzFramework/API/ApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicInterface.h>
#include <AzToolsFramework/UI/Outliner/EntityOutlinerListModel.hxx>
@@ -35,10 +35,10 @@ namespace AzToolsFramework
return;
}
m_prefabFocusInterface = AZ::Interface<Prefab::PrefabFocusInterface>::Get();
if (m_prefabFocusInterface == nullptr)
m_prefabFocusPublicInterface = AZ::Interface<Prefab::PrefabFocusPublicInterface>::Get();
if (m_prefabFocusPublicInterface == nullptr)
{
AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusInterface on PrefabUiHandler construction.");
AZ_Assert(false, "PrefabUiHandler - could not get PrefabFocusPublicInterface on PrefabUiHandler construction.");
return;
}
}
@@ -83,7 +83,7 @@ namespace AzToolsFramework
QIcon PrefabUiHandler::GenerateItemIcon(AZ::EntityId entityId) const
{
if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
return QIcon(m_prefabEditIconPath);
}
@@ -105,7 +105,7 @@ namespace AzToolsFramework
const bool hasVisibleChildren = index.data(EntityOutlinerListModel::ExpandedRole).value<bool>() && index.model()->hasChildren(index);
QColor backgroundColor = m_prefabCapsuleColor;
if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
backgroundColor = m_prefabCapsuleEditColor;
}
@@ -191,7 +191,7 @@ namespace AzToolsFramework
const bool isLastColumn = descendantIndex.column() == EntityOutlinerListModel::ColumnLockToggle;
QColor borderColor = m_prefabCapsuleColor;
if (m_prefabFocusInterface->IsOwningPrefabBeingFocused(entityId))
if (m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(entityId))
{
borderColor = m_prefabCapsuleEditColor;
}
@@ -329,7 +329,7 @@ namespace AzToolsFramework
if (prefabWipFeaturesEnabled)
{
// Focus on this prefab
m_prefabFocusInterface->FocusOnOwningPrefab(entityId);
m_prefabFocusPublicInterface->FocusOnOwningPrefab(entityId);
}
}
}
@@ -15,7 +15,7 @@ namespace AzToolsFramework
namespace Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
class PrefabPublicInterface;
};
@@ -39,7 +39,7 @@ namespace AzToolsFramework
void OnDoubleClick(AZ::EntityId entityId) const override;
private:
Prefab::PrefabFocusInterface* m_prefabFocusInterface = nullptr;
Prefab::PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
Prefab::PrefabPublicInterface* m_prefabPublicInterface = nullptr;
static bool IsLastVisibleChild(const QModelIndex& parent, const QModelIndex& child);
@@ -8,7 +8,7 @@
#include <AzToolsFramework/UI/Prefab/PrefabViewportFocusPathHandler.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
namespace AzToolsFramework::Prefab
{
@@ -31,8 +31,8 @@ namespace AzToolsFramework::Prefab
void PrefabViewportFocusPathHandler::Initialize(AzQtComponents::BreadCrumbs* breadcrumbsWidget, QToolButton* backButton)
{
// Get reference to the PrefabFocusInterface handler
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
if (m_prefabFocusInterface == nullptr)
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
if (m_prefabFocusPublicInterface == nullptr)
{
AZ_Assert(false, "Prefab - could not get PrefabFocusInterface on PrefabViewportFocusPathHandler construction.");
return;
@@ -46,7 +46,7 @@ namespace AzToolsFramework::Prefab
connect(m_breadcrumbsWidget, &AzQtComponents::BreadCrumbs::linkClicked, this,
[&](const QString&, int linkIndex)
{
m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, linkIndex);
m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, linkIndex);
}
);
@@ -54,9 +54,9 @@ namespace AzToolsFramework::Prefab
connect(m_backButton, &QToolButton::clicked, this,
[&]()
{
if (int length = m_prefabFocusInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1)
if (int length = m_prefabFocusPublicInterface->GetPrefabFocusPathLength(m_editorEntityContextId); length > 1)
{
m_prefabFocusInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2);
m_prefabFocusPublicInterface->FocusOnPathIndex(m_editorEntityContextId, length - 2);
}
}
);
@@ -65,7 +65,7 @@ namespace AzToolsFramework::Prefab
void PrefabViewportFocusPathHandler::OnPrefabFocusChanged()
{
// Push new Path
m_breadcrumbsWidget->pushPath(m_prefabFocusInterface->GetPrefabFocusPath(m_editorEntityContextId).c_str());
m_breadcrumbsWidget->pushPath(m_prefabFocusPublicInterface->GetPrefabFocusPath(m_editorEntityContextId).c_str());
}
} // namespace AzToolsFramework::Prefab
@@ -19,7 +19,7 @@
namespace AzToolsFramework::Prefab
{
class PrefabFocusInterface;
class PrefabFocusPublicInterface;
class PrefabViewportFocusPathHandler
: public PrefabFocusNotificationBus::Handler
@@ -40,6 +40,6 @@ namespace AzToolsFramework::Prefab
AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
PrefabFocusInterface* m_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
};
} // namespace AzToolsFramework::Prefab
@@ -407,7 +407,7 @@ namespace AzToolsFramework
const AzFramework::CameraState cameraState = GetCameraState(viewportId);
for (size_t entityCacheIndex = 0; entityCacheIndex < entityDataCache.VisibleEntityDataCount(); ++entityCacheIndex)
{
if (entityDataCache.IsVisibleEntityLocked(entityCacheIndex) || !entityDataCache.IsVisibleEntityVisible(entityCacheIndex))
if (!entityDataCache.IsVisibleEntitySelectableInViewport(entityCacheIndex))
{
continue;
}
@@ -9,7 +9,9 @@
#include "EditorVisibleEntityDataCache.h"
#include <AzCore/std/sort.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityInterface.h>
#include <AzToolsFramework/Entity/EditorEntityModel.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <Entity/EditorEntityHelpers.h>
@@ -21,13 +23,23 @@ namespace AzToolsFramework
using ComponentEntityAccentType = Components::EditorSelectionAccentSystemComponent::ComponentEntityAccentType;
EntityData() = default;
EntityData(AZ::EntityId entityId, const AZ::Transform& worldFromLocal, bool locked, bool visible, bool selected, bool iconHidden);
EntityData(
AZ::EntityId entityId,
const AZ::Transform& worldFromLocal,
bool locked,
bool visible,
bool inFocus,
bool descendantOfClosedContainer,
bool selected,
bool iconHidden);
AZ::Transform m_worldFromLocal;
AZ::EntityId m_entityId;
ComponentEntityAccentType m_accent = ComponentEntityAccentType::None;
bool m_locked = false;
bool m_visible = true;
bool m_inFocus = true;
bool m_descendantOfClosedContainer = false;
bool m_selected = false;
bool m_iconHidden = false;
};
@@ -57,12 +69,16 @@ namespace AzToolsFramework
const AZ::Transform& worldFromLocal,
const bool locked,
const bool visible,
const bool inFocus,
const bool descendantOfClosedContainer,
const bool selected,
const bool iconHidden)
: m_worldFromLocal(worldFromLocal)
, m_entityId(entityId)
, m_locked(locked)
, m_visible(visible)
, m_inFocus(inFocus)
, m_descendantOfClosedContainer(descendantOfClosedContainer)
, m_selected(selected)
, m_iconHidden(iconHidden)
{
@@ -106,6 +122,18 @@ namespace AzToolsFramework
bool locked = false;
EditorEntityInfoRequestBus::EventResult(locked, entityId, &EditorEntityInfoRequestBus::Events::IsLocked);
bool inFocus = false;
if (auto focusModeInterface = AZ::Interface<FocusModeInterface>::Get())
{
inFocus = focusModeInterface->IsInFocusSubTree(entityId);
}
bool descendantOfClosedContainer = false;
if (ContainerEntityInterface* containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
{
descendantOfClosedContainer = containerEntityInterface->IsUnderClosedContainerEntity(entityId);
}
bool iconHidden = false;
EditorEntityIconComponentRequestBus::EventResult(
iconHidden, entityId, &EditorEntityIconComponentRequests::IsEntityIconHiddenInViewport);
@@ -113,7 +141,7 @@ namespace AzToolsFramework
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldFromLocal, entityId, &AZ::TransformBus::Events::GetWorldTM);
return { entityId, worldFromLocal, locked, visible, IsSelected(entityId), iconHidden };
return { entityId, worldFromLocal, locked, visible, inFocus, descendantOfClosedContainer, IsSelected(entityId), iconHidden };
}
EditorVisibleEntityDataCache::EditorVisibleEntityDataCache()
@@ -126,10 +154,17 @@ namespace AzToolsFramework
EntitySelectionEvents::Bus::Router::BusRouterConnect();
EditorEntityIconComponentNotificationBus::Router::BusRouterConnect();
ToolsApplicationNotificationBus::Handler::BusConnect();
AzFramework::EntityContextId editorEntityContextId = AzToolsFramework::GetEntityContextId();
ContainerEntityNotificationBus::Handler::BusConnect(editorEntityContextId);
FocusModeNotificationBus::Handler::BusConnect(editorEntityContextId);
}
EditorVisibleEntityDataCache::~EditorVisibleEntityDataCache()
{
FocusModeNotificationBus::Handler::BusDisconnect();
ContainerEntityNotificationBus::Handler::BusDisconnect();
ToolsApplicationNotificationBus::Handler::BusDisconnect();
EditorEntityIconComponentNotificationBus::Router::BusRouterDisconnect();
EntitySelectionEvents::Bus::Router::BusRouterDisconnect();
@@ -260,7 +295,10 @@ namespace AzToolsFramework
bool EditorVisibleEntityDataCache::IsVisibleEntitySelectableInViewport(size_t index) const
{
return m_impl->m_visibleEntityDatas[index].m_visible && !m_impl->m_visibleEntityDatas[index].m_locked;
return m_impl->m_visibleEntityDatas[index].m_visible
&& !m_impl->m_visibleEntityDatas[index].m_locked
&& m_impl->m_visibleEntityDatas[index].m_inFocus
&& !m_impl->m_visibleEntityDatas[index].m_descendantOfClosedContainer;
}
AZStd::optional<size_t> EditorVisibleEntityDataCache::GetVisibleEntityIndexFromId(const AZ::EntityId entityId) const
@@ -371,4 +409,72 @@ namespace AzToolsFramework
m_impl->m_visibleEntityDatas[entityIndex.value()].m_iconHidden = iconHidden;
}
}
void EditorVisibleEntityDataCache::OnContainerEntityStatusChanged(AZ::EntityId entityId, [[maybe_unused]] bool open)
{
// Get container descendants
AzToolsFramework::EntityIdList descendantIds;
AZ::TransformBus::EventResult(descendantIds, entityId, &AZ::TransformBus::Events::GetAllDescendants);
// Update cached values
if (auto containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get())
{
for (AZ::EntityId descendantId : descendantIds)
{
if (AZStd::optional<size_t> entityIndex = GetVisibleEntityIndexFromId(descendantId))
{
m_impl->m_visibleEntityDatas[entityIndex.value()].m_descendantOfClosedContainer =
containerEntityInterface->IsUnderClosedContainerEntity(descendantId);
}
}
}
}
void EditorVisibleEntityDataCache::OnEditorFocusChanged(AZ::EntityId previousFocusEntityId, AZ::EntityId newFocusEntityId)
{
if (previousFocusEntityId.IsValid() && newFocusEntityId.IsValid())
{
// Get previous focus root descendants
AzToolsFramework::EntityIdList previousDescendantIds;
AZ::TransformBus::EventResult(previousDescendantIds, previousFocusEntityId, &AZ::TransformBus::Events::GetAllDescendants);
// Get new focus root descendants
AzToolsFramework::EntityIdList newDescendantIds;
AZ::TransformBus::EventResult(newDescendantIds, newFocusEntityId, &AZ::TransformBus::Events::GetAllDescendants);
// Merge EntityId Lists to avoid refreshing values twice
AzToolsFramework::EntityIdSet descendantsSet;
descendantsSet.insert(previousFocusEntityId);
descendantsSet.insert(newFocusEntityId);
descendantsSet.insert(previousDescendantIds.begin(), previousDescendantIds.end());
descendantsSet.insert(newDescendantIds.begin(), newDescendantIds.end());
// Update cached values
if (auto focusModeInterface = AZ::Interface<FocusModeInterface>::Get())
{
for (const AZ::EntityId& descendantId : descendantsSet)
{
if (AZStd::optional<size_t> entityIndex = GetVisibleEntityIndexFromId(descendantId))
{
m_impl->m_visibleEntityDatas[entityIndex.value()].m_inFocus = focusModeInterface->IsInFocusSubTree(descendantId);
}
}
}
}
else
{
// If either focus was the invalid entity, refresh all entities.
if (auto focusModeInterface = AZ::Interface<FocusModeInterface>::Get())
{
for (size_t entityIndex = 0; entityIndex < m_impl->m_visibleEntityDatas.size(); ++entityIndex)
{
if (AZ::EntityId descendantId = GetVisibleEntityId(entityIndex); descendantId.IsValid())
{
m_impl->m_visibleEntityDatas[entityIndex].m_inFocus = focusModeInterface->IsInFocusSubTree(descendantId);
}
}
}
}
}
} // namespace AzToolsFramework
@@ -11,6 +11,8 @@
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/optional.h>
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
#include <AzToolsFramework/ContainerEntity/ContainerEntityNotificationBus.h>
#include <AzToolsFramework/FocusMode/FocusModeNotificationBus.h>
#include <AzToolsFramework/ToolsComponents/EditorEntityIconComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorLockComponentBus.h>
#include <AzToolsFramework/ToolsComponents/EditorSelectionAccentSystemComponent.h>
@@ -28,6 +30,8 @@ namespace AzToolsFramework
, private EntitySelectionEvents::Bus::Router
, private EditorEntityIconComponentNotificationBus::Router
, private ToolsApplicationNotificationBus::Handler
, private ContainerEntityNotificationBus::Handler
, private FocusModeNotificationBus::Handler
{
public:
EditorVisibleEntityDataCache();
@@ -58,28 +62,34 @@ namespace AzToolsFramework
void AddEntityIds(const EntityIdList& entityIds);
private:
// ToolsApplicationNotificationBus
// ToolsApplicationNotificationBus overrides ...
void AfterUndoRedo() override;
// EditorEntityVisibilityNotificationBus
// EditorEntityVisibilityNotificationBus overrides ...
void OnEntityVisibilityChanged(bool visibility) override;
// EditorEntityLockComponentNotificationBus
// EditorEntityLockComponentNotificationBus overrides ...
void OnEntityLockChanged(bool locked) override;
// TransformNotificationBus
// TransformNotificationBus overrides ...
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
// EditorComponentSelectionNotificationsBus
// EditorComponentSelectionNotificationsBus overrides ...
void OnAccentTypeChanged(EntityAccentType accent) override;
// EntitySelectionEvents::Bus
// EntitySelectionEvents::Bus overrides ...
void OnSelected() override;
void OnDeselected() override;
// EditorEntityIconComponentNotificationBus
// EditorEntityIconComponentNotificationBus overrides ...
void OnEntityIconChanged(const AZ::Data::AssetId& entityIconAssetId) override;
// ContainerEntityNotificationBus overrides ...
void OnContainerEntityStatusChanged(AZ::EntityId entityId, bool open) override;
// FocusModeNotificationBus overrides ...
void OnEditorFocusChanged(AZ::EntityId previousFocusEntityId, AZ::EntityId newFocusEntityId) override;
class EditorVisibleEntityDataCacheImpl;
AZStd::unique_ptr<EditorVisibleEntityDataCacheImpl> m_impl; //!< Internal representation of entity data cache.
};
@@ -646,6 +646,9 @@ set(FILES
Prefab/PrefabFocusHandler.cpp
Prefab/PrefabFocusInterface.h
Prefab/PrefabFocusNotificationBus.h
Prefab/PrefabFocusPublicInterface.h
Prefab/PrefabFocusUndo.h
Prefab/PrefabFocusUndo.cpp
Prefab/PrefabIdTypes.h
Prefab/PrefabLoader.h
Prefab/PrefabLoader.cpp
@@ -10,6 +10,7 @@
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
#include <AzToolsFramework/Entity/PrefabEditorEntityOwnershipInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <Prefab/PrefabTestFixture.h>
namespace UnitTest
@@ -72,6 +73,9 @@ namespace UnitTest
m_prefabFocusInterface = AZ::Interface<PrefabFocusInterface>::Get();
ASSERT_TRUE(m_prefabFocusInterface != nullptr);
m_prefabFocusPublicInterface = AZ::Interface<PrefabFocusPublicInterface>::Get();
ASSERT_TRUE(m_prefabFocusPublicInterface != nullptr);
AzToolsFramework::EditorEntityContextRequestBus::BroadcastResult(
m_editorEntityContextId, &AzToolsFramework::EditorEntityContextRequestBus::Events::GetEditorEntityContextId);
@@ -91,6 +95,7 @@ namespace UnitTest
AZStd::unique_ptr<AzToolsFramework::Prefab::Instance> m_rootInstance;
PrefabFocusInterface* m_prefabFocusInterface = nullptr;
PrefabFocusPublicInterface* m_prefabFocusPublicInterface = nullptr;
AzFramework::EntityContextId m_editorEntityContextId = AzFramework::EntityContextId::CreateNull();
inline static const char* CityEntityName = "City";
@@ -105,7 +110,7 @@ namespace UnitTest
{
// Verify FocusOnOwningPrefab works when passing the container entity of the root prefab.
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CityEntityName]->GetContainerEntityId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_instanceMap[CityEntityName]->GetContainerEntityId());
EXPECT_EQ(
m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId),
m_instanceMap[CityEntityName]->GetTemplateId());
@@ -120,7 +125,7 @@ namespace UnitTest
{
// Verify FocusOnOwningPrefab works when passing a nested entity of the root prefab.
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_entityMap[CityEntityName]->GetId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_entityMap[CityEntityName]->GetId());
EXPECT_EQ(
m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId),
m_instanceMap[CityEntityName]->GetTemplateId());
@@ -135,7 +140,7 @@ namespace UnitTest
{
// Verify FocusOnOwningPrefab works when passing the container entity of a nested prefab.
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CarEntityName]->GetContainerEntityId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_instanceMap[CarEntityName]->GetContainerEntityId());
EXPECT_EQ(
m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), m_instanceMap[CarEntityName]->GetTemplateId());
@@ -149,7 +154,7 @@ namespace UnitTest
{
// Verify FocusOnOwningPrefab works when passing a nested entity of the a nested prefab.
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_entityMap[Passenger1EntityName]->GetId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_entityMap[Passenger1EntityName]->GetId());
EXPECT_EQ(
m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), m_instanceMap[CarEntityName]->GetTemplateId());
@@ -169,7 +174,7 @@ namespace UnitTest
prefabEditorEntityOwnershipInterface->GetRootPrefabInstance();
EXPECT_TRUE(rootPrefabInstance.has_value());
m_prefabFocusInterface->FocusOnOwningPrefab(AZ::EntityId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(AZ::EntityId());
EXPECT_EQ(
m_prefabFocusInterface->GetFocusedPrefabTemplateId(m_editorEntityContextId), rootPrefabInstance->get().GetTemplateId());
@@ -183,10 +188,10 @@ namespace UnitTest
{
// Verify IsOwningPrefabBeingFocused returns true for all entities in a focused prefab (container/nested)
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[CityEntityName]->GetContainerEntityId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_instanceMap[CityEntityName]->GetContainerEntityId());
EXPECT_TRUE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap[CityEntityName]->GetContainerEntityId()));
EXPECT_TRUE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap[CityEntityName]->GetId()));
EXPECT_TRUE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_instanceMap[CityEntityName]->GetContainerEntityId()));
EXPECT_TRUE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_entityMap[CityEntityName]->GetId()));
}
}
@@ -194,13 +199,13 @@ namespace UnitTest
{
// Verify IsOwningPrefabBeingFocused returns false for all entities not in a focused prefab (ancestors/descendants)
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[StreetEntityName]->GetContainerEntityId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_instanceMap[StreetEntityName]->GetContainerEntityId());
EXPECT_TRUE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap[StreetEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap[CityEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap[CityEntityName]->GetId()));
EXPECT_FALSE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap[CarEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap[Passenger1EntityName]->GetId()));
EXPECT_TRUE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_instanceMap[StreetEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_instanceMap[CityEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_entityMap[CityEntityName]->GetId()));
EXPECT_FALSE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_instanceMap[CarEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_entityMap[Passenger1EntityName]->GetId()));
}
}
@@ -208,12 +213,12 @@ namespace UnitTest
{
// Verify IsOwningPrefabBeingFocused returns false for all entities not in a focused prefab (siblings)
{
m_prefabFocusInterface->FocusOnOwningPrefab(m_instanceMap[SportsCarEntityName]->GetContainerEntityId());
m_prefabFocusPublicInterface->FocusOnOwningPrefab(m_instanceMap[SportsCarEntityName]->GetContainerEntityId());
EXPECT_TRUE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap[SportsCarEntityName]->GetContainerEntityId()));
EXPECT_TRUE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap[Passenger2EntityName]->GetId()));
EXPECT_FALSE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_instanceMap[CarEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusInterface->IsOwningPrefabBeingFocused(m_entityMap[Passenger1EntityName]->GetId()));
EXPECT_TRUE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_instanceMap[SportsCarEntityName]->GetContainerEntityId()));
EXPECT_TRUE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_entityMap[Passenger2EntityName]->GetId()));
EXPECT_FALSE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_instanceMap[CarEntityName]->GetContainerEntityId()));
EXPECT_FALSE(m_prefabFocusPublicInterface->IsOwningPrefabBeingFocused(m_entityMap[Passenger1EntityName]->GetId()));
}
}
+1 -1
View File
@@ -27,7 +27,7 @@ ly_add_target(
3rdParty::expat
3rdParty::lz4
3rdParty::md5
3rdParty::tiff
3rdParty::TIFF
3rdParty::zstd
Legacy::CryCommon
Legacy::CrySystem.XMLBinary
@@ -590,20 +590,22 @@ TEST_F(PlatformConfigurationUnitTests, Test_GemHandling)
AssetUtilities::ResetAssetRoot();
ASSERT_EQ(2, config.GetScanFolderCount());
ASSERT_EQ(4, config.GetScanFolderCount());
EXPECT_FALSE(config.GetScanFolderAt(0).IsRoot());
EXPECT_TRUE(config.GetScanFolderAt(0).RecurseSubFolders());
// the first one is a game gem, so its order should be above 1 but below 100.
EXPECT_GE(config.GetScanFolderAt(0).GetOrder(), 100);
EXPECT_EQ(0, config.GetScanFolderAt(0).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
// for each gem, there are currently 1 scan folder, the gem assets folder, with no output prefix
// for each gem, there are currently 2 scan folders:
// The Gem's 'Assets' folder
// The Gem's 'Registry' folder
expectedScanFolder = tempPath.absoluteFilePath("Gems/LmbrCentral/v2/Assets");
EXPECT_FALSE(config.GetScanFolderAt(1).IsRoot() );
EXPECT_TRUE(config.GetScanFolderAt(1).RecurseSubFolders());
EXPECT_GT(config.GetScanFolderAt(1).GetOrder(), config.GetScanFolderAt(0).GetOrder());
EXPECT_EQ(0, config.GetScanFolderAt(1).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
EXPECT_FALSE(config.GetScanFolderAt(2).IsRoot() );
EXPECT_TRUE(config.GetScanFolderAt(2).RecurseSubFolders());
EXPECT_GT(config.GetScanFolderAt(2).GetOrder(), config.GetScanFolderAt(0).GetOrder());
EXPECT_EQ(0, config.GetScanFolderAt(2).ScanPath().compare(expectedScanFolder, Qt::CaseInsensitive));
}
TEST_F(PlatformConfigurationUnitTests, Test_MetaFileTypes)
@@ -1582,6 +1582,24 @@ namespace AssetProcessor
gemOrder,
/*scanFolderId*/ 0,
/*canSaveNewAssets*/ true)); // Users can create assets like slices in Gem asset folders.
// Now add another scan folder on Gem/GemName/Registry...
gemFolder = gemDir.absoluteFilePath(AzFramework::GemInfo::GetGemRegistryFolder());
gemFolder = AssetUtilities::NormalizeDirectoryPath(gemFolder);
assetBrowserDisplayName = AzFramework::GemInfo::GetGemRegistryFolder();
portableKey = QString("gemregistry-%1").arg(gemNameAsUuid);
gemOrder++;
AZ_TracePrintf(AssetProcessor::DebugChannel, "Adding GEM registry folder for monitoring / scanning: %s.\n", gemFolder.toUtf8().data());
AddScanFolder(ScanFolderInfo(
gemFolder,
assetBrowserDisplayName,
portableKey,
isRoot,
isRecursive,
platforms,
gemOrder));
}
}
}
@@ -0,0 +1,3 @@
<svg width="16" height="11" viewBox="0 0 16 11" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.7872 2.91865C11.4965 1.25029 10.0476 0 8.31169 0C6.91408 0 5.66005 0.823481 5.09382 2.08042C5.05787 2.07875 5.02234 2.07792 4.98701 2.07792C3.81195 2.07792 2.7921 2.8241 2.42743 3.92062C0.986389 4.39377 0 5.73881 0 7.27273C0 9.22057 1.58462 10.8052 3.53247 10.8052H12.0519C14.229 10.8052 16 9.03418 16 6.85714C16 4.59242 14.085 2.75595 11.7872 2.91865ZM8.31169 9.35065L5.61039 6.44156H7.27273V4.57143H9.35065V6.44156H11.013L8.31169 9.35065Z" fill="#E4E8EB"/>
</svg>

After

Width:  |  Height:  |  Size: 575 B

@@ -34,9 +34,11 @@
<file>Warning.svg</file>
<file>Backgrounds/DefaultBackground.jpg</file>
<file>Backgrounds/FtueBackground.jpg</file>
<file>FeatureTagClose.svg</file>
<file>X.svg</file>
<file>Refresh.svg</file>
<file>Edit.svg</file>
<file>Delete.svg</file>
<file>Download.svg</file>
<file>in_progress.gif</file>
</qresource>
</RCC>

Before

Width:  |  Height:  |  Size: 400 B

After

Width:  |  Height:  |  Size: 400 B

@@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:64985a78205da45f4bb92b040c348d96fe7cd7277549c1f79c430469a0d3bab7
size 166393
@@ -33,7 +33,7 @@ namespace O3DE::ProjectManager
m_closeButton = new QPushButton();
m_closeButton->setFlat(true);
m_closeButton->setIcon(QIcon(":/FeatureTagClose.svg"));
m_closeButton->setIcon(QIcon(":/X.svg"));
m_closeButton->setIconSize(QSize(12, 12));
m_closeButton->setStyleSheet("QPushButton { background-color: transparent; border: 0px }");
layout->addWidget(m_closeButton);
@@ -8,6 +8,8 @@
#include "GemInfo.h"
#include <QObject>
namespace O3DE::ProjectManager
{
GemInfo::GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded)
@@ -29,17 +31,17 @@ namespace O3DE::ProjectManager
switch (platform)
{
case Android:
return "Android";
return QObject::tr("Android");
case iOS:
return "iOS";
return QObject::tr("iOS");
case Linux:
return "Linux";
return QObject::tr("Linux");
case macOS:
return "macOS";
return QObject::tr("macOS");
case Windows:
return "Windows";
return QObject::tr("Windows");
default:
return "<Unknown Platform>";
return QObject::tr("<Unknown Platform>");
}
}
@@ -48,13 +50,13 @@ namespace O3DE::ProjectManager
switch (type)
{
case Asset:
return "Asset";
return QObject::tr("Asset");
case Code:
return "Code";
return QObject::tr("Code");
case Tool:
return "Tool";
return QObject::tr("Tool");
default:
return "<Unknown Type>";
return QObject::tr("<Unknown Type>");
}
}
@@ -62,15 +64,33 @@ namespace O3DE::ProjectManager
{
switch (origin)
{
case Open3DEEngine:
return "Open 3D Engine";
case Open3DEngine:
return QObject::tr("Open 3D Engine");
case Local:
return "Local";
return QObject::tr("Local");
case Remote:
return QObject::tr("Remote");
default:
return "<Unknown Gem Origin>";
return QObject::tr("<Unknown Gem Origin>");
}
}
QString GemInfo::GetDownloadStatusString(DownloadStatus status)
{
switch (status)
{
case NotDownloaded:
return QObject::tr("Not Downloaded");
case Downloading:
return QObject::tr("Downloading");
case Downloaded:
return QObject::tr("Downloaded");
case UnknownDownloadStatus:
default:
return QObject::tr("<Unknown Download Status>");
}
};
bool GemInfo::IsPlatformSupported(Platform platform) const
{
return (m_platforms & platform);
@@ -44,13 +44,23 @@ namespace O3DE::ProjectManager
enum GemOrigin
{
Open3DEEngine = 1 << 0,
Open3DEngine = 1 << 0,
Local = 1 << 1,
NumGemOrigins = 2
Remote = 1 << 2,
NumGemOrigins = 3
};
Q_DECLARE_FLAGS(GemOrigins, GemOrigin)
static QString GetGemOriginString(GemOrigin origin);
enum DownloadStatus
{
UnknownDownloadStatus = -1,
NotDownloaded,
Downloading,
Downloaded,
};
static QString GetDownloadStatusString(DownloadStatus status);
GemInfo() = default;
GemInfo(const QString& name, const QString& creator, const QString& summary, Platforms platforms, bool isAdded);
bool IsPlatformSupported(Platform platform) const;
@@ -68,6 +78,7 @@ namespace O3DE::ProjectManager
QString m_summary = "No summary provided.";
Platforms m_platforms;
Types m_types; //! Asset and/or Code and/or Tool
DownloadStatus m_downloadStatus = UnknownDownloadStatus;
QStringList m_features;
QString m_requirement;
QString m_directoryLink;
@@ -10,6 +10,7 @@
#include <GemCatalog/GemModel.h>
#include <GemCatalog/GemSortFilterProxyModel.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <QEvent>
#include <QAbstractItemView>
#include <QPainter>
@@ -20,6 +21,7 @@
#include <QTextDocument>
#include <QAbstractTextDocumentLayout>
#include <QDesktopServices>
#include <QMovie>
namespace O3DE::ProjectManager
{
@@ -32,6 +34,11 @@ namespace O3DE::ProjectManager
AddPlatformIcon(GemInfo::Linux, ":/Linux.svg");
AddPlatformIcon(GemInfo::macOS, ":/macOS.svg");
AddPlatformIcon(GemInfo::Windows, ":/Windows.svg");
SetStatusIcon(m_notDownloadedPixmap, ":/Download.svg");
SetStatusIcon(m_unknownStatusPixmap, ":/X.svg");
m_downloadingMovie = new QMovie(":/in_progress.gif");
}
void GemItemDelegate::AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath)
@@ -41,6 +48,25 @@ namespace O3DE::ProjectManager
m_platformIcons.insert(platform, QIcon(iconPath).pixmap(static_cast<int>(static_cast<qreal>(s_platformIconSize) * aspectRatio), s_platformIconSize));
}
void GemItemDelegate::SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath)
{
QPixmap pixmap(iconPath);
float aspectRatio = static_cast<float>(pixmap.width()) / pixmap.height();
int xScaler = s_statusIconSize;
int yScaler = s_statusIconSize;
if (aspectRatio > 1.0f)
{
yScaler = static_cast<int>(1.0f / aspectRatio * s_statusIconSize);
}
else if (aspectRatio < 1.0f)
{
xScaler = static_cast<int>(aspectRatio * s_statusIconSize);
}
m_iconPixmap = QPixmap(QIcon(iconPath).pixmap(xScaler, yScaler));
}
void GemItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) const
{
if (!modelIndex.isValid())
@@ -56,6 +82,8 @@ namespace O3DE::ProjectManager
QRect fullRect, itemRect, contentRect;
CalcRects(options, fullRect, itemRect, contentRect);
QRect buttonRect = CalcButtonRect(contentRect);
QFont standardFont(options.font);
standardFont.setPixelSize(static_cast<int>(s_fontSize));
QFontMetrics standardFontMetrics(standardFont);
@@ -114,7 +142,8 @@ namespace O3DE::ProjectManager
const QRect summaryRect = CalcSummaryRect(contentRect, hasTags);
DrawText(summary, painter, summaryRect, standardFont);
DrawButton(painter, contentRect, modelIndex);
DrawDownloadStatusIcon(painter, contentRect, buttonRect, modelIndex);
DrawButton(painter, buttonRect, modelIndex);
DrawPlatformIcons(painter, contentRect, modelIndex);
DrawFeatureTags(painter, contentRect, featureTags, standardFont, summaryRect);
@@ -270,7 +299,7 @@ namespace O3DE::ProjectManager
QRect GemItemDelegate::CalcButtonRect(const QRect& contentRect) const
{
const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth - s_itemMargins.right(), contentRect.top() + contentRect.height() / 2 - s_buttonHeight / 2);
const QPoint topLeft = QPoint(contentRect.right() - s_buttonWidth, contentRect.center().y() - s_buttonHeight / 2);
const QSize size = QSize(s_buttonWidth, s_buttonHeight);
return QRect(topLeft, size);
}
@@ -378,10 +407,9 @@ namespace O3DE::ProjectManager
painter->restore();
}
void GemItemDelegate::DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const
void GemItemDelegate::DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const
{
painter->save();
const QRect buttonRect = CalcButtonRect(contentRect);
QPoint circleCenter;
if (GemModel::IsAdded(modelIndex))
@@ -427,4 +455,45 @@ namespace O3DE::ProjectManager
return QString();
}
void GemItemDelegate::DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const
{
const GemInfo::DownloadStatus downloadStatus = GemModel::GetDownloadStatus(modelIndex);
// Show no icon if gem is already downloaded
if (downloadStatus == GemInfo::DownloadStatus::Downloaded)
{
return;
}
QPixmap currentFrame;
const QPixmap* statusPixmap;
if (downloadStatus == GemInfo::DownloadStatus::Downloading)
{
if (m_downloadingMovie->state() != QMovie::Running)
{
m_downloadingMovie->start();
emit MovieStartedPlaying(m_downloadingMovie);
}
currentFrame = m_downloadingMovie->currentPixmap();
currentFrame = currentFrame.scaled(s_statusIconSize, s_statusIconSize);
statusPixmap = &currentFrame;
}
else if (downloadStatus == GemInfo::DownloadStatus::NotDownloaded)
{
statusPixmap = &m_notDownloadedPixmap;
}
else
{
statusPixmap = &m_unknownStatusPixmap;
}
QSize statusSize = statusPixmap->size();
painter->drawPixmap(
buttonRect.left() - s_statusButtonSpacing - statusSize.width(),
contentRect.center().y() - statusSize.height() / 2,
*statusPixmap);
}
} // namespace O3DE::ProjectManager
@@ -49,13 +49,13 @@ namespace O3DE::ProjectManager
// Margin and borders
inline constexpr static QMargins s_itemMargins = QMargins(/*left=*/16, /*top=*/8, /*right=*/16, /*bottom=*/8); // Item border distances
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/15, /*bottom=*/12); // Distances of the elements within an item to the item borders
inline constexpr static QMargins s_contentMargins = QMargins(/*left=*/20, /*top=*/12, /*right=*/20, /*bottom=*/12); // Distances of the elements within an item to the item borders
inline constexpr static int s_borderWidth = 4;
// Button
inline constexpr static int s_buttonWidth = 55;
inline constexpr static int s_buttonHeight = 18;
inline constexpr static int s_buttonBorderRadius = 9;
inline constexpr static int s_buttonWidth = 32;
inline constexpr static int s_buttonHeight = 16;
inline constexpr static int s_buttonBorderRadius = s_buttonHeight / 2;
inline constexpr static int s_buttonCircleRadius = s_buttonBorderRadius - 2;
inline constexpr static qreal s_buttonFontSize = 10.0;
@@ -65,6 +65,9 @@ namespace O3DE::ProjectManager
inline constexpr static int s_featureTagBorderMarginY = 3;
inline constexpr static int s_featureTagSpacing = 7;
signals:
void MovieStartedPlaying(const QMovie* playingMovie) const;
protected:
bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& modelIndex) override;
bool helpEvent(QHelpEvent* event, QAbstractItemView* view, const QStyleOptionViewItem& option, const QModelIndex& index) override;
@@ -74,9 +77,10 @@ namespace O3DE::ProjectManager
QRect CalcButtonRect(const QRect& contentRect) const;
QRect CalcSummaryRect(const QRect& contentRect, bool hasTags) const;
void DrawPlatformIcons(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawButton(QPainter* painter, const QRect& contentRect, const QModelIndex& modelIndex) const;
void DrawButton(QPainter* painter, const QRect& buttonRect, const QModelIndex& modelIndex) const;
void DrawFeatureTags(QPainter* painter, const QRect& contentRect, const QStringList& featureTags, const QFont& standardFont, const QRect& summaryRect) const;
void DrawText(const QString& text, QPainter* painter, const QRect& rect, const QFont& standardFont) const;
void DrawDownloadStatusIcon(QPainter* painter, const QRect& contentRect, const QRect& buttonRect, const QModelIndex& modelIndex) const;
QAbstractItemModel* m_model = nullptr;
@@ -85,5 +89,14 @@ namespace O3DE::ProjectManager
void AddPlatformIcon(GemInfo::Platform platform, const QString& iconPath);
inline constexpr static int s_platformIconSize = 12;
QHash<GemInfo::Platform, QPixmap> m_platformIcons;
// Status icons
void SetStatusIcon(QPixmap& m_iconPixmap, const QString& iconPath);
inline constexpr static int s_statusIconSize = 16;
inline constexpr static int s_statusButtonSpacing = 5;
QPixmap m_unknownStatusPixmap;
QPixmap m_notDownloadedPixmap;
QMovie* m_downloadingMovie = nullptr;
};
} // namespace O3DE::ProjectManager
@@ -103,11 +103,11 @@ namespace O3DE::ProjectManager
QSpacerItem* horizontalSpacer = new QSpacerItem(0, 0, QSizePolicy::Expanding, QSizePolicy::Minimum);
columnHeaderLayout->addSpacerItem(horizontalSpacer);
QLabel* gemSelectedLabel = new QLabel(tr("Selected"));
QLabel* gemSelectedLabel = new QLabel(tr("Status"));
gemSelectedLabel->setObjectName("GemCatalogHeaderLabel");
columnHeaderLayout->addWidget(gemSelectedLabel);
columnHeaderLayout->addSpacing(65);
columnHeaderLayout->addSpacing(72);
vLayout->addLayout(columnHeaderLayout);
}
@@ -9,6 +9,8 @@
#include <GemCatalog/GemListView.h>
#include <GemCatalog/GemItemDelegate.h>
#include <QMovie>
namespace O3DE::ProjectManager
{
GemListView::GemListView(QAbstractItemModel* model, QItemSelectionModel* selectionModel, QWidget* parent)
@@ -19,6 +21,17 @@ namespace O3DE::ProjectManager
setModel(model);
setSelectionModel(selectionModel);
setItemDelegate(new GemItemDelegate(model, this));
GemItemDelegate* itemDelegate = new GemItemDelegate(model, this);
connect(itemDelegate, &GemItemDelegate::MovieStartedPlaying, [=](const QMovie* playingMovie)
{
// Force redraw when movie is playing so animation is smooth
connect(playingMovie, &QMovie::frameChanged, this, [=]
{
this->viewport()->repaint();
});
});
setItemDelegate(itemDelegate);
}
} // namespace O3DE::ProjectManager
@@ -48,6 +48,7 @@ namespace O3DE::ProjectManager
item->setData(gemInfo.m_features, RoleFeatures);
item->setData(gemInfo.m_path, RolePath);
item->setData(gemInfo.m_requirement, RoleRequirement);
item->setData(gemInfo.m_downloadStatus, RoleDownloadStatus);
appendRow(item);
@@ -132,6 +133,11 @@ namespace O3DE::ProjectManager
return static_cast<GemInfo::Types>(modelIndex.data(RoleTypes).toInt());
}
GemInfo::DownloadStatus GemModel::GetDownloadStatus(const QModelIndex& modelIndex)
{
return static_cast<GemInfo::DownloadStatus>(modelIndex.data(RoleDownloadStatus).toInt());
}
QString GemModel::GetSummary(const QModelIndex& modelIndex)
{
return modelIndex.data(RoleSummary).toString();
@@ -373,6 +379,11 @@ namespace O3DE::ProjectManager
return previouslyAdded && !added;
}
void GemModel::SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status)
{
model.setData(modelIndex, status, RoleDownloadStatus);
}
bool GemModel::HasRequirement(const QModelIndex& modelIndex)
{
return !modelIndex.data(RoleRequirement).toString().isEmpty();
@@ -40,6 +40,7 @@ namespace O3DE::ProjectManager
static GemInfo::GemOrigin GetGemOrigin(const QModelIndex& modelIndex);
static GemInfo::Platforms GetPlatforms(const QModelIndex& modelIndex);
static GemInfo::Types GetTypes(const QModelIndex& modelIndex);
static GemInfo::DownloadStatus GetDownloadStatus(const QModelIndex& modelIndex);
static QString GetSummary(const QModelIndex& modelIndex);
static QString GetDirectoryLink(const QModelIndex& modelIndex);
static QString GetDocLink(const QModelIndex& modelIndex);
@@ -64,6 +65,7 @@ namespace O3DE::ProjectManager
static bool NeedsToBeRemoved(const QModelIndex& modelIndex, bool includeDependencies = false);
static bool HasRequirement(const QModelIndex& modelIndex);
static void UpdateDependencies(QAbstractItemModel& model, const QModelIndex& modelIndex);
static void SetDownloadStatus(QAbstractItemModel& model, const QModelIndex& modelIndex, GemInfo::DownloadStatus status);
bool DoGemsToBeAddedHaveRequirements() const;
bool HasDependentGemsToRemove() const;
@@ -101,7 +103,8 @@ namespace O3DE::ProjectManager
RoleFeatures,
RoleTypes,
RolePath,
RoleRequirement
RoleRequirement,
RoleDownloadStatus
};
QHash<QString, QModelIndex> m_nameToIndexMap;
@@ -668,7 +668,21 @@ namespace O3DE::ProjectManager
if (gemInfo.m_creator.contains("Open 3D Engine"))
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Open3DEEngine;
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Open3DEngine;
}
else if (gemInfo.m_creator.contains("Amazon Web Services"))
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Local;
}
else if (data.contains("origin"))
{
gemInfo.m_gemOrigin = GemInfo::GemOrigin::Remote;
}
// As long Base Open3DEngine gems are installed before first startup non-remote gems will be downloaded
if (gemInfo.m_gemOrigin != GemInfo::GemOrigin::Remote)
{
gemInfo.m_downloadStatus = GemInfo::DownloadStatus::Downloaded;
}
if (data.contains("user_tags"))
@@ -33,7 +33,7 @@ namespace AWSClientAuth
AZ::SerializeContext* serialize = azrtti_cast<AZ::SerializeContext*>(context);
if (serialize)
{
serialize->Class<AWSClientAuthSystemComponent, AZ::Component>()->Version(1);
serialize->Class<AWSClientAuthSystemComponent, AZ::Component>()->Version(2);
if (AZ::EditContext* ec = serialize->GetEditContext())
{
@@ -105,12 +105,22 @@ namespace AWSClientAuth
behaviorContext->EBus<AWSCognitoUserManagementRequestBus>("AWSCognitoUserManagementRequestBus")
->Attribute(AZ::Script::Attributes::Category, SerializeComponentName)
->Event("Initialize", &AWSCognitoUserManagementRequestBus::Events::Initialize)
->Event("EmailSignUpAsync", &AWSCognitoUserManagementRequestBus::Events::EmailSignUpAsync)
->Event("PhoneSignUpAsync", &AWSCognitoUserManagementRequestBus::Events::PhoneSignUpAsync)
->Event("ConfirmSignUpAsync", &AWSCognitoUserManagementRequestBus::Events::ConfirmSignUpAsync)
->Event("ForgotPasswordAsync", &AWSCognitoUserManagementRequestBus::Events::ForgotPasswordAsync)
->Event("ConfirmForgotPasswordAsync", &AWSCognitoUserManagementRequestBus::Events::ConfirmForgotPasswordAsync)
->Event("EnableMFAAsync", &AWSCognitoUserManagementRequestBus::Events::EnableMFAAsync);
->Event(
"EmailSignUpAsync", &AWSCognitoUserManagementRequestBus::Events::EmailSignUpAsync,
{ { { "Username", "The client's username" }, { "Password", "The client's password" }, { "Email", "The email address used to sign up" } } })
->Event(
"PhoneSignUpAsync", &AWSCognitoUserManagementRequestBus::Events::PhoneSignUpAsync,
{ { { "Username", "The client's username" }, { "Password", "The client's password" }, { "Phone number", "The phone number used to sign up" } } })
->Event(
"ConfirmSignUpAsync", &AWSCognitoUserManagementRequestBus::Events::ConfirmSignUpAsync,
{ { { "Username", "The client's username" }, { "Confirmation code", "The client's confirmation code" } } })
->Event(
"ForgotPasswordAsync", &AWSCognitoUserManagementRequestBus::Events::ForgotPasswordAsync,
{ { { "Username", "The client's username" } } })
->Event(
"ConfirmForgotPasswordAsync", &AWSCognitoUserManagementRequestBus::Events::ConfirmForgotPasswordAsync,
{ { { "Username", "The client's username" }, { "Confirmation code", "The client's confirmation code" }, { "New password", "The new password for the client" } } })
->Event("EnableMFAAsync", &AWSCognitoUserManagementRequestBus::Events::EnableMFAAsync, { { { "Access token", "The MFA access token" } } });
behaviorContext->EBus<AuthenticationProviderNotificationBus>("AuthenticationProviderNotificationBus")
@@ -15,10 +15,11 @@ ly_add_target(
awsgamelift_client_files.cmake
INCLUDE_DIRECTORIES
PUBLIC
../AWSGameLiftCommon/Include
Include
PRIVATE
Source
../AWSGameLiftCommon/Source
Source
COMPILE_DEFINITIONS
PRIVATE
${awsgameliftclient_compile_definition}
@@ -78,10 +79,11 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
awsgamelift_client_tests_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
../AWSGameLiftCommon/Include
../AWSGameLiftCommon/Source
Include
Tests
Source
../AWSGameLiftCommon/Source
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
@@ -10,36 +10,12 @@
#include <AzCore/std/containers/unordered_map.h>
#include <AzCore/std/string/string.h>
#include <AzFramework/Matchmaking/MatchmakingRequests.h>
#include <AWSGameLiftPlayer.h>
namespace AWSGameLift
{
//! AWSGameLiftPlayerInformation
//! Information on each player to be matched
//! This information must include a player ID, and may contain player attributes and latency data to be used in the matchmaking process
//! After a successful match, Player objects contain the name of the team the player is assigned to
struct AWSGameLiftPlayerInformation
{
AZ_RTTI(AWSGameLiftPlayerInformation, "{B62C118E-C55D-4903-8ECB-E58E8CA613C4}");
static void Reflect(AZ::ReflectContext* context);
AWSGameLiftPlayerInformation() = default;
virtual ~AWSGameLiftPlayerInformation() = default;
// A map of region names to latencies in millseconds, that indicates
// the amount of latency that a player experiences when connected to AWS Regions
AZStd::unordered_map<AZStd::string, int> m_latencyInMs;
// A collection of key:value pairs containing player information for use in matchmaking
// Player attribute keys must match the playerAttributes used in a matchmaking rule set
// Example: {"skill": "{\"N\": \"23\"}", "gameMode": "{\"S\": \"deathmatch\"}"}
AZStd::unordered_map<AZStd::string, AZStd::string> m_playerAttributes;
// A unique identifier for a player
AZStd::string m_playerId;
// Name of the team that the player is assigned to in a match
AZStd::string m_team;
};
//! AWSGameLiftStartMatchmakingRequest
//! GameLift start matchmaking request which corresponds to Amazon GameLift
//! Uses FlexMatch to create a game match for a group of players based on custom matchmaking rules
@@ -57,6 +33,6 @@ namespace AWSGameLift
// Name of the matchmaking configuration to use for this request
AZStd::string m_configurationName;
// Information on each player to be matched
AZStd::vector<AWSGameLiftPlayerInformation> m_players;
AZStd::vector<AWSGameLiftPlayer> m_players;
};
} // namespace AWSGameLift
@@ -10,6 +10,7 @@
#include <AzCore/std/bind/bind.h>
#include <AzCore/std/smart_ptr/shared_ptr.h>
#include <AzFramework/Session/ISessionHandlingRequests.h>
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
#include <AWSGameLiftClientLocalTicketTracker.h>
#include <AWSGameLiftSessionConstants.h>
@@ -109,6 +110,7 @@ namespace AWSGameLift
else if (ticket.GetStatus() == Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE)
{
// broadcast acceptance requires to player
AzFramework::MatchAcceptanceNotificationBus::Broadcast(&AzFramework::MatchAcceptanceNotifications::OnMatchAcceptance);
}
else
{
@@ -18,6 +18,7 @@
#include <AWSGameLiftClientManager.h>
#include <AWSGameLiftSessionConstants.h>
#include <Activity/AWSGameLiftAcceptMatchActivity.h>
#include <Activity/AWSGameLiftCreateSessionActivity.h>
#include <Activity/AWSGameLiftCreateSessionOnQueueActivity.h>
#include <Activity/AWSGameLiftJoinSessionActivity.h>
@@ -125,12 +126,53 @@ namespace AWSGameLift
void AWSGameLiftClientManager::AcceptMatch(const AzFramework::AcceptMatchRequest& acceptMatchRequest)
{
AZ_UNUSED(acceptMatchRequest);
if (AcceptMatchActivity::ValidateAcceptMatchRequest(acceptMatchRequest))
{
const AWSGameLiftAcceptMatchRequest& gameliftStartMatchmakingRequest =
static_cast<const AWSGameLiftAcceptMatchRequest&>(acceptMatchRequest);
AcceptMatchHelper(gameliftStartMatchmakingRequest);
}
}
void AWSGameLiftClientManager::AcceptMatchAsync(const AzFramework::AcceptMatchRequest& acceptMatchRequest)
{
AZ_UNUSED(acceptMatchRequest);
if (!AcceptMatchActivity::ValidateAcceptMatchRequest(acceptMatchRequest))
{
AzFramework::MatchmakingAsyncRequestNotificationBus::Broadcast(
&AzFramework::MatchmakingAsyncRequestNotifications::OnAcceptMatchAsyncComplete);
return;
}
const AWSGameLiftAcceptMatchRequest& gameliftStartMatchmakingRequest = static_cast<const AWSGameLiftAcceptMatchRequest&>(acceptMatchRequest);
AZ::JobContext* jobContext = nullptr;
AWSCore::AWSCoreRequestBus::BroadcastResult(jobContext, &AWSCore::AWSCoreRequests::GetDefaultJobContext);
AZ::Job* acceptMatchJob = AZ::CreateJobFunction(
[this, gameliftStartMatchmakingRequest]()
{
AcceptMatchHelper(gameliftStartMatchmakingRequest);
AzFramework::MatchmakingAsyncRequestNotificationBus::Broadcast(
&AzFramework::MatchmakingAsyncRequestNotifications::OnAcceptMatchAsyncComplete);
},
true, jobContext);
acceptMatchJob->Start();
}
void AWSGameLiftClientManager::AcceptMatchHelper(const AWSGameLiftAcceptMatchRequest& acceptMatchRequest)
{
auto gameliftClient = AZ::Interface<IAWSGameLiftInternalRequests>::Get()->GetGameLiftClient();
AZStd::string response;
if (!gameliftClient)
{
AZ_Error(AWSGameLiftClientManagerName, false, AWSGameLiftClientMissingErrorMessage);
}
else
{
AcceptMatchActivity::AcceptMatch(*gameliftClient, acceptMatchRequest);
}
}
AZStd::string AWSGameLiftClientManager::CreateSession(const AzFramework::CreateSessionRequest& createSessionRequest)
@@ -15,6 +15,7 @@
namespace AWSGameLift
{
struct AWSGameLiftAcceptMatchRequest;
struct AWSGameLiftCreateSessionRequest;
struct AWSGameLiftCreateSessionOnQueueRequest;
struct AWSGameLiftJoinSessionRequest;
@@ -158,6 +159,7 @@ namespace AWSGameLift
void LeaveSession() override;
private:
void AcceptMatchHelper(const AWSGameLiftAcceptMatchRequest& createSessionRequest);
AZStd::string CreateSessionHelper(const AWSGameLiftCreateSessionRequest& createSessionRequest);
AZStd::string CreateSessionOnQueueHelper(const AWSGameLiftCreateSessionOnQueueRequest& createSessionOnQueueRequest);
bool JoinSessionHelper(const AWSGameLiftJoinSessionRequest& joinSessionRequest);
@@ -210,6 +210,7 @@ namespace AWSGameLift
->Property("SessionId", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionId))
->Property("SessionName", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionName))
->Property("SessionProperties", BehaviorValueProperty(&AzFramework::SessionConfig::m_sessionProperties))
->Property("MatchmakingData", BehaviorValueProperty(&AzFramework::SessionConfig::m_matchmakingData))
->Property("Status", BehaviorValueProperty(&AzFramework::SessionConfig::m_status))
->Property("StatusReason", BehaviorValueProperty(&AzFramework::SessionConfig::m_statusReason))
->Property("TerminationTime", BehaviorValueProperty(&AzFramework::SessionConfig::m_terminationTime))
@@ -0,0 +1,76 @@
/*
* 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.AcceptMatch
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Interface/Interface.h>
#include <Activity/AWSGameLiftAcceptMatchActivity.h>
#include <AWSGameLiftSessionConstants.h>
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/model/AcceptMatchRequest.h>
namespace AWSGameLift
{
namespace AcceptMatchActivity
{
Aws::GameLift::Model::AcceptMatchRequest BuildAWSGameLiftAcceptMatchRequest(
const AWSGameLiftAcceptMatchRequest& acceptMatchRequest)
{
Aws::GameLift::Model::AcceptMatchRequest request;
request.SetAcceptanceType(acceptMatchRequest.m_acceptMatch ?
Aws::GameLift::Model::AcceptanceType::ACCEPT : Aws::GameLift::Model::AcceptanceType::REJECT);
Aws::Vector<Aws::String> playerIds;
for (const AZStd::string& playerId : acceptMatchRequest.m_playerIds)
{
playerIds.emplace_back(playerId.c_str());
}
request.SetPlayerIds(playerIds);
if (!acceptMatchRequest.m_ticketId.empty())
{
request.SetTicketId(acceptMatchRequest.m_ticketId.c_str());
}
AZ_TracePrintf(AWSGameLiftAcceptMatchActivityName, "Built AcceptMatchRequest with TicketId=%s", request.GetTicketId().c_str());
return request;
}
void AcceptMatch(const Aws::GameLift::GameLiftClient& gameliftClient,
const AWSGameLiftAcceptMatchRequest& AcceptMatchRequest)
{
AZ_TracePrintf(AWSGameLiftAcceptMatchActivityName, "Requesting AcceptMatch against Amazon GameLift service ...");
Aws::GameLift::Model::AcceptMatchRequest request = BuildAWSGameLiftAcceptMatchRequest(AcceptMatchRequest);
auto AcceptMatchOutcome = gameliftClient.AcceptMatch(request);
if (AcceptMatchOutcome.IsSuccess())
{
AZ_TracePrintf(AWSGameLiftAcceptMatchActivityName, "AcceptMatch request against Amazon GameLift service is complete");
}
else
{
AZ_Error(AWSGameLiftAcceptMatchActivityName, false, AWSGameLiftErrorMessageTemplate,
AcceptMatchOutcome.GetError().GetExceptionName().c_str(), AcceptMatchOutcome.GetError().GetMessage().c_str());
}
}
bool ValidateAcceptMatchRequest(const AzFramework::AcceptMatchRequest& AcceptMatchRequest)
{
auto gameliftAcceptMatchRequest = azrtti_cast<const AWSGameLiftAcceptMatchRequest*>(&AcceptMatchRequest);
bool isValid = gameliftAcceptMatchRequest &&
(gameliftAcceptMatchRequest->m_playerIds.size() > 0) &&
(!gameliftAcceptMatchRequest->m_ticketId.empty());
AZ_Error(AWSGameLiftAcceptMatchActivityName, isValid, AWSGameLiftAcceptMatchRequestInvalidErrorMessage);
return isValid;
}
} // namespace AcceptMatchActivity
} // namespace AWSGameLift
@@ -0,0 +1,31 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <Request/AWSGameLiftAcceptMatchRequest.h>
#include <aws/gamelift/GameLiftClient.h>
namespace AWSGameLift
{
namespace AcceptMatchActivity
{
static constexpr const char AWSGameLiftAcceptMatchActivityName[] = "AWSGameLiftAcceptMatchActivity";
static constexpr const char AWSGameLiftAcceptMatchRequestInvalidErrorMessage[] = "Invalid GameLift AcceptMatch request.";
// Build AWS GameLift AcceptMatchRequest by using AWSGameLiftAcceptMatchRequest
Aws::GameLift::Model::AcceptMatchRequest BuildAWSGameLiftAcceptMatchRequest(const AWSGameLiftAcceptMatchRequest& AcceptMatchRequest);
// Create AcceptMatchRequest and make a AcceptMatch call through GameLift client
void AcceptMatch(const Aws::GameLift::GameLiftClient& gameliftClient, const AWSGameLiftAcceptMatchRequest& AcceptMatchRequest);
// Validate AcceptMatchRequest and check required request parameters
bool ValidateAcceptMatchRequest(const AzFramework::AcceptMatchRequest& AcceptMatchRequest);
} // namespace AcceptMatchActivity
} // namespace AWSGameLift
@@ -105,6 +105,7 @@ namespace AWSGameLift
session.m_status = AWSGameLiftSessionStatusNames[(int)gameSession.GetStatus()];
session.m_statusReason = AWSGameLiftSessionStatusReasons[(int)gameSession.GetStatusReason()];
session.m_terminationTime = gameSession.GetTerminationTime().Millis();
session.m_matchmakingData = gameSession.GetMatchmakerData().c_str();
// TODO: Update the AWS Native SDK to get the new game session attributes.
//session.m_dnsName = gameSession.GetDnsName();
@@ -5,12 +5,17 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Interface/Interface.h>
#include <Activity/AWSGameLiftActivityUtils.h>
#include <Activity/AWSGameLiftStartMatchmakingActivity.h>
#include <AWSGameLiftPlayer.h>
#include <AWSGameLiftSessionConstants.h>
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/model/StartMatchmakingRequest.h>
namespace AWSGameLift
{
namespace StartMatchmakingActivity
@@ -25,7 +30,7 @@ namespace AWSGameLift
}
Aws::Vector<Aws::GameLift::Model::Player> players;
for (const AWSGameLiftPlayerInformation& playerInfo : startMatchmakingRequest.m_players)
for (const AWSGameLiftPlayer& playerInfo : startMatchmakingRequest.m_players)
{
Aws::GameLift::Model::Player player;
if (!playerInfo.m_playerId.empty())
@@ -105,7 +110,7 @@ namespace AWSGameLift
if (isValid)
{
for (const AWSGameLiftPlayerInformation& playerInfo : gameliftStartMatchmakingRequest->m_players)
for (const AWSGameLiftPlayer& playerInfo : gameliftStartMatchmakingRequest->m_players)
{
isValid &= !playerInfo.m_playerId.empty();
isValid &= AWSGameLiftActivityUtils::ValidatePlayerAttributes(playerInfo.m_playerAttributes);
@@ -10,9 +10,7 @@
#include <Request/AWSGameLiftStartMatchmakingRequest.h>
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/GameLiftClient.h>
#include <aws/gamelift/model/StartMatchmakingRequest.h>
namespace AWSGameLift
{
@@ -25,7 +23,6 @@ namespace AWSGameLift
Aws::GameLift::Model::StartMatchmakingRequest BuildAWSGameLiftStartMatchmakingRequest(const AWSGameLiftStartMatchmakingRequest& startMatchmakingRequest);
// Create StartMatchmakingRequest and make a StartMatchmaking call through GameLift client
// Will also start polling the matchmaking ticket when get success outcome from GameLift client
AZStd::string StartMatchmaking(const Aws::GameLift::GameLiftClient& gameliftClient, const AWSGameLiftStartMatchmakingRequest& startMatchmakingRequest);
// Validate StartMatchmakingRequest and check required request parameters
@@ -11,6 +11,9 @@
#include <Activity/AWSGameLiftStopMatchmakingActivity.h>
#include <AWSGameLiftSessionConstants.h>
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/model/StopMatchmakingRequest.h>
namespace AWSGameLift
{
namespace StopMatchmakingActivity
@@ -10,9 +10,7 @@
#include <Request/AWSGameLiftStopMatchmakingRequest.h>
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/GameLiftClient.h>
#include <aws/gamelift/model/StopMatchmakingRequest.h>
namespace AWSGameLift
{
@@ -25,7 +23,6 @@ namespace AWSGameLift
Aws::GameLift::Model::StopMatchmakingRequest BuildAWSGameLiftStopMatchmakingRequest(const AWSGameLiftStopMatchmakingRequest& stopMatchmakingRequest);
// Create StopMatchmakingRequest and make a StopMatchmaking call through GameLift client
// Will also stop polling the matchmaking ticket when get success outcome from GameLift client
void StopMatchmaking(const Aws::GameLift::GameLiftClient& gameliftClient, const AWSGameLiftStopMatchmakingRequest& stopMatchmakingRequest);
// Validate StopMatchmakingRequest and check required request parameters
@@ -14,53 +14,10 @@
namespace AWSGameLift
{
void AWSGameLiftPlayerInformation::Reflect(AZ::ReflectContext* context)
{
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
serializeContext->Class<AWSGameLiftPlayerInformation>()
->Version(0)
->Field("latencyInMs", &AWSGameLiftPlayerInformation::m_latencyInMs)
->Field("playerAttributes", &AWSGameLiftPlayerInformation::m_playerAttributes)
->Field("playerId", &AWSGameLiftPlayerInformation::m_playerId)
->Field("team", &AWSGameLiftPlayerInformation::m_team);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<AWSGameLiftPlayerInformation>("AWSGameLiftPlayerInformation", "")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Visibility, AZ::Edit::PropertyVisibility::ShowChildrenOnly)
->DataElement(
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_latencyInMs, "LatencyInMs",
"A set of values, expressed in milliseconds, that indicates the amount of latency that"
"a player experiences when connected to AWS Regions")
->DataElement(
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_playerAttributes, "PlayerAttributes",
"A collection of key:value pairs containing player information for use in matchmaking")
->DataElement(
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_playerId, "PlayerId",
"A unique identifier for a player")
->DataElement(
AZ::Edit::UIHandlers::Default, &AWSGameLiftPlayerInformation::m_team, "Team",
"Name of the team that the player is assigned to in a match");
}
}
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context))
{
behaviorContext->Class<AWSGameLiftPlayerInformation>("AWSGameLiftPlayerInformation")
->Attribute(AZ::Script::Attributes::Storage, AZ::Script::Attributes::StorageType::Value)
->Property("LatencyInMs", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_latencyInMs))
->Property("PlayerAttributes", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_playerAttributes))
->Property("PlayerId", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_playerId))
->Property("Team", BehaviorValueProperty(&AWSGameLiftPlayerInformation::m_team));
}
}
void AWSGameLiftStartMatchmakingRequest::Reflect(AZ::ReflectContext* context)
{
AzFramework::StartMatchmakingRequest::Reflect(context);
AWSGameLiftPlayerInformation::Reflect(context);
AWSGameLiftPlayer::Reflect(context);
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
{
@@ -351,3 +351,41 @@ TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_CallAndTicketComple
WaitForProcessFinish();
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
}
TEST_F(AWSGameLiftClientLocalTicketTrackerTest, StartPolling_RequiresAcceptanceAndTicketCompleteAtLast_ProcessContinuesAndStop)
{
Aws::GameLift::Model::MatchmakingTicket ticket1;
ticket1.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::REQUIRES_ACCEPTANCE);
Aws::GameLift::Model::DescribeMatchmakingResult result1;
result1.AddTicketList(ticket1);
Aws::GameLift::Model::DescribeMatchmakingOutcome outcome1(result1);
Aws::GameLift::Model::GameSessionConnectionInfo connectionInfo;
connectionInfo.SetIpAddress("DummyIpAddress");
connectionInfo.SetPort(123);
connectionInfo.AddMatchedPlayerSessions(
Aws::GameLift::Model::MatchedPlayerSession().WithPlayerId("player1").WithPlayerSessionId("playersession1"));
Aws::GameLift::Model::MatchmakingTicket ticket2;
ticket2.SetStatus(Aws::GameLift::Model::MatchmakingConfigurationStatus::COMPLETED);
ticket2.SetGameSessionConnectionInfo(connectionInfo);
Aws::GameLift::Model::DescribeMatchmakingResult result2;
result2.AddTicketList(ticket2);
Aws::GameLift::Model::DescribeMatchmakingOutcome outcome2(result2);
EXPECT_CALL(*m_gameliftClientMockPtr, DescribeMatchmaking(::testing::_))
.WillOnce(::testing::Return(outcome1))
.WillOnce(::testing::Return(outcome2));
MatchAcceptanceNotificationsHandlerMock handlerMock1;
EXPECT_CALL(handlerMock1, OnMatchAcceptance()).Times(1);
SessionHandlingClientRequestsMock handlerMock2;
EXPECT_CALL(handlerMock2, RequestPlayerJoinSession(::testing::_)).Times(1).WillOnce(::testing::Return(true));
m_gameliftClientTicketTracker->StartPolling("ticket1", "player1");
WaitForProcessFinish();
ASSERT_TRUE(m_gameliftClientTicketTracker->IsTrackerIdle());
}
@@ -19,6 +19,7 @@
#include <AWSGameLiftClientManager.h>
#include <AWSGameLiftClientMocks.h>
#include <Request/AWSGameLiftAcceptMatchRequest.h>
#include <Request/AWSGameLiftCreateSessionOnQueueRequest.h>
#include <Request/AWSGameLiftCreateSessionRequest.h>
#include <Request/AWSGameLiftJoinSessionRequest.h>
@@ -207,6 +208,7 @@ protected:
sessionConfig.m_terminationTime = 0;
sessionConfig.m_creatorId = "dummyCreatorId";
sessionConfig.m_sessionProperties["dummyKey"] = "dummyValue";
sessionConfig.m_matchmakingData = "dummyMatchmakingData";
sessionConfig.m_sessionId = "dummyGameSessionId";
sessionConfig.m_sessionName = "dummyGameSessionName";
sessionConfig.m_ipAddress = "dummyIpAddress";
@@ -231,7 +233,7 @@ protected:
request.m_configurationName = "dummyConfiguration";
request.m_ticketId = DummyMatchmakingTicketId;
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"N\": \"1\"}";
player.m_playerId = DummyPlayerId;
player.m_latencyInMs["us-east-1"] = 10;
@@ -812,7 +814,7 @@ TEST_F(AWSGameLiftClientManagerTest, StartMatchmaking_CallWithInvalidRequest_Get
{
AWSGameLiftStartMatchmakingRequest request;
request.m_configurationName = "dummyConfiguration";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"A\": \"1\"}";
request.m_players.emplace_back(player);
@@ -854,7 +856,7 @@ TEST_F(AWSGameLiftClientManagerTest, StartMatchmakingAsync_CallWithInvalidReques
{
AWSGameLiftStartMatchmakingRequest request;
request.m_configurationName = "dummyConfiguration";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"A\": \"1\"}";
request.m_players.emplace_back(player);
@@ -1005,3 +1007,106 @@ TEST_F(AWSGameLiftClientManagerTest, StopMatchmakingAsync_CallWithValidRequest_G
m_gameliftClientManager->StopMatchmakingAsync(request);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatch_CallWithoutClientSetup_GetError)
{
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientManager->ConfigureGameLiftClient("");
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { DummyPlayerId };
request.m_ticketId = DummyMatchmakingTicketId;
m_gameliftClientManager->AcceptMatch(request);
AZ_TEST_STOP_TRACE_SUPPRESSION(2); // capture 2 error message
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatch_CallWithInvalidRequest_GetError)
{
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientManager->AcceptMatch(AzFramework::AcceptMatchRequest());
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatch_CallWithValidRequest_Success)
{
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { DummyPlayerId };
request.m_ticketId = DummyMatchmakingTicketId;
Aws::GameLift::Model::AcceptMatchResult result;
Aws::GameLift::Model::AcceptMatchResult outcome(result);
EXPECT_CALL(*m_gameliftClientMockPtr, AcceptMatch(::testing::_)).Times(1).WillOnce(::testing::Return(outcome));
m_gameliftClientManager->AcceptMatch(request);
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatch_CallWithValidRequest_GetError)
{
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { DummyPlayerId };
request.m_ticketId = DummyMatchmakingTicketId;
Aws::Client::AWSError<Aws::GameLift::GameLiftErrors> error;
Aws::GameLift::Model::AcceptMatchOutcome outcome(error);
EXPECT_CALL(*m_gameliftClientMockPtr, AcceptMatch(::testing::_)).Times(1).WillOnce(::testing::Return(outcome));
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientManager->AcceptMatch(request);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatchAsync_CallWithInvalidRequest_GetNotificationWithError)
{
AWSGameLiftAcceptMatchRequest request;
MatchmakingAsyncRequestNotificationsHandlerMock matchmakingHandlerMock;
EXPECT_CALL(matchmakingHandlerMock, OnAcceptMatchAsyncComplete()).Times(1);
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientManager->AcceptMatchAsync(request);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatchAsync_CallWithValidRequest_GetNotification)
{
AWSCoreRequestsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get()));
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { DummyPlayerId };
request.m_ticketId = DummyMatchmakingTicketId;
Aws::GameLift::Model::AcceptMatchResult result;
Aws::GameLift::Model::AcceptMatchOutcome outcome(result);
EXPECT_CALL(*m_gameliftClientMockPtr, AcceptMatch(::testing::_)).Times(1).WillOnce(::testing::Return(outcome));
MatchmakingAsyncRequestNotificationsHandlerMock matchmakingHandlerMock;
EXPECT_CALL(matchmakingHandlerMock, OnAcceptMatchAsyncComplete()).Times(1);
m_gameliftClientManager->AcceptMatchAsync(request);
}
TEST_F(AWSGameLiftClientManagerTest, AcceptMatchAsync_CallWithValidRequest_GetNotificationWithError)
{
AWSCoreRequestsHandlerMock handlerMock;
EXPECT_CALL(handlerMock, GetDefaultJobContext()).Times(1).WillOnce(::testing::Return(m_jobContext.get()));
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { DummyPlayerId };
request.m_ticketId = DummyMatchmakingTicketId;
Aws::Client::AWSError<Aws::GameLift::GameLiftErrors> error;
Aws::GameLift::Model::AcceptMatchOutcome outcome(error);
EXPECT_CALL(*m_gameliftClientMockPtr, AcceptMatch(::testing::_)).Times(1).WillOnce(::testing::Return(outcome));
MatchmakingAsyncRequestNotificationsHandlerMock matchmakingHandlerMock;
EXPECT_CALL(matchmakingHandlerMock, OnAcceptMatchAsyncComplete()).Times(1);
AZ_TEST_START_TRACE_SUPPRESSION;
m_gameliftClientManager->AcceptMatchAsync(request);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
@@ -9,6 +9,7 @@
#pragma once
#include <AzCore/Interface/Interface.h>
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
#include <AzFramework/Session/ISessionRequests.h>
#include <AzFramework/Session/ISessionHandlingRequests.h>
#include <AzFramework/Matchmaking/MatchmakingNotifications.h>
@@ -18,6 +19,8 @@
#include <aws/core/utils/Outcome.h>
#include <aws/gamelift/GameLiftClient.h>
#include <aws/gamelift/GameLiftErrors.h>
#include <aws/gamelift/model/AcceptMatchRequest.h>
#include <aws/gamelift/model/AcceptMatchResult.h>
#include <aws/gamelift/model/CreateGameSessionRequest.h>
#include <aws/gamelift/model/CreateGameSessionResult.h>
#include <aws/gamelift/model/CreatePlayerSessionRequest.h>
@@ -46,6 +49,7 @@ public:
{
}
MOCK_CONST_METHOD1(AcceptMatch, Model::AcceptMatchOutcome(const Model::AcceptMatchRequest&));
MOCK_CONST_METHOD1(CreateGameSession, Model::CreateGameSessionOutcome(const Model::CreateGameSessionRequest&));
MOCK_CONST_METHOD1(CreatePlayerSession, Model::CreatePlayerSessionOutcome(const Model::CreatePlayerSessionRequest&));
MOCK_CONST_METHOD1(DescribeMatchmaking, Model::DescribeMatchmakingOutcome(const Model::DescribeMatchmakingRequest&));
@@ -74,6 +78,23 @@ public:
MOCK_METHOD0(OnStopMatchmakingAsyncComplete, void());
};
class MatchAcceptanceNotificationsHandlerMock
: public AzFramework::MatchAcceptanceNotificationBus::Handler
{
public:
MatchAcceptanceNotificationsHandlerMock()
{
AzFramework::MatchAcceptanceNotificationBus::Handler::BusConnect();
}
~MatchAcceptanceNotificationsHandlerMock()
{
AzFramework::MatchAcceptanceNotificationBus::Handler::BusDisconnect();
}
MOCK_METHOD0(OnMatchAcceptance, void());
};
class SessionAsyncRequestNotificationsHandlerMock
: public AzFramework::SessionAsyncRequestNotificationBus::Handler
{
@@ -0,0 +1,77 @@
/*
* 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 <aws/gamelift/model/AcceptMatchRequest.h>
#include <AWSGameLiftClientFixture.h>
#include <Activity/AWSGameLiftAcceptMatchActivity.h>
#include <aws/gamelift/model/AcceptMatchRequest.h>
using namespace AWSGameLift;
using AWSGameLiftAcceptMatchActivityTest = AWSGameLiftClientFixture;
TEST_F(AWSGameLiftAcceptMatchActivityTest, BuildAWSGameLiftAcceptMatchRequest_Call_GetExpectedResult)
{
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_ticketId = "dummyTicketId";
request.m_playerIds = { "dummyPlayerId" };
auto awsRequest = AcceptMatchActivity::BuildAWSGameLiftAcceptMatchRequest(request);
EXPECT_EQ(awsRequest.GetAcceptanceType(), Aws::GameLift::Model::AcceptanceType::ACCEPT);
EXPECT_TRUE(strcmp(awsRequest.GetTicketId().c_str(), request.m_ticketId.c_str()) == 0);
EXPECT_EQ(awsRequest.GetPlayerIds().size(), request.m_playerIds.size());
EXPECT_TRUE(strcmp(awsRequest.GetPlayerIds().begin()->c_str(), request.m_playerIds.begin()->c_str()) == 0);
}
TEST_F(AWSGameLiftAcceptMatchActivityTest, ValidateAcceptMatchRequest_CallWithBaseType_GetFalseResult)
{
AZ_TEST_START_TRACE_SUPPRESSION;
auto result = AcceptMatchActivity::ValidateAcceptMatchRequest(AzFramework::AcceptMatchRequest());
EXPECT_FALSE(result);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftAcceptMatchActivityTest, ValidateAcceptMatchRequest_CallWithoutTicketId_GetFalseResult)
{
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { "dummyPlayerId" };
AZ_TEST_START_TRACE_SUPPRESSION;
auto result = AcceptMatchActivity::ValidateAcceptMatchRequest(request);
EXPECT_FALSE(result);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftAcceptMatchActivityTest, ValidateAcceptMatchRequest_CallWithoutPlayerIds_GetFalseResult)
{
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_playerIds = { "dummyPlayerId" };
AZ_TEST_START_TRACE_SUPPRESSION;
auto result = AcceptMatchActivity::ValidateAcceptMatchRequest(request);
EXPECT_FALSE(result);
AZ_TEST_STOP_TRACE_SUPPRESSION(1); // capture 1 error message
}
TEST_F(AWSGameLiftAcceptMatchActivityTest, ValidateAcceptMatchRequest_CallWithValidAttributes_GetTrueResult)
{
AWSGameLiftAcceptMatchRequest request;
request.m_acceptMatch = true;
request.m_ticketId = "dummyTicketId";
request.m_playerIds = { "dummyPlayerId" };
auto result = AcceptMatchActivity::ValidateAcceptMatchRequest(request);
EXPECT_TRUE(result);
}
@@ -8,6 +8,9 @@
#include <Activity/AWSGameLiftStartMatchmakingActivity.h>
#include <AWSGameLiftClientFixture.h>
#include <AWSGameLiftPlayer.h>
#include <aws/gamelift/model/StartMatchmakingRequest.h>
using namespace AWSGameLift;
@@ -19,7 +22,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, BuildAWSGameLiftStartMatchmaking
request.m_configurationName = "dummyConfiguration";
request.m_ticketId = "dummyTicketId";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
player.m_playerId = "dummyPlayerId";
player.m_team = "dummyTeam";
@@ -56,7 +59,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
AWSGameLiftStartMatchmakingRequest request;
request.m_ticketId = "dummyTicketId";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
player.m_playerId = "dummyPlayerId";
player.m_team = "dummyTeam";
@@ -87,7 +90,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
request.m_configurationName = "dummyConfiguration";
request.m_ticketId = "dummyTicketId";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
player.m_team = "dummyTeam";
player.m_latencyInMs["us-east-1"] = 10;
@@ -105,7 +108,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
request.m_configurationName = "dummyConfiguration";
request.m_ticketId = "dummyTicketId";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"A\": \"test\"}";
player.m_playerId = "dummyPlayerId";
player.m_team = "dummyTeam";
@@ -123,7 +126,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
AWSGameLiftStartMatchmakingRequest request;
request.m_configurationName = "dummyConfiguration";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
player.m_playerId = "dummyPlayerId";
player.m_team = "dummyTeam";
@@ -140,7 +143,7 @@ TEST_F(AWSGameLiftStartMatchmakingActivityTest, ValidateStartMatchmakingRequest_
request.m_ticketId = "dummyTicketId";
request.m_configurationName = "dummyConfiguration";
AWSGameLiftPlayerInformation player;
AWSGameLiftPlayer player;
player.m_playerAttributes["dummy"] = "{\"S\": \"test\"}";
player.m_playerId = "dummyPlayerId";
player.m_team = "dummyTeam";
@@ -9,6 +9,8 @@
#include <Activity/AWSGameLiftStopMatchmakingActivity.h>
#include <AWSGameLiftClientFixture.h>
#include <aws/gamelift/model/StopMatchmakingRequest.h>
using namespace AWSGameLift;
using AWSGameLiftStopMatchmakingActivityTest = AWSGameLiftClientFixture;

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