Fix merge conflicts.
Signed-off-by: John <jonawals@amazon.com>monroegm-disable-blank-issue-2
commit
cf41ff020a
@ -0,0 +1,179 @@
|
||||
"""
|
||||
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_gradient_weight_creation = (
|
||||
"PostFX Gradient Weight Modifier Entity successfully created",
|
||||
"PostFX Gradient Weight Modifier Entity failed to be created")
|
||||
postfx_gradient_weight_component = (
|
||||
"Entity has a PostFX Gradient Weight Modifier component",
|
||||
"Entity failed to find PostFX Gradient Weight Modifier component")
|
||||
postfx_gradient_weight_disabled = (
|
||||
"PostFX Gradient Weight Modifier component disabled",
|
||||
"PostFX Gradient Weight Modifier component was not disabled.")
|
||||
postfx_layer_component = (
|
||||
"Entity has a PostFX Layer component",
|
||||
"Entity did not have an PostFX Layer component")
|
||||
postfx_gradient_weight_enabled = (
|
||||
"PostFX Gradient Weight Modifier component enabled",
|
||||
"PostFX Gradient 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_PostFXGradientWeightModifier_AddedToEntity():
|
||||
"""
|
||||
Summary:
|
||||
Tests the PostFX Gradient 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 Gradient Weight Modifier entity with no components.
|
||||
2) Add a PostFX Gradient Weight Modifier component to PostFX Gradient Weight Modifier entity.
|
||||
3) UNDO the entity creation and component addition.
|
||||
4) REDO the entity creation and component addition.
|
||||
5) Verify PostFX Gradient Weight Modifier component not enabled.
|
||||
6) Add PostFX Layer component since it is required by the PostFX Gradient Weight Modifier component.
|
||||
7) Verify PostFX Gradient Weight Modifier component is enabled.
|
||||
8) Enter/Exit game mode.
|
||||
9) Test IsHidden.
|
||||
10) Test IsVisible.
|
||||
11) Delete PostFX Gradient Weight Modifier entity.
|
||||
12) UNDO deletion.
|
||||
13) REDO deletion.
|
||||
14) 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 Gradient Weight Modifier entity with no components.
|
||||
postfx_gradient_weight_name = "PostFX Gradient Weight Modifier"
|
||||
postfx_gradient_weight_entity = EditorEntity.create_editor_entity(postfx_gradient_weight_name)
|
||||
Report.critical_result(Tests.postfx_gradient_weight_creation, postfx_gradient_weight_entity.exists())
|
||||
|
||||
# 2. Add a PostFX Gradient Weight Modifier component to PostFX Gradient Weight Modifier entity.
|
||||
postfx_gradient_weight_component = postfx_gradient_weight_entity.add_component(postfx_gradient_weight_name)
|
||||
Report.critical_result(
|
||||
Tests.postfx_gradient_weight_component,
|
||||
postfx_gradient_weight_entity.has_component(postfx_gradient_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_gradient_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_gradient_weight_entity.exists())
|
||||
|
||||
# 5. Verify PostFX Gradient Weight Modifier component not enabled.
|
||||
Report.result(Tests.postfx_gradient_weight_disabled, not postfx_gradient_weight_component.is_enabled())
|
||||
|
||||
# 6. Add PostFX Layer component since it is required by the PostFX Gradient Weight Modifier component.
|
||||
postfx_layer_name = "PostFX Layer"
|
||||
postfx_gradient_weight_entity.add_component(postfx_layer_name)
|
||||
Report.result(Tests.postfx_layer_component, postfx_gradient_weight_entity.has_component(postfx_layer_name))
|
||||
|
||||
# 7. Verify PostFX Gradient Weight Modifier component is enabled.
|
||||
Report.result(Tests.postfx_gradient_weight_enabled, postfx_gradient_weight_component.is_enabled())
|
||||
|
||||
# 8. 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)
|
||||
|
||||
# 9. Test IsHidden.
|
||||
postfx_gradient_weight_entity.set_visibility_state(False)
|
||||
Report.result(Tests.is_hidden, postfx_gradient_weight_entity.is_hidden() is True)
|
||||
|
||||
# 10. Test IsVisible.
|
||||
postfx_gradient_weight_entity.set_visibility_state(True)
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, postfx_gradient_weight_entity.is_visible() is True)
|
||||
|
||||
# 11. Delete PostFX Gradient Weight Modifier entity.
|
||||
postfx_gradient_weight_entity.delete()
|
||||
Report.result(Tests.entity_deleted, not postfx_gradient_weight_entity.exists())
|
||||
|
||||
# 12. UNDO deletion.
|
||||
general.undo()
|
||||
Report.result(Tests.deletion_undo, postfx_gradient_weight_entity.exists())
|
||||
|
||||
# 13. REDO deletion.
|
||||
general.redo()
|
||||
Report.result(Tests.deletion_redo, not postfx_gradient_weight_entity.exists())
|
||||
|
||||
# 14. 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_PostFXGradientWeightModifier_AddedToEntity)
|
||||
@ -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
|
||||
@ -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
|
||||
@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
class UnhandledExceptions
|
||||
: public ScopedAllocatorSetupFixture
|
||||
{
|
||||
|
||||
public:
|
||||
void causeAccessViolation()
|
||||
{
|
||||
int* someVariable = reinterpret_cast<int*>(0);
|
||||
*someVariable = 0;
|
||||
}
|
||||
};
|
||||
|
||||
#if GTEST_HAS_DEATH_TEST
|
||||
TEST_F(UnhandledExceptions, Handle)
|
||||
{
|
||||
EXPECT_DEATH(causeAccessViolation(), "");
|
||||
}
|
||||
#endif
|
||||
}
|
||||
@ -1,234 +0,0 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Math/Matrix3x3.h>
|
||||
#include <AzCore/Math/Vector3.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
|
||||
#include <AzFramework/Physics/WorldBody.h>
|
||||
#include <AzFramework/Physics/ShapeConfiguration.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
class ReflectContext;
|
||||
}
|
||||
|
||||
namespace Physics
|
||||
{
|
||||
class ShapeConfiguration;
|
||||
class World;
|
||||
class Shape;
|
||||
|
||||
/// Default values used for initializing RigidBodySettings.
|
||||
/// These can be modified by Physics Implementation gems. // O3DE_DEPRECATED(LY-114472) - DefaultRigidBodyConfiguration values are not shared across modules.
|
||||
// Use RigidBodyConfiguration default values.
|
||||
struct DefaultRigidBodyConfiguration
|
||||
{
|
||||
static float m_mass;
|
||||
static bool m_computeInertiaTensor;
|
||||
static float m_linearDamping;
|
||||
static float m_angularDamping;
|
||||
static float m_sleepMinEnergy;
|
||||
static float m_maxAngularVelocity;
|
||||
};
|
||||
|
||||
enum class MassComputeFlags : AZ::u8
|
||||
{
|
||||
NONE = 0,
|
||||
|
||||
//! Flags indicating whether a certain mass property should be auto-computed or not.
|
||||
COMPUTE_MASS = 1,
|
||||
COMPUTE_INERTIA = 1 << 1,
|
||||
COMPUTE_COM = 1 << 2,
|
||||
|
||||
//! If set, non-simulated shapes will also be included in the mass properties calculation.
|
||||
INCLUDE_ALL_SHAPES = 1 << 3,
|
||||
|
||||
DEFAULT = COMPUTE_COM | COMPUTE_INERTIA | COMPUTE_MASS
|
||||
};
|
||||
|
||||
class RigidBodyConfiguration
|
||||
: public WorldBodyConfiguration
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(RigidBodyConfiguration, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(RigidBodyConfiguration, "{ACFA8900-8530-4744-AF00-AA533C868A8E}", WorldBodyConfiguration);
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
enum PropertyVisibility : AZ::u16
|
||||
{
|
||||
InitialVelocities = 1 << 0, ///< Whether the initial linear and angular velocities are visible.
|
||||
InertiaProperties = 1 << 1, ///< Whether the whole category of inertia properties (mass, compute inertia,
|
||||
///< inertia tensor etc) is visible.
|
||||
Damping = 1 << 2, ///< Whether linear and angular damping are visible.
|
||||
SleepOptions = 1 << 3, ///< Whether the sleep threshold and start asleep options are visible.
|
||||
Interpolation = 1 << 4, ///< Whether the interpolation option is visible.
|
||||
Gravity = 1 << 5, ///< Whether the effected by gravity option is visible.
|
||||
Kinematic = 1 << 6, ///< Whether the option to make the body kinematic is visible.
|
||||
ContinuousCollisionDetection = 1 << 7, ///< Whether the option to enable continuous collision detection is visible.
|
||||
MaxVelocities = 1 << 8 ///< Whether upper limits on velocities are visible.
|
||||
};
|
||||
|
||||
RigidBodyConfiguration() = default;
|
||||
RigidBodyConfiguration(const RigidBodyConfiguration& settings) = default;
|
||||
|
||||
// Visibility functions.
|
||||
AZ::Crc32 GetPropertyVisibility(PropertyVisibility property) const;
|
||||
void SetPropertyVisibility(PropertyVisibility property, bool isVisible);
|
||||
|
||||
AZ::Crc32 GetInitialVelocitiesVisibility() const;
|
||||
/// Returns whether the whole category of inertia settings (mass, inertia, center of mass offset etc) is visible.
|
||||
AZ::Crc32 GetInertiaSettingsVisibility() const;
|
||||
/// Returns whether the individual inertia tensor field is visible or is hidden because the compute inertia option is selected.
|
||||
AZ::Crc32 GetInertiaVisibility() const;
|
||||
/// Returns whether the mass field is visible or is hidden because compute mass option is selected.
|
||||
AZ::Crc32 GetMassVisibility() const;
|
||||
/// Returns whether the individual centre of mass offset field is visible or is hidden because compute CoM option is selected.
|
||||
AZ::Crc32 GetCoMVisibility() const;
|
||||
AZ::Crc32 GetDampingVisibility() const;
|
||||
AZ::Crc32 GetSleepOptionsVisibility() const;
|
||||
AZ::Crc32 GetInterpolationVisibility() const;
|
||||
AZ::Crc32 GetGravityVisibility() const;
|
||||
AZ::Crc32 GetKinematicVisibility() const;
|
||||
AZ::Crc32 GetCCDVisibility() const;
|
||||
AZ::Crc32 GetMaxVelocitiesVisibility() const;
|
||||
MassComputeFlags GetMassComputeFlags() const;
|
||||
void SetMassComputeFlags(MassComputeFlags flags);
|
||||
|
||||
bool IsCCDEnabled() const;
|
||||
|
||||
// Basic initial settings.
|
||||
AZ::Vector3 m_initialLinearVelocity = AZ::Vector3::CreateZero();
|
||||
AZ::Vector3 m_initialAngularVelocity = AZ::Vector3::CreateZero();
|
||||
AZ::Vector3 m_centerOfMassOffset = AZ::Vector3::CreateZero();
|
||||
|
||||
// Simulation parameters.
|
||||
float m_mass = DefaultRigidBodyConfiguration::m_mass;
|
||||
AZ::Matrix3x3 m_inertiaTensor = AZ::Matrix3x3::CreateIdentity();
|
||||
float m_linearDamping = DefaultRigidBodyConfiguration::m_linearDamping;
|
||||
float m_angularDamping = DefaultRigidBodyConfiguration::m_angularDamping;
|
||||
float m_sleepMinEnergy = DefaultRigidBodyConfiguration::m_sleepMinEnergy;
|
||||
float m_maxAngularVelocity = DefaultRigidBodyConfiguration::m_maxAngularVelocity;
|
||||
|
||||
// Visibility settings.
|
||||
AZ::u16 m_propertyVisibilityFlags = (std::numeric_limits<AZ::u16>::max)();
|
||||
|
||||
bool m_startAsleep = false;
|
||||
bool m_interpolateMotion = false;
|
||||
bool m_gravityEnabled = true;
|
||||
bool m_simulated = true;
|
||||
bool m_kinematic = false;
|
||||
bool m_ccdEnabled = false; ///< Whether continuous collision detection is enabled.
|
||||
float m_ccdMinAdvanceCoefficient = 0.15f; ///< Coefficient affecting how granularly time is subdivided in CCD.
|
||||
bool m_ccdFrictionEnabled = false; ///< Whether friction is applied when resolving CCD collisions.
|
||||
|
||||
bool m_computeCenterOfMass = true;
|
||||
bool m_computeInertiaTensor = true;
|
||||
bool m_computeMass = true;
|
||||
|
||||
//! If set, non-simulated shapes will also be included in the mass properties calculation.
|
||||
bool m_includeAllShapesInMassCalculation = false;
|
||||
};
|
||||
|
||||
/// Dynamic rigid body.
|
||||
class RigidBody
|
||||
: public WorldBody
|
||||
{
|
||||
public:
|
||||
|
||||
AZ_CLASS_ALLOCATOR(RigidBody, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(RigidBody, "{156E459F-7BB7-4B4E-ADA0-2130D96B7E80}", WorldBody);
|
||||
|
||||
public:
|
||||
RigidBody() = default;
|
||||
explicit RigidBody(const RigidBodyConfiguration& settings);
|
||||
|
||||
|
||||
virtual void AddShape(AZStd::shared_ptr<Shape> shape) = 0;
|
||||
virtual void RemoveShape(AZStd::shared_ptr<Shape> shape) = 0;
|
||||
virtual AZ::u32 GetShapeCount() { return 0; }
|
||||
virtual AZStd::shared_ptr<Shape> GetShape(AZ::u32 /*index*/) { return nullptr; }
|
||||
|
||||
virtual AZ::Vector3 GetCenterOfMassWorld() const = 0;
|
||||
virtual AZ::Vector3 GetCenterOfMassLocal() const = 0;
|
||||
|
||||
virtual AZ::Matrix3x3 GetInverseInertiaWorld() const = 0;
|
||||
virtual AZ::Matrix3x3 GetInverseInertiaLocal() const = 0;
|
||||
|
||||
virtual float GetMass() const = 0;
|
||||
virtual float GetInverseMass() const = 0;
|
||||
virtual void SetMass(float mass) = 0;
|
||||
virtual void SetCenterOfMassOffset(const AZ::Vector3& comOffset) = 0;
|
||||
|
||||
/// Retrieves the velocity at center of mass; only linear velocity, no rotational velocity contribution.
|
||||
virtual AZ::Vector3 GetLinearVelocity() const = 0;
|
||||
virtual void SetLinearVelocity(const AZ::Vector3& velocity) = 0;
|
||||
virtual AZ::Vector3 GetAngularVelocity() const = 0;
|
||||
virtual void SetAngularVelocity(const AZ::Vector3& angularVelocity) = 0;
|
||||
virtual AZ::Vector3 GetLinearVelocityAtWorldPoint(const AZ::Vector3& worldPoint) = 0;
|
||||
virtual void ApplyLinearImpulse(const AZ::Vector3& impulse) = 0;
|
||||
virtual void ApplyLinearImpulseAtWorldPoint(const AZ::Vector3& impulse, const AZ::Vector3& worldPoint) = 0;
|
||||
virtual void ApplyAngularImpulse(const AZ::Vector3& angularImpulse) = 0;
|
||||
|
||||
virtual float GetLinearDamping() const = 0;
|
||||
virtual void SetLinearDamping(float damping) = 0;
|
||||
virtual float GetAngularDamping() const = 0;
|
||||
virtual void SetAngularDamping(float damping) = 0;
|
||||
|
||||
virtual bool IsAwake() const = 0;
|
||||
virtual void ForceAsleep() = 0;
|
||||
virtual void ForceAwake() = 0;
|
||||
virtual float GetSleepThreshold() const = 0;
|
||||
virtual void SetSleepThreshold(float threshold) = 0;
|
||||
|
||||
virtual bool IsKinematic() const = 0;
|
||||
virtual void SetKinematic(bool kinematic) = 0;
|
||||
virtual void SetKinematicTarget(const AZ::Transform& targetPosition) = 0;
|
||||
|
||||
virtual bool IsGravityEnabled() const = 0;
|
||||
virtual void SetGravityEnabled(bool enabled) = 0;
|
||||
virtual void SetSimulationEnabled(bool enabled) = 0;
|
||||
virtual void SetCCDEnabled(bool enabled) = 0;
|
||||
|
||||
//! Recalculates mass, inertia and center of mass based on the flags passed.
|
||||
//! @param flags MassComputeFlags specifying which properties should be recomputed.
|
||||
//! @param centerOfMassOffsetOverride Optional override of the center of mass. Note: This parameter will be ignored if COMPUTE_COM is passed in flags.
|
||||
//! @param inertiaTensorOverride Optional override of the inertia. Note: This parameter will be ignored if COMPUTE_INERTIA is passed in flags.
|
||||
//! @param massOverride Optional override of the mass. Note: This parameter will be ignored if COMPUTE_MASS is passed in flags.
|
||||
virtual void UpdateMassProperties(MassComputeFlags flags = MassComputeFlags::DEFAULT,
|
||||
const AZ::Vector3* centerOfMassOffsetOverride = nullptr,
|
||||
const AZ::Matrix3x3* inertiaTensorOverride = nullptr,
|
||||
const float* massOverride = nullptr) = 0;
|
||||
};
|
||||
|
||||
/// Bitwise operators for MassComputeFlags
|
||||
inline MassComputeFlags operator|(MassComputeFlags lhs, MassComputeFlags rhs)
|
||||
{
|
||||
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) | aznumeric_cast<AZ::u8>(rhs));
|
||||
}
|
||||
|
||||
inline MassComputeFlags operator&(MassComputeFlags lhs, MassComputeFlags rhs)
|
||||
{
|
||||
return aznumeric_cast<MassComputeFlags>(aznumeric_cast<AZ::u8>(lhs) & aznumeric_cast<AZ::u8>(rhs));
|
||||
}
|
||||
|
||||
/// Static rigid body.
|
||||
class RigidBodyStatic
|
||||
: public WorldBody
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(RigidBodyStatic, AZ::SystemAllocator, 0);
|
||||
AZ_RTTI(RigidBodyStatic, "{13A677BB-7085-4EDB-BCC8-306548238692}", WorldBody);
|
||||
|
||||
virtual void AddShape(const AZStd::shared_ptr<Shape>& shape) = 0;
|
||||
virtual AZ::u32 GetShapeCount() { return 0; }
|
||||
virtual AZStd::shared_ptr<Shape> GetShape(AZ::u32 /*index*/) { return nullptr; }
|
||||
};
|
||||
} // namespace Physics
|
||||
@ -0,0 +1,652 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzCore/std/typetraits/integral_constant.h>
|
||||
#include <AzFramework/API/ApplicationAPI_Linux.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <AzFramework/XcbInputDeviceMouse.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
xcb_window_t GetSystemCursorFocusWindow()
|
||||
{
|
||||
void* systemCursorFocusWindow = nullptr;
|
||||
AzFramework::InputSystemCursorConstraintRequestBus::BroadcastResult(
|
||||
systemCursorFocusWindow, &AzFramework::InputSystemCursorConstraintRequests::GetSystemCursorConstraintWindow);
|
||||
|
||||
if (!systemCursorFocusWindow)
|
||||
{
|
||||
return XCB_NONE;
|
||||
}
|
||||
|
||||
// TODO Clang compile error because cast .... loses information. On GNU/Linux HWND is void* and on 64-bit
|
||||
// machines its obviously 64 bit but we receive the window id from m_renderOverlay.winId() which is xcb_window_t 32-bit.
|
||||
|
||||
return static_cast<xcb_window_t>(reinterpret_cast<uint64_t>(systemCursorFocusWindow));
|
||||
}
|
||||
|
||||
xcb_connection_t* XcbInputDeviceMouse::s_xcbConnection = nullptr;
|
||||
xcb_screen_t* XcbInputDeviceMouse::s_xcbScreen = nullptr;
|
||||
bool XcbInputDeviceMouse::m_xfixesInitialized = false;
|
||||
bool XcbInputDeviceMouse::m_xInputInitialized = false;
|
||||
|
||||
XcbInputDeviceMouse::XcbInputDeviceMouse(InputDeviceMouse& inputDevice)
|
||||
: InputDeviceMouse::Implementation(inputDevice)
|
||||
, m_systemCursorState(SystemCursorState::Unknown)
|
||||
, m_systemCursorPositionNormalized(0.5f, 0.5f)
|
||||
, m_prevConstraintWindow(XCB_NONE)
|
||||
, m_focusWindow(XCB_NONE)
|
||||
, m_cursorShown(true)
|
||||
{
|
||||
XcbEventHandlerBus::Handler::BusConnect();
|
||||
|
||||
SetSystemCursorState(SystemCursorState::Unknown);
|
||||
}
|
||||
|
||||
XcbInputDeviceMouse::~XcbInputDeviceMouse()
|
||||
{
|
||||
XcbEventHandlerBus::Handler::BusDisconnect();
|
||||
|
||||
SetSystemCursorState(SystemCursorState::Unknown);
|
||||
}
|
||||
|
||||
InputDeviceMouse::Implementation* XcbInputDeviceMouse::Create(InputDeviceMouse& inputDevice)
|
||||
{
|
||||
auto* interface = AzFramework::XcbConnectionManagerInterface::Get();
|
||||
if (!interface)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB interface not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
s_xcbConnection = AzFramework::XcbConnectionManagerInterface::Get()->GetXcbConnection();
|
||||
if (!s_xcbConnection)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB connection not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const xcb_setup_t* xcbSetup = xcb_get_setup(s_xcbConnection);
|
||||
s_xcbScreen = xcb_setup_roots_iterator(xcbSetup).data;
|
||||
if (!s_xcbScreen)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB screen not available");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize XFixes extension which we use to create pointer barriers.
|
||||
if (!InitializeXFixes())
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB XFixes initialization failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Initialize XInput extension which is used to get RAW Input events.
|
||||
if (!InitializeXInput())
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XCB XInput initialization failed");
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
return aznew XcbInputDeviceMouse(inputDevice);
|
||||
}
|
||||
|
||||
bool XcbInputDeviceMouse::IsConnected() const
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::CreateBarriers(xcb_window_t window, bool create)
|
||||
{
|
||||
// Don't create any barriers if we are debugging. This will cause artifacts but better then
|
||||
// a confined cursor during debugging.
|
||||
if (AZ::Debug::Trace::IsDebuggerPresent())
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Debugger running. Barriers will not be created.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (create)
|
||||
{
|
||||
// Destroy barriers if they are active already.
|
||||
if (!m_activeBarriers.empty())
|
||||
{
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
|
||||
}
|
||||
|
||||
m_activeBarriers.clear();
|
||||
}
|
||||
|
||||
// Get window information.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xcbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
|
||||
if (!xcbGeometryReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const xcb_translate_coordinates_cookie_t translate_coord =
|
||||
xcb_translate_coordinates(s_xcbConnection, window, s_xcbScreen->root, 0, 0);
|
||||
|
||||
const XcbStdFreePtr<xcb_translate_coordinates_reply_t> xkbTranslateCoordReply{ xcb_translate_coordinates_reply(
|
||||
s_xcbConnection, translate_coord, NULL) };
|
||||
|
||||
if (!xkbTranslateCoordReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t x0 = xkbTranslateCoordReply->dst_x < 0 ? 0 : xkbTranslateCoordReply->dst_x;
|
||||
const int16_t y0 = xkbTranslateCoordReply->dst_y < 0 ? 0 : xkbTranslateCoordReply->dst_y;
|
||||
const int16_t x1 = xkbTranslateCoordReply->dst_x + xcbGeometryReply->width;
|
||||
const int16_t y1 = xkbTranslateCoordReply->dst_y + xcbGeometryReply->height;
|
||||
|
||||
// ATTN For whatever reason, when making an exact rectangle the pointer will escape the top right corner in some cases. Adding
|
||||
// an offset to the lines so that they cross each other prevents that.
|
||||
const int16_t offset = 30;
|
||||
|
||||
// Create the left barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_X, x0, Clamp(y0 - offset),
|
||||
x0, Clamp(y1 + offset) });
|
||||
|
||||
// Create the right barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_X, x1, Clamp(y0 - offset),
|
||||
x1, Clamp(y1 + offset) });
|
||||
|
||||
// Create the top barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_POSITIVE_Y, Clamp(x0 - offset), y0,
|
||||
Clamp(x1 + offset), y0 });
|
||||
|
||||
// Create the bottom barrier info.
|
||||
m_activeBarriers.push_back({ xcb_generate_id(s_xcbConnection), XCB_XFIXES_BARRIER_DIRECTIONS_NEGATIVE_Y, Clamp(x0 - offset), y1,
|
||||
Clamp(x1 + offset), y1 });
|
||||
|
||||
// Create the xfixes barriers.
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_void_cookie_t cookie = xcb_xfixes_create_pointer_barrier_checked(
|
||||
s_xcbConnection, barrier.id, window, barrier.x0, barrier.y0, barrier.x1, barrier.y1, barrier.direction, 0, NULL);
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
AZ_Warning(
|
||||
"XcbInput", !xkbError, "XFixes, failed to create barrier %d at (%d %d %d %d)", barrier.id, barrier.x0, barrier.y0,
|
||||
barrier.x1, barrier.y1);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (const auto& barrier : m_activeBarriers)
|
||||
{
|
||||
xcb_xfixes_delete_pointer_barrier_checked(s_xcbConnection, barrier.id);
|
||||
}
|
||||
|
||||
m_activeBarriers.clear();
|
||||
}
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
bool XcbInputDeviceMouse::InitializeXFixes()
|
||||
{
|
||||
m_xfixesInitialized = false;
|
||||
|
||||
// We don't have to free query_extension_reply according to xcb documentation.
|
||||
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_xfixes_id);
|
||||
if (!query_extension_reply || !query_extension_reply->present)
|
||||
{
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
|
||||
const xcb_xfixes_query_version_cookie_t query_cookie = xcb_xfixes_query_version(s_xcbConnection, 5, 0);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
const XcbStdFreePtr<xcb_xfixes_query_version_reply_t> xkbQueryRequestReply{ xcb_xfixes_query_version_reply(
|
||||
s_xcbConnection, query_cookie, &error) };
|
||||
|
||||
if (!xkbQueryRequestReply || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving XFixes version failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
else if (xkbQueryRequestReply->major_version < 5)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XFixes version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
|
||||
m_xfixesInitialized = true;
|
||||
|
||||
return m_xfixesInitialized;
|
||||
}
|
||||
|
||||
bool XcbInputDeviceMouse::InitializeXInput()
|
||||
{
|
||||
m_xInputInitialized = false;
|
||||
|
||||
// We don't have to free query_extension_reply according to xcb documentation.
|
||||
const xcb_query_extension_reply_t* query_extension_reply = xcb_get_extension_data(s_xcbConnection, &xcb_input_id);
|
||||
if (!query_extension_reply || !query_extension_reply->present)
|
||||
{
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
const xcb_input_xi_query_version_cookie_t query_version_cookie = xcb_input_xi_query_version(s_xcbConnection, 2, 2);
|
||||
|
||||
xcb_generic_error_t* error = NULL;
|
||||
const XcbStdFreePtr<xcb_input_xi_query_version_reply_t> xkbQueryRequestReply{ xcb_input_xi_query_version_reply(
|
||||
s_xcbConnection, query_version_cookie, &error) };
|
||||
|
||||
if (!xkbQueryRequestReply || error)
|
||||
{
|
||||
if (error)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "Retrieving XInput version failed : Error code %d", error->error_code);
|
||||
free(error);
|
||||
}
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
else if (xkbQueryRequestReply->major_version < 2)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "XInput version fails the minimum version check (%d<5)", xkbQueryRequestReply->major_version);
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
m_xInputInitialized = true;
|
||||
|
||||
return m_xInputInitialized;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetEnableXInput(bool enable)
|
||||
{
|
||||
struct
|
||||
{
|
||||
xcb_input_event_mask_t head;
|
||||
int mask;
|
||||
} mask;
|
||||
|
||||
mask.head.deviceid = XCB_INPUT_DEVICE_ALL;
|
||||
mask.head.mask_len = 1;
|
||||
|
||||
if (enable)
|
||||
{
|
||||
mask.mask = XCB_INPUT_XI_EVENT_MASK_RAW_MOTION | XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_PRESS |
|
||||
XCB_INPUT_XI_EVENT_MASK_RAW_BUTTON_RELEASE | XCB_INPUT_XI_EVENT_MASK_MOTION | XCB_INPUT_XI_EVENT_MASK_BUTTON_PRESS |
|
||||
XCB_INPUT_XI_EVENT_MASK_BUTTON_RELEASE;
|
||||
}
|
||||
else
|
||||
{
|
||||
mask.mask = XCB_NONE;
|
||||
}
|
||||
|
||||
xcb_input_xi_select_events(s_xcbConnection, s_xcbScreen->root, 1, &mask.head);
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorState(SystemCursorState systemCursorState)
|
||||
{
|
||||
if (systemCursorState != m_systemCursorState)
|
||||
{
|
||||
m_systemCursorState = systemCursorState;
|
||||
|
||||
m_focusWindow = GetSystemCursorFocusWindow();
|
||||
|
||||
HandleCursorState(m_focusWindow, systemCursorState);
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState)
|
||||
{
|
||||
bool confined = false, cursorShown = true;
|
||||
switch (systemCursorState)
|
||||
{
|
||||
case SystemCursorState::ConstrainedAndHidden:
|
||||
{
|
||||
//!< Constrained to the application's main window and hidden
|
||||
confined = true;
|
||||
cursorShown = false;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::ConstrainedAndVisible:
|
||||
{
|
||||
//!< Constrained to the application's main window and visible
|
||||
confined = true;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::UnconstrainedAndHidden:
|
||||
{
|
||||
//!< Free to move outside the main window but hidden while inside
|
||||
cursorShown = false;
|
||||
}
|
||||
break;
|
||||
case SystemCursorState::UnconstrainedAndVisible:
|
||||
{
|
||||
//!< Free to move outside the application's main window and visible
|
||||
}
|
||||
case SystemCursorState::Unknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// ATTN GetSystemCursorFocusWindow when getting out of the play in editor will return XCB_NONE
|
||||
// We need however the window id to reset the cursor.
|
||||
if (XCB_NONE == window && (confined || cursorShown))
|
||||
{
|
||||
// Reuse the previous window to reset states.
|
||||
window = m_prevConstraintWindow;
|
||||
m_prevConstraintWindow = XCB_NONE;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Remember the window we used to modify cursor and barrier states.
|
||||
m_prevConstraintWindow = window;
|
||||
}
|
||||
|
||||
SetEnableXInput(!cursorShown);
|
||||
|
||||
CreateBarriers(window, confined);
|
||||
ShowCursor(window, cursorShown);
|
||||
}
|
||||
|
||||
SystemCursorState XcbInputDeviceMouse::GetSystemCursorState() const
|
||||
{
|
||||
return m_systemCursorState;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized)
|
||||
{
|
||||
// TODO Basically not done at all. Added only the basic functions needed.
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
const int16_t x = static_cast<int16_t>(positionNormalized.GetX() * xkbGeometryReply->width);
|
||||
const int16_t y = static_cast<int16_t>(positionNormalized.GetY() * xkbGeometryReply->height);
|
||||
|
||||
xcb_warp_pointer(s_xcbConnection, XCB_NONE, window, 0, 0, 0, 0, x, y);
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized)
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
SetSystemCursorPositionNormalizedInternal(window, positionNormalized);
|
||||
}
|
||||
|
||||
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const
|
||||
{
|
||||
AZ::Vector2 position = AZ::Vector2::CreateZero();
|
||||
|
||||
const xcb_query_pointer_cookie_t pointer = xcb_query_pointer(s_xcbConnection, window);
|
||||
|
||||
const XcbStdFreePtr<xcb_query_pointer_reply_t> xkbQueryPointerReply{ xcb_query_pointer_reply(s_xcbConnection, pointer, NULL) };
|
||||
|
||||
if (!xkbQueryPointerReply)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_get_geometry_reply_t> xkbGeometryReply{ xcb_get_geometry_reply(
|
||||
s_xcbConnection, xcb_get_geometry(s_xcbConnection, window), NULL) };
|
||||
|
||||
if (!xkbGeometryReply)
|
||||
{
|
||||
return position;
|
||||
}
|
||||
|
||||
AZ_Assert(xkbGeometryReply->width != 0, "xkbGeometry response width must be non-zero. (%d)", xkbGeometryReply->width);
|
||||
const float normalizedCursorPostionX = static_cast<float>(xkbQueryPointerReply->win_x) / xkbGeometryReply->width;
|
||||
|
||||
AZ_Assert(xkbGeometryReply->height != 0, "xkbGeometry response height must be non-zero. (%d)", xkbGeometryReply->height);
|
||||
const float normalizedCursorPostionY = static_cast<float>(xkbQueryPointerReply->win_y) / xkbGeometryReply->height;
|
||||
|
||||
position = AZ::Vector2(normalizedCursorPostionX, normalizedCursorPostionY);
|
||||
|
||||
return position;
|
||||
}
|
||||
|
||||
AZ::Vector2 XcbInputDeviceMouse::GetSystemCursorPositionNormalized() const
|
||||
{
|
||||
const xcb_window_t window = GetSystemCursorFocusWindow();
|
||||
if (XCB_NONE == window)
|
||||
{
|
||||
return AZ::Vector2::CreateZero();
|
||||
}
|
||||
|
||||
return GetSystemCursorPositionNormalizedInternal(window);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::TickInputDevice()
|
||||
{
|
||||
ProcessRawEventQueues();
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::ShowCursor(xcb_window_t window, bool show)
|
||||
{
|
||||
xcb_void_cookie_t cookie;
|
||||
if (show)
|
||||
{
|
||||
cookie = xcb_xfixes_show_cursor_checked(s_xcbConnection, window);
|
||||
}
|
||||
else
|
||||
{
|
||||
cookie = xcb_xfixes_hide_cursor_checked(s_xcbConnection, window);
|
||||
}
|
||||
|
||||
const XcbStdFreePtr<xcb_generic_error_t> xkbError{ xcb_request_check(s_xcbConnection, cookie) };
|
||||
|
||||
if (xkbError)
|
||||
{
|
||||
AZ_Warning("XcbInput", false, "ShowCursor failed: %d", xkbError->error_code);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// ATTN In the following part we will when cursor gets hidden store the position of the cursor in screen space
|
||||
// not window space. We use that to re-position when showing the cursor again. Is this the correct
|
||||
// behavior?
|
||||
|
||||
const bool cursorWasHidden = !m_cursorShown;
|
||||
m_cursorShown = show;
|
||||
if (!m_cursorShown)
|
||||
{
|
||||
m_cursorHiddenPosition = GetSystemCursorPositionNormalizedInternal(s_xcbScreen->root);
|
||||
|
||||
SetSystemCursorPositionNormalized(AZ::Vector2(0.5f, 0.5f));
|
||||
}
|
||||
else if (cursorWasHidden)
|
||||
{
|
||||
SetSystemCursorPositionNormalizedInternal(s_xcbScreen->root, m_cursorHiddenPosition);
|
||||
}
|
||||
|
||||
xcb_flush(s_xcbConnection);
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleButtonPressEvents(uint32_t detail, bool pressed)
|
||||
{
|
||||
bool isWheel;
|
||||
float wheelDirection;
|
||||
const auto* button = InputChannelFromMouseEvent(detail, isWheel, wheelDirection);
|
||||
if (button)
|
||||
{
|
||||
QueueRawButtonEvent(*button, pressed);
|
||||
}
|
||||
if (isWheel)
|
||||
{
|
||||
float axisValue = MAX_XI_WHEEL_SENSITIVITY * wheelDirection;
|
||||
QueueRawMovementEvent(InputDeviceMouse::Movement::Z, axisValue);
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandlePointerMotionEvents(const xcb_generic_event_t* event)
|
||||
{
|
||||
const xcb_input_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_motion_event_t*>(event);
|
||||
|
||||
m_systemCursorPosition[0] = mouseMotionEvent->event_x;
|
||||
m_systemCursorPosition[1] = mouseMotionEvent->event_y;
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleRawInputEvents(const xcb_ge_generic_event_t* event)
|
||||
{
|
||||
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
|
||||
switch (genericEvent->event_type)
|
||||
{
|
||||
case XCB_INPUT_RAW_BUTTON_PRESS:
|
||||
{
|
||||
const xcb_input_raw_button_press_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_raw_button_press_event_t*>(event);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, true);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_RAW_BUTTON_RELEASE:
|
||||
{
|
||||
const xcb_input_raw_button_release_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_raw_button_release_event_t*>(event);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, false);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_RAW_MOTION:
|
||||
{
|
||||
const xcb_input_raw_motion_event_t* mouseMotionEvent = reinterpret_cast<const xcb_input_raw_motion_event_t*>(event);
|
||||
|
||||
int axisLen = xcb_input_raw_button_press_axisvalues_length(mouseMotionEvent);
|
||||
const xcb_input_fp3232_t* axisvalues = xcb_input_raw_button_press_axisvalues_raw(mouseMotionEvent);
|
||||
for (int i = 0; i < axisLen; ++i)
|
||||
{
|
||||
const float axisValue = fp3232ToFloat(axisvalues[i]);
|
||||
|
||||
switch (i)
|
||||
{
|
||||
case 0:
|
||||
QueueRawMovementEvent(InputDeviceMouse::Movement::X, axisValue);
|
||||
break;
|
||||
case 1:
|
||||
QueueRawMovementEvent(InputDeviceMouse::Movement::Y, axisValue);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::PollSpecialEvents()
|
||||
{
|
||||
while (xcb_generic_event_t* genericEvent = xcb_poll_for_queued_event(s_xcbConnection))
|
||||
{
|
||||
// TODO Is the following correct? If we are showing the cursor, don't poll RAW Input events.
|
||||
switch (genericEvent->response_type & ~0x80)
|
||||
{
|
||||
case XCB_GE_GENERIC:
|
||||
{
|
||||
const xcb_ge_generic_event_t* geGenericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(genericEvent);
|
||||
|
||||
// Only handle raw inputs if we have focus.
|
||||
// Handle Raw Input events first.
|
||||
if ((geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
|
||||
(geGenericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) ||
|
||||
(geGenericEvent->event_type == XCB_INPUT_RAW_MOTION))
|
||||
{
|
||||
HandleRawInputEvents(geGenericEvent);
|
||||
|
||||
free(genericEvent);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void XcbInputDeviceMouse::HandleXcbEvent(xcb_generic_event_t* event)
|
||||
{
|
||||
switch (event->response_type & ~0x80)
|
||||
{
|
||||
// QT5 is using by default XInput which means we do need to check for XCB_GE_GENERIC event to parse all mouse related events.
|
||||
case XCB_GE_GENERIC:
|
||||
{
|
||||
const xcb_ge_generic_event_t* genericEvent = reinterpret_cast<const xcb_ge_generic_event_t*>(event);
|
||||
|
||||
// Handling RAW Inputs here works in GameMode but not in Editor mode because QT is
|
||||
// not handling RAW input events and passing to.
|
||||
if (!m_cursorShown)
|
||||
{
|
||||
// Handle Raw Input events first.
|
||||
if ((genericEvent->event_type == XCB_INPUT_RAW_BUTTON_PRESS) ||
|
||||
(genericEvent->event_type == XCB_INPUT_RAW_BUTTON_RELEASE) || (genericEvent->event_type == XCB_INPUT_RAW_MOTION))
|
||||
{
|
||||
HandleRawInputEvents(genericEvent);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
switch (genericEvent->event_type)
|
||||
{
|
||||
case XCB_INPUT_BUTTON_PRESS:
|
||||
{
|
||||
const xcb_input_button_press_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_button_press_event_t*>(genericEvent);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, true);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_BUTTON_RELEASE:
|
||||
{
|
||||
const xcb_input_button_release_event_t* mouseButtonEvent =
|
||||
reinterpret_cast<const xcb_input_button_release_event_t*>(genericEvent);
|
||||
HandleButtonPressEvents(mouseButtonEvent->detail, false);
|
||||
}
|
||||
break;
|
||||
case XCB_INPUT_MOTION:
|
||||
{
|
||||
HandlePointerMotionEvents(event);
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_IN:
|
||||
{
|
||||
const xcb_focus_in_event_t* focusInEvent = reinterpret_cast<const xcb_focus_in_event_t*>(event);
|
||||
if (m_focusWindow != focusInEvent->event)
|
||||
{
|
||||
m_focusWindow = focusInEvent->event;
|
||||
HandleCursorState(m_focusWindow, m_systemCursorState);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case XCB_FOCUS_OUT:
|
||||
{
|
||||
const xcb_focus_out_event_t* focusOutEvent = reinterpret_cast<const xcb_focus_out_event_t*>(event);
|
||||
HandleCursorState(focusOutEvent->event, SystemCursorState::UnconstrainedAndVisible);
|
||||
|
||||
ProcessRawEventQueues();
|
||||
ResetInputChannelStates();
|
||||
|
||||
m_focusWindow = XCB_NONE;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // namespace AzFramework
|
||||
@ -0,0 +1,193 @@
|
||||
/*
|
||||
* 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 <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
|
||||
#include <AzFramework/XcbConnectionManager.h>
|
||||
#include <AzFramework/XcbEventHandler.h>
|
||||
#include <AzFramework/XcbInterface.h>
|
||||
|
||||
#include <xcb/xfixes.h>
|
||||
#include <xcb/xinput.h>
|
||||
|
||||
// The maximum number of raw input axis this mouse device supports.
|
||||
constexpr uint32_t MAX_XI_RAW_AXIS = 2;
|
||||
|
||||
// The sensitivity of the wheel.
|
||||
constexpr float MAX_XI_WHEEL_SENSITIVITY = 140.0f;
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
class XcbInputDeviceMouse
|
||||
: public InputDeviceMouse::Implementation
|
||||
, public XcbEventHandlerBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(XcbInputDeviceMouse, AZ::SystemAllocator, 0);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Constructor
|
||||
//! \param[in] inputDevice Reference to the input device being implemented
|
||||
XcbInputDeviceMouse(InputDeviceMouse& inputDevice);
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! Destructor
|
||||
virtual ~XcbInputDeviceMouse();
|
||||
|
||||
static XcbInputDeviceMouse::Implementation* Create(InputDeviceMouse& inputDevice);
|
||||
|
||||
protected:
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::IsConnected
|
||||
bool IsConnected() const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorState
|
||||
void SetSystemCursorState(SystemCursorState systemCursorState) override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorState
|
||||
SystemCursorState GetSystemCursorState() const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::SetSystemCursorPositionNormalized
|
||||
void SetSystemCursorPositionNormalized(AZ::Vector2 positionNormalized) override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::GetSystemCursorPositionNormalized
|
||||
AZ::Vector2 GetSystemCursorPositionNormalized() const override;
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////////////////
|
||||
//! \ref AzFramework::InputDeviceMouse::Implementation::TickInputDevice
|
||||
void TickInputDevice() override;
|
||||
|
||||
//! This method is called by the Editor to accommodate some events with the Editor. Never called in Game mode.
|
||||
void PollSpecialEvents() override;
|
||||
|
||||
//! Handle X11 events.
|
||||
void HandleXcbEvent(xcb_generic_event_t* event) override;
|
||||
|
||||
//! Initialize XFixes extension. Used for barriers.
|
||||
static bool InitializeXFixes();
|
||||
|
||||
//! Initialize XInput extension. Used for raw input during confinement and showing/hiding the cursor.
|
||||
static bool InitializeXInput();
|
||||
|
||||
//! Enables/Disables XInput Raw Input events.
|
||||
void SetEnableXInput(bool enable);
|
||||
|
||||
//! Create barriers.
|
||||
void CreateBarriers(xcb_window_t window, bool create);
|
||||
|
||||
//! Helper function.
|
||||
void SystemCursorStateToLogic(SystemCursorState systemCursorState, bool& confined, bool& cursorShown);
|
||||
|
||||
//! Shows/Hides the cursor.
|
||||
void ShowCursor(xcb_window_t window, bool show);
|
||||
|
||||
//! Get the normalized cursor position. The coordinates returned are relative to the specified window.
|
||||
AZ::Vector2 GetSystemCursorPositionNormalizedInternal(xcb_window_t window) const;
|
||||
|
||||
//! Set the normalized cursor position. The normalized position will be relative to the specified window.
|
||||
void SetSystemCursorPositionNormalizedInternal(xcb_window_t window, AZ::Vector2 positionNormalized);
|
||||
|
||||
//! Handle button press/release events.
|
||||
void HandleButtonPressEvents(uint32_t detail, bool pressed);
|
||||
|
||||
//! Handle motion notify events.
|
||||
void HandlePointerMotionEvents(const xcb_generic_event_t* event);
|
||||
|
||||
//! Will set cursor states and confinement modes.
|
||||
void HandleCursorState(xcb_window_t window, SystemCursorState systemCursorState);
|
||||
|
||||
//! Will handle all raw input events.
|
||||
void HandleRawInputEvents(const xcb_ge_generic_event_t* event);
|
||||
|
||||
//! Convert XInput fp1616 to float.
|
||||
inline float fp1616ToFloat(xcb_input_fp1616_t value) const
|
||||
{
|
||||
return static_cast<float>((value >> 16) + (value & 0xffff) / 0xffff);
|
||||
}
|
||||
|
||||
//! Convert XInput fp3232 to float.
|
||||
inline float fp3232ToFloat(xcb_input_fp3232_t value) const
|
||||
{
|
||||
return static_cast<float>(value.integral) + static_cast<float>(value.frac / (float)(1ull << 32));
|
||||
}
|
||||
|
||||
const InputChannelId* InputChannelFromMouseEvent(xcb_button_t button, bool& isWheel, float& direction) const
|
||||
{
|
||||
isWheel = false;
|
||||
direction = 1.0f;
|
||||
switch (button)
|
||||
{
|
||||
case XCB_BUTTON_INDEX_1:
|
||||
return &InputDeviceMouse::Button::Left;
|
||||
case XCB_BUTTON_INDEX_2:
|
||||
return &InputDeviceMouse::Button::Right;
|
||||
case XCB_BUTTON_INDEX_3:
|
||||
return &InputDeviceMouse::Button::Middle;
|
||||
case XCB_BUTTON_INDEX_4:
|
||||
isWheel = true;
|
||||
direction = 1.0f;
|
||||
break;
|
||||
case XCB_BUTTON_INDEX_5:
|
||||
isWheel = true;
|
||||
direction = -1.0f;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Barriers work only with positive values. We clamp here to zero.
|
||||
inline int16_t Clamp(int16_t value) const
|
||||
{
|
||||
return value < 0 ? 0 : value;
|
||||
}
|
||||
|
||||
private:
|
||||
//! The current system cursor state
|
||||
SystemCursorState m_systemCursorState;
|
||||
|
||||
//! The cursor position before it got hidden.
|
||||
AZ::Vector2 m_cursorHiddenPosition;
|
||||
|
||||
AZ::Vector2 m_systemCursorPositionNormalized;
|
||||
uint32_t m_systemCursorPosition[MAX_XI_RAW_AXIS];
|
||||
|
||||
static xcb_connection_t* s_xcbConnection;
|
||||
static xcb_screen_t* s_xcbScreen;
|
||||
|
||||
//! Will be true if the xfixes extension could be initialized.
|
||||
static bool m_xfixesInitialized;
|
||||
|
||||
//! Will be true if the xinput2 extension could be initialized.
|
||||
static bool m_xInputInitialized;
|
||||
|
||||
//! The window that had focus
|
||||
xcb_window_t m_prevConstraintWindow;
|
||||
|
||||
//! The current window that has focus
|
||||
xcb_window_t m_focusWindow;
|
||||
|
||||
//! Will be true if the cursor is shown else false.
|
||||
bool m_cursorShown;
|
||||
|
||||
struct XFixesBarrierProperty
|
||||
{
|
||||
xcb_xfixes_barrier_t id;
|
||||
uint32_t direction;
|
||||
int16_t x0, y0, x1, y1;
|
||||
};
|
||||
|
||||
//! Array that holds barrier information used to confine the cursor.
|
||||
std::vector<XFixesBarrierProperty> m_activeBarriers;
|
||||
};
|
||||
} // namespace AzFramework
|
||||
@ -0,0 +1,27 @@
|
||||
/*
|
||||
* 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
|
||||
*
|
||||
*/
|
||||
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
#include <AzFramework/XcbInputDeviceMouse.h>
|
||||
#endif
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
InputDeviceMouse::Implementation* InputDeviceMouse::Implementation::Create(InputDeviceMouse& inputDevice)
|
||||
{
|
||||
#if PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
return XcbInputDeviceMouse::Create(inputDevice);
|
||||
#elif PAL_TRAIT_LINUX_WINDOW_MANAGER_WAYLAND
|
||||
#error "Linux Window Manager Wayland not supported."
|
||||
return nullptr;
|
||||
#else
|
||||
#error "Linux Window Manager not recognized."
|
||||
return nullptr;
|
||||
#endif // PAL_TRAIT_LINUX_WINDOW_MANAGER_XCB
|
||||
}
|
||||
} // 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
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue