Merge branch 'development' of https://github.com/o3de/o3de into jckand/EditorPrefabTests

Signed-off-by: jckand-amzn <82226555+jckand-amzn@users.noreply.github.com>
This commit is contained in:
jckand-amzn
2022-01-04 08:18:53 -06:00
145 changed files with 14004 additions and 4223 deletions
@@ -19,8 +19,8 @@
<RemoteProcedure Name="AutonomousToAuthorityNoParams" InvokeFrom="Autonomous" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
<RemoteProcedure Name="AuthorityToAutonomous" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" >
<Param Type="float" Name="SomeFloat" />
<RemoteProcedure Name="AuthorityToAutonomous_PlayerNumber" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" >
<Param Type="int" Name="player_number" />
</RemoteProcedure>
<RemoteProcedure Name="AuthorityToAutonomousNoParams" InvokeFrom="Authority" HandleOn="Autonomous" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
@@ -29,10 +29,10 @@
<Param Type="float" Name="SomeFloat" />
</RemoteProcedure>
<RemoteProcedure Name="AuthorityToClientNoParams" InvokeFrom="Authority" HandleOn="Client" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
<RemoteProcedure Name="AuthorityToClientNoParams_PlayFx" InvokeFrom="Authority" HandleOn="Client" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" />
<RemoteProcedure Name="ServerToAuthority" InvokeFrom="Server" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" >
<Param Type="float" Name="SomeFloat" />
<RemoteProcedure Name="ServerToAuthority_DealDamage" InvokeFrom="Server" HandleOn="Authority" IsPublic="false" IsReliable="true" GenerateEventBindings="true" Description="" >
<Param Type="float" Name="damage" />
</RemoteProcedure>
<RemoteProcedure Name="ServerToAuthorityNoParam" InvokeFrom="Server" HandleOn="Authority" IsPublic="false" IsReliable="false" GenerateEventBindings="true" Description="" />
@@ -100,6 +100,49 @@ class TestHelper:
TestHelper.wait_for_condition(lambda : general.is_in_game_mode(), 1.0)
Report.critical_result(msgtuple_success_fail, general.is_in_game_mode())
@staticmethod
def find_line(window, line, print_infos):
"""
Looks for an expected line in a list of tracer log lines
:param window: The log's window name. For example, logs printed via script-canvas use the "Script" window.
:param line: The log message to search for.
:param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints
:return: True if the line is found, otherwise false.
"""
for printInfo in print_infos:
if printInfo.window == window.strip() and printInfo.message.strip() == line:
return True
return False
@staticmethod
def succeed_if_log_line_found(window, line, print_infos, time_out):
"""
Looks for a line in a list of tracer log lines and reports success if found.
:param window: The log's window name. For example, logs printed via script-canvas use the "Script" window.
:param line: The log message we're hoping to find.
:param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints
:param time_out: The total amount of time to wait before giving up looking for the expected line.
:return: No return value, but if the message is found, a successful critical result is reported; otherwise failure.
"""
TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out)
Report.critical_result(("Found expected line: " + line, "Failed to find expected line: " + line), TestHelper.find_line(window, line, print_infos))
@staticmethod
def fail_if_log_line_found(window, line, print_infos, time_out):
"""
Reports a failure if a log line in a list of tracer log lines is found.
:param window: The log's window name. For example, logs printed via script-canvas use the "Script" window.
:param line: The log message we're hoping to not find.
:param print_infos: A list of PrintInfos collected by Tracer to search. Example options: your_tracer.warnings, your_tracer.errors, your_tracer.asserts, or your_tracer.prints
:param time_out: The total amount of time to wait before giving up looking for the unexpected line. If time runs out and we don't see the unexpected line then report a success.
:return: No return value, but if the line is found, a failed critical result is reported; otherwise success.
"""
TestHelper.wait_for_condition(lambda : TestHelper.find_line(window, line, print_infos), time_out)
Report.critical_result(("Unexpected line not found: " + line, "Unexpected line found: " + line), not TestHelper.find_line(window, line, print_infos))
@staticmethod
def multiplayer_enter_game_mode(msgtuple_success_fail: Tuple[str, str], sv_default_player_spawn_asset: str) -> None:
"""
@@ -108,23 +151,6 @@ class TestHelper:
:return: None
"""
# looks for an expected line in a list of tracers lines
# lines: the tracer list of lines to search. options are section_tracer.warnings, section_tracer.errors, section_tracer.asserts, section_tracer.prints
# return: true if the line is found, otherwise false
def find_expected_line(expected_line, lines):
found_lines = [printInfo.message.strip() for printInfo in lines]
return expected_line in found_lines
def wait_for_critical_expected_line(expected_line, lines, time_out):
TestHelper.wait_for_condition(lambda : find_expected_line(expected_line, lines), time_out)
Report.critical_result(("Found expected line: " + expected_line, "Failed to find expected line: " + expected_line), find_expected_line(expected_line, lines))
def wait_for_critical_unexpected_line(unexpected_line, lines, time_out):
TestHelper.wait_for_condition(lambda : find_expected_line(unexpected_line, lines), time_out)
Report.critical_result(("Unexpected line not found: " + unexpected_line, "Unexpected line found: " + unexpected_line), not find_expected_line(unexpected_line, lines))
Report.info("Entering game mode")
if sv_default_player_spawn_asset :
general.set_cvar("sv_defaultPlayerSpawnAsset", sv_default_player_spawn_asset)
@@ -135,20 +161,20 @@ class TestHelper:
multiplayer.PythonEditorFuncs_enter_game_mode()
# make sure the server launcher binary exists
wait_for_critical_unexpected_line("LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5)
TestHelper.fail_if_log_line_found("MultiplayerEditor", "LaunchEditorServer failed! The ServerLauncher binary is missing!", section_tracer.errors, 0.5)
# make sure the server launcher is running
waiter.wait_for(lambda: process_utils.process_exists("AutomatedTesting.ServerLauncher", ignore_extensions=True), timeout=5.0, exc=AssertionError("AutomatedTesting.ServerLauncher has NOT launched!"), interval=1.0)
wait_for_critical_expected_line("MultiplayerEditorConnection: Editor-server activation has found and connected to the editor.", section_tracer.prints, 15.0)
TestHelper.succeed_if_log_line_found("EditorServer", "MultiplayerEditorConnection: Editor-server activation has found and connected to the editor.", section_tracer.prints, 15.0)
wait_for_critical_expected_line("Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0)
TestHelper.succeed_if_log_line_found("MultiplayerEditor", "Editor is sending the editor-server the level data packet.", section_tracer.prints, 5.0)
wait_for_critical_expected_line("Logger: Editor Server completed receiving the editor's level assets, responding to Editor...", section_tracer.prints, 5.0)
TestHelper.succeed_if_log_line_found("EditorServer", "Logger: Editor Server completed receiving the editor's level assets, responding to Editor...", section_tracer.prints, 5.0)
wait_for_critical_expected_line("Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0)
TestHelper.succeed_if_log_line_found("MultiplayerEditorConnection", "Editor-server ready. Editor has successfully connected to the editor-server's network simulation.", section_tracer.prints, 5.0)
wait_for_critical_unexpected_line(f"MultiplayerSystemComponent: SpawnDefaultPlayerPrefab failed. Missing sv_defaultPlayerSpawnAsset at path '{sv_default_player_spawn_asset.lower()}'.", section_tracer.prints, 0.5)
TestHelper.fail_if_log_line_found("EditorServer", f"MultiplayerSystemComponent: SpawnDefaultPlayerPrefab failed. Missing sv_defaultPlayerSpawnAsset at path '{sv_default_player_spawn_asset.lower()}'.", section_tracer.prints, 0.5)
TestHelper.wait_for_condition(lambda : multiplayer.PythonEditorFuncs_is_in_game_mode(), 5.0)
Report.critical_result(msgtuple_success_fail, multiplayer.PythonEditorFuncs_is_in_game_mode())
@@ -32,3 +32,6 @@ class TestAutomation(TestAutomationBase):
from .tests import Multiplayer_AutoComponent_NetworkInput as test_module
self._run_prefab_test(request, workspace, editor, test_module)
def test_Multiplayer_AutoComponent_RPC(self, request, workspace, editor, launcher_platform):
from .tests import Multiplayer_AutoComponent_RPC as test_module
self._run_prefab_test(request, workspace, editor, test_module)
@@ -0,0 +1,83 @@
"""
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
"""
# Test Case Title : Check that the four network RPCs can be sent and received
# fmt: off
class TestSuccessFailTuples():
enter_game_mode = ("Entered game mode", "Failed to enter game mode")
exit_game_mode = ("Exited game mode", "Couldn't exit game mode")
find_network_player = ("Found network player", "Couldn't find network player")
# fmt: on
def Multiplayer_AutoComponent_RPC():
r"""
Summary:
Runs a test to make sure that RPCs can be sent and received via script canvas
Level Description:
- Dynamic
1. Although the level is nearly empty, when the server and editor connect the server will spawn and replicate the player network prefab.
a. The player network prefab has a NetworkTestPlayerComponent.AutoComponent and a script canvas attached which sends and receives various RPCs.
Print logs occur upon sending and receiving the RPCs; we are testing to make sure the expected events and values are received.
- Static
1. NetLevelEntity. This is a networked entity which has a script attached. Used for cross-entity communication. The net-player prefab will send this level entity Server->Authority RPCs
Expected Outcome:
We should see editor logs stating that RPCs have been sent and received.
However, if the script receives unexpected values for the Process event we will see print logs for bad data as well.
:return:
"""
import azlmbr.legacy.general as general
from editor_python_test_tools.utils import Report
from editor_python_test_tools.utils import Tracer
from editor_python_test_tools.utils import TestHelper as helper
from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
level_name = "AutoComponent_RPC"
player_prefab_name = "Player"
player_prefab_path = f"levels/multiplayer/{level_name}/{player_prefab_name}.network.spawnable"
helper.init_idle()
# 1) Open Level
helper.open_level("Multiplayer", level_name)
with Tracer() as section_tracer:
# 2) Enter game mode
helper.multiplayer_enter_game_mode(TestSuccessFailTuples.enter_game_mode, player_prefab_path.lower())
# 3) Make sure the network player was spawned
player_id = general.find_game_entity(player_prefab_name)
Report.critical_result(TestSuccessFailTuples.find_network_player, player_id.IsValid())
# 4) Check the editor logs for expected and unexpected log output
PLAYERID_RPC_WAIT_TIME_SECONDS = 1.0 # The player id is sent from the server as soon as the player script is spawned. 1 second should be more than enough time to send/receive that RPC.
helper.succeed_if_log_line_found('EditorServer', 'Script: AutoComponent_RPC: Sending client PlayerNumber 1', section_tracer.prints, PLAYERID_RPC_WAIT_TIME_SECONDS)
helper.succeed_if_log_line_found('Script', "AutoComponent_RPC: I'm Player #1", section_tracer.prints, PLAYERID_RPC_WAIT_TIME_SECONDS)
# Uncomment once editor game-play mode supports level entities with net-binding
#PLAYFX_RPC_WAIT_TIME_SECONDS = 1.1 # The server will send an RPC to play an fx on the client every second.
#helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity Activated on entity: NetLevelEntity", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
#helper.succeed_if_log_line_found('EditorServer', "Script: AutoComponent_RPC_NetLevelEntity: Authority sending RPC to play some fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
#helper.succeed_if_log_line_found('Script', "AutoComponent_RPC_NetLevelEntity: I'm a client playing some superficial fx.", section_tracer.prints, PLAYFX_RPC_WAIT_TIME_SECONDS)
# Exit game mode
helper.exit_game_mode(TestSuccessFailTuples.exit_game_mode)
if __name__ == "__main__":
from editor_python_test_tools.utils import Report
Report.start_test(Multiplayer_AutoComponent_RPC)
@@ -0,0 +1,747 @@
{
"ContainerEntity": {
"Id": "Entity_[1146574390643]",
"Name": "Level",
"Components": {
"Component_[10641544592923449938]": {
"$type": "EditorInspectorComponent",
"Id": 10641544592923449938
},
"Component_[12039882709170782873]": {
"$type": "EditorOnlyEntityComponent",
"Id": 12039882709170782873
},
"Component_[12265484671603697631]": {
"$type": "EditorPendingCompositionComponent",
"Id": 12265484671603697631
},
"Component_[14126657869720434043]": {
"$type": "EditorEntitySortComponent",
"Id": 14126657869720434043,
"Child Entity Order": [
"Entity_[1176639161715]",
"Entity_[12685882829720]",
"Entity_[31145534614197]"
]
},
"Component_[15230859088967841193]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 15230859088967841193,
"Parent Entity": ""
},
"Component_[16239496886950819870]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 16239496886950819870
},
"Component_[5688118765544765547]": {
"$type": "EditorEntityIconComponent",
"Id": 5688118765544765547
},
"Component_[6545738857812235305]": {
"$type": "SelectionComponent",
"Id": 6545738857812235305
},
"Component_[7247035804068349658]": {
"$type": "EditorPrefabComponent",
"Id": 7247035804068349658
},
"Component_[9307224322037797205]": {
"$type": "EditorLockComponent",
"Id": 9307224322037797205
},
"Component_[9562516168917670048]": {
"$type": "EditorVisibilityComponent",
"Id": 9562516168917670048
}
}
},
"Entities": {
"Entity_[1155164325235]": {
"Id": "Entity_[1155164325235]",
"Name": "Sun",
"Components": {
"Component_[10440557478882592717]": {
"$type": "SelectionComponent",
"Id": 10440557478882592717
},
"Component_[13620450453324765907]": {
"$type": "EditorLockComponent",
"Id": 13620450453324765907
},
"Component_[2134313378593666258]": {
"$type": "EditorInspectorComponent",
"Id": 2134313378593666258
},
"Component_[234010807770404186]": {
"$type": "EditorVisibilityComponent",
"Id": 234010807770404186
},
"Component_[2970359110423865725]": {
"$type": "EditorEntityIconComponent",
"Id": 2970359110423865725
},
"Component_[3722854130373041803]": {
"$type": "EditorOnlyEntityComponent",
"Id": 3722854130373041803
},
"Component_[5992533738676323195]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 5992533738676323195
},
"Component_[7378860763541895402]": {
"$type": "AZ::Render::EditorDirectionalLightComponent",
"Id": 7378860763541895402,
"Controller": {
"Configuration": {
"Intensity": 1.0,
"CameraEntityId": "",
"ShadowFilterMethod": 1
}
}
},
"Component_[7892834440890947578]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 7892834440890947578,
"Parent Entity": "Entity_[1176639161715]",
"Transform Data": {
"Translate": [
0.0,
0.0,
13.487043380737305
],
"Rotate": [
-76.13099670410156,
-0.847000002861023,
-15.8100004196167
]
}
},
"Component_[8599729549570828259]": {
"$type": "EditorEntitySortComponent",
"Id": 8599729549570828259
},
"Component_[952797371922080273]": {
"$type": "EditorPendingCompositionComponent",
"Id": 952797371922080273
}
}
},
"Entity_[1159459292531]": {
"Id": "Entity_[1159459292531]",
"Name": "Ground",
"Components": {
"Component_[11701138785793981042]": {
"$type": "SelectionComponent",
"Id": 11701138785793981042
},
"Component_[12260880513256986252]": {
"$type": "EditorEntityIconComponent",
"Id": 12260880513256986252
},
"Component_[13711420870643673468]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 13711420870643673468
},
"Component_[138002849734991713]": {
"$type": "EditorOnlyEntityComponent",
"Id": 138002849734991713
},
"Component_[16578565737331764849]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 16578565737331764849,
"Parent Entity": "Entity_[1176639161715]"
},
"Component_[16919232076966545697]": {
"$type": "EditorInspectorComponent",
"Id": 16919232076966545697
},
"Component_[5182430712893438093]": {
"$type": "EditorMaterialComponent",
"Id": 5182430712893438093
},
"Component_[5675108321710651991]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 5675108321710651991,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{0CD745C0-6AA8-569A-A68A-73A3270986C4}",
"subId": 277889906
},
"assetHint": "objects/groudplane/groundplane_512x512m.azmodel"
}
}
}
},
"Component_[5681893399601237518]": {
"$type": "EditorEntitySortComponent",
"Id": 5681893399601237518
},
"Component_[592692962543397545]": {
"$type": "EditorPendingCompositionComponent",
"Id": 592692962543397545
},
"Component_[7090012899106946164]": {
"$type": "EditorLockComponent",
"Id": 7090012899106946164
},
"Component_[9410832619875640998]": {
"$type": "EditorVisibilityComponent",
"Id": 9410832619875640998
}
}
},
"Entity_[1163754259827]": {
"Id": "Entity_[1163754259827]",
"Name": "Camera",
"Components": {
"Component_[11895140916889160460]": {
"$type": "EditorEntityIconComponent",
"Id": 11895140916889160460
},
"Component_[16880285896855930892]": {
"$type": "{CA11DA46-29FF-4083-B5F6-E02C3A8C3A3D} EditorCameraComponent",
"Id": 16880285896855930892,
"Controller": {
"Configuration": {
"Field of View": 55.0,
"EditorEntityId": 11050384689878106598
}
}
},
"Component_[17187464423780271193]": {
"$type": "EditorLockComponent",
"Id": 17187464423780271193
},
"Component_[17495696818315413311]": {
"$type": "EditorEntitySortComponent",
"Id": 17495696818315413311
},
"Component_[18086214374043522055]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 18086214374043522055,
"Parent Entity": "Entity_[1176639161715]",
"Transform Data": {
"Translate": [
-2.3000001907348633,
-3.9368600845336914,
1.0
],
"Rotate": [
-2.050307512283325,
1.9552897214889526,
-43.623355865478516
]
}
},
"Component_[18387556550380114975]": {
"$type": "SelectionComponent",
"Id": 18387556550380114975
},
"Component_[2654521436129313160]": {
"$type": "EditorVisibilityComponent",
"Id": 2654521436129313160
},
"Component_[5265045084611556958]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 5265045084611556958
},
"Component_[7169798125182238623]": {
"$type": "EditorPendingCompositionComponent",
"Id": 7169798125182238623
},
"Component_[7255796294953281766]": {
"$type": "GenericComponentWrapper",
"Id": 7255796294953281766,
"m_template": {
"$type": "FlyCameraInputComponent"
}
},
"Component_[8866210352157164042]": {
"$type": "EditorInspectorComponent",
"Id": 8866210352157164042
},
"Component_[9129253381063760879]": {
"$type": "EditorOnlyEntityComponent",
"Id": 9129253381063760879
}
}
},
"Entity_[1168049227123]": {
"Id": "Entity_[1168049227123]",
"Name": "Grid",
"Components": {
"Component_[11443347433215807130]": {
"$type": "EditorEntityIconComponent",
"Id": 11443347433215807130
},
"Component_[11779275529534764488]": {
"$type": "SelectionComponent",
"Id": 11779275529534764488
},
"Component_[14249419413039427459]": {
"$type": "EditorInspectorComponent",
"Id": 14249419413039427459
},
"Component_[15448581635946161318]": {
"$type": "AZ::Render::EditorGridComponent",
"Id": 15448581635946161318,
"Controller": {
"Configuration": {
"primarySpacing": 4.0,
"primaryColor": [
0.501960813999176,
0.501960813999176,
0.501960813999176
],
"secondarySpacing": 0.5,
"secondaryColor": [
0.250980406999588,
0.250980406999588,
0.250980406999588
]
}
}
},
"Component_[1843303322527297409]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 1843303322527297409
},
"Component_[380249072065273654]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 380249072065273654,
"Parent Entity": "Entity_[1176639161715]"
},
"Component_[7476660583684339787]": {
"$type": "EditorPendingCompositionComponent",
"Id": 7476660583684339787
},
"Component_[7557626501215118375]": {
"$type": "EditorEntitySortComponent",
"Id": 7557626501215118375
},
"Component_[7984048488947365511]": {
"$type": "EditorVisibilityComponent",
"Id": 7984048488947365511
},
"Component_[8118181039276487398]": {
"$type": "EditorOnlyEntityComponent",
"Id": 8118181039276487398
},
"Component_[9189909764215270515]": {
"$type": "EditorLockComponent",
"Id": 9189909764215270515
}
}
},
"Entity_[1172344194419]": {
"Id": "Entity_[1172344194419]",
"Name": "Shader Ball",
"Components": {
"Component_[10789351944715265527]": {
"$type": "EditorOnlyEntityComponent",
"Id": 10789351944715265527
},
"Component_[12037033284781049225]": {
"$type": "EditorEntitySortComponent",
"Id": 12037033284781049225
},
"Component_[13759153306105970079]": {
"$type": "EditorPendingCompositionComponent",
"Id": 13759153306105970079
},
"Component_[14135560884830586279]": {
"$type": "EditorInspectorComponent",
"Id": 14135560884830586279
},
"Component_[16247165675903986673]": {
"$type": "EditorVisibilityComponent",
"Id": 16247165675903986673
},
"Component_[18082433625958885247]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 18082433625958885247
},
"Component_[6472623349872972660]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 6472623349872972660,
"Parent Entity": "Entity_[1176639161715]",
"Transform Data": {
"Rotate": [
0.0,
0.10000000149011612,
180.0
]
}
},
"Component_[6495255223970673916]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 6495255223970673916,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{FD340C30-755C-5911-92A3-19A3F7A77931}",
"subId": 281415304
},
"assetHint": "objects/shaderball/shaderball_default_1m.azmodel"
}
}
}
},
"Component_[8056625192494070973]": {
"$type": "SelectionComponent",
"Id": 8056625192494070973
},
"Component_[8550141614185782969]": {
"$type": "EditorEntityIconComponent",
"Id": 8550141614185782969
},
"Component_[9439770997198325425]": {
"$type": "EditorLockComponent",
"Id": 9439770997198325425
}
}
},
"Entity_[1176639161715]": {
"Id": "Entity_[1176639161715]",
"Name": "Atom Default Environment",
"Components": {
"Component_[10757302973393310045]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 10757302973393310045,
"Parent Entity": "Entity_[1146574390643]"
},
"Component_[14505817420424255464]": {
"$type": "EditorInspectorComponent",
"Id": 14505817420424255464,
"ComponentOrderEntryArray": [
{
"ComponentId": 10757302973393310045
}
]
},
"Component_[14988041764659020032]": {
"$type": "EditorLockComponent",
"Id": 14988041764659020032
},
"Component_[15808690248755038124]": {
"$type": "SelectionComponent",
"Id": 15808690248755038124
},
"Component_[15900837685796817138]": {
"$type": "EditorVisibilityComponent",
"Id": 15900837685796817138
},
"Component_[3298767348226484884]": {
"$type": "EditorOnlyEntityComponent",
"Id": 3298767348226484884
},
"Component_[4076975109609220594]": {
"$type": "EditorPendingCompositionComponent",
"Id": 4076975109609220594
},
"Component_[5679760548946028854]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 5679760548946028854
},
"Component_[5855590796136709437]": {
"$type": "EditorEntitySortComponent",
"Id": 5855590796136709437,
"Child Entity Order": [
"Entity_[1155164325235]",
"Entity_[1180934129011]",
"Entity_[1172344194419]",
"Entity_[1168049227123]",
"Entity_[1163754259827]",
"Entity_[1159459292531]"
]
},
"Component_[9277695270015777859]": {
"$type": "EditorEntityIconComponent",
"Id": 9277695270015777859
}
}
},
"Entity_[1180934129011]": {
"Id": "Entity_[1180934129011]",
"Name": "Global Sky",
"Components": {
"Component_[11231930600558681245]": {
"$type": "AZ::Render::EditorHDRiSkyboxComponent",
"Id": 11231930600558681245,
"Controller": {
"Configuration": {
"CubemapAsset": {
"assetId": {
"guid": "{215E47FD-D181-5832-B1AB-91673ABF6399}",
"subId": 1000
},
"assetHint": "lightingpresets/highcontrast/goegap_4k_skyboxcm.exr.streamingimage"
}
}
}
},
"Component_[11980494120202836095]": {
"$type": "SelectionComponent",
"Id": 11980494120202836095
},
"Component_[1428633914413949476]": {
"$type": "EditorLockComponent",
"Id": 1428633914413949476
},
"Component_[14936200426671614999]": {
"$type": "AZ::Render::EditorImageBasedLightComponent",
"Id": 14936200426671614999,
"Controller": {
"Configuration": {
"diffuseImageAsset": {
"assetId": {
"guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}",
"subId": 3000
},
"assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_ibldiffuse.exr.streamingimage"
},
"specularImageAsset": {
"assetId": {
"guid": "{3FD09945-D0F2-55C8-B9AF-B2FD421FE3BE}",
"subId": 2000
},
"assetHint": "lightingpresets/highcontrast/goegap_4k_iblglobalcm_iblspecular.exr.streamingimage"
}
}
}
},
"Component_[14994774102579326069]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 14994774102579326069
},
"Component_[15417479889044493340]": {
"$type": "EditorPendingCompositionComponent",
"Id": 15417479889044493340
},
"Component_[15826613364991382688]": {
"$type": "EditorEntitySortComponent",
"Id": 15826613364991382688
},
"Component_[1665003113283562343]": {
"$type": "EditorOnlyEntityComponent",
"Id": 1665003113283562343
},
"Component_[3704934735944502280]": {
"$type": "EditorEntityIconComponent",
"Id": 3704934735944502280
},
"Component_[5698542331457326479]": {
"$type": "EditorVisibilityComponent",
"Id": 5698542331457326479
},
"Component_[6644513399057217122]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 6644513399057217122,
"Parent Entity": "Entity_[1176639161715]"
},
"Component_[931091830724002070]": {
"$type": "EditorInspectorComponent",
"Id": 931091830724002070
}
}
},
"Entity_[12685882829720]": {
"Id": "Entity_[12685882829720]",
"Name": "GlobalGameData",
"Components": {
"Component_[11240656689650225106]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 11240656689650225106
},
"Component_[13863201640354873385]": {
"$type": "EditorPendingCompositionComponent",
"Id": 13863201640354873385
},
"Component_[14671754037021562789]": {
"$type": "EditorInspectorComponent",
"Id": 14671754037021562789
},
"Component_[14750978061505735417]": {
"$type": "EditorScriptCanvasComponent",
"Id": 14750978061505735417,
"m_name": "GlobalGameData",
"m_assetHolder": {
"m_asset": {
"assetId": {
"guid": "{B16589A0-EA01-56BC-8141-91A3967FB95F}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/globalgamedata.scriptcanvas"
}
},
"runtimeDataIsValid": true,
"runtimeDataOverrides": {
"source": {
"assetId": {
"guid": "{B16589A0-EA01-56BC-8141-91A3967FB95F}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/globalgamedata.scriptcanvas"
}
}
},
"Component_[16436925042043744033]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 16436925042043744033,
"Parent Entity": "Entity_[1146574390643]",
"Transform Data": {
"Translate": [
0.0,
0.10000038146972656,
4.005393981933594
]
}
},
"Component_[16974524495698916088]": {
"$type": "EditorOnlyEntityComponent",
"Id": 16974524495698916088
},
"Component_[2753700837834389204]": {
"$type": "EditorLockComponent",
"Id": 2753700837834389204
},
"Component_[3766473509503096065]": {
"$type": "SelectionComponent",
"Id": 3766473509503096065
},
"Component_[4025955184206569130]": {
"$type": "EditorEntitySortComponent",
"Id": 4025955184206569130
},
"Component_[7909743395732791573]": {
"$type": "EditorVisibilityComponent",
"Id": 7909743395732791573
},
"Component_[9550161640684119498]": {
"$type": "EditorEntityIconComponent",
"Id": 9550161640684119498
}
}
},
"Entity_[31145534614197]": {
"Id": "Entity_[31145534614197]",
"Name": "NetLevelEntity",
"Components": {
"Component_[12132849363414901338]": {
"$type": "EditorEntityIconComponent",
"Id": 12132849363414901338
},
"Component_[12302672911455629152]": {
"$type": "SelectionComponent",
"Id": 12302672911455629152
},
"Component_[14169903623243423134]": {
"$type": "EditorVisibilityComponent",
"Id": 14169903623243423134
},
"Component_[14607413934411389854]": {
"$type": "EditorInspectorComponent",
"Id": 14607413934411389854
},
"Component_[15396284312416541768]": {
"$type": "GenericComponentWrapper",
"Id": 15396284312416541768,
"m_template": {
"$type": "Multiplayer::LocalPredictionPlayerInputComponent"
}
},
"Component_[15494977028055234270]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 15494977028055234270
},
"Component_[16325088972532345964]": {
"$type": "EditorLockComponent",
"Id": 16325088972532345964
},
"Component_[1986030426392465743]": {
"$type": "EditorEntitySortComponent",
"Id": 1986030426392465743
},
"Component_[4591476848838823508]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 4591476848838823508,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{80FA8BAF-4A5B-5937-9679-2E592E841A3A}",
"subId": 275306041
},
"assetHint": "assets/physics/collider_pxmeshautoassigned/spherebot/r0-b_body.azmodel"
}
}
}
},
"Component_[7256163899440301540]": {
"$type": "EditorScriptCanvasComponent",
"Id": 7256163899440301540,
"m_name": "AutoComponent_RPC_NetLevelEntity",
"m_assetHolder": {
"m_asset": {
"assetId": {
"guid": "{1D517006-AC01-5ECA-AE66-0E007871F0CD}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc_netlevelentity.scriptcanvas"
}
},
"runtimeDataIsValid": true,
"runtimeDataOverrides": {
"source": {
"assetId": {
"guid": "{1D517006-AC01-5ECA-AE66-0E007871F0CD}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc_netlevelentity.scriptcanvas"
}
}
},
"Component_[731336627222243355]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 731336627222243355,
"Parent Entity": "Entity_[1146574390643]",
"Transform Data": {
"Translate": [
2.561863899230957,
-1.038161277770996,
0.1487259864807129
]
}
},
"Component_[8012379125499217348]": {
"$type": "EditorPendingCompositionComponent",
"Id": 8012379125499217348
},
"Component_[8122568562140740597]": {
"$type": "GenericComponentWrapper",
"Id": 8122568562140740597,
"m_template": {
"$type": "Multiplayer::NetworkTransformComponent"
}
},
"Component_[8805228647591404845]": {
"$type": "GenericComponentWrapper",
"Id": 8805228647591404845,
"m_template": {
"$type": "NetBindComponent"
}
},
"Component_[9816897251206708579]": {
"$type": "GenericComponentWrapper",
"Id": 9816897251206708579,
"m_template": {
"$type": "AutomatedTesting::NetworkTestPlayerComponent"
}
},
"Component_[9880860858035405475]": {
"$type": "EditorOnlyEntityComponent",
"Id": 9880860858035405475
}
}
}
}
}
@@ -0,0 +1,662 @@
{
"Type": "JsonSerialization",
"Version": 1,
"ClassName": "ScriptCanvasData",
"ClassData": {
"m_scriptCanvas": {
"Id": {
"id": 17732469402520
},
"Name": "Untitled-1",
"Components": {
"Component_[16492301523567686923]": {
"$type": "{4D755CA9-AB92-462C-B24F-0B3376F19967} Graph",
"Id": 16492301523567686923,
"m_graphData": {
"m_nodes": [
{
"Id": {
"id": 17741059337112
},
"Name": "SC-Node(OperatorAdd)",
"Components": {
"Component_[11612963594766700030]": {
"$type": "OperatorAdd",
"Id": 11612963594766700030,
"Slots": [
{
"id": {
"m_id": "{B7529112-C29F-45F0-811C-DB8EE18EB8B8}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "In",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 1
}
},
{
"id": {
"m_id": "{49618851-F6B2-4B90-BFDF-ADBAAA84BBA4}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Out",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
},
{
"id": {
"m_id": "{4612C904-82DF-4B48-8485-4C878AF9A4D1}"
},
"DynamicTypeOverride": 3,
"contracts": [
{
"$type": "SlotTypeContract"
},
{
"$type": "MathOperatorContract",
"NativeTypes": [
{
"m_type": 3
},
{
"m_type": 6
},
{
"m_type": 8
},
{
"m_type": 9
},
{
"m_type": 10
},
{
"m_type": 11
},
{
"m_type": 12
},
{
"m_type": 14
},
{
"m_type": 15
}
]
}
],
"slotName": "Number",
"toolTip": "An operand to use in performing the specified Operation",
"DisplayDataType": {
"m_type": 3
},
"DisplayGroup": {
"Value": 1114760223
},
"Descriptor": {
"ConnectionType": 1,
"SlotType": 2
},
"DynamicGroup": {
"Value": 1114760223
},
"DataType": 1,
"IsReference": true,
"VariableReference": {
"m_id": "{8451E795-6A0E-44CE-81FD-AEF0EE5B0400}"
}
},
{
"id": {
"m_id": "{610B3BFB-9043-47A0-9694-5F14A1947E36}"
},
"DynamicTypeOverride": 3,
"contracts": [
{
"$type": "SlotTypeContract"
},
{
"$type": "MathOperatorContract",
"NativeTypes": [
{
"m_type": 3
},
{
"m_type": 6
},
{
"m_type": 8
},
{
"m_type": 9
},
{
"m_type": 10
},
{
"m_type": 11
},
{
"m_type": 12
},
{
"m_type": 14
},
{
"m_type": 15
}
]
}
],
"slotName": "Number",
"toolTip": "An operand to use in performing the specified Operation",
"DisplayDataType": {
"m_type": 3
},
"DisplayGroup": {
"Value": 1114760223
},
"Descriptor": {
"ConnectionType": 1,
"SlotType": 2
},
"DynamicGroup": {
"Value": 1114760223
},
"DataType": 1
},
{
"id": {
"m_id": "{36FFF1AB-C208-47CB-8271-436C85E0AE42}"
},
"DynamicTypeOverride": 3,
"contracts": [
{
"$type": "SlotTypeContract"
},
{
"$type": "MathOperatorContract",
"NativeTypes": [
{
"m_type": 3
},
{
"m_type": 6
},
{
"m_type": 8
},
{
"m_type": 9
},
{
"m_type": 10
},
{
"m_type": 11
},
{
"m_type": 12
},
{
"m_type": 14
},
{
"m_type": 15
}
]
}
],
"slotName": "Result",
"toolTip": "The result of the specified operation",
"DisplayDataType": {
"m_type": 3
},
"DisplayGroup": {
"Value": 1114760223
},
"Descriptor": {
"ConnectionType": 2,
"SlotType": 2
},
"DynamicGroup": {
"Value": 1114760223
},
"DataType": 1,
"IsReference": true,
"VariableReference": {
"m_id": "{8451E795-6A0E-44CE-81FD-AEF0EE5B0400}"
}
}
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 3
},
"isNullPointer": false,
"$type": "double",
"value": 0.0,
"label": "Number"
},
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 3
},
"isNullPointer": false,
"$type": "double",
"value": 1.0,
"label": "Number"
}
]
}
}
},
{
"Id": {
"id": 17736764369816
},
"Name": "ReceiveScriptEvent",
"Components": {
"Component_[16408183651077237195]": {
"$type": "ReceiveScriptEvent",
"Id": 16408183651077237195,
"Slots": [
{
"id": {
"m_id": "{8DC10581-B8DF-473C-9C75-996111DBF560}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Connect",
"toolTip": "Connect this event handler to the specified entity.",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 1
}
},
{
"id": {
"m_id": "{E60D1951-E56D-41F8-84C5-AD0BA803DD51}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Disconnect",
"toolTip": "Disconnect this event handler.",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 1
}
},
{
"id": {
"m_id": "{1BCE6CAC-B1C0-43FA-B4F5-E9A34D5064E5}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "OnConnected",
"toolTip": "Signaled when a connection has taken place.",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
},
{
"id": {
"m_id": "{FEB42E9A-D562-4BBD-90AB-32255124BFE8}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "OnDisconnected",
"toolTip": "Signaled when this event handler is disconnected.",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
},
{
"id": {
"m_id": "{C9EF936B-8C74-42B4-8793-67FC7FD3BCBC}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "OnFailure",
"toolTip": "Signaled when it is not possible to connect this handler.",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
}
},
{
"id": {
"m_id": "{76985C7A-761A-4CEF-9F55-6DD2B136317A}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "ExecutionSlot:NewPlayerScriptActive",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
},
"IsLatent": true
},
{
"id": {
"m_id": "{5CD8E1E9-6192-4B7D-9C2C-6C18BE99CF44}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "Number",
"Descriptor": {
"ConnectionType": 1,
"SlotType": 2
},
"DataType": 1,
"IsReference": true,
"VariableReference": {
"m_id": "{8451E795-6A0E-44CE-81FD-AEF0EE5B0400}"
}
},
{
"id": {
"m_id": "{9FDB71AA-0F19-406D-82CA-508A2CA10F95}"
},
"contracts": [
{
"$type": "SlotTypeContract"
}
],
"slotName": "ExecutionSlot:GetNumberOfActivePlayers",
"Descriptor": {
"ConnectionType": 2,
"SlotType": 1
},
"IsLatent": true
}
],
"Datums": [
{
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 3
},
"isNullPointer": false,
"$type": "double",
"value": 0.0,
"label": "Number"
}
],
"m_version": 1,
"m_eventMap": [
{
"Key": {
"Value": 242067946
},
"Value": {
"m_scriptEventAssetId": {
"guid": "{FE1B1992-8220-5DD3-A60A-AEC85EB91C54}"
},
"m_eventName": "GetNumberOfActivePlayers",
"m_eventSlotId": {
"m_id": "{9FDB71AA-0F19-406D-82CA-508A2CA10F95}"
},
"m_resultSlotId": {
"m_id": "{5CD8E1E9-6192-4B7D-9C2C-6C18BE99CF44}"
}
}
},
{
"Key": {
"Value": 2930121176
},
"Value": {
"m_scriptEventAssetId": {
"guid": "{FE1B1992-8220-5DD3-A60A-AEC85EB91C54}"
},
"m_eventName": "NewPlayerScriptActive",
"m_eventSlotId": {
"m_id": "{76985C7A-761A-4CEF-9F55-6DD2B136317A}"
}
}
}
],
"m_eventSlotMapping": {
"{155BF981-AD70-4D29-81A6-1517FAE59FB1}": {
"m_id": "{5CD8E1E9-6192-4B7D-9C2C-6C18BE99CF44}"
},
"{65D394D3-F90D-4F10-94BF-F5E1581CF2CF}": {
"m_id": "{76985C7A-761A-4CEF-9F55-6DD2B136317A}"
},
"{67784749-9B41-429C-9C97-3D296182EB67}": {
"m_id": "{9FDB71AA-0F19-406D-82CA-508A2CA10F95}"
}
},
"m_scriptEventAssetId": {
"guid": "{FE1B1992-8220-5DD3-A60A-AEC85EB91C54}"
},
"m_asset": {
"assetId": {
"guid": "{FE1B1992-8220-5DD3-A60A-AEC85EB91C54}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/globalgamedata.scriptevents"
}
}
}
}
],
"m_connections": [
{
"Id": {
"id": 17745354304408
},
"Name": "srcEndpoint=(Receive Script Event: ExecutionSlot:NewPlayerScriptActive), destEndpoint=(Add (+): In)",
"Components": {
"Component_[8782209668839578826]": {
"$type": "{64CA5016-E803-4AC4-9A36-BDA2C890C6EB} Connection",
"Id": 8782209668839578826,
"sourceEndpoint": {
"nodeId": {
"id": 17736764369816
},
"slotId": {
"m_id": "{76985C7A-761A-4CEF-9F55-6DD2B136317A}"
}
},
"targetEndpoint": {
"nodeId": {
"id": 17741059337112
},
"slotId": {
"m_id": "{B7529112-C29F-45F0-811C-DB8EE18EB8B8}"
}
}
}
}
}
],
"m_scriptEventAssets": [
[
{
"id": 17736764369816
},
{}
]
]
},
"m_assetType": "{3E2AC8CD-713F-453E-967F-29517F331784}",
"versionData": {
"_grammarVersion": 1,
"_runtimeVersion": 1,
"_fileVersion": 1
},
"m_variableCounter": 1,
"GraphCanvasData": [
{
"Key": {
"id": 17732469402520
},
"Value": {
"ComponentData": {
"{5F84B500-8C45-40D1-8EFC-A5306B241444}": {
"$type": "SceneComponentSaveData"
}
}
}
},
{
"Key": {
"id": 17736764369816
},
"Value": {
"ComponentData": {
"{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": {
"$type": "NodeSaveData"
},
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
-360.0,
-60.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
"$type": "StylingComponentSaveData"
},
"{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": {
"$type": "PersistentIdComponentSaveData",
"PersistentId": "{C419A1CF-CBA8-416B-BF6C-4B574C3E59E3}"
},
"{D8BBE799-7E4D-495A-B69A-1E3940670891}": {
"$type": "ScriptEventReceiverHandlerNodeDescriptorSaveData",
"EventNames": [
[
{
"Value": 242067946
},
"GetNumberOfActivePlayers"
],
[
{
"Value": 2930121176
},
"NewPlayerScriptActive"
]
]
}
}
}
},
{
"Key": {
"id": 17741059337112
},
"Value": {
"ComponentData": {
"{24CB38BB-1705-4EC5-8F63-B574571B4DCD}": {
"$type": "NodeSaveData"
},
"{328FF15C-C302-458F-A43D-E1794DE0904E}": {
"$type": "GeneralNodeTitleComponentSaveData",
"PaletteOverride": "MathNodeTitlePalette"
},
"{7CC444B1-F9B3-41B5-841B-0C4F2179F111}": {
"$type": "GeometrySaveData",
"Position": [
120.0,
100.0
]
},
"{B0B99C8A-03AF-4CF6-A926-F65C874C3D97}": {
"$type": "StylingComponentSaveData"
},
"{B1F49A35-8408-40DA-B79E-F1E3B64322CE}": {
"$type": "PersistentIdComponentSaveData",
"PersistentId": "{0D0751AD-8164-4196-9C09-8CDB9AAA296F}"
}
}
}
}
],
"StatisticsHelper": {
"InstanceCounter": [
{
"Key": 1244476766431948410,
"Value": 1
},
{
"Key": 1678857390775488101,
"Value": 1
},
{
"Key": 1678857392390856307,
"Value": 1
}
]
}
},
"Component_[16498171485036643402]": {
"$type": "EditorGraphVariableManagerComponent",
"Id": 16498171485036643402,
"m_variableData": {
"m_nameVariableMap": [
{
"Key": {
"m_id": "{8451E795-6A0E-44CE-81FD-AEF0EE5B0400}"
},
"Value": {
"Datum": {
"isOverloadedStorage": false,
"scriptCanvasType": {
"m_type": 3
},
"isNullPointer": false,
"$type": "double",
"value": 0.0
},
"VariableId": {
"m_id": "{8451E795-6A0E-44CE-81FD-AEF0EE5B0400}"
},
"VariableName": "ActivePlayerCount"
}
}
]
}
}
}
}
}
}
@@ -0,0 +1,106 @@
<ObjectStream version="3">
<Class name="ScriptEventsAsset" version="1" type="{CB4D603E-8CB0-4D80-8165-4244F28AF187}">
<Class name="ScriptEvent" field="m_definition" version="1" type="{10A08CD3-32C9-4E18-8039-4B8A8157918E}">
<Class name="unsigned int" field="m_version" value="1" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="VersionedProperty" field="m_name" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{6C6C69BC-EACC-450D-86FB-4CFCEED68F59}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Name" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="GlobalGameData" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_category" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{2DF5A7AC-201B-4538-9903-8573C90CA595}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Category" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="Script Events" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_tooltip" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{E2175455-06AB-402A-B4B5-C33D7CB6EAF9}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Tooltip" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_addressType" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{1B2DA4A8-A367-4A58-A8CA-8BB6F1F56E66}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Address Type" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZ::Uuid" field="m_data" value="{C0F1AFAD-5CB3-450E-B0F5-ADB5D46B0E22}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
</Class>
</Class>
<Class name="AZStd::vector" field="m_methods" type="{D9866B79-D11A-58E6-B974-0B45783F53A4}">
<Class name="Method" field="element" type="{E034EA83-C798-413D-ACE8-4923C51CF4F7}">
<Class name="VersionedProperty" field="m_name" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{65D394D3-F90D-4F10-94BF-F5E1581CF2CF}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Name" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="NewPlayerScriptActive" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_tooltip" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{372785C7-51F9-4F51-989B-2C851F4D000E}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Tooltip" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_returnType" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{359740D1-3764-4267-8BEE-1D734C7D81FB}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Return Type" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZ::Uuid" field="m_data" value="{C0F1AFAD-5CB3-450E-B0F5-ADB5D46B0E22}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
</Class>
</Class>
<Class name="AZStd::vector" field="m_parameters" type="{6ED13EA7-791B-57A8-A4F1-560B5F35B472}"/>
</Class>
<Class name="Method" field="element" type="{E034EA83-C798-413D-ACE8-4923C51CF4F7}">
<Class name="VersionedProperty" field="m_name" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{67784749-9B41-429C-9C97-3D296182EB67}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Name" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="GetNumberOfActivePlayers" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_tooltip" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{92E33EC2-4B10-42A7-A568-2E1392E85D9F}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Tooltip" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZStd::string" field="m_data" value="" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
</Class>
</Class>
<Class name="VersionedProperty" field="m_returnType" version="4" type="{828CA9C0-32F1-40B3-8018-EE7C3C38192A}">
<Class name="AZ::Uuid" field="m_id" value="{155BF981-AD70-4D29-81A6-1517FAE59FB1}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
<Class name="AZStd::string" field="m_label" value="Number" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="unsigned int" field="m_version" value="0" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZStd::vector" field="m_versions" type="{326CAAFE-9101-56E2-B869-D770629A6B19}"/>
<Class name="any" field="m_data" type="{03924488-C7F4-4D6D-948B-ABC2D1AE2FD3}">
<Class name="AZ::Uuid" field="m_data" value="{110C4B14-11A8-4E9D-8638-5051013A56AC}" type="{E152C105-A133-4D03-BBF8-3D4B2FBA3E2A}"/>
</Class>
</Class>
<Class name="AZStd::vector" field="m_parameters" type="{6ED13EA7-791B-57A8-A4F1-560B5F35B472}"/>
</Class>
</Class>
</Class>
</Class>
</ObjectStream>
@@ -0,0 +1,195 @@
{
"ContainerEntity": {
"Id": "ContainerEntity",
"Name": "Player",
"Components": {
"Component_[10603663676997462041]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 10603663676997462041
},
"Component_[11066377844757909329]": {
"$type": "EditorPrefabComponent",
"Id": 11066377844757909329
},
"Component_[11664640320098005944]": {
"$type": "EditorEntityIconComponent",
"Id": 11664640320098005944
},
"Component_[12551690377468870725]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 12551690377468870725,
"Parent Entity": ""
},
"Component_[16402163080075698011]": {
"$type": "EditorOnlyEntityComponent",
"Id": 16402163080075698011
},
"Component_[3491366785918494447]": {
"$type": "EditorLockComponent",
"Id": 3491366785918494447
},
"Component_[4830373679514129871]": {
"$type": "EditorVisibilityComponent",
"Id": 4830373679514129871
},
"Component_[5144323498211834874]": {
"$type": "SelectionComponent",
"Id": 5144323498211834874
},
"Component_[5267607163086533733]": {
"$type": "EditorInspectorComponent",
"Id": 5267607163086533733
},
"Component_[6678300504118618849]": {
"$type": "EditorPendingCompositionComponent",
"Id": 6678300504118618849
},
"Component_[8384628950786300469]": {
"$type": "EditorEntitySortComponent",
"Id": 8384628950786300469,
"Child Entity Order": [
"Entity_[10070247746456]"
]
}
}
},
"Entities": {
"Entity_[10070247746456]": {
"Id": "Entity_[10070247746456]",
"Name": "Player",
"Components": {
"Component_[1059478843478789313]": {
"$type": "EditorInspectorComponent",
"Id": 1059478843478789313,
"ComponentOrderEntryArray": [
{
"ComponentId": 9878555871810913249
},
{
"ComponentId": 11481641385923146202,
"SortIndex": 1
},
{
"ComponentId": 11440172471478606933,
"SortIndex": 2
},
{
"ComponentId": 17461691807054668218,
"SortIndex": 3
},
{
"ComponentId": 15530420875454157766,
"SortIndex": 4
},
{
"ComponentId": 10596595655489113153,
"SortIndex": 5
}
]
},
"Component_[10596595655489113153]": {
"$type": "EditorScriptCanvasComponent",
"Id": 10596595655489113153,
"m_name": "AutoComponent_RPC",
"m_assetHolder": {
"m_asset": {
"assetId": {
"guid": "{5ED120C4-07DC-56F1-80A7-37BFC98FD74E}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc.scriptcanvas"
}
},
"runtimeDataIsValid": true,
"runtimeDataOverrides": {
"source": {
"assetId": {
"guid": "{5ED120C4-07DC-56F1-80A7-37BFC98FD74E}"
},
"assetHint": "levels/multiplayer/autocomponent_rpc/autocomponent_rpc.scriptcanvas"
}
}
},
"Component_[11440172471478606933]": {
"$type": "GenericComponentWrapper",
"Id": 11440172471478606933,
"m_template": {
"$type": "Multiplayer::NetworkTransformComponent"
}
},
"Component_[11481641385923146202]": {
"$type": "GenericComponentWrapper",
"Id": 11481641385923146202,
"m_template": {
"$type": "NetBindComponent"
}
},
"Component_[13110996849704981748]": {
"$type": "EditorVisibilityComponent",
"Id": 13110996849704981748
},
"Component_[1472895075383059499]": {
"$type": "EditorLockComponent",
"Id": 1472895075383059499
},
"Component_[1526920553231193509]": {
"$type": "EditorPendingCompositionComponent",
"Id": 1526920553231193509
},
"Component_[15530420875454157766]": {
"$type": "GenericComponentWrapper",
"Id": 15530420875454157766,
"m_template": {
"$type": "Multiplayer::LocalPredictionPlayerInputComponent"
}
},
"Component_[1699895912837266792]": {
"$type": "EditorDisabledCompositionComponent",
"Id": 1699895912837266792
},
"Component_[17461691807054668218]": {
"$type": "GenericComponentWrapper",
"Id": 17461691807054668218,
"m_template": {
"$type": "AutomatedTesting::NetworkTestPlayerComponent"
}
},
"Component_[3622545398462507871]": {
"$type": "EditorOnlyEntityComponent",
"Id": 3622545398462507871
},
"Component_[5778259918231688598]": {
"$type": "AZ::Render::EditorMeshComponent",
"Id": 5778259918231688598,
"Controller": {
"Configuration": {
"ModelAsset": {
"assetId": {
"guid": "{F322592F-43BC-50E7-903C-CC231846093F}",
"subId": 276443623
},
"assetHint": "objects/_primitives/_cylinder_1x1.azmodel"
}
}
}
},
"Component_[7004633483882343256]": {
"$type": "SelectionComponent",
"Id": 7004633483882343256
},
"Component_[8469628382507693850]": {
"$type": "EditorEntitySortComponent",
"Id": 8469628382507693850
},
"Component_[9407892837096707905]": {
"$type": "EditorEntityIconComponent",
"Id": 9407892837096707905
},
"Component_[9878555871810913249]": {
"$type": "{27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0} TransformComponent",
"Id": 9878555871810913249,
"Parent Entity": "ContainerEntity"
}
}
}
}
}
@@ -0,0 +1,12 @@
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
0,0,0,0,0,0
+1 -1
View File
@@ -7,7 +7,7 @@
"android_settings": {
"package_name": "com.lumberyard.yourgame",
"version_number": 1,
"version_name": "1.0.0.0",
"version_name": "1.0.0",
"orientation": "landscape"
},
"engine": "o3de",
-7
View File
@@ -30,8 +30,6 @@ CAboutDialog::CAboutDialog(QString versionText, QString richTextCopyrightNotice,
m_ui->setupUi(this);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
connect(m_ui->m_transparentAgreement, &QLabel::linkActivated, this, &CAboutDialog::OnCustomerAgreement);
m_ui->m_transparentTrademarks->setText(versionText);
m_ui->m_transparentAllRightReserved->setObjectName("copyrightNotice");
@@ -84,9 +82,4 @@ void CAboutDialog::mouseReleaseEvent(QMouseEvent* event)
QDialog::mouseReleaseEvent(event);
}
void CAboutDialog::OnCustomerAgreement()
{
QDesktopServices::openUrl(QUrl(QStringLiteral("https://www.o3debinaries.org/license")));
}
#include <moc_AboutDialog.cpp>
-2
View File
@@ -30,8 +30,6 @@ public:
private:
void OnCustomerAgreement();
void mouseReleaseEvent(QMouseEvent* event) override;
void paintEvent(QPaintEvent* event) override;
+6 -8
View File
@@ -181,14 +181,17 @@
</spacer>
</item>
<item>
<widget class="ClickableLabel" name="m_transparentAgreement">
<widget class="QLabel" name="m_transparentAgreement">
<property name="text">
<string>Terms of Use</string>
<string>&lt;a href=&quot;https://www.o3debinaries.org/license&quot;&gt;Terms of Use&lt;/a&gt;</string>
</property>
<property name="textFormat">
<enum>Qt::RichText</enum>
</property>
<property name="alignment">
<set>Qt::AlignLeading|Qt::AlignLeft|Qt::AlignTop</set>
</property>
<property name="showDecoration" stdset="0">
<property name="openExternalLinks">
<bool>true</bool>
</property>
</widget>
@@ -274,11 +277,6 @@
<extends>QWidget</extends>
<header>qsvgwidget.h</header>
</customwidget>
<customwidget>
<class>ClickableLabel</class>
<extends>QLabel</extends>
<header>QtUI/ClickableLabel.h</header>
</customwidget>
</customwidgets>
<resources/>
<connections/>
+2 -2
View File
@@ -319,8 +319,8 @@ public:
keys.push_back(QPair<QString, QString>("Edit Menu.Duplicate", "Ctrl+D"));
keys.push_back(QPair<QString, QString>("Edit Menu.Undo", "Ctrl+Z"));
keys.push_back(QPair<QString, QString>("Edit Menu.Redo", "Ctrl+Shift+Z"));
keys.push_back(QPair<QString, QString>("Edit Menu.Group", "Ctrl+G"));
keys.push_back(QPair<QString, QString>("Edit Menu.Ungroup", "Ctrl+Shift+G"));
keys.push_back(QPair<QString, QString>("Edit Menu.Group", "Ctrl+Alt+O"));
keys.push_back(QPair<QString, QString>("Edit Menu.Ungroup", "Ctrl+Alt+P"));
keys.push_back(QPair<QString, QString>("Edit Menu.Rename", "Ctrl+R"));
keys.push_back(QPair<QString, QString>("Edit Menu.Reset", ""));
keys.push_back(QPair<QString, QString>("Edit Menu.Edit Hotkeys", ""));
@@ -1,58 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include <AzTest/AzTest.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <QtUI/ClickableLabel.h>
#include <QApplication>
using namespace AZ;
using namespace ::testing;
namespace UnitTest
{
class TestingClickableLabel
: public ScopedAllocatorSetupFixture
{
public:
ClickableLabel m_clickableLabel;
};
TEST_F(TestingClickableLabel, CursorDoesNotUpdateWhileDisabled)
{
m_clickableLabel.setEnabled(false);
QApplication::setOverrideCursor(QCursor(Qt::BlankCursor));
QEnterEvent enterEvent{ QPointF(), QPointF(), QPointF() };
QApplication::sendEvent(&m_clickableLabel, &enterEvent);
const Qt::CursorShape cursorShape = QApplication::overrideCursor()->shape();
EXPECT_THAT(cursorShape, Ne(Qt::PointingHandCursor));
EXPECT_THAT(cursorShape, Eq(Qt::BlankCursor));
}
TEST_F(TestingClickableLabel, DoesNotRespondToDblClickWhileDisabled)
{
m_clickableLabel.setEnabled(false);
bool linkActivated = false;
QObject::connect(&m_clickableLabel, &QLabel::linkActivated, [&linkActivated]()
{
linkActivated = true;
});
QMouseEvent mouseEvent {
QEvent::MouseButtonDblClick, QPointF(),
Qt::LeftButton, Qt::LeftButton, Qt::NoModifier };
QApplication::sendEvent(&m_clickableLabel, &mouseEvent);
EXPECT_THAT(linkActivated, Eq(false));
}
} // namespace UnitTest
-101
View File
@@ -1,101 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ClickableLabel.h"
ClickableLabel::ClickableLabel(const QString& text, QWidget* parent)
: QLabel(parent)
, m_text(text)
, m_showDecoration(false)
{
setTextFormat(Qt::RichText);
setTextInteractionFlags(Qt::TextBrowserInteraction);
}
ClickableLabel::ClickableLabel(QWidget* parent)
: QLabel(parent)
, m_showDecoration(false)
{
setTextFormat(Qt::RichText);
setTextInteractionFlags(Qt::TextBrowserInteraction);
}
void ClickableLabel::showEvent([[maybe_unused]] QShowEvent* event)
{
updateFormatting(false);
}
void ClickableLabel::enterEvent(QEvent* ev)
{
if (!isEnabled())
{
return;
}
updateFormatting(true);
QApplication::setOverrideCursor(QCursor(Qt::PointingHandCursor));
QLabel::enterEvent(ev);
}
void ClickableLabel::leaveEvent(QEvent* ev)
{
if (!isEnabled())
{
return;
}
updateFormatting(false);
QApplication::restoreOverrideCursor();
QLabel::leaveEvent(ev);
}
void ClickableLabel::setText(const QString& text)
{
m_text = text;
QLabel::setText(text);
updateFormatting(false);
}
void ClickableLabel::setShowDecoration(bool b)
{
m_showDecoration = b;
updateFormatting(false);
}
void ClickableLabel::updateFormatting(bool mouseOver)
{
//FIXME: this should be done differently. Using a style sheet would be easiest.
QColor c = palette().color(QPalette::WindowText);
if (mouseOver || m_showDecoration)
{
QLabel::setText(QString(R"(<a href="dummy" style="color: %1";>%2</a>)").arg(c.name(), m_text));
}
else
{
QLabel::setText(m_text);
}
}
bool ClickableLabel::event(QEvent* e)
{
if (isEnabled())
{
if (e->type() == QEvent::MouseButtonDblClick)
{
emit linkActivated(QString());
return true; //ignore
}
}
return QLabel::event(e);
}
#include <QtUI/moc_ClickableLabel.cpp>
-39
View File
@@ -1,39 +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
#ifndef CRYINCLUDE_EDITORCOMMON_CLICKABLELABEL_H
#define CRYINCLUDE_EDITORCOMMON_CLICKABLELABEL_H
#if !defined(Q_MOC_RUN)
#include <QLabel>
#endif
class SANDBOX_API ClickableLabel
: public QLabel
{
Q_OBJECT
public:
explicit ClickableLabel(const QString& text, QWidget* parent = nullptr);
explicit ClickableLabel(QWidget* parent = nullptr);
bool event(QEvent* e) override;
void setText(const QString& text);
void setShowDecoration(bool b);
protected:
void showEvent(QShowEvent* event) override;
void enterEvent(QEvent* ev) override;
void leaveEvent(QEvent* ev) override;
private:
void updateFormatting(bool mouseOver);
QString m_text;
bool m_showDecoration;
};
#endif
-2
View File
@@ -526,8 +526,6 @@ set(FILES
PythonEditorFuncs.h
QtUI/QCollapsibleGroupBox.h
QtUI/QCollapsibleGroupBox.cpp
QtUI/ClickableLabel.h
QtUI/ClickableLabel.cpp
QtUI/PixmapLabelPreview.h
QtUI/PixmapLabelPreview.cpp
QtUI/WaitCursor.h
-1
View File
@@ -8,7 +8,6 @@
set(FILES
Lib/Tests/IEditorMock.h
Lib/Tests/test_ClickableLabel.cpp
Lib/Tests/test_CryEditPythonBindings.cpp
Lib/Tests/test_CryEditDocPythonBindings.cpp
Lib/Tests/test_EditorPythonBindings.cpp
@@ -19,12 +19,14 @@ namespace AZ
AZ_MATH_INLINE Plane Plane::CreateFromNormalAndPoint(const Vector3& normal, const Vector3& point)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is not normalized");
return Plane(Simd::Vec4::ConstructPlane(normal.GetSimdValue(), point.GetSimdValue()));
}
AZ_MATH_INLINE Plane Plane::CreateFromNormalAndDistance(const Vector3& normal, float dist)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is not normalized");
Plane result;
result.Set(normal, dist);
return result;
@@ -33,6 +35,7 @@ namespace AZ
AZ_MATH_INLINE Plane Plane::CreateFromCoefficients(const float a, const float b, const float c, const float d)
{
AZ_MATH_ASSERT(Vector3(a, b, c).IsNormalized(), "This normal is notormalized");
Plane result;
result.Set(a, b, c, d);
return result;
@@ -65,18 +68,21 @@ namespace AZ
AZ_MATH_INLINE void Plane::Set(const Vector3& normal, float d)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is notormalized");
m_plane.Set(normal, d);
}
AZ_MATH_INLINE void Plane::Set(float a, float b, float c, float d)
{
AZ_MATH_ASSERT(Vector3(a, b, c).IsNormalized(), "This normal is notormalized");
m_plane.Set(a, b, c, d);
}
AZ_MATH_INLINE void Plane::SetNormal(const Vector3& normal)
{
AZ_MATH_ASSERT(normal.IsNormalized(), "This normal is notormalized");
m_plane.SetX(normal.GetX());
m_plane.SetY(normal.GetY());
m_plane.SetZ(normal.GetZ());
+12 -4
View File
@@ -54,11 +54,11 @@ namespace AZ
//! Sets components using a Vector3 for the imaginary part and a float for the real part.
static Quaternion CreateFromVector3AndValue(const Vector3& v, float w);
//! Sets the quaternion to be a rotation around a specified axis.
//! Sets the quaternion to be a rotation around a specified axis in radians.
//! @{
static Quaternion CreateRotationX(float angle);
static Quaternion CreateRotationY(float angle);
static Quaternion CreateRotationZ(float angle);
static Quaternion CreateRotationX(float angleInRadians);
static Quaternion CreateRotationY(float angleInRadians);
static Quaternion CreateRotationZ(float angleInRadians);
//! @}
//! Creates a quaternion from a Matrix3x3
@@ -168,6 +168,14 @@ namespace AZ
float NormalizeWithLengthEstimate();
//! @}
//! Get the shortest equivalent of the rotation.
//! In case the w component of the quaternion is negative the rotation is > 180° and taking the longer path.
//! The quaternion will be inverted in that case to take the shortest path of rotation.
//! @{
Quaternion GetShortestEquivalent() const;
void ShortestEquivalent();
//! @}
//! Linearly interpolate towards a destination quaternion.
//! @param[in] dest The quaternion to interpolate towards.
//! @param[in] t Normalized interpolation value where 0.0 represents the current and 1.0 the destination value.
@@ -73,27 +73,27 @@ namespace AZ
}
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationX(float angle)
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationX(float angleInRadians)
{
const float halfAngle = 0.5f * angle;
const float halfAngle = 0.5f * angleInRadians;
float sin, cos;
SinCos(halfAngle, sin, cos);
return Quaternion(sin, 0.0f, 0.0f, cos);
}
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationY(float angle)
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationY(float angleInRadians)
{
const float halfAngle = 0.5f * angle;
const float halfAngle = 0.5f * angleInRadians;
float sin, cos;
SinCos(halfAngle, sin, cos);
return Quaternion(0.0f, sin, 0.0f, cos);
}
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationZ(float angle)
AZ_MATH_INLINE Quaternion Quaternion::CreateRotationZ(float angleInRadians)
{
const float halfAngle = 0.5f * angle;
const float halfAngle = 0.5f * angleInRadians;
float sin, cos;
SinCos(halfAngle, sin, cos);
return Quaternion(0.0f, 0.0f, sin, cos);
@@ -345,6 +345,23 @@ namespace AZ
}
AZ_MATH_INLINE Quaternion Quaternion::GetShortestEquivalent() const
{
if (GetW() < 0.0f)
{
return -(*this);
}
return *this;
}
AZ_MATH_INLINE void Quaternion::ShortestEquivalent()
{
*this = GetShortestEquivalent();
}
AZ_MATH_INLINE Quaternion Quaternion::Lerp(const Quaternion& dest, float t) const
{
if (Dot(dest) >= 0.0f)
@@ -11,6 +11,7 @@
#include <AzCore/Math/Matrix3x3.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AZTestShared/Math/MathTestHelpers.h>
using namespace AZ;
@@ -453,7 +454,7 @@ namespace UnitTest
AZ::Quaternion backFromScaledAxisAngle = AZ::Quaternion::CreateFromScaledAxisAngle(scaledAxisAngle);
// Compare the original quaternion with the one after the conversion.
EXPECT_TRUE(testQuat.IsClose(backFromScaledAxisAngle, 1e-6f));
EXPECT_THAT(testQuat, IsCloseTolerance(backFromScaledAxisAngle, 1e-6f));
}
TEST_P(QuaternionScaledAxisAngleConversionFixture, AxisAngleQuatRoundtripTests)
@@ -467,7 +468,7 @@ namespace UnitTest
// Convert the axis-angle back into a quaternion and compare the original quaternion with the one after the conversion.
const AZ::Quaternion backFromAxisAngle = AZ::Quaternion::CreateFromAxisAngle(axis, angle);
EXPECT_TRUE(testQuat.IsClose(backFromAxisAngle, 1e-6f));
EXPECT_THAT(testQuat, IsCloseTolerance(backFromAxisAngle, 1e-6f));
}
TEST_P(QuaternionScaledAxisAngleConversionFixture, CompareAxisAngleConversionTests)
@@ -506,8 +507,20 @@ namespace UnitTest
}
const AZ::Quaternion backFromAxisAngle = AZ::Quaternion::CreateFromAxisAngle(axisFromScaledResult, angleFromScaledResult);
EXPECT_TRUE(testQuat.IsClose(backFromAxisAngle, 1e-6f));
EXPECT_THAT(testQuat, IsCloseTolerance(backFromAxisAngle, 1e-6f));
}
INSTANTIATE_TEST_CASE_P(MATH_Quaternion, QuaternionScaledAxisAngleConversionFixture, ::testing::ValuesIn(RotationRepresentationConversionTestQuats));
TEST(MATH_Quaternion, ShortestEquivalent)
{
const AZ::Quaternion testQuat = AZ::Quaternion::CreateRotationX(AZ::Constants::HalfPi * 3.0f);
AZ::Quaternion absQuat = testQuat;
absQuat.ShortestEquivalent();
EXPECT_THAT(testQuat.GetShortestEquivalent(), IsCloseTolerance(absQuat, 1e-6f));
const float angle = absQuat.GetEulerRadians().GetX();
EXPECT_THAT(angle, testing::FloatEq(-AZ::Constants::HalfPi));
}
}
+9 -6
View File
@@ -52,7 +52,7 @@ get_property(asset_builders GLOBAL PROPERTY LY_ASSET_BUILDERS)
string (REPLACE ";" "," asset_builders "${asset_builders}")
ly_add_source_properties(
SOURCES native/utilities/ApplicationManager.cpp
PROPERTY COMPILE_DEFINITIONS
PROPERTY COMPILE_DEFINITIONS
VALUES LY_ASSET_BUILDERS="${asset_builders}"
)
@@ -147,9 +147,9 @@ endif()
# Tests
################################################################################
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_target(
NAME AssetProcessor.Tests EXECUTABLE
NAME AssetProcessor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE AZ
AUTOMOC
AUTORCC
@@ -167,12 +167,12 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
)
ly_add_source_properties(
SOURCES native/tests/assetBuilderSDK/assetBuilderSDKTest.cpp
PROPERTY COMPILE_DEFINITIONS
PROPERTY COMPILE_DEFINITIONS
VALUES ${LY_PAL_TOOLS_DEFINES}
)
ly_add_source_properties(
SOURCES native/unittests/AssetProcessorManagerUnitTests.cpp
PROPERTY COMPILE_DEFINITIONS
PROPERTY COMPILE_DEFINITIONS
VALUES LY_CMAKE_BINARY_DIR="${CMAKE_BINARY_DIR}"
)
@@ -266,7 +266,10 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_googletest(
NAME AZ::AssetProcessor.Tests
TEST_COMMAND $<TARGET_FILE:AZ::AssetProcessor.Tests> --unittest --gtest_filter=-*.SUITE_sandbox*
)
ly_add_googlebenchmark(
NAME AZ::AssetProcessor.Benchmarks
TARGET AZ::AssetProcessor.Tests
)
endif()
@@ -63,13 +63,6 @@ set(FILES
native/resourcecompiler/RCJobSortFilterProxyModel.h
native/resourcecompiler/RCQueueSortModel.cpp
native/resourcecompiler/RCQueueSortModel.h
native/shadercompiler/shadercompilerjob.cpp
native/shadercompiler/shadercompilerjob.h
native/shadercompiler/shadercompilerManager.cpp
native/shadercompiler/shadercompilerManager.h
native/shadercompiler/shadercompilerMessages.h
native/shadercompiler/shadercompilerModel.cpp
native/shadercompiler/shadercompilerModel.h
native/utilities/ApplicationManagerAPI.h
native/utilities/ApplicationManager.cpp
native/utilities/ApplicationManager.h
@@ -67,8 +67,6 @@ set(FILES
native/unittests/PlatformConfigurationUnitTests.h
native/unittests/RCcontrollerUnitTests.cpp
native/unittests/RCcontrollerUnitTests.h
native/unittests/ShaderCompilerUnitTests.cpp
native/unittests/ShaderCompilerUnitTests.h
native/unittests/UnitTestRunner.cpp
native/unittests/UnitTestRunner.h
native/unittests/UtilitiesUnitTests.cpp
@@ -1,162 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "shadercompilerManager.h"
#include "shadercompilerjob.h"
#include <QThreadPool>
#include "native/utilities/assetUtils.h"
ShaderCompilerManager::ShaderCompilerManager(QObject* parent)
: QObject(parent)
, m_isUnitTesting(false)
, m_numberOfJobsStarted(0)
, m_numberOfJobsEnded(0)
, m_numberOfErrors(0)
{
}
ShaderCompilerManager::~ShaderCompilerManager()
{
}
void ShaderCompilerManager::process(unsigned int connID, unsigned int type, unsigned int serial, QByteArray payload)
{
(void)type;
(void)serial;
Q_ASSERT(AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest") == type);
decodeShaderCompilerRequest(connID, payload);
}
void ShaderCompilerManager::decodeShaderCompilerRequest(unsigned int connID, QByteArray payload)
{
if (payload.length() < sizeof(unsigned int) + sizeof(unsigned int) + 2 + sizeof(unsigned short))
{
QString error = "Payload size is too small";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
unsigned char* data_end = reinterpret_cast<unsigned char*>(payload.data() + payload.size());
unsigned int* requestId = reinterpret_cast<unsigned int*>(data_end - sizeof(unsigned int));
unsigned int* serverListSizePtr = reinterpret_cast<unsigned int*>(data_end - sizeof(unsigned int) - sizeof(unsigned int));
unsigned short* serverPortPtr = reinterpret_cast<unsigned short*>(data_end - sizeof(unsigned int) - sizeof(unsigned int) - sizeof(unsigned short));
ShaderCompilerRequestMessage msg;
QString error;
msg.requestId = *requestId;
msg.serverListSize = *serverListSizePtr;
msg.serverPort = *serverPortPtr;
if ((msg.serverListSize <= 0) || (msg.serverListSize > 100000))
{
error = "Shader Compiler Server List is wrong";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
if (msg.serverPort == 0)
{
error = "Shader Compiler port is wrong";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
char* position_of_first_null = reinterpret_cast<char*>(serverPortPtr) - 1;// -1 for null
if ((*position_of_first_null) != '\0')
{
error = "Shader Compiler payload is corrupt,position is not null";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
char* beginning_of_serverList = position_of_first_null - msg.serverListSize;
char* position_of_second_null = beginning_of_serverList - 1;//-1 for null
if ((*position_of_second_null) != '\0')
{
error = "Shader Compiler payload is corrupt,position is not null";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
return;
}
unsigned int originalPayloadSize = static_cast<unsigned int>(payload.size()) - sizeof(unsigned int) - sizeof(unsigned int) - sizeof(unsigned short) - static_cast<unsigned int>(msg.serverListSize) - 2;
msg.serverList = beginning_of_serverList;
msg.originalPayload.insert(0, payload.data(), static_cast<unsigned int>(originalPayloadSize));
ShaderCompilerJob* shaderCompilerJob = new ShaderCompilerJob();
shaderCompilerJob->initialize(this, msg);
shaderCompilerJob->setIsUnitTesting(m_isUnitTesting);
m_shaderCompilerJobMap[msg.requestId] = connID;
shaderCompilerJob->setAutoDelete(true);
QThreadPool* threadPool = QThreadPool::globalInstance();
threadPool->start(shaderCompilerJob);
}
void ShaderCompilerManager::OnShaderCompilerJobComplete(QByteArray payload, unsigned int requestId)
{
auto iterator = m_shaderCompilerJobMap.find(requestId);
if (iterator != m_shaderCompilerJobMap.end())
{
sendResponse(iterator.value(), AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyResponse"), 0, payload);
}
else
{
QString error = "Shader Compiler cannot find the connection id";
AZ_Warning(AssetProcessor::ConsoleChannel, false, error.toUtf8().data());
emit sendErrorMessage(error);
}
}
void ShaderCompilerManager::sendResponse(unsigned int connId, unsigned int /*type*/, unsigned int /*serial*/, QByteArray payload)
{
EBUS_EVENT_ID(connId, AssetProcessor::ConnectionBus, SendRaw, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyResponse"), 0, payload);
}
void ShaderCompilerManager::shaderCompilerError(QString errorMessage, QString server, QString timestamp, QString payload)
{
m_numberOfErrors++;
emit numberOfErrorsChanged();
emit sendErrorMessageFromShaderJob(errorMessage, server, timestamp, payload);
}
void ShaderCompilerManager::jobStarted()
{
m_numberOfJobsStarted++;
emit numberOfJobsStartedChanged();
}
void ShaderCompilerManager::jobEnded()
{
m_numberOfJobsEnded++;
numberOfJobsEndedChanged();
}
void ShaderCompilerManager::setIsUnitTesting(bool isUnitTesting)
{
m_isUnitTesting = isUnitTesting;
}
int ShaderCompilerManager::numberOfJobsStarted()
{
return m_numberOfJobsStarted;
}
int ShaderCompilerManager::numberOfJobsEnded()
{
return m_numberOfJobsEnded;
}
int ShaderCompilerManager::numberOfErrors()
{
return m_numberOfErrors;
}
@@ -1,67 +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
*
*/
#ifndef SHADERCOMPILERMANAGER_H
#define SHADERCOMPILERMANAGER_H
#if !defined(Q_MOC_RUN)
#include <QObject>
#include <QHash>
#include <QString>
#include <QByteArray>
#endif
typedef QHash<unsigned int, unsigned int> ShaderCompilerJobMap;
/**
* The Shader Compiler Manager class receive a shader compile request
* and starts a shader compiler job for it
*/
class ShaderCompilerManager
: public QObject
{
Q_OBJECT
Q_PROPERTY(int numberOfJobsStarted READ numberOfJobsStarted NOTIFY numberOfJobsStartedChanged)
Q_PROPERTY(int numberOfJobsEnded READ numberOfJobsEnded NOTIFY numberOfJobsEndedChanged)
Q_PROPERTY(int numberOfErrors READ numberOfErrors NOTIFY numberOfErrorsChanged)
public:
explicit ShaderCompilerManager(QObject* parent = 0);
virtual ~ShaderCompilerManager();
void process(unsigned int connID, unsigned int type, unsigned int serial, QByteArray payload);
void decodeShaderCompilerRequest(unsigned int connID, QByteArray payload);
void setIsUnitTesting(bool isUnitTesting);
int numberOfJobsStarted();
int numberOfJobsEnded();
int numberOfErrors();
virtual void sendResponse(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
signals:
void sendErrorMessage(QString errorMessage);
void sendErrorMessageFromShaderJob(QString errorMessage, QString server, QString timestamp, QString payload);
void numberOfJobsStartedChanged();
void numberOfJobsEndedChanged();
void numberOfErrorsChanged();
public slots:
void OnShaderCompilerJobComplete(QByteArray payload, unsigned int requestId);
void shaderCompilerError(QString errorMessage, QString server, QString timestamp, QString payload);
void jobStarted();
void jobEnded();
private:
ShaderCompilerJobMap m_shaderCompilerJobMap;
bool m_isUnitTesting;
int m_numberOfJobsStarted;
int m_numberOfJobsEnded;
int m_numberOfErrors;
};
#endif // SHADERCOMPILERMANAGER_H
@@ -1,24 +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
*
*/
#ifndef SHADERCOMPILERMESSAGES_H
#define SHADERCOMPILERMESSAGES_H
#include <QByteArray>
#include <QString>
struct ShaderCompilerRequestMessage
{
QByteArray originalPayload;
QString serverList;
unsigned short serverPort;
unsigned int serverListSize;
unsigned int requestId;
};
#endif //SHADERCOMPILERMESSAGES_H
@@ -1,150 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "shadercompilerModel.h"
namespace
{
ShaderCompilerModel* s_singleton = nullptr;
}
ShaderCompilerModel::ShaderCompilerModel(QObject* parent)
: QAbstractItemModel(parent)
{
Q_ASSERT(s_singleton == nullptr);
s_singleton = this;
}
ShaderCompilerModel::~ShaderCompilerModel()
{
s_singleton = nullptr;
}
ShaderCompilerModel* ShaderCompilerModel::Get()
{
return s_singleton;
}
QVariant ShaderCompilerModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
int row = index.row();
if (row < 0)
{
return QVariant();
}
if (row >= m_shaderErrorInfoList.count())
{
return QVariant();
}
switch (role)
{
case TimeStampRole:
return m_shaderErrorInfoList[row].m_shaderTimestamp;
case ServerRole:
return m_shaderErrorInfoList[row].m_shaderServerName;
case ErrorRole:
return m_shaderErrorInfoList[row].m_shaderError;
case OriginalRequestRole:
return m_shaderErrorInfoList[row].m_shaderOriginalPayload;
case Qt::DisplayRole:
switch (index.column())
{
case ColumnTimeStamp:
return m_shaderErrorInfoList[row].m_shaderTimestamp;
case ColumnServer:
return m_shaderErrorInfoList[row].m_shaderServerName;
case ColumnError:
return m_shaderErrorInfoList[row].m_shaderServerName;
}
}
return QVariant();
}
Qt::ItemFlags ShaderCompilerModel::flags(const QModelIndex& index) const
{
(void)index;
return Qt::ItemIsSelectable | Qt::ItemIsEnabled;
}
int ShaderCompilerModel::rowCount(const QModelIndex& parent) const
{
(void)parent;
return m_shaderErrorInfoList.count();
}
QModelIndex ShaderCompilerModel::parent(const QModelIndex&) const
{
return QModelIndex();
}
QModelIndex ShaderCompilerModel::index(int row, int column, const QModelIndex& parent) const
{
if (row >= rowCount(parent) || column >= columnCount(parent))
{
return QModelIndex();
}
return createIndex(row, column);
}
int ShaderCompilerModel::columnCount(const QModelIndex& parent) const
{
return parent.isValid() ? 0 : Column::Max;
}
QVariant ShaderCompilerModel::headerData(int section, Qt::Orientation orientation, int role) const
{
if (orientation == Qt::Horizontal && role == Qt::DisplayRole)
{
switch (section)
{
case ColumnTimeStamp:
return tr("Time Stamp");
case ColumnServer:
return tr("Server");
case ColumnError:
return tr("Error");
default:
break;
}
}
return QAbstractItemModel::headerData(section, orientation, role);
}
QHash<int, QByteArray> ShaderCompilerModel::roleNames() const
{
QHash<int, QByteArray> result;
result[TimeStampRole] = "timestamp";
result[ServerRole] = "server";
result[ErrorRole] = "error";
result[OriginalRequestRole] = "originalRequest";
return result;
}
void ShaderCompilerModel::addShaderErrorInfoEntry(QString errorMessage, QString timestamp, QString payload, QString server)
{
ShaderCompilerErrorInfo shaderCompileErrorInfo(errorMessage, timestamp, payload, server);
beginInsertRows(QModelIndex(), m_shaderErrorInfoList.size(), m_shaderErrorInfoList.size());
m_shaderErrorInfoList.append(shaderCompileErrorInfo);
endInsertRows();
}
@@ -1,93 +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
*
*/
#ifndef SHADERCOMPILERMODEL_H
#define SHADERCOMPILERMODEL_H
#if !defined(Q_MOC_RUN)
#include <QAbstractItemModel>
#include <QList>
#include <QVariant>
#include <QHash>
#include <QByteArray>
#include <QString>
#endif
class QModelIndex;
class QObject;
struct ShaderCompilerErrorInfo
{
QString m_shaderError;
QString m_shaderTimestamp;
QString m_shaderOriginalPayload;
QString m_shaderServerName;
ShaderCompilerErrorInfo(QString shaderError, QString shaderTimestamp, QString shaderOriginalPayload, QString shaderServerName)
: m_shaderError(shaderError)
, m_shaderTimestamp(shaderTimestamp)
, m_shaderOriginalPayload(shaderOriginalPayload)
, m_shaderServerName(shaderServerName)
{
}
};
/** The Shader Compiler model is responsible for capturing error requests
*/
class ShaderCompilerModel
: public QAbstractItemModel
{
Q_OBJECT
public:
enum DataRoles
{
TimeStampRole = Qt::UserRole + 1,
ServerRole,
ErrorRole,
OriginalRequestRole,
};
enum Column
{
ColumnTimeStamp,
ColumnServer,
ColumnError,
Max
};
/// standard Qt constructor
explicit ShaderCompilerModel(QObject* parent = 0);
virtual ~ShaderCompilerModel();
// singleton pattern
static ShaderCompilerModel* Get();
/// QAbstractListModel interface
QModelIndex parent(const QModelIndex&) const override;
QModelIndex index(int row, int column, const QModelIndex& parent) const override;
int columnCount(const QModelIndex&) const override;
virtual int rowCount(const QModelIndex& parent) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role) const override;
virtual QVariant data(const QModelIndex& index, int role) const override;
virtual QHash<int, QByteArray> roleNames() const override;
virtual Qt::ItemFlags flags(const QModelIndex& index) const override;
public slots:
void addShaderErrorInfoEntry(QString errorMessage, QString timestamp, QString payload, QString server);
private:
QList<ShaderCompilerErrorInfo> m_shaderErrorInfoList;
};
#endif // SHADERCOMPILERMODEL_H
@@ -1,194 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "shadercompilerjob.h"
#include "native/assetprocessor.h"
#include <QTcpSocket>
ShaderCompilerJob::ShaderCompilerJob()
: m_isUnitTesting(false)
, m_manager(nullptr)
{
}
ShaderCompilerJob::~ShaderCompilerJob()
{
m_manager = nullptr;
}
ShaderCompilerRequestMessage ShaderCompilerJob::ShaderCompilerMessage() const
{
return m_ShaderCompilerMessage;
}
void ShaderCompilerJob::initialize(QObject* pManager, const ShaderCompilerRequestMessage& ShaderCompilerMessage)
{
m_manager = pManager;
m_ShaderCompilerMessage = ShaderCompilerMessage;
}
QString ShaderCompilerJob::getServerAddress()
{
if (isServerListEmpty())
{
return QString();
}
QString serverAddress;
if (!m_ShaderCompilerMessage.serverList.contains(","))
{
serverAddress = m_ShaderCompilerMessage.serverList;
m_ShaderCompilerMessage.serverList.clear();
return serverAddress;
}
QStringList serverList = m_ShaderCompilerMessage.serverList.split(",");
serverAddress = serverList.takeAt(0);
m_ShaderCompilerMessage.serverList = serverList.join(",");
return serverAddress;
}
bool ShaderCompilerJob::isServerListEmpty()
{
return m_ShaderCompilerMessage.serverList.isEmpty();
}
bool ShaderCompilerJob::attemptDelivery(QString serverAddress, QByteArray& payload)
{
QTcpSocket socket;
QString error;
int waitingTime = 8000; // 8 sec timeout for sending.
int jobCompileMaxTime = 1000 * 60; // 60 sec timeout for compilation
if (m_isUnitTesting)
{
waitingTime = 500;
jobCompileMaxTime = 500;
}
socket.connectToHost(serverAddress, m_ShaderCompilerMessage.serverPort, QIODevice::ReadWrite);
if (socket.waitForConnected(waitingTime))
{
qint64 bytesWritten = 0;
qint64 payloadSize = static_cast<qint64>(m_ShaderCompilerMessage.originalPayload.size());
// send payload size to server
while (bytesWritten != sizeof(qint64))
{
qint64 currentWrite = socket.write(reinterpret_cast<char*>(&payloadSize) + bytesWritten,
sizeof(qint64) - bytesWritten);
if (currentWrite == -1)
{
//It is important to note that we are only outputting the error to debugchannel only here because
//we are forwarding these error messages upstream to the manager,who will take the appropriate action
error = "Connection Lost:Unable to send data";
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
return false;
}
socket.flush();
bytesWritten += currentWrite;
}
bytesWritten = 0;
//send actual payload to server
while (bytesWritten != m_ShaderCompilerMessage.originalPayload.size())
{
qint64 currentWrite = socket.write(m_ShaderCompilerMessage.originalPayload.data() + bytesWritten,
m_ShaderCompilerMessage.originalPayload.size() - bytesWritten);
if (currentWrite == -1)
{
error = "Connection Lost:Unable to send data";
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
}
socket.flush();
bytesWritten += currentWrite;
}
}
else
{
error = "Unable to connect to IP Address " + serverAddress;
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
return false;
}
unsigned int expectedBytes = sizeof(unsigned int) + sizeof(qint8);
unsigned int bytesReadTotal = 0;
unsigned int messageSize = 0;
bool isMessageSizeKnown = false;
//read the entire payload
while ((bytesReadTotal < expectedBytes + messageSize))
{
if (socket.bytesAvailable() == 0)
{
if (!socket.waitForReadyRead(jobCompileMaxTime))
{
error = "Remote IP is taking too long to respond: " + serverAddress;
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
payload.clear();
return false;
}
}
qint64 bytesAvailable = socket.bytesAvailable();
if (bytesAvailable >= expectedBytes && !isMessageSizeKnown)
{
socket.peek(reinterpret_cast<char*>(&messageSize), sizeof(unsigned int));
payload.resize(expectedBytes + messageSize);
isMessageSizeKnown = true;
}
if (bytesAvailable > 0)
{
qint64 bytesRead = socket.read(payload.data() + bytesReadTotal, bytesAvailable);
if (bytesRead <= 0)
{
error = "Connection closed by remote IP Address " + serverAddress;
AZ_TracePrintf(AssetProcessor::DebugChannel, error.toUtf8().data());
QMetaObject::invokeMethod(m_manager, "shaderCompilerError", Qt::QueuedConnection, Q_ARG(QString, error), Q_ARG(QString, QDateTime::currentDateTime().toString()), Q_ARG(QString, QString(m_ShaderCompilerMessage.originalPayload)), Q_ARG(QString, serverAddress));
payload.clear();
return false;
}
bytesReadTotal = aznumeric_cast<uint32_t>(bytesReadTotal + bytesRead);
}
}
return true; // payload successfully send
}
void ShaderCompilerJob::run()
{
QMetaObject::invokeMethod(m_manager, "jobStarted", Qt::QueuedConnection);
QByteArray payload;
//until server list is empty, keep trying
while (!isServerListEmpty())
{
QString serverAddress = getServerAddress();
//attempt to send payload
if (attemptDelivery(serverAddress, payload))
{
break;
}
}
//we are appending request id at the end of every payload,
//therefore in the case of any errors also
//we will be sending atleast four bytes to the game
payload.append(reinterpret_cast<char*>(&m_ShaderCompilerMessage.requestId), sizeof(unsigned int));
QMetaObject::invokeMethod(m_manager, "OnShaderCompilerJobComplete", Qt::QueuedConnection, Q_ARG(QByteArray, payload), Q_ARG(unsigned int, m_ShaderCompilerMessage.requestId));
QMetaObject::invokeMethod(m_manager, "jobEnded", Qt::QueuedConnection);
}
void ShaderCompilerJob::setIsUnitTesting(bool isUnitTesting)
{
m_isUnitTesting = isUnitTesting;
}
@@ -1,44 +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
*
*/
#ifndef SHADERCOMPILERJOB_H
#define SHADERCOMPILERJOB_H
#include <QRunnable>
#include "shadercompilerMessages.h"
class QByteArray;
class QObject;
/**
* This class is responsible for connecting to the shader compiler server
* and getting back the response to the shader compiler manager
*/
class ShaderCompilerJob
: public QRunnable
{
public:
explicit ShaderCompilerJob();
virtual ~ShaderCompilerJob();
ShaderCompilerRequestMessage ShaderCompilerMessage() const;
void initialize(QObject* pManager, const ShaderCompilerRequestMessage& ShaderCompilerMessage);
QString getServerAddress();
bool isServerListEmpty();
virtual void run() override;
void setIsUnitTesting(bool isUnitTesting);
bool attemptDelivery(QString serverAddress, QByteArray& payload);
private:
ShaderCompilerRequestMessage m_ShaderCompilerMessage;
QObject* m_manager;
bool m_isUnitTesting;
};
#endif // SHADERCOMPILERJOB_H
@@ -18,8 +18,6 @@
#include <QCoreApplication>
AZ_UNIT_TEST_HOOK(new BaseAssetProcessorTestEnvironment)
namespace AssetProcessor
{
class UnitTestAppManager : public BatchApplicationManager
@@ -35,7 +33,7 @@ namespace AssetProcessor
{
return false;
}
// tests which use the builder bus plug in their own mock version, so disconnect ours.
AssetProcessor::AssetBuilderInfoBus::Handler::BusDisconnect();
@@ -59,7 +57,7 @@ namespace AssetProcessor
void SetUp() override
{
AssetProcessorTest::SetUp();
static int numParams = 1;
static char processName[] = {"AssetProcessorBatch"};
static char* namePtr = &processName[0];
@@ -147,7 +145,7 @@ namespace AssetProcessor
time.start();
actualTest->StartTest();
while (!testIsComplete)
{
QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete);
@@ -5,76 +5,8 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "utilities/BatchApplicationManager.h"
#include <AzTest/AzTest.h>
#include <AzTest/Utils.h>
#include <native/tests/BaseAssetProcessorTest.h>
DECLARE_AZ_UNIT_TEST_MAIN()
int RunUnitTests(int argc, char* argv[], bool& ranUnitTests)
{
ranUnitTests = true;
INVOKE_AZ_UNIT_TEST_MAIN(nullptr); // nullptr turns off default test environment used to catch stray asserts
// This looks a bit weird, but the macro returns conditionally, so *if* we get here, it means the unit tests didn't run
ranUnitTests = false;
return 0;
}
int main(int argc, char* argv[])
{
qputenv("QT_MAC_DISABLE_FOREGROUND_APPLICATION_TRANSFORM", "1");
AZ::Debug::Trace::HandleExceptions(true);
AZ::Test::ApplyGlobalParameters(&argc, argv);
// If "--unittest" is present on the command line, run unit testing
// and return immediately. Otherwise, continue as normal.
AZ::Test::addTestEnvironment(new BaseAssetProcessorTestEnvironment());
bool pauseOnComplete = false;
if (AZ::Test::ContainsParameter(argc, argv, "--pause-on-completion"))
{
pauseOnComplete = true;
}
bool ranUnitTests;
int result = RunUnitTests(argc, argv, ranUnitTests);
if (ranUnitTests)
{
if (pauseOnComplete)
{
system("pause");
}
return result;
}
BatchApplicationManager applicationManager(&argc, &argv);
setvbuf(stdout, NULL, _IONBF, 0); // Disabling output buffering to fix test failures due to incomplete logs
ApplicationManager::BeforeRunStatus status = applicationManager.BeforeRun();
if (status != ApplicationManager::BeforeRunStatus::Status_Success)
{
if (status == ApplicationManager::BeforeRunStatus::Status_Restarting)
{
//AssetProcessor will restart
return 0;
}
else
{
//Initialization failed
return 1;
}
}
return applicationManager.Run() ? 0 : 1;
}
AZ_UNIT_TEST_HOOK(new BaseAssetProcessorTestEnvironment)
@@ -29,14 +29,16 @@ namespace AssetProcessor
AssetTreeItem::AssetTreeItem(
AZStd::shared_ptr<AssetTreeItemData> data,
QIcon errorIcon,
QIcon folderIcon,
QIcon fileIcon,
AssetTreeItem* parentItem) :
m_data(data),
m_parent(parentItem),
m_errorIcon(errorIcon), // QIcon is implicitily shared.
m_folderIcon(QIcon(QStringLiteral(":/Gallery/Asset_Folder.svg"))),
m_fileIcon(QIcon(QStringLiteral(":/Gallery/Asset_File.svg")))
m_errorIcon(errorIcon), // QIcon is implicitly shared.
m_folderIcon(folderIcon),
m_fileIcon(fileIcon)
{
m_folderIcon.addFile(QStringLiteral(":/Gallery/Asset_Folder.svg"), QSize(), QIcon::Selected);
}
AssetTreeItem::~AssetTreeItem()
@@ -45,7 +47,7 @@ namespace AssetProcessor
AssetTreeItem* AssetTreeItem::CreateChild(AZStd::shared_ptr<AssetTreeItemData> data)
{
m_childItems.emplace_back(new AssetTreeItem(data, m_errorIcon, this));
m_childItems.emplace_back(new AssetTreeItem(data, m_errorIcon, m_folderIcon, m_fileIcon, this));
return m_childItems.back().get();
}
@@ -50,6 +50,8 @@ namespace AssetProcessor
explicit AssetTreeItem(
AZStd::shared_ptr<AssetTreeItemData> data,
QIcon errorIcon,
QIcon folderIcon,
QIcon fileIcon,
AssetTreeItem* parentItem = nullptr);
virtual ~AssetTreeItem();
@@ -16,9 +16,12 @@ namespace AssetProcessor
AssetTreeModel::AssetTreeModel(AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> sharedDbConnection, QObject *parent) :
QAbstractItemModel(parent),
m_sharedDbConnection(sharedDbConnection),
m_errorIcon(QStringLiteral(":/stylesheet/img/logging/error.svg"))
m_sharedDbConnection(sharedDbConnection)
, m_errorIcon(QStringLiteral(":/stylesheet/img/logging/error.svg"))
, m_folderIcon(QIcon(QStringLiteral(":/Gallery/Asset_Folder.svg")))
, m_fileIcon(QIcon(QStringLiteral(":/Gallery/Asset_File.svg")))
{
m_folderIcon.addFile(QStringLiteral(":/Gallery/Asset_Folder.svg"), QSize(), QIcon::Selected);
ApplicationManagerNotifications::Bus::Handler::BusConnect();
AzToolsFramework::AssetDatabase::AssetDatabaseNotificationBus::Handler::BusConnect();
}
@@ -40,7 +43,7 @@ namespace AssetProcessor
void AssetTreeModel::Reset()
{
beginResetModel();
m_root.reset(new AssetTreeItem(AZStd::make_shared<AssetTreeItemData>("", "", true, AZ::Uuid::CreateNull()), m_errorIcon));
m_root.reset(new AssetTreeItem(AZStd::make_shared<AssetTreeItemData>("", "", true, AZ::Uuid::CreateNull()), m_errorIcon, m_folderIcon, m_fileIcon));
ResetModel();
@@ -54,5 +54,7 @@ namespace AssetProcessor
AZStd::shared_ptr<AzToolsFramework::AssetDatabase::AssetDatabaseConnection> m_sharedDbConnection;
QIcon m_errorIcon;
QIcon m_folderIcon;
QIcon m_fileIcon;
};
}
@@ -37,7 +37,6 @@
#include "../connection/connection.h"
#include "../resourcecompiler/rccontroller.h"
#include "../resourcecompiler/RCJobSortFilterProxyModel.h"
#include "../shadercompiler/shadercompilerModel.h"
#include <QClipboard>
@@ -148,7 +147,6 @@ void MainWindow::Activate()
ui->buttonList->addTab(QStringLiteral("Jobs"));
ui->buttonList->addTab(QStringLiteral("Assets"));
ui->buttonList->addTab(QStringLiteral("Logs"));
ui->buttonList->addTab(QStringLiteral("Shaders"));
ui->buttonList->addTab(QStringLiteral("Connections"));
ui->buttonList->addTab(QStringLiteral("Tools"));
@@ -317,14 +315,6 @@ void MainWindow::Activate()
connect(ui->jobFilteredSearchWidget, &AzQtComponents::FilteredSearchWidget::TextFilterChanged,
this, writeJobFilterSettings);
//Shader view
ui->shaderTreeView->setModel(m_guiApplicationManager->GetShaderCompilerModel());
ui->shaderTreeView->header()->resizeSection(ShaderCompilerModel::ColumnTimeStamp, 80);
ui->shaderTreeView->header()->resizeSection(ShaderCompilerModel::ColumnServer, 40);
ui->shaderTreeView->header()->resizeSection(ShaderCompilerModel::ColumnError, 220);
ui->shaderTreeView->header()->setSectionResizeMode(ShaderCompilerModel::ColumnError, QHeaderView::Stretch);
ui->shaderTreeView->header()->setStretchLastSection(false);
// Asset view
m_sourceAssetTreeFilterModel = new AssetProcessor::AssetTreeFilterModel(this);
m_sourceModel = new AssetProcessor::SourceAssetTreeModel(m_sharedDbConnection, this);
@@ -65,7 +65,6 @@ public:
Jobs,
Assets,
Logs,
Shaders,
Connections,
Tools
};
@@ -763,59 +763,6 @@
</item>
</layout>
</widget>
<widget class="QWidget" name="shaderDialog">
<layout class="QVBoxLayout" name="verticalLayout_7">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="QLabel" name="shaderLabel">
<property name="text">
<string>Shaders</string>
</property>
</widget>
</item>
<item>
<widget class="QWidget" name="shaderInfoPane" native="true">
<property name="styleSheet">
<string notr="true"/>
</property>
<layout class="QHBoxLayout" name="horizontalLayout_6">
<property name="spacing">
<number>0</number>
</property>
<property name="leftMargin">
<number>0</number>
</property>
<property name="topMargin">
<number>0</number>
</property>
<property name="rightMargin">
<number>0</number>
</property>
<property name="bottomMargin">
<number>0</number>
</property>
<item>
<widget class="AzQtComponents::TableView" name="shaderTreeView"/>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
<widget class="QWidget" name="connectionsDialog">
<layout class="QVBoxLayout" name="verticalLayout_5">
<property name="spacing">
@@ -1,188 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ShaderCompilerUnitTests.h"
#include "native/connection/connectionManager.h"
#include "native/connection/connection.h"
#include "native/utilities/assetUtils.h"
#define UNIT_TEST_CONNECT_PORT 12125
ShaderCompilerUnitTest::ShaderCompilerUnitTest()
{
m_connectionManager = ConnectionManager::Get();
connect(this, SIGNAL(StartUnitTestForGoodShaderCompiler()), this, SLOT(UnitTestForGoodShaderCompiler()));
connect(this, SIGNAL(StartUnitTestForFirstBadShaderCompiler()), this, SLOT(UnitTestForFirstBadShaderCompiler()));
connect(this, SIGNAL(StartUnitTestForSecondBadShaderCompiler()), this, SLOT(UnitTestForSecondBadShaderCompiler()));
connect(this, SIGNAL(StartUnitTestForThirdBadShaderCompiler()), this, SLOT(UnitTestForThirdBadShaderCompiler()));
connect(&m_shaderCompilerManager, SIGNAL(sendErrorMessageFromShaderJob(QString, QString, QString, QString)), this, SLOT(ReceiveShaderCompilerErrorMessage(QString, QString, QString, QString)));
m_shaderCompilerManager.setIsUnitTesting(true);
m_connectionManager->RegisterService(AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), AZStd::bind(&ShaderCompilerManager::process, &m_shaderCompilerManager, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4));
ContructPayloadForShaderCompilerServer(m_testPayload);
}
ShaderCompilerUnitTest::~ShaderCompilerUnitTest()
{
m_connectionManager->removeConnection(m_connectionId);
}
void ShaderCompilerUnitTest::ContructPayloadForShaderCompilerServer(QByteArray& payload)
{
QString testString = "This is a test string";
QString testServerList = "127.0.0.3,198.51.100.0,127.0.0.1"; // note - 198.51.100.0 is in the 'test' range that will never be assigned to anyone.
unsigned int testServerListLength = static_cast<unsigned int>(testServerList.size());
unsigned short testServerPort = 12348;
unsigned int testRequestId = 1;
qint64 testStringLength = static_cast<qint64>(testString.size());
payload.resize(static_cast<unsigned int>(testStringLength));
memcpy(payload.data(), (testString.toStdString().c_str()), testStringLength);
unsigned int payloadSize = payload.size();
payload.resize(payloadSize + 1 + static_cast<unsigned int>(testServerListLength) + 1 + sizeof(unsigned short) + sizeof(unsigned int) + sizeof(unsigned int));
char* dataStart = payload.data() + payloadSize;
*dataStart = 0;// null
memcpy(payload.data() + payloadSize + 1, (testServerList.toStdString().c_str()), testServerListLength);
dataStart += 1 + testServerListLength;
*dataStart = 0; //null
memcpy(payload.data() + payloadSize + 1 + testServerListLength + 1, reinterpret_cast<char*>(&testServerPort), sizeof(unsigned short));
memcpy(payload.data() + payloadSize + 1 + testServerListLength + 1 + sizeof(unsigned short), reinterpret_cast<char*>(&testServerListLength), sizeof(unsigned int));
memcpy(payload.data() + payloadSize + 1 + testServerListLength + 1 + sizeof(unsigned short) + sizeof(unsigned int), reinterpret_cast<char*>(&testRequestId), sizeof(unsigned int));
}
void ShaderCompilerUnitTest::StartTest()
{
m_connectionId = m_connectionManager->addConnection();
Connection* connection = m_connectionManager->getConnection(m_connectionId);
connection->SetPort(UNIT_TEST_CONNECT_PORT);
connection->SetIpAddress("127.0.0.1");
connection->SetAutoConnect(true);
UnitTestForGoodShaderCompiler();
}
int ShaderCompilerUnitTest::UnitTestPriority() const
{
return -4;
}
void ShaderCompilerUnitTest::UnitTestForGoodShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'good' shader compiler...\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForGoodShaderCompiler, this , AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.Init("127.0.0.1", 12348);
m_server.setServerStatus(UnitTestShaderCompilerServer::GoodServer);
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::UnitTestForFirstBadShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'bad' shader compiler... (Incomplete Payload)\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForFirstBadShaderCompiler, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.setServerStatus(UnitTestShaderCompilerServer::BadServer_SendsIncompletePayload);
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::UnitTestForSecondBadShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'bad' shader compiler... (Payload followed by disconnection)\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForSecondBadShaderCompiler, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.setServerStatus(UnitTestShaderCompilerServer::BadServer_ReadsPayloadAndDisconnect);
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::UnitTestForThirdBadShaderCompiler()
{
AZ_TracePrintf("ShaderCompilerUnitTest", " ... Starting test of 'bad' shader compiler... (Connect but disconnect without data)\n");
m_shaderCompilerManager.m_sendResponseCallbackFn = AZStd::bind(&ShaderCompilerUnitTest::VerifyPayloadForThirdBadShaderCompiler, this, AZStd::placeholders::_1, AZStd::placeholders::_2, AZStd::placeholders::_3, AZStd::placeholders::_4);
m_server.setServerStatus(UnitTestShaderCompilerServer::BadServer_DisconnectAfterConnect);
m_server.startServer();
m_connectionManager->SendMessageToService(m_connectionId, AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), 0, m_testPayload);
}
void ShaderCompilerUnitTest::VerifyPayloadForGoodShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
unsigned int messageSize;
quint8 status;
QByteArray payloadToCheck;
unsigned int requestId;
memcpy((&messageSize), payload.data(), sizeof(unsigned int));
memcpy((&status), payload.data() + sizeof(unsigned int), sizeof(unsigned char));
payloadToCheck.resize(messageSize);
memcpy((payloadToCheck.data()), payload.data() + sizeof(unsigned int) + sizeof(unsigned char), messageSize);
memcpy((&requestId), payload.data() + sizeof(unsigned int) + sizeof(unsigned char) + messageSize, sizeof(unsigned int));
QString outgoingTestString = "Test string validated";
if (QString::compare(QString(payloadToCheck), outgoingTestString, Qt::CaseSensitive) != 0)
{
Q_EMIT UnitTestFailed("Unit Test for Good Shader Compiler Failed");
return;
}
Q_EMIT StartUnitTestForFirstBadShaderCompiler();
}
void ShaderCompilerUnitTest::VerifyPayloadForFirstBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
QString error = "Remote IP is taking too long to respond: 127.0.0.1";
if ((payload.size() != 4) || (QString::compare(m_lastShaderCompilerErrorMessage, error, Qt::CaseSensitive) != 0))
{
Q_EMIT UnitTestFailed("Unit Test for First Bad Shader Compiler Failed");
return;
}
m_lastShaderCompilerErrorMessage.clear();
Q_EMIT StartUnitTestForSecondBadShaderCompiler();
}
void ShaderCompilerUnitTest::VerifyPayloadForSecondBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
QString error = "Remote IP is taking too long to respond: 127.0.0.1";
if ((payload.size() != 4) || (QString::compare(m_lastShaderCompilerErrorMessage, error, Qt::CaseSensitive) != 0))
{
Q_EMIT UnitTestFailed("Unit Test for Second Bad Shader Compiler Failed");
return;
}
m_lastShaderCompilerErrorMessage.clear();
Q_EMIT StartUnitTestForThirdBadShaderCompiler();
}
void ShaderCompilerUnitTest::VerifyPayloadForThirdBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload)
{
(void) connId;
(void) type;
(void) serial;
m_shaderCompilerManager.m_sendResponseCallbackFn = nullptr;
QString error = "Remote IP is taking too long to respond: 127.0.0.1";
if ((payload.size() != 4) || (QString::compare(m_lastShaderCompilerErrorMessage, error, Qt::CaseSensitive) != 0))
{
Q_EMIT UnitTestFailed("Unit Test for Third Bad Shader Compiler Failed");
return;
}
m_lastShaderCompilerErrorMessage.clear();
Q_EMIT UnitTestPassed();
}
void ShaderCompilerUnitTest::ReceiveShaderCompilerErrorMessage(QString error, QString server, QString timestamp, QString payload)
{
(void) server;
(void) timestamp;
(void) payload;
m_lastShaderCompilerErrorMessage = error;
}
REGISTER_UNIT_TEST(ShaderCompilerUnitTest)
@@ -1,81 +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
*
*/
#ifndef SHADERCOMPILERUNITTEST_H
#define SHADERCOMPILERUNITTEST_H
#if !defined(Q_MOC_RUN)
#include "UnitTestRunner.h"
#include <AzCore/std/functional.h>
#include "native/shadercompiler/shadercompilerManager.h"
//#include "native/shadercompiler/shadercompilerMessages.h"
#include "native/utilities/UnitTestShaderCompilerServer.h"
#include <QString>
#include <QByteArray>
#endif
class ConnectionManager;
class ShaderCompilerManagerForUnitTest : public ShaderCompilerManager
{
public:
explicit ShaderCompilerManagerForUnitTest(QObject* parent = 0) : ShaderCompilerManager(parent) {};
// for this test, we override sendResponse and make it so that it just calls a callback instead of actually sending it to the connection manager.
void sendResponse(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload) override
{
if (m_sendResponseCallbackFn)
{
m_sendResponseCallbackFn(connId, type, serial, payload);
}
}
AZStd::function<void(unsigned int, unsigned int, unsigned int, QByteArray)> m_sendResponseCallbackFn;
};
class ShaderCompilerUnitTest
: public UnitTestRun
{
Q_OBJECT
public:
ShaderCompilerUnitTest();
~ShaderCompilerUnitTest();
virtual void StartTest() override;
virtual int UnitTestPriority() const override;
void ContructPayloadForShaderCompilerServer(QByteArray& payload);
Q_SIGNALS:
void StartUnitTestForGoodShaderCompiler();
void StartUnitTestForFirstBadShaderCompiler();
void StartUnitTestForSecondBadShaderCompiler();
void StartUnitTestForThirdBadShaderCompiler();
public Q_SLOTS:
void UnitTestForGoodShaderCompiler();
void UnitTestForFirstBadShaderCompiler();
void UnitTestForSecondBadShaderCompiler();
void UnitTestForThirdBadShaderCompiler();
void VerifyPayloadForGoodShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void VerifyPayloadForFirstBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void VerifyPayloadForSecondBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void VerifyPayloadForThirdBadShaderCompiler(unsigned int connId, unsigned int type, unsigned int serial, QByteArray payload);
void ReceiveShaderCompilerErrorMessage(QString error, QString server, QString timestamp, QString payload);
private:
UnitTestShaderCompilerServer m_server;
ShaderCompilerManagerForUnitTest m_shaderCompilerManager;
ConnectionManager* m_connectionManager;
QByteArray m_testPayload;
QString m_lastShaderCompilerErrorMessage;
unsigned int m_connectionId = 0;
};
#endif // SHADERCOMPILERUNITTEST_H
@@ -12,8 +12,6 @@
#include "native/resourcecompiler/rccontroller.h"
#include "native/FileServer/fileServer.h"
#include "native/AssetManager/assetScanner.h"
#include "native/shadercompiler/shadercompilerManager.h"
#include "native/shadercompiler/shadercompilerModel.h"
#include <QApplication>
#include <QDialogButtonBox>
@@ -55,7 +53,7 @@ namespace
{
moduleFileInfo.setFile(executableDirectory);
}
QDir binaryDir = moduleFileInfo.absoluteDir();
// strip extension
QString applicationBase = moduleFileInfo.completeBaseName();
@@ -70,7 +68,7 @@ namespace
binaryDir.remove(tempFile);
}
}
}
@@ -145,8 +143,6 @@ void GUIApplicationManager::Destroy()
DestroyIniConfiguration();
DestroyFileServer();
DestroyShaderCompilerManager();
DestroyShaderCompilerModel();
}
@@ -192,7 +188,7 @@ bool GUIApplicationManager::Run()
wrapper->enableSaveRestoreGeometry(GetOrganizationName(), GetApplicationName(), "MainWindow", restoreOnFirstShow);
AzQtComponents::StyleManager::setStyleSheet(m_mainWindow, QStringLiteral("style:AssetProcessor.qss"));
auto refreshStyleSheets = [styleManager]()
{
styleManager->Refresh();
@@ -334,7 +330,7 @@ bool GUIApplicationManager::Run()
m_duringStartup = false;
int resultCode = qApp->exec(); // this blocks until the last window is closed.
if(!InitiatedShutdown())
{
// if we are here it implies that AP did not stop the Qt event loop and is shutting down prematurely
@@ -427,7 +423,7 @@ bool GUIApplicationManager::OnError(const char* /*window*/, const char* message)
return true;
}
// If we're the main thread, then consider showing the message box directly.
// If we're the main thread, then consider showing the message box directly.
// note that all other threads will PAUSE if they emit a message while the main thread is showing this box
// due to the way the trace system EBUS is mutex-protected.
Qt::ConnectionType connection = Qt::DirectConnection;
@@ -470,7 +466,7 @@ bool GUIApplicationManager::Activate()
AssetUtilities::ComputeProjectCacheRoot(projectCacheRoot);
m_localUserSettings.Load(projectCacheRoot.filePath("AssetProcessorUserSettings.xml").toUtf8().data(), context);
m_localUserSettings.Activate(AZ::UserSettings::CT_LOCAL);
InitIniConfiguration();
InitFileServer();
@@ -479,9 +475,6 @@ bool GUIApplicationManager::Activate()
{
return false;
}
InitShaderCompilerModel();
InitShaderCompilerManager();
return true;
}
@@ -606,7 +599,7 @@ void GUIApplicationManager::InitConnectionManager()
QObject::connect(m_fileServer, SIGNAL(AddRenameRequest(unsigned int, bool)), m_connectionManager, SLOT(AddRenameRequest(unsigned int, bool)));
QObject::connect(m_fileServer, SIGNAL(AddFindFileNamesRequest(unsigned int, bool)), m_connectionManager, SLOT(AddFindFileNamesRequest(unsigned int, bool)));
QObject::connect(m_fileServer, SIGNAL(UpdateConnectionMetrics()), m_connectionManager, SLOT(UpdateConnectionMetrics()));
m_connectionManager->RegisterService(ShowAssetProcessorRequest::MessageType,
std::bind([this](unsigned int /*connId*/, unsigned int /*type*/, unsigned int /*serial*/, QByteArray /*payload*/)
{
@@ -661,40 +654,6 @@ void GUIApplicationManager::DestroyFileServer()
}
}
void GUIApplicationManager::InitShaderCompilerManager()
{
m_shaderCompilerManager = new ShaderCompilerManager();
//Shader compiler stuff
m_connectionManager->RegisterService(AssetUtilities::ComputeCRC32Lowercase("ShaderCompilerProxyRequest"), std::bind(&ShaderCompilerManager::process, m_shaderCompilerManager, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3, std::placeholders::_4));
QObject::connect(m_shaderCompilerManager, SIGNAL(sendErrorMessageFromShaderJob(QString, QString, QString, QString)), m_shaderCompilerModel, SLOT(addShaderErrorInfoEntry(QString, QString, QString, QString)));
}
void GUIApplicationManager::DestroyShaderCompilerManager()
{
if (m_shaderCompilerManager)
{
delete m_shaderCompilerManager;
m_shaderCompilerManager = nullptr;
}
}
void GUIApplicationManager::InitShaderCompilerModel()
{
m_shaderCompilerModel = new ShaderCompilerModel();
}
void GUIApplicationManager::DestroyShaderCompilerModel()
{
if (m_shaderCompilerModel)
{
delete m_shaderCompilerModel;
m_shaderCompilerModel = nullptr;
}
}
IniConfiguration* GUIApplicationManager::GetIniConfiguration() const
{
return m_iniConfiguration;
@@ -704,14 +663,6 @@ FileServer* GUIApplicationManager::GetFileServer() const
{
return m_fileServer;
}
ShaderCompilerManager* GUIApplicationManager::GetShaderCompilerManager() const
{
return m_shaderCompilerManager;
}
ShaderCompilerModel* GUIApplicationManager::GetShaderCompilerModel() const
{
return m_shaderCompilerModel;
}
void GUIApplicationManager::ShowTrayIconErrorMessage(QString msg)
{
@@ -24,8 +24,6 @@ class ConnectionManager;
class IniConfiguration;
class ApplicationServer;
class FileServer;
class ShaderCompilerManager;
class ShaderCompilerModel;
namespace AssetProcessor
{
@@ -47,8 +45,6 @@ public:
ApplicationManager::BeforeRunStatus BeforeRun() override;
IniConfiguration* GetIniConfiguration() const;
FileServer* GetFileServer() const;
ShaderCompilerManager* GetShaderCompilerManager() const;
ShaderCompilerModel* GetShaderCompilerModel() const;
bool Run() override;
////////////////////////////////////////////////////
@@ -72,10 +68,6 @@ private:
void DestroyIniConfiguration();
void InitFileServer();
void DestroyFileServer();
void InitShaderCompilerManager();
void DestroyShaderCompilerManager();
void InitShaderCompilerModel();
void DestroyShaderCompilerModel();
void Destroy() override;
Q_SIGNALS:
@@ -99,8 +91,7 @@ private:
IniConfiguration* m_iniConfiguration = nullptr;
FileServer* m_fileServer = nullptr;
ShaderCompilerManager* m_shaderCompilerManager = nullptr;
ShaderCompilerModel* m_shaderCompilerModel = nullptr;
QFileSystemWatcher m_qtFileWatcher;
AZ::UserSettingsProvider m_localUserSettings;
bool m_messageBoxIsVisible = false;
@@ -13,4 +13,5 @@
#ifdef AZ_COLLECTING_PARTIAL_SRGS
#include <Atom/RPI/ShaderResourceGroups/DefaultSceneSrg.azsli>
#include <Atom/Feature/Common/Assets/ShaderResourceGroups/SceneSrg.azsli>
#include <Terrain/Assets/Shaders/Terrain/SceneSrg.azsli> // Temporary until gem partial view srgs can be included automatically.
#endif
@@ -12,5 +12,4 @@
#ifdef AZ_COLLECTING_PARTIAL_SRGS
#include <Atom/Feature/Common/Assets/ShaderResourceGroups/ViewSrg.azsli>
#include <Terrain/Assets/Shaders/Terrain/ViewSrg.azsli> // Temporary until gem partial view srgs can be included automatically.
#endif
@@ -101,6 +101,13 @@ namespace AZ
void EsmShadowmapsPass::UpdateChildren()
{
const RPI::PassAttachmentBinding& inputBinding = GetInputBinding(0);
if (!inputBinding.m_attachment)
{
AZ_Assert(false, "[EsmShadowmapsPass %s] requires an input attachment", GetPathName().GetCStr());
return;
}
AZ_Assert(inputBinding.m_attachment->m_descriptor.m_type == RHI::AttachmentType::Image, "[EsmShadowmapsPass %s] input attachment requires an image attachment", GetPathName().GetCStr());
m_shadowmapImageSize = inputBinding.m_attachment->m_descriptor.m_image.m_size;
m_shadowmapArraySize = inputBinding.m_attachment->m_descriptor.m_image.m_arraySize;
@@ -24,14 +24,14 @@ namespace AZ
{
m_elementSize = descriptor.m_elementSize;
m_elementCount = 0;
m_bufferIndex = descriptor.m_srgLayout->FindShaderInputBufferIndex(Name(descriptor.m_bufferSrgName));
AZ_Error(ClassName, m_bufferIndex.IsValid(), "Unable to find %s in view shader resource group.", descriptor.m_bufferSrgName.c_str());
AZ_Error(ClassName, m_bufferIndex.IsValid(), "Unable to find %s in %s shader resource group.", descriptor.m_bufferSrgName.c_str(), descriptor.m_srgLayout->GetName().GetCStr());
if (!descriptor.m_elementCountSrgName.empty())
{
m_elementCountIndex = descriptor.m_srgLayout->FindShaderInputConstantIndex(Name(descriptor.m_elementCountSrgName));
AZ_Error(ClassName, m_elementCountIndex.IsValid(), "Unable to find %s in view shader resource group.", descriptor.m_elementCountSrgName.c_str());
AZ_Error(ClassName, m_elementCountIndex.IsValid(), "Unable to find %s in %s shader resource group.", descriptor.m_elementCountSrgName.c_str(), descriptor.m_srgLayout->GetName().GetCStr());
}
if (m_bufferIndex.IsValid())
@@ -28,101 +28,105 @@
</property>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QDockWidget" name="m_pATLControlsDockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
<widget class="QSplitter" name="splitter">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>ATL Controls</string>
</property>
<widget class="QWidget" name="m_pATLControlsWidget">
<layout class="QVBoxLayout" name="m_pATLControlsDockLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDockWidget" name="m_pInspectorDockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>Inspector</string>
</property>
<widget class="QWidget" name="m_pInspectorWidget">
<layout class="QVBoxLayout" name="m_pInspectorDockLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
</layout>
</widget>
</widget>
</item>
<item>
<widget class="QDockWidget" name="m_pMiddlewareDockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="floating">
<property name="childrenCollapsible">
<bool>false</bool>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>Audio Middleware Controls</string>
</property>
<widget class="QWidget" name="m_pMiddlewareWidget">
<layout class="QVBoxLayout" name="m_pMiddlewareDockLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
</layout>
<widget class="QDockWidget" name="m_pATLControlsDockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>ATL Controls</string>
</property>
<widget class="QWidget" name="m_pATLControlsWidget">
<layout class="QVBoxLayout" name="m_pATLControlsDockLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="m_pInspectorDockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>2</horstretch>
<verstretch>1</verstretch>
</sizepolicy>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>Inspector</string>
</property>
<widget class="QWidget" name="m_pInspectorWidget">
<layout class="QVBoxLayout" name="m_pInspectorDockLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
</layout>
</widget>
</widget>
<widget class="QDockWidget" name="m_pMiddlewareDockWidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Preferred">
<horstretch>1</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="floating">
<bool>false</bool>
</property>
<property name="features">
<set>QDockWidget::NoDockWidgetFeatures</set>
</property>
<property name="windowTitle">
<string>Audio Middleware Controls</string>
</property>
<widget class="QWidget" name="m_pMiddlewareWidget">
<layout class="QVBoxLayout" name="m_pMiddlewareDockLayout">
<property name="leftMargin">
<number>4</number>
</property>
<property name="topMargin">
<number>9</number>
</property>
<property name="rightMargin">
<number>4</number>
</property>
<property name="bottomMargin">
<number>9</number>
</property>
</layout>
</widget>
</widget>
</widget>
</item>
@@ -134,7 +138,7 @@
<x>0</x>
<y>0</y>
<width>972</width>
<height>21</height>
<height>26</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
@@ -143,6 +147,7 @@
</property>
<addaction name="actionSaveAll"/>
<addaction name="actionReload"/>
<addaction name="actionRefreshAudioSystem"/>
</widget>
<addaction name="menuFile"/>
</widget>
@@ -166,6 +171,17 @@
<property name="text">
<string>Reload</string>
</property>
<property name="shortcut">
<string>Ctrl+R</string>
</property>
</action>
<action name="actionRefreshAudioSystem">
<property name="text">
<string>Refresh Audio System</string>
</property>
<property name="shortcut">
<string>Ctrl+Shift+R</string>
</property>
</action>
</widget>
<resources>
@@ -204,6 +220,22 @@
</hint>
</hints>
</connection>
<connection>
<sender>actionRefreshAudioSystem</sender>
<signal>triggered()</signal>
<receiver>MainWindow</receiver>
<slot>RefreshAudioSystem()</slot>
<hints>
<hint type="sourcelabel">
<x>-1</x>
<y>-1</y>
</hint>
<hint type="destinationlabel">
<x>485</x>
<y>336</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<signal>CurrentControlNameChanged(QString)</signal>
@@ -198,6 +198,20 @@ namespace AudioControls
}
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsEditorWindow::RefreshAudioSystem()
{
QString sLevelName = GetIEditor()->GetLevelName();
if (QString::compare(sLevelName, "Untitled", Qt::CaseInsensitive) == 0)
{
// Rather pass empty QString to indicate that no level is loaded!
sLevelName = QString();
}
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::RefreshAudioSystem, sLevelName.toUtf8().data());
}
//-------------------------------------------------------------------------------------------//
void CAudioControlsEditorWindow::Save()
{
@@ -215,15 +229,7 @@ namespace AudioControls
messageBox.setWindowTitle("Audio Controls Editor");
if (messageBox.exec() == QMessageBox::Yes)
{
QString sLevelName = GetIEditor()->GetLevelName();
if (QString::compare(sLevelName, "Untitled", Qt::CaseInsensitive) == 0)
{
// Rather pass empty QString to indicate that no level is loaded!
sLevelName = QString();
}
Audio::AudioSystemRequestBus::Broadcast(&Audio::AudioSystemRequestBus::Events::RefreshAudioSystem, sLevelName.toUtf8().data());
RefreshAudioSystem();
}
}
m_pATLModel->ClearDirtyFlags();
@@ -61,6 +61,7 @@ namespace AudioControls
void UpdateInspector();
void FilterControlType(EACEControlType type, bool bShow);
void Update();
void RefreshAudioSystem();
protected:
void closeEvent(QCloseEvent* pEvent) override;
@@ -439,6 +439,12 @@ namespace CommandSystem
return false;
}
if (actorInstance->GetEntity())
{
outResult = AZStd::string::format("Cannot remove actor instance. Actor instance %i belongs to an entity.", actorInstanceID);
return false;
}
// store the old values before removing the instance
m_oldPosition = actorInstance->GetLocalSpaceTransform().m_position;
m_oldRotation = actorInstance->GetLocalSpaceTransform().m_rotation;
@@ -618,7 +624,7 @@ namespace CommandSystem
MCore::CommandGroup commandGroup("Remove actor instances", numActorInstances);
AZStd::string tempString;
// iterate over the selected instances and clone them
// iterate over the selected instances and remove them
for (size_t i = 0; i < numActorInstances; ++i)
{
// get the current actor instance
@@ -628,6 +634,18 @@ namespace CommandSystem
continue;
}
// Do not remove any runtime instance from the manager using the commands.
if (actorInstance->GetIsOwnedByRuntime())
{
continue;
}
// Do not remove the any instances owned by an entity from the manager using the commands.
if (actorInstance->GetEntity())
{
continue;
}
tempString = AZStd::string::format("RemoveActorInstance -actorInstanceID %i", actorInstance->GetID());
commandGroup.AddCommandString(tempString.c_str());
}
@@ -455,8 +455,17 @@ namespace CommandSystem
EMotionFX::ActorInstance* actorInstance = nullptr;
if (parameters.CheckIfHasParameter("actorInstanceID"))
{
const uint32 actorInstanceID = parameters.GetValueAsInt("actorInstanceID", this);
actorInstance = EMotionFX::GetActorManager().FindActorInstanceByID(actorInstanceID);
const int actorInstanceID = parameters.GetValueAsInt("actorInstanceID", this);
if (actorInstanceID == -1)
{
// If there isn't an actorInstanceId, grab the first actor instance.
actorInstance = EMotionFX::GetActorManager().GetFirstEditorActorInstance();
}
else
{
actorInstance = EMotionFX::GetActorManager().FindActorInstanceByID(actorInstanceID);
}
if (!actorInstance)
{
outResult = AZStd::string::format("Cannot activate anim graph. Actor instance id '%i' is not valid.", actorInstanceID);
@@ -434,6 +434,20 @@ namespace EMotionFX
}
ActorInstance* ActorManager::GetFirstEditorActorInstance() const
{
const size_t numActorInstances = m_actorInstances.size();
for (size_t i = 0; i < numActorInstances; ++i)
{
if (!m_actorInstances[i]->GetIsOwnedByRuntime())
{
return m_actorInstances[i];
}
}
return nullptr;
}
const AZStd::vector<ActorInstance*>& ActorManager::GetActorInstanceArray() const
{
return m_actorInstances;
@@ -136,6 +136,12 @@ namespace EMotionFX
*/
MCORE_INLINE ActorInstance* GetActorInstance(size_t nr) const { return m_actorInstances[nr]; }
/**
* Get a given registered actor instance owned by editor (not owned by runtime).
* @result A pointer to the actor instance.
*/
ActorInstance* GetFirstEditorActorInstance() const;
/**
* Get the array of actor instances.
* @result The const reference to the actor instance array.
@@ -0,0 +1,83 @@
/*
* 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 <EMotionFX/Source/Velocity.h>
#include <AzCore/Math/MathUtils.h>
namespace EMotionFX
{
AZ::Vector3 CalculateLinearVelocity(const AZ::Vector3& lastPosition,
const AZ::Vector3& currentPosition,
float timeDelta)
{
if (timeDelta <= AZ::Constants::FloatEpsilon)
{
return AZ::Vector3::CreateZero();
}
const AZ::Vector3 deltaPosition = currentPosition - lastPosition;
const AZ::Vector3 velocity = deltaPosition / timeDelta;
if (velocity.GetLength() > AZ::Constants::FloatEpsilon)
{
return velocity;
}
return AZ::Vector3::CreateZero();
}
AZ::Vector3 CalculateAngularVelocity(const AZ::Quaternion& lastRotation,
const AZ::Quaternion& currentRotation,
float timeDelta)
{
if (timeDelta <= AZ::Constants::FloatEpsilon)
{
return AZ::Vector3::CreateZero();
}
const AZ::Quaternion deltaRotation = currentRotation * lastRotation.GetInverseFull();
const AZ::Quaternion shortestEquivalent = deltaRotation.GetShortestEquivalent().GetNormalized();
const AZ::Vector3 scaledAxisAngle = shortestEquivalent.ConvertToScaledAxisAngle();
const AZ::Vector3 angularVelocity = scaledAxisAngle / timeDelta;
if (angularVelocity.GetLength() > AZ::Constants::FloatEpsilon)
{
return angularVelocity;
}
return AZ::Vector3::CreateZero();
}
void DebugDrawVelocity(AzFramework::DebugDisplayRequests& debugDisplay,
const AZ::Vector3& position, const AZ::Vector3& velocity, const AZ::Color& color)
{
// Don't visualize joints that remain motionless (zero velocity).
if (velocity.GetLength() < AZ::Constants::FloatEpsilon)
{
return;
}
const float scale = 0.15f;
const AZ::Vector3 arrowPosition = position + velocity;
debugDisplay.DepthTestOff();
debugDisplay.SetColor(color);
debugDisplay.DrawSolidCylinder(/*center=*/(arrowPosition + position) * 0.5f,
/*direction=*/(arrowPosition - position).GetNormalizedSafe(),
/*radius=*/0.003f,
/*height=*/(arrowPosition - position).GetLength(),
/*drawShaded=*/false);
debugDisplay.DrawSolidCone(position + velocity,
velocity,
0.1f * scale,
scale * 0.5f,
/*drawShaded=*/false);
}
} // namespace EMotionFX
@@ -0,0 +1,23 @@
/*
* 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/Vector3.h>
#include <AzCore/Math/Quaternion.h>
#include <AzFramework/Entity/EntityDebugDisplayBus.h>
#include <EMotionFX/Source/EMotionFXConfig.h>
namespace EMotionFX
{
AZ::Vector3 EMFX_API CalculateLinearVelocity(const AZ::Vector3& lastPosition, const AZ::Vector3& currentPosition, float timeDelta);
AZ::Vector3 EMFX_API CalculateAngularVelocity(const AZ::Quaternion& lastRotation, const AZ::Quaternion& currentRotation, float timeDelta);
void EMFX_API DebugDrawVelocity(AzFramework::DebugDisplayRequests& debugDisplay,
const AZ::Vector3& position, const AZ::Vector3& velocity, const AZ::Color& color);
} // namespace EMotionFX
@@ -26,6 +26,7 @@
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/CommandSystem/Source/MiscCommands.h>
#include <EMotionFX/CommandSystem/Source/SelectionCommands.h>
#include <EMotionFX/Tools/EMotionStudio/Plugins/RenderPlugins/Source/OpenGLRender/OpenGLRenderPlugin.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzQtComponents/Components/FancyDocking.h>
@@ -1340,8 +1341,15 @@ namespace EMStudio
// add the load and the create instance commands
commandGroup.AddCommandString(loadActorCommand.c_str());
commandGroup.AddCommandString("CreateActorInstance -actorID %LASTRESULT%");
// Temp solution after we refactor / remove the actor manager.
// We only need to create the actor instance by ourselves when openGLRenderPlugin is present.
// Atom render viewport will create actor instance along with the actor component.
PluginManager* pluginManager = GetPluginManager();
if (pluginManager->FindActivePlugin(static_cast<uint32>(OpenGLRenderPlugin::CLASS_ID)))
{
commandGroup.AddCommandString("CreateActorInstance -actorID %LASTRESULT%");
}
// execute the group command
if (GetCommandManager()->ExecuteCommandGroup(commandGroup, outResult) == false)
@@ -429,6 +429,7 @@ namespace EMStudio
// 3. Relink the actor instances with the emstudio actors
const size_t numActorInstances = EMotionFX::GetActorManager().GetNumActorInstances();
size_t numActorInstancesInRenderPlugin = 0;
for (size_t i = 0; i < numActorInstances; ++i)
{
EMotionFX::ActorInstance* actorInstance = EMotionFX::GetActorManager().GetActorInstance(i);
@@ -440,6 +441,12 @@ namespace EMStudio
continue;
}
if (actorInstance->GetEntity())
{
continue;
}
numActorInstancesInRenderPlugin++;
if (!emstudioActor)
{
for (EMStudioRenderActor* currentEMStudioActor : m_actors)
@@ -485,6 +492,7 @@ namespace EMStudio
if (found == false)
{
emstudioActor->m_actorInstances.erase(AZStd::next(begin(emstudioActor->m_actorInstances), j));
numActorInstancesInRenderPlugin--;
}
else
{
@@ -497,7 +505,7 @@ namespace EMStudio
m_reinitRequested = false;
// zoom the camera to the available character only in case we're dealing with a single instance
if (resetViewCloseup && numActorInstances == 1)
if (resetViewCloseup && numActorInstancesInRenderPlugin == 1)
{
ViewCloseup(false);
}
@@ -23,6 +23,7 @@
#include <EMotionFX/CommandSystem/Source/CommandManager.h>
#include <EMotionFX/CommandSystem/Source/MotionSetCommands.h>
#include <EMotionFX/Source/ActorManager.h>
#include <EMotionFX/Tools/EMotionStudio/Plugins/RenderPlugins/Source/OpenGLRender/OpenGLRenderPlugin.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
@@ -430,6 +431,18 @@ namespace EMStudio
continue;
}
// Temp solution after we refactor / remove the actor manager.
// We only need to create the actor instance by ourselves when openGLRenderPlugin is present.
// Atom render viewport will create actor instance along with the actor component.
PluginManager* pluginManager = GetPluginManager();
if (!pluginManager->FindActivePlugin(static_cast<uint32>(OpenGLRenderPlugin::CLASS_ID)))
{
if (commands[i].find("CreateActorInstance") == 0)
{
continue;
}
}
AzFramework::StringFunc::Replace(commands[i], "@products@", assetCacheFolder.c_str());
AzFramework::StringFunc::Replace(commands[i], "@assets@", assetCacheFolder.c_str());
AzFramework::StringFunc::Replace(commands[i], "@root@", assetCacheFolder.c_str());
@@ -144,6 +144,8 @@ set(FILES
Source/TransformData.h
Source/TriggerActionSetup.cpp
Source/TriggerActionSetup.h
Source/Velocity.cpp
Source/Velocity.h
Source/VertexAttributeLayer.cpp
Source/VertexAttributeLayer.h
Source/VertexAttributeLayerAbstractData.cpp
@@ -547,7 +547,12 @@ namespace MCore
}
tmpStr = commandString.substr(lastResultIndex, rightPercentagePos - lastResultIndex + 1);
AzFramework::StringFunc::Replace(commandString, tmpStr.c_str(), intermediateCommandResults[i - relativeIndex].c_str());
AZStd::string replaceStr = intermediateCommandResults[i - relativeIndex];
if (replaceStr.empty())
{
replaceStr = "-1";
}
AzFramework::StringFunc::Replace(commandString, tmpStr.c_str(), replaceStr.c_str());
replaceHappen = true;
// Search again in case the command group is referring to other results
@@ -234,6 +234,10 @@ namespace EMotionFX
const float updateRateInSeconds = animGraphSampleRate > 0.0f ? 1.0f / animGraphSampleRate : 0.0f;
actorInstance->SetMotionSamplingRate(updateRateInSeconds);
}
else if (actorInstance->GetMotionSamplingRate() != 0)
{
actorInstance->SetMotionSamplingRate(0);
}
// Disable the automatic mesh LOD level adjustment based on screen space in case a simple LOD component is present.
// The simple LOD component overrides the mesh LOD level and syncs the skeleton with the mesh LOD level.
@@ -249,6 +249,9 @@ namespace FastNoiseGem
void FastNoiseGradientComponent::Activate()
{
// This will immediately call OnGradientTransformChanged and initialize m_gradientTransform.
GradientSignal::GradientTransformNotificationBus::Handler::BusConnect(GetEntityId());
// Some platforms require random seeds to be > 0. Clamp to a positive range to ensure we're always safe.
m_generator.SetSeed(AZ::GetMax(m_configuration.m_seed, 1));
m_generator.SetFrequency(m_configuration.m_frequency);
@@ -272,6 +275,7 @@ namespace FastNoiseGem
{
GradientSignal::GradientRequestBus::Handler::BusDisconnect();
FastNoiseGradientRequestBus::Handler::BusDisconnect();
GradientSignal::GradientTransformNotificationBus::Handler::BusDisconnect();
}
bool FastNoiseGradientComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
@@ -294,14 +298,21 @@ namespace FastNoiseGem
return false;
}
void FastNoiseGradientComponent::OnGradientTransformChanged(const GradientSignal::GradientTransform& newTransform)
{
AZStd::unique_lock<decltype(m_transformMutex)> lock(m_transformMutex);
m_gradientTransform = newTransform;
}
float FastNoiseGradientComponent::GetValue(const GradientSignal::GradientSampleParams& sampleParams) const
{
AZ::Vector3 uvw = sampleParams.m_position;
bool wasPointRejected = false;
const bool shouldNormalizeOutput = false;
GradientSignal::GradientTransformRequestBus::Event(
GetEntityId(), &GradientSignal::GradientTransformRequestBus::Events::TransformPositionToUVW, sampleParams.m_position, uvw, shouldNormalizeOutput, wasPointRejected);
{
AZStd::shared_lock<decltype(m_transformMutex)> lock(m_transformMutex);
m_gradientTransform.TransformPositionToUVW(sampleParams.m_position, uvw, wasPointRejected);
}
if (!wasPointRejected)
{
@@ -12,6 +12,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/RTTI/TypeInfo.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <GradientSignal/Ebuses/GradientTransformRequestBus.h>
#include <FastNoise/Ebuses/FastNoiseGradientRequestBus.h>
#include <External/FastNoise/FastNoise.h>
@@ -67,6 +68,7 @@ namespace FastNoiseGem
: public AZ::Component
, private GradientSignal::GradientRequestBus::Handler
, private FastNoiseGradientRequestBus::Handler
, private GradientSignal::GradientTransformNotificationBus::Handler
{
public:
friend class EditorFastNoiseGradientComponent;
@@ -80,23 +82,25 @@ namespace FastNoiseGem
FastNoiseGradientComponent(const FastNoiseGradientConfig& configuration);
FastNoiseGradientComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// GradientRequestBus
// GradientRequestBus overrides...
float GetValue(const GradientSignal::GradientSampleParams& sampleParams) const override;
protected:
FastNoiseGradientConfig m_configuration;
FastNoise m_generator;
GradientSignal::GradientTransform m_gradientTransform;
mutable AZStd::shared_mutex m_transformMutex;
/////////////////////////////////////////////////////////////////////////
// FastNoiseGradientRequest overrides
// GradientTransformNotificationBus overrides...
void OnGradientTransformChanged(const GradientSignal::GradientTransform& newTransform) override;
// FastNoiseGradientRequest overrides...
int GetRandomSeed() const override;
void SetRandomSeed(int seed) override;
+6 -3
View File
@@ -46,9 +46,10 @@ public:
////////////////////////////////////////////////////////////////////////////
//// GradientTransformRequestBus
void TransformPositionToUVW([[maybe_unused]] const AZ::Vector3& inPosition, [[maybe_unused]] AZ::Vector3& outUVW, [[maybe_unused]] const bool shouldNormalizeOutput, [[maybe_unused]] bool& wasPointRejected) const override {}
void GetGradientLocalBounds([[maybe_unused]] AZ::Aabb& bounds) const override {}
void GetGradientEncompassingBounds([[maybe_unused]] AZ::Aabb& bounds) const override {}
const GradientSignal::GradientTransform& GetGradientTransform() const override
{
return m_gradientTransform;
}
//////////////////////////////////////////////////////////////////////////
// GradientTransformModifierRequestBus
@@ -96,6 +97,8 @@ public:
bool GetAdvancedMode() const override { return false; }
void SetAdvancedMode([[maybe_unused]] bool value) override {}
GradientSignal::GradientTransform m_gradientTransform;
};
TEST(FastNoiseTest, ComponentsWithComponentApplication)
+8
View File
@@ -136,6 +136,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzTestShared
Gem::GradientSignal.Static
Gem::LmbrCentral
Gem::GradientSignal.Mocks
@@ -144,6 +145,12 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
NAME Gem::GradientSignal.Tests
)
ly_add_googlebenchmark(
NAME Gem::GradientSignal.Benchmarks
TARGET Gem::GradientSignal.Tests
)
if(PAL_TRAIT_BUILD_HOST_TOOLS)
ly_add_target(
NAME GradientSignal.Editor.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
@@ -157,6 +164,7 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
BUILD_DEPENDENCIES
PRIVATE
AZ::AzTest
AZ::AzTestShared
Gem::GradientSignal.Static
Gem::GradientSignal.Editor.Static
Gem::LmbrCentral.Editor
@@ -13,14 +13,30 @@
namespace GradientSignal
{
//! TransformType describes where the gradient's origin is mapped to.
enum class TransformType : AZ::u8
{
//! The gradient's origin is the world position of this entity.
World_ThisEntity = 0,
//! The gradient's origin is the local position of this entity, but in world space.
//! i.e. If the parent is at (2, 2), and the gradient is at (3,3) in local space, the gradient entity itself will be at (5,5) in
//! world space but its origin will frozen at (3,3) in world space, no matter how much the parent moves around.
Local_ThisEntity,
//! The gradient's origin is the world position of the reference entity.
World_ReferenceEntity,
//! The gradient's origin is the local position of the reference entity, but in world space.
Local_ReferenceEntity,
//! The gradient's origin is at (0,0,0) in world space.
World_Origin,
//! The gradient's origin is in translated world space relative to the reference entity.
Relative,
};
class GradientTransformModifierRequests
: public AZ::ComponentBus
{
public:
/**
* Overrides the default AZ::EBusTraits handler policy to allow one
* listener only.
*/
//! Overrides the default AZ::EBusTraits handler policy to allow only one listener.
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
virtual bool GetAllowReference() const = 0;
@@ -11,6 +11,7 @@
#include <AzCore/EBus/EBus.h>
#include <AzCore/Math/Aabb.h>
#include <AzCore/Math/Vector3.h>
#include <GradientSignal/GradientTransform.h>
namespace GradientSignal
{
@@ -22,15 +23,56 @@ namespace GradientSignal
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::EntityId;
//! allows multiple threads to call shape requests
//! allows multiple threads to call gradient transform requests
using MutexType = AZStd::recursive_mutex;
virtual ~GradientTransformRequests() = default;
virtual void TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, const bool shouldNormalizeOutput, bool& wasPointRejected) const = 0;
virtual void GetGradientLocalBounds(AZ::Aabb& bounds) const = 0;
virtual void GetGradientEncompassingBounds(AZ::Aabb& bounds) const = 0;
//! Get the GradientTransform that's been configured by the bus listener.
//! \return the GradientTransform instance that can be used to transform world points into gradient lookup space.
virtual const GradientTransform& GetGradientTransform() const = 0;
};
using GradientTransformRequestBus = AZ::EBus<GradientTransformRequests>;
/**
* Notifies about changes to the GradientTransform configuration
*/
class GradientTransformNotifications
: public AZ::EBusTraits
{
public:
////////////////////////////////////////////////////////////////////////
// EBusTraits
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = AZ::EntityId;
using MutexType = AZStd::recursive_mutex;
////////////////////////////////////////////////////////////////////////
//! Notify listeners that the GradientTransform configuration has changed.
//! \return the GradientTransform instance that can be used to transform world points into gradient lookup space.
virtual void OnGradientTransformChanged(const GradientTransform& newTransform) = 0;
//! Connection policy that auto-calls OnGradientTransformChanged on connection with the current GradientTransform data.
template<class Bus>
struct ConnectionPolicy : public AZ::EBusConnectionPolicy<Bus>
{
static void Connect(
typename Bus::BusPtr& busPtr,
typename Bus::Context& context,
typename Bus::HandlerNode& handler,
typename Bus::Context::ConnectLockGuard& connectLock,
const typename Bus::BusIdType& id = 0)
{
AZ::EBusConnectionPolicy<Bus>::Connect(busPtr, context, handler, connectLock, id);
GradientTransform transform;
GradientTransformRequestBus::EventResult(transform, id, &GradientTransformRequests::GetGradientTransform);
handler->OnGradientTransformChanged(transform);
}
};
};
using GradientTransformNotificationBus = AZ::EBus<GradientTransformNotifications>;
} //namespace GradientSignal
@@ -0,0 +1,155 @@
/*
* 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/Aabb.h>
#include <AzCore/Math/Vector3.h>
#include <AzCore/Math/Matrix3x4.h>
#include <AzCore/Math/Transform.h>
#include <AzCore/std/functional.h>
namespace GradientSignal
{
//! Controls how the gradient repeats itself when queried outside the bounds of the shape.
enum class WrappingType : AZ::u8
{
None = 0, //! Unbounded - the gradient ignores the shape bounds.
ClampToEdge, //! The values on the edge of the shape will be extended outward in each direction.
Mirror, //! The gradient signal will be repeated but mirrored on every repeat.
Repeat, //! The gradient signal will be repeated in every direction.
ClampToZero, //! The value will always be 0 outside of the shape.
};
class GradientTransform
{
public:
GradientTransform() = default;
/**
* Create a GradientTransform with the given parameters.
* GradientTransform is a utility class that converts world space positions to gradient space UVW values which can be used
* to look up deterministic gradient values for the input spatial locations.
* \param shapeBounds The bounds of the shape associated with the gradient, in local space.
* \param transform The transform to use to convert from world space to gradient space.
* \param use3d True for 3D gradient lookup outputs, false for 2D gradient lookup outputs. (i.e. output W will be nonzero or zero)
* \param frequencyZoom Amount to scale the UVW results after wrapping is applied.
* \param wrappingType The way in which the gradient repeats itself outside the shape bounds.
*/
GradientTransform(
const AZ::Aabb& shapeBounds,
const AZ::Matrix3x4& transform,
bool use3d,
float frequencyZoom,
GradientSignal::WrappingType wrappingType);
/**
* Checks to see if two GradientTransform instances are equivalent.
* Useful for being able to send out notifications when a GradientTransform has changed.
* \param rhs The second GradientTranform to compare against.
* \return True if they're equal, False if they aren't.
*/
bool operator==(const GradientTransform& rhs) const
{
return (
(m_shapeBounds == rhs.m_shapeBounds) &&
(m_inverseTransform == rhs.m_inverseTransform) &&
(m_alwaysAcceptPoint == rhs.m_alwaysAcceptPoint) &&
(m_frequencyZoom == rhs.m_frequencyZoom) &&
(m_wrappingType == rhs.m_wrappingType) &&
(m_normalizeExtentsReciprocal == rhs.m_normalizeExtentsReciprocal));
}
/**
* Checks to see if two GradientTransform instances aren't equivalent.
* Useful for being able to send out notifications when a GradientTransform has changed.
* \param rhs The second GradientTranform to compare against.
* \return True if they're not equal, False if they are.
*/
bool operator!=(const GradientTransform& rhs) const
{
return !(*this == rhs);
}
/**
* Transform the given world space position to a gradient space UVW lookup value.
* \param inPosition The input world space position to transform.
* \param outUVW [out] The UVW value that can be used to look up a deterministic gradient value.
* \param wasPointRejected [out] True if the input position doesn't have a gradient value, false if it does.
* Most gradients have values mapped to infinite world space, so wasPointRejected will almost always be false.
* It will only be true when using ClampToZero and the world space position falls outside the shape bounds.
*/
void TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const;
/**
* Transform the given world space position to a gradient space UVW lookup value and normalize to the shape bounds.
* "Normalizing" in this context means that regardless of the world space coordinates, (0,0,0) represents the minimum
* shape bounds corner, and (1,1,1) represents the maximum shape bounds corner. Depending on the wrapping type, it's possible
* (and even likely) to get values outside the 0-1 range.
* \param inPosition The input world space position to transform.
* \param outUVW [out] The UVW value that can be used to look up a deterministic gradient value.
* \param wasPointRejected [out] True if the input position doesn't have a gradient value, false if it does.
* Most gradients have values mapped to infinite world space, so wasPointRejected will almost always be false.
* It will only be true when using ClampToZero and the world space position falls outside the shape bounds.
*/
void TransformPositionToUVWNormalized(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const;
/**
* Epsilon value to allow our UVW range to go to [min, max) by using the range [min, max - epsilon].
* To keep things behaving consistently between clamped and unbounded uv ranges, we want our clamped uvs to use a
* range of [min, max), so we'll actually clamp to [min, max - epsilon]. Since our floating-point numbers are likely in the
* -16384 to 16384 range, an epsilon of 0.001 will work without rounding to 0.
* (This constant is public so that it can be used from unit tests for validating transformation results)
*/
static constexpr float UvEpsilon = 0.001f;
private:
//! These are the various transformations that will be performed, based on wrapping type.
static AZ::Vector3 NoTransform(const AZ::Vector3& point, const AZ::Aabb& bounds);
static AZ::Vector3 GetUnboundedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
static AZ::Vector3 GetClampedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
static AZ::Vector3 GetMirroredPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
static AZ::Vector3 GetRelativePointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
static AZ::Vector3 GetWrappedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
//! The shape bounds are used for determining the wrapping bounds, and to normalize the UVW results into if requested.
AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull();
/**
* The relative transform to use for converting from world space to gradient space, stored as an inverse transform.
* We only ever need to use the inverse transform, so we compute it once and store it instead of keeping the original
* transform around. Note that the GradientTransformComponent has many options for choosing which relative space to use
* for the transform, so the transform passed in to this class might already have many modifications applied to it.
* The inverse transform will also get its 3rd row cleared out if "use3d" is false and we're only performing 2D gradient
* transformations, so that the W component of the UVW output will always be 0.
*/
AZ::Matrix3x4 m_inverseTransform = AZ::Matrix3x4::CreateIdentity();
/**
* Whether or not to always accept the input point as a valid output point.
* Most of the time, the gradient exists everywhere in world space, so we always accept the input point.
* The one exception is ClampToZero, which will return that the point is rejected if it falls outside the shape bounds.
*/
bool m_alwaysAcceptPoint = true;
//! Apply a scale to the point *after* the wrapping is applied.
float m_frequencyZoom = 1.0f;
//! How the gradient should repeat itself outside of the shape bounds.
WrappingType m_wrappingType = WrappingType::None;
/**
* Cached reciprocal for performing an inverse lerp back to shape bounds.
* When normalizing the output UVW back into the shape bounds, we perform an inverse lerp. The inverse lerp
* equation is (point - min) * (1 / (max-min)), so we save off the (1 / (max-min)) term to avoid recalculating it on every point.
*/
AZ::Vector3 m_normalizeExtentsReciprocal = AZ::Vector3(1.0f);
};
} // namespace GradientSignal
@@ -35,6 +35,13 @@ namespace GradientSignal
static bool VersionConverter(AZ::SerializeContext& context,
AZ::SerializeContext::DataElementNode& classElement);
ImageAsset() = default;
ImageAsset(const AZ::Data::AssetId& assetId, AZ::Data::AssetData::AssetStatus status)
: AssetData(assetId, status)
{
}
AZ::u32 m_imageWidth = 0;
AZ::u32 m_imageHeight = 0;
AZ::u8 m_bytesPerPixel = 0;
@@ -13,49 +13,10 @@
#include <AzCore/Math/Matrix3x4.h>
#include <AzCore/Math/Transform.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <GradientSignal/GradientTransform.h>
namespace GradientSignal
{
enum class WrappingType : AZ::u8
{
None = 0,
ClampToEdge,
Mirror,
Repeat,
ClampToZero,
};
enum class TransformType : AZ::u8
{
World_ThisEntity = 0,
Local_ThisEntity,
World_ReferenceEntity,
Local_ReferenceEntity,
World_Origin,
Relative,
};
AZ::Vector3 GetUnboundedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
AZ::Vector3 GetClampedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
AZ::Vector3 GetMirroredPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
AZ::Vector3 GetRelativePointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds);
inline AZ::Vector3 GetWrappedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
return AZ::Vector3(
AZ::Wrap(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
AZ::Wrap(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
AZ::Wrap(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
}
inline AZ::Vector3 GetNormalizedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
return AZ::Vector3(
AZ::LerpInverse(bounds.GetMin().GetX(), bounds.GetMax().GetX(), point.GetX()),
AZ::LerpInverse(bounds.GetMin().GetY(), bounds.GetMax().GetY(), point.GetY()),
AZ::LerpInverse(bounds.GetMin().GetZ(), bounds.GetMax().GetZ(), point.GetZ()));
}
inline void GetObbParamsFromShape(const AZ::EntityId& entity, AZ::Aabb& bounds, AZ::Matrix3x4& worldToBoundsTransform)
{
//get bound and transform data for associated shape
@@ -115,4 +76,5 @@ namespace GradientSignal
return AZ::Lerp(outputMin, outputMax, inputCorrected);
}
} // namespace GradientSignal
@@ -276,24 +276,29 @@ namespace GradientSignal
void GradientTransformComponent::Activate()
{
m_dirty = false;
m_gradientTransform = GradientTransform();
// Update our GradientTransform to be configured correctly. We don't need to notify dependents of the change though.
// If anyone is listening, they're already getting notified below.
const bool notifyDependentsOfChange = false;
UpdateFromShape(notifyDependentsOfChange);
GradientTransformRequestBus::Handler::BusConnect(GetEntityId());
LmbrCentral::DependencyNotificationBus::Handler::BusConnect(GetEntityId());
AZ::TickBus::Handler::BusConnect();
GradientTransformModifierRequestBus::Handler::BusConnect(GetEntityId());
m_dirty = false;
m_dependencyMonitor.Reset();
m_dependencyMonitor.ConnectOwner(GetEntityId());
m_dependencyMonitor.ConnectDependency(GetEntityId());
m_dependencyMonitor.ConnectDependency(GetShapeEntityId());
UpdateFromShape();
}
void GradientTransformComponent::Deactivate()
{
m_dirty = false;
m_gradientTransform = GradientTransform();
m_dependencyMonitor.Reset();
GradientTransformRequestBus::Handler::BusDisconnect();
@@ -322,66 +327,10 @@ namespace GradientSignal
return false;
}
void GradientTransformComponent::TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, const bool shouldNormalizeOutput, bool& wasPointRejected) const
const GradientTransform& GradientTransformComponent::GetGradientTransform() const
{
AZStd::lock_guard<decltype(m_cacheMutex)> lock(m_cacheMutex);
//transforming coordinate into "local" relative space of shape bounds
outUVW = m_shapeTransformInverse * inPosition;
if (!m_configuration.m_advancedMode || !m_configuration.m_is3d)
{
outUVW.SetZ(0.0f);
}
wasPointRejected = false;
if (m_shapeBounds.IsValid())
{
//all wrap types and transformations are applied after the coordinate is transformed into shape relative space
//this allows all calculations to be simplified and done using the shapes untransformed aabb
//outputting a value that can be used to sample a gradient in its local space
switch (m_configuration.m_wrappingType)
{
default:
case WrappingType::None:
outUVW = GetUnboundedPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::ClampToEdge:
outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::ClampToZero:
// We don't want to use m_shapeBounds.Contains() here because Contains() is inclusive on all edges.
// For uv consistency between clamped and unclamped states, we only want to accept uv ranges of [min, max),
// so we specifically need to exclude the max edges here.
wasPointRejected = !(outUVW.IsGreaterEqualThan(m_shapeBounds.GetMin()) && outUVW.IsLessThan(m_shapeBounds.GetMax()));
outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::Mirror:
outUVW = GetMirroredPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::Repeat:
outUVW = GetWrappedPointInAabb(outUVW, m_shapeBounds);
break;
}
}
outUVW *= m_configuration.m_frequencyZoom;
if (shouldNormalizeOutput)
{
outUVW = GetNormalizedPointInAabb(outUVW, m_shapeBounds);
}
}
void GradientTransformComponent::GetGradientLocalBounds(AZ::Aabb& bounds) const
{
bounds = m_shapeBounds;
}
void GradientTransformComponent::GetGradientEncompassingBounds(AZ::Aabb& bounds) const
{
bounds = m_shapeBounds;
bounds.ApplyMatrix3x4(m_shapeTransformInverse.GetInverseFull());
return m_gradientTransform;
}
void GradientTransformComponent::OnCompositionChanged()
@@ -393,25 +342,16 @@ namespace GradientSignal
{
if (m_dirty)
{
const auto configurationOld = m_configuration;
const auto shapeBoundsOld = m_shapeBounds;
const auto shapeTransformInverseOld = m_shapeTransformInverse;
// Updating on tick to query transform bus on main thread.
// Also, if the GradientTransform configuration changes, notify listeners so they can refresh themselves.
const bool notifyDependentsOfChange = true;
UpdateFromShape(notifyDependentsOfChange);
//updating on tick to query transform bus on main thread
UpdateFromShape();
//notify observers if content has changed
if (configurationOld != m_configuration ||
shapeBoundsOld != m_shapeBounds ||
shapeTransformInverseOld != m_shapeTransformInverse)
{
LmbrCentral::DependencyNotificationBus::Event(GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
}
m_dirty = false;
}
}
void GradientTransformComponent::UpdateFromShape()
void GradientTransformComponent::UpdateFromShape(bool notifyDependentsOfChange)
{
AZ_PROFILE_FUNCTION(Entity);
@@ -423,6 +363,10 @@ namespace GradientSignal
return;
}
const GradientTransform oldGradientTransform = m_gradientTransform;
AZ::Aabb shapeBounds = AZ::Aabb::CreateNull();
AZ::Matrix3x4 shapeTransformInverse = AZ::Matrix3x4::CreateIdentity();
AZ::Transform shapeTransform = AZ::Transform::CreateIdentity();
switch (m_configuration.m_transformType)
{
@@ -466,10 +410,10 @@ namespace GradientSignal
if (!m_configuration.m_advancedMode || !m_configuration.m_overrideBounds)
{
// If we have a shape reference, grab its local space bounds and (inverse) transform into that local space
GetObbParamsFromShape(shapeReference, m_shapeBounds, m_shapeTransformInverse);
if (m_shapeBounds.IsValid())
GetObbParamsFromShape(shapeReference, shapeBounds, shapeTransformInverse);
if (shapeBounds.IsValid())
{
m_configuration.m_bounds = m_shapeBounds.GetExtents();
m_configuration.m_bounds = shapeBounds.GetExtents();
}
}
@@ -491,14 +435,34 @@ namespace GradientSignal
//rebuild bounds from parameters
m_configuration.m_bounds = m_configuration.m_bounds.GetAbs();
m_shapeBounds = AZ::Aabb::CreateFromMinMax(-m_configuration.m_bounds * 0.5f, m_configuration.m_bounds * 0.5f);
shapeBounds = AZ::Aabb::CreateFromMinMax(-m_configuration.m_bounds * 0.5f, m_configuration.m_bounds * 0.5f);
//rebuild transform from parameters
AZ::Matrix3x4 shapeTransformFinal;
shapeTransformFinal.SetFromEulerDegrees(m_configuration.m_rotate);
shapeTransformFinal.SetTranslation(m_configuration.m_translate);
shapeTransformFinal.MultiplyByScale(m_configuration.m_scale);
m_shapeTransformInverse = shapeTransformFinal.GetInverseFull();
shapeTransformInverse = shapeTransformFinal.GetInverseFull();
// Set everything up on the Gradient Transform
const bool use3dGradients = m_configuration.m_advancedMode && m_configuration.m_is3d;
m_gradientTransform = GradientTransform(
shapeBounds, shapeTransformFinal, use3dGradients, m_configuration.m_frequencyZoom, m_configuration.m_wrappingType);
// If the transform has changed, send out notifications.
if (oldGradientTransform != m_gradientTransform)
{
// Always notify on the GradientTransformNotificationBus.
GradientTransformNotificationBus::Event(
GetEntityId(), &GradientTransformNotificationBus::Events::OnGradientTransformChanged, m_gradientTransform);
// Only notify the DependencyNotificationBus when requested by the caller.
if (notifyDependentsOfChange)
{
LmbrCentral::DependencyNotificationBus::Event(
GetEntityId(), &LmbrCentral::DependencyNotificationBus::Events::OnCompositionChanged);
}
}
}
AZ::EntityId GradientTransformComponent::GetShapeEntityId() const
@@ -101,9 +101,7 @@ namespace GradientSignal
//////////////////////////////////////////////////////////////////////////
// GradientTransformRequestBus
void TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, const bool shouldNormalizeOutput, bool& wasPointRejected) const override;
void GetGradientLocalBounds(AZ::Aabb& bounds) const override;
void GetGradientEncompassingBounds(AZ::Aabb& bounds) const override;
const GradientTransform& GetGradientTransform() const override;
//////////////////////////////////////////////////////////////////////////
// DependencyNotificationBus
@@ -113,7 +111,7 @@ namespace GradientSignal
// AZ::TickBus::Handler
void OnTick(float deltaTime, AZ::ScriptTimePoint time) override;
void UpdateFromShape();
void UpdateFromShape(bool notifyDependentsOfChange);
AZ::EntityId GetShapeEntityId() const;
@@ -168,9 +166,8 @@ namespace GradientSignal
private:
mutable AZStd::recursive_mutex m_cacheMutex;
GradientTransformConfig m_configuration;
AZ::Aabb m_shapeBounds = AZ::Aabb::CreateNull();
AZ::Matrix3x4 m_shapeTransformInverse = AZ::Matrix3x4::CreateIdentity();
LmbrCentral::DependencyMonitor m_dependencyMonitor;
AZStd::atomic_bool m_dirty{ false };
GradientTransform m_gradientTransform;
};
} //namespace GradientSignal
@@ -129,6 +129,9 @@ namespace GradientSignal
void ImageGradientComponent::Activate()
{
// This will immediately call OnGradientTransformChanged and initialize m_gradientTransform.
GradientTransformNotificationBus::Handler::BusConnect(GetEntityId());
SetupDependencies();
ImageGradientRequestBus::Handler::BusConnect(GetEntityId());
@@ -144,6 +147,7 @@ namespace GradientSignal
AZ::Data::AssetBus::Handler::BusDisconnect();
GradientRequestBus::Handler::BusDisconnect();
ImageGradientRequestBus::Handler::BusDisconnect();
GradientTransformNotificationBus::Handler::BusDisconnect();
m_dependencyMonitor.Reset();
@@ -189,19 +193,27 @@ namespace GradientSignal
m_configuration.m_imageAsset = asset;
}
void ImageGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform)
{
AZStd::unique_lock<decltype(m_imageMutex)> lock(m_imageMutex);
m_gradientTransform = newTransform;
}
float ImageGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
{
AZ::Vector3 uvw = sampleParams.m_position;
bool wasPointRejected = false;
const bool shouldNormalizeOutput = true;
GradientTransformRequestBus::Event(
GetEntityId(), &GradientTransformRequestBus::Events::TransformPositionToUVW, sampleParams.m_position, uvw, shouldNormalizeOutput, wasPointRejected);
if (!wasPointRejected)
{
AZStd::shared_lock<decltype(m_imageMutex)> imageLock(m_imageMutex);
return GetValueFromImageAsset(m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f);
m_gradientTransform.TransformPositionToUVWNormalized(sampleParams.m_position, uvw, wasPointRejected);
if (!wasPointRejected)
{
return GetValueFromImageAsset(
m_configuration.m_imageAsset, uvw, m_configuration.m_tilingX, m_configuration.m_tilingY, 0.0f);
}
}
return 0.0f;
@@ -11,6 +11,7 @@
#include <AzCore/Asset/AssetCommon.h>
#include <AzCore/Component/Component.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <GradientSignal/Ebuses/GradientTransformRequestBus.h>
#include <GradientSignal/Ebuses/ImageGradientRequestBus.h>
#include <GradientSignal/ImageAsset.h>
#include <GradientSignal/Util.h>
@@ -46,6 +47,7 @@ namespace GradientSignal
, private AZ::Data::AssetBus::Handler
, private GradientRequestBus::Handler
, private ImageGradientRequestBus::Handler
, private GradientTransformNotificationBus::Handler
{
public:
template<typename, typename> friend class LmbrCentral::EditorWrappedComponentBase;
@@ -59,29 +61,27 @@ namespace GradientSignal
ImageGradientComponent() = default;
~ImageGradientComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// GradientRequestBus
// GradientRequestBus overrides...
float GetValue(const GradientSampleParams& sampleParams) const override;
//////////////////////////////////////////////////////////////////////////
// AZ::Data::AssetBus::Handler
// AZ::Data::AssetBus overrides...
void OnAssetReady(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
void OnAssetMoved(AZ::Data::Asset<AZ::Data::AssetData> asset, void* oldDataPointer) override;
void OnAssetReloaded(AZ::Data::Asset<AZ::Data::AssetData> asset) override;
protected:
// GradientTransformNotificationBus overrides...
void OnGradientTransformChanged(const GradientTransform& newTransform) override;
void SetupDependencies();
//////////////////////////////////////////////////////////////////////////
// ImageGradientRequestBus
// ImageGradientRequestBus overrides...
AZStd::string GetImageAssetPath() const override;
void SetImageAssetPath(const AZStd::string& assetPath) override;
@@ -95,5 +95,6 @@ namespace GradientSignal
ImageGradientConfig m_configuration;
LmbrCentral::DependencyMonitor m_dependencyMonitor;
mutable AZStd::shared_mutex m_imageMutex;
GradientTransform m_gradientTransform;
};
}
@@ -138,6 +138,9 @@ namespace GradientSignal
void PerlinGradientComponent::Activate()
{
// This will immediately call OnGradientTransformChanged and initialize m_gradientTransform.
GradientTransformNotificationBus::Handler::BusConnect(GetEntityId());
m_perlinImprovedNoise.reset(aznew PerlinImprovedNoise(AZ::GetMax(m_configuration.m_randomSeed, 1)));
GradientRequestBus::Handler::BusConnect(GetEntityId());
PerlinGradientRequestBus::Handler::BusConnect(GetEntityId());
@@ -148,6 +151,7 @@ namespace GradientSignal
m_perlinImprovedNoise.reset();
GradientRequestBus::Handler::BusDisconnect();
PerlinGradientRequestBus::Handler::BusDisconnect();
GradientTransformNotificationBus::Handler::BusDisconnect();
}
bool PerlinGradientComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
@@ -170,22 +174,29 @@ namespace GradientSignal
return false;
}
void PerlinGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform)
{
AZStd::unique_lock<decltype(m_transformMutex)> lock(m_transformMutex);
m_gradientTransform = newTransform;
}
float PerlinGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
{
AZ_PROFILE_FUNCTION(Entity);
if (m_perlinImprovedNoise)
{
AZ::Vector3 uvw = sampleParams.m_position;
bool wasPointRejected = false;
const bool shouldNormalizeOutput = false;
GradientTransformRequestBus::Event(
GetEntityId(), &GradientTransformRequestBus::Events::TransformPositionToUVW, sampleParams.m_position, uvw, shouldNormalizeOutput, wasPointRejected);
{
AZStd::shared_lock<decltype(m_transformMutex)> lock(m_transformMutex);
m_gradientTransform.TransformPositionToUVW(sampleParams.m_position, uvw, wasPointRejected);
}
if (!wasPointRejected)
{
return m_perlinImprovedNoise->GenerateOctaveNoise(uvw.GetX(), uvw.GetY(), uvw.GetZ(), m_configuration.m_octave, m_configuration.m_amplitude, m_configuration.m_frequency);
return m_perlinImprovedNoise->GenerateOctaveNoise(
uvw.GetX(), uvw.GetY(), uvw.GetZ(), m_configuration.m_octave, m_configuration.m_amplitude,
m_configuration.m_frequency);
}
}
@@ -12,6 +12,7 @@
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <GradientSignal/Ebuses/GradientTransformRequestBus.h>
#include <GradientSignal/Ebuses/PerlinGradientRequestBus.h>
#include <GradientSignal/PerlinImprovedNoise.h>
@@ -47,6 +48,7 @@ namespace GradientSignal
: public AZ::Component
, private GradientRequestBus::Handler
, private PerlinGradientRequestBus::Handler
, private GradientTransformNotificationBus::Handler
{
public:
template<typename, typename> friend class LmbrCentral::EditorWrappedComponentBase;
@@ -60,23 +62,25 @@ namespace GradientSignal
PerlinGradientComponent() = default;
~PerlinGradientComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// GradientRequestBus
// GradientRequestBus overrides...
float GetValue(const GradientSampleParams& sampleParams) const override;
private:
PerlinGradientConfig m_configuration;
AZStd::unique_ptr<PerlinImprovedNoise> m_perlinImprovedNoise;
GradientTransform m_gradientTransform;
mutable AZStd::shared_mutex m_transformMutex;
/////////////////////////////////////////////////////////////////////////
//PerlinGradientRequest overrides
// GradientTransformNotificationBus overrides...
void OnGradientTransformChanged(const GradientTransform& newTransform) override;
// PerlinGradientRequestBus overrides...
int GetRandomSeed() const override;
void SetRandomSeed(int seed) override;
@@ -105,6 +105,9 @@ namespace GradientSignal
void RandomGradientComponent::Activate()
{
// This will immediately call OnGradientTransformChanged and initialize m_gradientTransform.
GradientTransformNotificationBus::Handler::BusConnect(GetEntityId());
GradientRequestBus::Handler::BusConnect(GetEntityId());
RandomGradientRequestBus::Handler::BusConnect(GetEntityId());
}
@@ -113,6 +116,7 @@ namespace GradientSignal
{
GradientRequestBus::Handler::BusDisconnect();
RandomGradientRequestBus::Handler::BusDisconnect();
GradientTransformNotificationBus::Handler::BusDisconnect();
}
bool RandomGradientComponent::ReadInConfig(const AZ::ComponentConfig* baseConfig)
@@ -135,16 +139,22 @@ namespace GradientSignal
return false;
}
void RandomGradientComponent::OnGradientTransformChanged(const GradientTransform& newTransform)
{
AZStd::unique_lock<decltype(m_transformMutex)> lock(m_transformMutex);
m_gradientTransform = newTransform;
}
float RandomGradientComponent::GetValue(const GradientSampleParams& sampleParams) const
{
AZ_PROFILE_FUNCTION(Entity);
AZ::Vector3 uvw = sampleParams.m_position;
bool wasPointRejected = false;
const bool shouldNormalizeOutput = false;
GradientTransformRequestBus::Event(
GetEntityId(), &GradientTransformRequestBus::Events::TransformPositionToUVW, sampleParams.m_position, uvw, shouldNormalizeOutput, wasPointRejected);
{
AZStd::shared_lock<decltype(m_transformMutex)> lock(m_transformMutex);
m_gradientTransform.TransformPositionToUVW(sampleParams.m_position, uvw, wasPointRejected);
}
if (!wasPointRejected)
{
@@ -10,6 +10,7 @@
#include <AzCore/Component/Component.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <GradientSignal/Ebuses/GradientTransformRequestBus.h>
#include <GradientSignal/Ebuses/RandomGradientRequestBus.h>
namespace LmbrCentral
@@ -38,6 +39,7 @@ namespace GradientSignal
: public AZ::Component
, private GradientRequestBus::Handler
, private RandomGradientRequestBus::Handler
, private GradientTransformNotificationBus::Handler
{
public:
template<typename, typename> friend class LmbrCentral::EditorWrappedComponentBase;
@@ -51,22 +53,24 @@ namespace GradientSignal
RandomGradientComponent() = default;
~RandomGradientComponent() = default;
//////////////////////////////////////////////////////////////////////////
// AZ::Component interface implementation
// AZ::Component overrides...
void Activate() override;
void Deactivate() override;
bool ReadInConfig(const AZ::ComponentConfig* baseConfig) override;
bool WriteOutConfig(AZ::ComponentConfig* outBaseConfig) const override;
//////////////////////////////////////////////////////////////////////////
// GradientRequestBus
// GradientRequestBus overrides...
float GetValue(const GradientSampleParams& sampleParams) const override;
private:
RandomGradientConfig m_configuration;
GradientTransform m_gradientTransform;
mutable AZStd::shared_mutex m_transformMutex;
/////////////////////////////////////////////////////////////////////////
// RandomGradientRequest overrides
// GradientTransformNotificationBus overrides...
void OnGradientTransformChanged(const GradientTransform& newTransform) override;
// RandomGradientRequestBus overrides...
int GetRandomSeed() const override;
void SetRandomSeed(int seed) override;
};
@@ -55,9 +55,13 @@ namespace GradientSignal
void EditorGradientTransformComponent::UpdateFromShape()
{
// Update config from shape on game component, copy that back to our config
m_component.UpdateFromShape();
m_component.WriteOutConfig(&m_configuration);
SetDirty();
if (m_runtimeComponentActive)
{
// Update config from shape on game component, copy that back to our config.
bool notifyDependentsOfChange = true;
m_component.UpdateFromShape(notifyDependentsOfChange);
m_component.WriteOutConfig(&m_configuration);
SetDirty();
}
}
} //namespace GradientSignal
@@ -0,0 +1,167 @@
/*
* 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/Math/MathUtils.h>
#include <GradientSignal/GradientTransform.h>
namespace GradientSignal
{
GradientTransform::GradientTransform(
const AZ::Aabb& shapeBounds, const AZ::Matrix3x4& transform, bool use3d,
float frequencyZoom, GradientSignal::WrappingType wrappingType)
: m_shapeBounds(shapeBounds)
, m_inverseTransform(transform.GetInverseFull())
, m_frequencyZoom(frequencyZoom)
, m_wrappingType(wrappingType)
, m_alwaysAcceptPoint(true)
{
// If we want this to be a 2D gradient lookup, we always want to set the W result in the output to 0.
// The easiest / cheapest way to make this happen is just to clear out the third row in the inverseTransform.
if (!use3d)
{
m_inverseTransform.SetRow(2, AZ::Vector4::CreateZero());
}
// If we have invalid shape bounds, reset the wrapping type back to None. Wrapping won't work without valid bounds.
if (!m_shapeBounds.IsValid())
{
m_wrappingType = WrappingType::None;
}
// ClampToZero is the only wrapping type that allows us to return a "pointIsRejected" result for points that fall
// outside the shape bounds.
if (m_wrappingType == WrappingType::ClampToZero)
{
m_alwaysAcceptPoint = false;
}
m_normalizeExtentsReciprocal = AZ::Vector3(
AZ::IsClose(0.0f, m_shapeBounds.GetXExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetXExtent()),
AZ::IsClose(0.0f, m_shapeBounds.GetYExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetYExtent()),
AZ::IsClose(0.0f, m_shapeBounds.GetZExtent()) ? 0.0f : (1.0f / m_shapeBounds.GetZExtent()));
}
void GradientTransform::TransformPositionToUVW(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
{
// Transform coordinate into "local" relative space of shape bounds, and set W to 0 if this is a 2D gradient.
outUVW = m_inverseTransform * inPosition;
// For most wrapping types, we always accept the point, but for ClampToZero we only accept it if it's within
// the shape bounds. We don't use m_shapeBounds.Contains() here because Contains() is inclusive on all edges.
// For uv consistency between clamped and unclamped states, we only want to accept uv ranges of [min, max),
// so we specifically need to exclude the max edges here.
bool wasPointAccepted = m_alwaysAcceptPoint ||
(outUVW.IsGreaterEqualThan(m_shapeBounds.GetMin()) && outUVW.IsLessThan(m_shapeBounds.GetMax()));
wasPointRejected = !wasPointAccepted;
switch (m_wrappingType)
{
default:
case WrappingType::None:
outUVW = GetUnboundedPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::ClampToEdge:
outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::ClampToZero:
outUVW = GetClampedPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::Mirror:
outUVW = GetMirroredPointInAabb(outUVW, m_shapeBounds);
break;
case WrappingType::Repeat:
outUVW = GetWrappedPointInAabb(outUVW, m_shapeBounds);
break;
}
outUVW *= m_frequencyZoom;
}
void GradientTransform::TransformPositionToUVWNormalized(const AZ::Vector3& inPosition, AZ::Vector3& outUVW, bool& wasPointRejected) const
{
TransformPositionToUVW(inPosition, outUVW, wasPointRejected);
// This effectively does AZ::LerpInverse(bounds.GetMin(), bounds.GetMax(), point) if shouldNormalize is true,
// and just returns outUVW if shouldNormalize is false.
outUVW = m_normalizeExtentsReciprocal * (outUVW - m_shapeBounds.GetMin());
}
AZ::Vector3 GradientTransform::NoTransform(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
{
return point;
}
AZ::Vector3 GradientTransform::GetUnboundedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
{
return point;
}
AZ::Vector3 GradientTransform::GetClampedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
// We want the clamped sampling states to clamp uvs to the [min, max) range.
return point.GetClamp(bounds.GetMin(), bounds.GetMax() - AZ::Vector3(UvEpsilon));
}
AZ::Vector3 GradientTransform::GetWrappedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
return AZ::Vector3(
AZ::Wrap(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
AZ::Wrap(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
AZ::Wrap(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
}
AZ::Vector3 GradientTransform::GetMirroredPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
/* For mirroring, we want to produce the following pattern:
* [min, max) : value
* [max, min) : max - value - epsilon
* [min, max) : value
* [max, min) : max - value - epsilon
* ...
* The epsilon is because we always want to keep our output values in the [min, max) range. We apply the epsilon to all
* the mirrored values so that we get consistent spacing between the values.
*/
auto GetMirror = [](float value, float min, float max) -> float
{
// To calculate the mirror value, we move our value into relative space of [0, rangeX2), then use
// the first half of the range for our "[min, max)" range, and the second half for our "[max, min)" mirrored range.
float relativeValue = value - min;
float range = max - min;
float rangeX2 = range * 2.0f;
// A positive relativeValue will produce a value of [0, rangeX2) from a single mod, but a negative relativeValue
// will produce a value of (-rangeX2, 0]. Adding rangeX2 to the result and taking the mod again puts us back in
// the range of [0, rangeX2) for both negative and positive values. This keeps our mirroring pattern consistent and
// unbroken across both negative and positive coordinate space.
relativeValue = AZ::Mod(AZ::Mod(relativeValue, rangeX2) + rangeX2, rangeX2);
// [range, rangeX2) is our mirrored range, so flip the value when we're in this range and apply the epsilon so that
// we never return the max value, and so that our mirrored values have consistent spacing in the results.
if (relativeValue >= range)
{
relativeValue = rangeX2 - (relativeValue + UvEpsilon);
}
return relativeValue + min;
};
return AZ::Vector3(
GetMirror(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
GetMirror(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
GetMirror(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
}
AZ::Vector3 GradientTransform::GetRelativePointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
return point - bounds.GetMin();
}
}
-77
View File
@@ -1,77 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Debug/Profiler.h>
#include <AzCore/Math/MathUtils.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <GradientSignal/Util.h>
namespace GradientSignal
{
// To keep things behaving consistently between clamped and unbounded uv ranges, we
// we want our clamped uvs to use a range of [min, max), so we'll actually clamp to
// [min, max - epsilon]. Since our floating-point numbers are likely in the
// -16384 to 16384 range, an epsilon of 0.001 will work without rounding to 0.
static const float uvEpsilon = 0.001f;
AZ::Vector3 GetUnboundedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& /*bounds*/)
{
return point;
}
AZ::Vector3 GetClampedPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
// We want the clamped sampling states to clamp uvs to the [min, max) range.
return AZ::Vector3(
AZ::GetClamp(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX() - uvEpsilon),
AZ::GetClamp(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY() - uvEpsilon),
AZ::GetClamp(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ() - uvEpsilon));
}
float GetMirror(float value, float min, float max)
{
float relativeValue = value - min;
float range = max - min;
float rangeX2 = range * 2.0f;
if (relativeValue < 0.0)
{
relativeValue = rangeX2 - fmod(-relativeValue, rangeX2);
}
else
{
relativeValue = fmod(relativeValue, rangeX2);
}
if (relativeValue >= range)
{
// Since we want our uv range to stay in the [min, max) range,
// it means that for mirroring, we want both the "forward" values
// and the "mirrored" values to be in [0, range). We don't want
// relativeValue == range, so we shift relativeValue by a small epsilon
// in the mirrored case.
relativeValue = rangeX2 - (relativeValue + uvEpsilon);
}
return relativeValue + min;
}
AZ::Vector3 GetMirroredPointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
return AZ::Vector3(
GetMirror(point.GetX(), bounds.GetMin().GetX(), bounds.GetMax().GetX()),
GetMirror(point.GetY(), bounds.GetMin().GetY(), bounds.GetMax().GetY()),
GetMirror(point.GetZ(), bounds.GetMin().GetZ(), bounds.GetMax().GetZ()));
}
AZ::Vector3 GetRelativePointInAabb(const AZ::Vector3& point, const AZ::Aabb& bounds)
{
return point - bounds.GetMin();
}
}
@@ -6,7 +6,7 @@
*
*/
#include "Tests/GradientSignalTestMocks.h"
#include <Tests/GradientSignalTestFixtures.h>
#include <GradientSignal/Editor/EditorGradientPreviewRenderer.h>
#include <AzTest/AzTest.h>
@@ -28,7 +28,6 @@ namespace UnitTest
void SetUp() override
{
GradientSignalTest::SetUp();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
// Set up job manager with two threads so that we can run and test the preview job logic.
AZ::JobManagerDesc desc;
@@ -46,7 +45,6 @@ namespace UnitTest
delete m_jobContext;
delete m_jobManager;
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
GradientSignalTest::TearDown();
}
@@ -0,0 +1,106 @@
/*
* 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
*
*/
#ifdef HAVE_BENCHMARK
#include <Tests/GradientSignalTestFixtures.h>
#include <AzTest/AzTest.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Math/Vector2.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Asset/AssetCatalogBus.h>
#include <Source/Components/ImageGradientComponent.h>
#include <Source/Components/PerlinGradientComponent.h>
#include <Source/Components/RandomGradientComponent.h>
#include <Source/Components/GradientTransformComponent.h>
namespace UnitTest
{
BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_ImageGradientGetValue)(benchmark::State& state)
{
// Create the Image Gradient Component with some default sizes and parameters.
GradientSignal::ImageGradientConfig config;
const uint32_t imageSize = 4096;
const int32_t imageSeed = 12345;
config.m_imageAsset = ImageAssetMockAssetHandler::CreateImageAsset(imageSize, imageSize, imageSeed);
config.m_tilingX = 1.0f;
config.m_tilingY = 1.0f;
CreateComponent<GradientSignal::ImageGradientComponent>(m_testEntity.get(), config);
// Create the Gradient Transform Component with some default parameters.
GradientSignal::GradientTransformConfig gradientTransformConfig;
gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
CreateComponent<GradientSignal::GradientTransformComponent>(m_testEntity.get(), gradientTransformConfig);
// Run the benchmark
RunGetValueBenchmark(state);
}
BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_ImageGradientGetValue)
->Args({ 1024, 1024 })
->Args({ 2048, 2048 })
->Args({ 4096, 4096 })
->Unit(::benchmark::kMillisecond);
BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_PerlinGradientGetValue)(benchmark::State& state)
{
// Create the Perlin Gradient Component with some default sizes and parameters.
GradientSignal::PerlinGradientConfig config;
config.m_amplitude = 1.0f;
config.m_frequency = 1.1f;
config.m_octave = 4;
config.m_randomSeed = 12345;
CreateComponent<GradientSignal::PerlinGradientComponent>(m_testEntity.get(), config);
// Create the Gradient Transform Component with some default parameters.
GradientSignal::GradientTransformConfig gradientTransformConfig;
gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
CreateComponent<GradientSignal::GradientTransformComponent>(m_testEntity.get(), gradientTransformConfig);
// Run the benchmark
RunGetValueBenchmark(state);
}
BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_PerlinGradientGetValue)
->Args({ 1024, 1024 })
->Args({ 2048, 2048 })
->Args({ 4096, 4096 })
->Unit(::benchmark::kMillisecond);
BENCHMARK_DEFINE_F(GradientSignalBenchmarkFixture, BM_RandomGradientGetValue)(benchmark::State& state)
{
// Create the Random Gradient Component with some default parameters.
GradientSignal::RandomGradientConfig config;
config.m_randomSeed = 12345;
CreateComponent<GradientSignal::RandomGradientComponent>(m_testEntity.get(), config);
// Create the Gradient Transform Component with some default parameters.
GradientSignal::GradientTransformConfig gradientTransformConfig;
gradientTransformConfig.m_wrappingType = GradientSignal::WrappingType::None;
CreateComponent<GradientSignal::GradientTransformComponent>(m_testEntity.get(), gradientTransformConfig);
// Run the benchmark
RunGetValueBenchmark(state);
}
BENCHMARK_REGISTER_F(GradientSignalBenchmarkFixture, BM_RandomGradientGetValue)
->Args({ 1024, 1024 })
->Args({ 2048, 2048 })
->Args({ 4096, 4096 })
->Unit(::benchmark::kMillisecond);
#endif
}
@@ -7,7 +7,7 @@
*/
#include "Tests/GradientSignalTestMocks.h"
#include <Tests/GradientSignalTestFixtures.h>
#include <AzTest/AzTest.h>
#include <AzCore/Asset/AssetManager.h>
@@ -23,69 +23,6 @@ namespace UnitTest
struct GradientSignalImageTestsFixture
: public GradientSignalTest
{
struct MockAssetHandler
: public AZ::Data::AssetHandler
{
AZ::Data::AssetPtr CreateAsset([[maybe_unused]] const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
{
return AZ::Data::AssetPtr();
}
void DestroyAsset(AZ::Data::AssetPtr ptr) override
{
if (ptr)
{
delete ptr;
}
}
void GetHandledAssetTypes([[maybe_unused]] AZStd::vector<AZ::Data::AssetType>& assetTypes) override
{
}
AZ::Data::AssetHandler::LoadResult LoadAssetData(
[[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
[[maybe_unused]] AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
{
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
}
};
MockAssetHandler* m_mockHandler = nullptr;
GradientSignal::ImageAsset* m_imageData = nullptr;
void SetUp() override
{
GradientSignalTest::SetUp();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
AZ::Data::AssetManager::Descriptor desc;
AZ::Data::AssetManager::Create(desc);
m_mockHandler = new MockAssetHandler();
AZ::Data::AssetManager::Instance().RegisterHandler(m_mockHandler, azrtti_typeid<GradientSignal::ImageAsset>());
}
void TearDown() override
{
AZ::Data::AssetManager::Instance().UnregisterHandler(m_mockHandler);
delete m_mockHandler; // delete after removing from the asset manager
AzFramework::LegacyAssetEventBus::ClearQueuedEvents();
AZ::Data::AssetManager::Destroy();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
GradientSignalTest::TearDown();
}
struct AssignIdToAsset
: public AZ::Data::AssetData
{
void MakeReady()
{
m_assetId = AZ::Data::AssetId(AZ::Uuid::CreateRandom());
m_status.store(AZ::Data::AssetData::AssetStatus::Ready);
}
};
struct PixelTestSetup
{
// How to create the source image
@@ -105,61 +42,6 @@ namespace UnitTest
static const AZ::Vector2 EndOfList;
};
AZ::Data::Asset<GradientSignal::ImageAsset> CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed)
{
m_imageData = aznew GradientSignal::ImageAsset();
m_imageData->m_imageWidth = width;
m_imageData->m_imageHeight = height;
m_imageData->m_bytesPerPixel = 1;
m_imageData->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
size_t value = 0;
AZStd::hash_combine(value, seed);
for (AZ::u32 x = 0; x < width; ++x)
{
for (AZ::u32 y = 0; y < height; ++y)
{
AZStd::hash_combine(value, x);
AZStd::hash_combine(value, y);
m_imageData->m_imageData.push_back(static_cast<AZ::u8>(value));
}
}
reinterpret_cast<AssignIdToAsset*>(m_imageData)->MakeReady();
return AZ::Data::Asset<GradientSignal::ImageAsset>(m_imageData, AZ::Data::AssetLoadBehavior::Default);
}
AZ::Data::Asset<GradientSignal::ImageAsset> CreateSpecificPixelImageAsset(AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY)
{
m_imageData = aznew GradientSignal::ImageAsset();
m_imageData->m_imageWidth = width;
m_imageData->m_imageHeight = height;
m_imageData->m_bytesPerPixel = 1;
m_imageData->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
const AZ::u8 pixelValue = 255;
// Image data should be stored inverted on the y axis relative to our engine, so loop backwards through y.
for (int y = static_cast<int>(height) - 1; y >= 0; --y)
{
for (AZ::u32 x = 0; x < width; ++x)
{
if ((x == static_cast<int>(pixelX)) && (y == static_cast<int>(pixelY)))
{
m_imageData->m_imageData.push_back(pixelValue);
}
else
{
m_imageData->m_imageData.push_back(0);
}
}
}
reinterpret_cast<AssignIdToAsset*>(m_imageData)->MakeReady();
return AZ::Data::Asset<GradientSignal::ImageAsset>(m_imageData, AZ::Data::AssetLoadBehavior::Default);
}
void TestPixels(GradientSignal::GradientSampler& sampler, AZ::u32 width, AZ::u32 height, float stepSize, const AZStd::vector<AZ::Vector3>& expectedPoints)
{
AZStd::vector<AZ::Vector3> foundPoints;
@@ -203,7 +85,8 @@ namespace UnitTest
// Create the Image Gradient Component.
GradientSignal::ImageGradientConfig config;
config.m_imageAsset = CreateSpecificPixelImageAsset(test.m_imageSize, test.m_imageSize, static_cast<AZ::u32>(test.m_pixel.GetX()), static_cast<AZ::u32>(test.m_pixel.GetY()));
config.m_imageAsset = ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(
test.m_imageSize, test.m_imageSize, static_cast<AZ::u32>(test.m_pixel.GetX()), static_cast<AZ::u32>(test.m_pixel.GetY()));
config.m_tilingX = test.m_tiling;
config.m_tilingY = test.m_tiling;
CreateComponent<GradientSignal::ImageGradientComponent>(entity.get(), config);
@@ -495,7 +378,7 @@ namespace UnitTest
// Create an ImageGradient with a 3x3 asset with the center pixel set.
GradientSignal::ImageGradientConfig gradientConfig;
gradientConfig.m_imageAsset = CreateSpecificPixelImageAsset(3, 3, 1, 1);
gradientConfig.m_imageAsset = ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(3, 3, 1, 1);
CreateComponent<GradientSignal::ImageGradientComponent>(entity.get(), gradientConfig);
// Create the test GradientTransform
@@ -10,7 +10,7 @@
#include <AzTest/AzTest.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Math/MathUtils.h>
#include "Tests/GradientSignalTestMocks.h"
#include <Tests/GradientSignalTestFixtures.h>
#include <Source/Components/MixedGradientComponent.h>
#include <Source/Components/ReferenceGradientComponent.h>
@@ -6,7 +6,7 @@
*
*/
#include "Tests/GradientSignalTestMocks.h"
#include <Tests/GradientSignalTestFixtures.h>
#include <AzTest/AzTest.h>
@@ -9,7 +9,7 @@
#include <AzTest/AzTest.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzCore/Math/MathUtils.h>
#include "Tests/GradientSignalTestMocks.h"
#include <Tests/GradientSignalTestFixtures.h>
#include <Source/Components/ConstantGradientComponent.h>
#include <Source/Components/GradientSurfaceDataComponent.h>
@@ -7,7 +7,7 @@
*/
#include "Tests/GradientSignalTestMocks.h"
#include <Tests/GradientSignalTestFixtures.h>
#include <GradientSignal/PerlinImprovedNoise.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
@@ -0,0 +1,121 @@
/*
* 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 <Tests/GradientSignalTestFixtures.h>
namespace UnitTest
{
void GradientSignalBaseFixture::SetupCoreSystems()
{
m_app = AZStd::make_unique<AZ::ComponentApplication>();
ASSERT_TRUE(m_app != nullptr);
AZ::ComponentApplication::Descriptor componentAppDesc;
m_systemEntity = m_app->Create(componentAppDesc);
ASSERT_TRUE(m_systemEntity != nullptr);
m_app->AddEntity(m_systemEntity);
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Create();
AZ::Data::AssetManager::Descriptor desc;
AZ::Data::AssetManager::Create(desc);
m_mockHandler = new ImageAssetMockAssetHandler();
AZ::Data::AssetManager::Instance().RegisterHandler(m_mockHandler, azrtti_typeid<GradientSignal::ImageAsset>());
}
void GradientSignalBaseFixture::TearDownCoreSystems()
{
AZ::Data::AssetManager::Instance().UnregisterHandler(m_mockHandler);
delete m_mockHandler; // delete after removing from the asset manager
AzFramework::LegacyAssetEventBus::ClearQueuedEvents();
AZ::Data::AssetManager::Destroy();
AZ::AllocatorInstance<AZ::ThreadPoolAllocator>::Destroy();
m_app->Destroy();
m_app.reset();
m_systemEntity = nullptr;
}
void GradientSignalTest::TestFixedDataSampler(const AZStd::vector<float>& expectedOutput, int size, AZ::EntityId gradientEntityId)
{
GradientSignal::GradientSampler gradientSampler;
gradientSampler.m_gradientId = gradientEntityId;
for (int y = 0; y < size; ++y)
{
for (int x = 0; x < size; ++x)
{
GradientSignal::GradientSampleParams params;
params.m_position = AZ::Vector3(static_cast<float>(x), static_cast<float>(y), 0.0f);
const int index = y * size + x;
float actualValue = gradientSampler.GetValue(params);
float expectedValue = expectedOutput[index];
EXPECT_NEAR(actualValue, expectedValue, 0.01f);
}
}
}
#ifdef HAVE_BENCHMARK
void GradientSignalBenchmarkFixture::CreateTestEntity(float shapeHalfBounds)
{
// Create the base entity
m_testEntity = CreateEntity();
// Create a mock Shape component that describes the bounds that we're using to map our gradient into world space.
CreateComponent<MockShapeComponent>(m_testEntity.get());
MockShapeComponentHandler mockShapeHandler(m_testEntity->GetId());
mockShapeHandler.m_GetLocalBounds = AZ::Aabb::CreateCenterRadius(AZ::Vector3(shapeHalfBounds), shapeHalfBounds);
// Create a mock Transform component that locates our gradient in the center of our desired mock Shape.
MockTransformHandler mockTransformHandler;
mockTransformHandler.m_GetLocalTMOutput = AZ::Transform::CreateTranslation(AZ::Vector3(shapeHalfBounds));
mockTransformHandler.m_GetWorldTMOutput = AZ::Transform::CreateTranslation(AZ::Vector3(shapeHalfBounds));
mockTransformHandler.BusConnect(m_testEntity->GetId());
}
void GradientSignalBenchmarkFixture::DestroyTestEntity()
{
m_testEntity.reset();
}
void GradientSignalBenchmarkFixture::RunGetValueBenchmark(benchmark::State& state)
{
// All components are created, so activate the entity
ActivateEntity(m_testEntity.get());
// Create a gradient sampler and run through a series of points to see if they match expectations.
GradientSignal::GradientSampler gradientSampler;
gradientSampler.m_gradientId = m_testEntity->GetId();
// Get the height and width ranges for querying from our benchmark parameters
float height = aznumeric_cast<float>(state.range(0));
float width = aznumeric_cast<float>(state.range(1));
// Call GetValue() for every height and width in our ranges.
for (auto _ : state)
{
for (float y = 0.0f; y < height; y += 1.0f)
{
for (float x = 0.0f; x < width; x += 1.0f)
{
GradientSignal::GradientSampleParams params;
params.m_position = AZ::Vector3(x, y, 0.0f);
float value = gradientSampler.GetValue(params);
benchmark::DoNotOptimize(value);
}
}
}
}
#endif
}
@@ -0,0 +1,124 @@
/*
* 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 <Tests/GradientSignalTestMocks.h>
namespace UnitTest
{
// Base test fixture used for GradientSignal unit tests and benchmark tests
class GradientSignalBaseFixture
{
public:
void SetupCoreSystems();
void TearDownCoreSystems();
AZStd::unique_ptr<AZ::Entity> CreateEntity()
{
return AZStd::make_unique<AZ::Entity>();
}
void ActivateEntity(AZ::Entity* entity)
{
entity->Init();
entity->Activate();
}
template<typename Component, typename Configuration>
AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config)
{
m_app->RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>(config);
}
template<typename Component>
AZ::Component* CreateComponent(AZ::Entity* entity)
{
m_app->RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>();
}
AZStd::unique_ptr<AZ::ComponentApplication> m_app;
AZ::Entity* m_systemEntity = nullptr;
ImageAssetMockAssetHandler* m_mockHandler = nullptr;
};
struct GradientSignalTest
: public GradientSignalBaseFixture
, public UnitTest::AllocatorsTestFixture
{
protected:
void SetUp() override
{
UnitTest::AllocatorsTestFixture::SetUp();
SetupCoreSystems();
}
void TearDown() override
{
TearDownCoreSystems();
UnitTest::AllocatorsTestFixture::TearDown();
}
void TestFixedDataSampler(const AZStd::vector<float>& expectedOutput, int size, AZ::EntityId gradientEntityId);
};
#ifdef HAVE_BENCHMARK
class GradientSignalBenchmarkFixture
: public GradientSignalBaseFixture
, public UnitTest::AllocatorsBenchmarkFixture
, public UnitTest::TraceBusRedirector
{
public:
void internalSetUp(const benchmark::State& state)
{
AZ::Debug::TraceMessageBus::Handler::BusConnect();
UnitTest::AllocatorsBenchmarkFixture::SetUp(state);
SetupCoreSystems();
// Create a default test entity with bounds of 256 m x 256 m x 256 m.
const float shapeHalfBounds = 128.0f;
CreateTestEntity(shapeHalfBounds);
}
void internalTearDown(const benchmark::State& state)
{
DestroyTestEntity();
TearDownCoreSystems();
UnitTest::AllocatorsBenchmarkFixture::TearDown(state);
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
void CreateTestEntity(float shapeHalfBounds);
void DestroyTestEntity();
void RunGetValueBenchmark(benchmark::State& state);
protected:
void SetUp(const benchmark::State& state) override
{
internalSetUp(state);
}
void SetUp(benchmark::State& state) override
{
internalSetUp(state);
}
void TearDown(const benchmark::State& state) override
{
internalTearDown(state);
}
void TearDown(benchmark::State& state) override
{
internalTearDown(state);
}
AZStd::unique_ptr<AZ::Entity> m_testEntity;
};
#endif
}
@@ -0,0 +1,72 @@
/*
* 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 <Tests/GradientSignalTestMocks.h>
namespace UnitTest
{
AZ::Data::Asset<GradientSignal::ImageAsset> ImageAssetMockAssetHandler::CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed)
{
GradientSignal::ImageAsset* imageData =
aznew GradientSignal::ImageAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetData::AssetStatus::Ready);
imageData->m_imageWidth = width;
imageData->m_imageHeight = height;
imageData->m_bytesPerPixel = 1;
imageData->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
imageData->m_imageData.reserve(width * height);
size_t value = 0;
AZStd::hash_combine(value, seed);
for (AZ::u32 x = 0; x < width; ++x)
{
for (AZ::u32 y = 0; y < height; ++y)
{
AZStd::hash_combine(value, x);
AZStd::hash_combine(value, y);
imageData->m_imageData.push_back(static_cast<AZ::u8>(value));
}
}
return AZ::Data::Asset<GradientSignal::ImageAsset>(imageData, AZ::Data::AssetLoadBehavior::Default);
}
AZ::Data::Asset<GradientSignal::ImageAsset> ImageAssetMockAssetHandler::CreateSpecificPixelImageAsset(
AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY)
{
GradientSignal::ImageAsset* imageData =
aznew GradientSignal::ImageAsset(AZ::Data::AssetId(AZ::Uuid::CreateRandom()), AZ::Data::AssetData::AssetStatus::Ready);
imageData->m_imageWidth = width;
imageData->m_imageHeight = height;
imageData->m_bytesPerPixel = 1;
imageData->m_imageFormat = ImageProcessingAtom::EPixelFormat::ePixelFormat_R8;
imageData->m_imageData.reserve(width * height);
const AZ::u8 pixelValue = 255;
// Image data should be stored inverted on the y axis relative to our engine, so loop backwards through y.
for (int y = static_cast<int>(height) - 1; y >= 0; --y)
{
for (AZ::u32 x = 0; x < width; ++x)
{
if ((x == static_cast<int>(pixelX)) && (y == static_cast<int>(pixelY)))
{
imageData->m_imageData.push_back(pixelValue);
}
else
{
imageData->m_imageData.push_back(0);
}
}
}
return AZ::Data::Asset<GradientSignal::ImageAsset>(imageData, AZ::Data::AssetLoadBehavior::Default);
}
}
@@ -8,90 +8,69 @@
#pragma once
#include <AzTest/AzTest.h>
#include <AzCore/std/hash.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Casting/lossy_cast.h>
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/ComponentApplication.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/std/hash.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <GradientSignal/Ebuses/GradientRequestBus.h>
#include <GradientSignal/Ebuses/GradientPreviewContextRequestBus.h>
#include <GradientSignal/GradientSampler.h>
#include <GradientSignal/ImageAsset.h>
#include <LmbrCentral/Shape/ShapeComponentBus.h>
#include <SurfaceData/SurfaceDataSystemRequestBus.h>
#include <AzCore/Casting/lossy_cast.h>
#include <SurfaceData/Tests/SurfaceDataTestMocks.h>
namespace UnitTest
{
struct GradientSignalTest
: public ::testing::Test
// Mock asset handler for GradientSignal::ImageAsset that we can use in unit tests to pretend to load an image asset with.
// Also includes utility functions for creating image assets with specific testable patterns.
struct ImageAssetMockAssetHandler : public AZ::Data::AssetHandler
{
protected:
AZ::ComponentApplication m_app;
AZ::Entity* m_systemEntity = nullptr;
//! Creates a deterministically random set of pixel data as an ImageAsset.
//! \param width The width of the ImageAsset
//! \param height The height of the ImageAsset
//! \param seed The random seed to use for generating the random data
//! \return The ImageAsset in a loaded ready state
static AZ::Data::Asset<GradientSignal::ImageAsset> CreateImageAsset(AZ::u32 width, AZ::u32 height, AZ::s32 seed);
void SetUp() override
//! Creates an ImageAsset where all the pixels are 0 except for the one pixel at the given coordinates, which is set to 1.
//! \param width The width of the ImageAsset
//! \param height The height of the ImageAsset
//! \param pixelX The X coordinate of the pixel to set to 1
//! \param pixelY The Y coordinate of the pixel to set to 1
//! \return The ImageAsset in a loaded ready state
static AZ::Data::Asset<GradientSignal::ImageAsset> CreateSpecificPixelImageAsset(
AZ::u32 width, AZ::u32 height, AZ::u32 pixelX, AZ::u32 pixelY);
AZ::Data::AssetPtr CreateAsset(
[[maybe_unused]] const AZ::Data::AssetId& id, [[maybe_unused]] const AZ::Data::AssetType& type) override
{
AZ::ComponentApplication::Descriptor appDesc;
appDesc.m_memoryBlocksByteSize = 128 * 1024 * 1024;
m_systemEntity = m_app.Create(appDesc);
m_app.AddEntity(m_systemEntity);
return AZ::Data::AssetPtr();
}
void TearDown() override
void DestroyAsset(AZ::Data::AssetPtr ptr) override
{
m_app.Destroy();
m_systemEntity = nullptr;
}
void TestFixedDataSampler(const AZStd::vector<float>& expectedOutput, int size, AZ::EntityId gradientEntityId)
{
GradientSignal::GradientSampler gradientSampler;
gradientSampler.m_gradientId = gradientEntityId;
for(int y = 0; y < size; ++y)
if (ptr)
{
for (int x = 0; x < size; ++x)
{
GradientSignal::GradientSampleParams params;
params.m_position = AZ::Vector3(static_cast<float>(x), static_cast<float>(y), 0.0f);
const int index = y * size + x;
float actualValue = gradientSampler.GetValue(params);
float expectedValue = expectedOutput[index];
EXPECT_NEAR(actualValue, expectedValue, 0.01f);
}
delete ptr;
}
}
AZStd::unique_ptr<AZ::Entity> CreateEntity()
void GetHandledAssetTypes([[maybe_unused]] AZStd::vector<AZ::Data::AssetType>& assetTypes) override
{
return AZStd::make_unique<AZ::Entity>();
}
void ActivateEntity(AZ::Entity* entity)
AZ::Data::AssetHandler::LoadResult LoadAssetData(
[[maybe_unused]] const AZ::Data::Asset<AZ::Data::AssetData>& asset,
[[maybe_unused]] AZStd::shared_ptr<AZ::Data::AssetDataStream> stream,
[[maybe_unused]] const AZ::Data::AssetFilterCB& assetLoadFilterCB) override
{
entity->Init();
EXPECT_EQ(AZ::Entity::State::Init, entity->GetState());
entity->Activate();
EXPECT_EQ(AZ::Entity::State::Active, entity->GetState());
}
template <typename Component, typename Configuration>
AZ::Component* CreateComponent(AZ::Entity* entity, const Configuration& config)
{
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>(config);
}
template <typename Component>
AZ::Component* CreateComponent(AZ::Entity* entity)
{
m_app.RegisterComponentDescriptor(Component::CreateDescriptor());
return entity->CreateComponent<Component>();
return AZ::Data::AssetHandler::LoadResult::LoadComplete;
}
};
@@ -0,0 +1,284 @@
/*
* 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 <Tests/GradientSignalTestFixtures.h>
#include <AzTest/AzTest.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Memory/PoolAllocator.h>
#include <AzCore/Math/Vector2.h>
#include <AZTestShared/Math/MathTestHelpers.h>
#include <Source/Components/GradientTransformComponent.h>
namespace UnitTest
{
struct GradientSignalTransformTestsFixture : public GradientSignalTest
{
// By default, we'll use a shape half extents of (5, 10, 20) for every test, and a world translation of (100, 200, 300).
struct GradientTransformSetupData
{
GradientSignal::WrappingType m_wrappingType{ GradientSignal::WrappingType::None };
AZ::Vector3 m_shapeHalfExtents{ 5.0f, 10.0f, 20.0f };
AZ::Vector3 m_worldTranslation{ 100.0f, 200.0f, 300.0f };
float m_frequencyZoom{ 1.0f };
};
struct GradientTransformTestData
{
AZ::Vector3 m_positionToTest;
AZ::Vector3 m_expectedOutputUVW;
bool m_expectedOutputRejectionResult;
};
static constexpr float UvEpsilon = GradientSignal::GradientTransform::UvEpsilon;
void TestGradientTransform(const GradientTransformSetupData& setup, const GradientTransformTestData& test)
{
AZ::Aabb shapeBounds = AZ::Aabb::CreateCenterHalfExtents(AZ::Vector3::CreateZero(), setup.m_shapeHalfExtents);
AZ::Matrix3x4 transform = AZ::Matrix3x4::CreateTranslation(setup.m_worldTranslation);
float frequencyZoom = setup.m_frequencyZoom;
GradientSignal::WrappingType wrappingType = setup.m_wrappingType;
AZ::Vector3 outUVW;
bool wasPointRejected;
// Perform the query with a 3D gradient and verify that the results match expectations.
GradientSignal::GradientTransform gradientTransform3d(shapeBounds, transform, true, frequencyZoom, wrappingType);
gradientTransform3d.TransformPositionToUVW(test.m_positionToTest, outUVW, wasPointRejected);
EXPECT_THAT(outUVW, IsClose(test.m_expectedOutputUVW));
EXPECT_EQ(wasPointRejected, test.m_expectedOutputRejectionResult);
// Perform the query with a 2D gradient and verify that the results match, but always returns a W value of 0.
GradientSignal::GradientTransform gradientTransform2d(shapeBounds, transform, false, frequencyZoom, wrappingType);
gradientTransform2d.TransformPositionToUVW(test.m_positionToTest, outUVW, wasPointRejected);
EXPECT_THAT(outUVW, IsClose(AZ::Vector3(test.m_expectedOutputUVW.GetX(), test.m_expectedOutputUVW.GetY(), 0.0f)));
EXPECT_EQ(wasPointRejected, test.m_expectedOutputRejectionResult);
}
};
TEST_F(GradientSignalTransformTestsFixture, UnboundedWrappingReturnsTranslatedInput)
{
GradientTransformSetupData setup = { GradientSignal::WrappingType::None };
GradientTransformTestData test = {
// Input position to query
{ 0.0f, 0.0f, 0.0f },
// Output: For no wrapping, the output is just the input position offset by the world translation.
{ -100.0f, -200.0f, -300.0f }, false
};
TestGradientTransform(setup, test);
}
TEST_F(GradientSignalTransformTestsFixture, ClampToEdgeReturnsValuesClampedToShapeBounds)
{
GradientTransformSetupData setup = { GradientSignal::WrappingType::ClampToEdge };
GradientTransformTestData tests[] = {
// Test: Input point far below minimum shape bounds
// Our input point is below the minimum of shape bounds, so the result should be the minimum corner of the shape.
{ { 0.0f, 0.0f, 0.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input point directly on minimum shape bounds
// Our input point is directly on the minimum of shape bounds, so the result should be the minimum corner of the shape.
{ { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input point inside shape bounds
// Our input point is inside the shape bounds, so the result is just input - translation.
{ { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
// Test: Input point directly on maximum shape bounds
// On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
// expected results are the max shape corner - epsilon.
{ { 105.0f, 210.0f, 320.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
// Test: Input point far above maximum shape bounds
// On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
// expected results are the max shape corner - epsilon.
{ { 1000.0f, 1000.0f, 1000.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
};
for (auto& test : tests)
{
TestGradientTransform(setup, test);
}
}
TEST_F(GradientSignalTransformTestsFixture, MirrorReturnsValuesMirroredBasedOnShapeBounds)
{
/* Here's how the results are expected to work for various inputs when using Mirror wrapping.
* This assumes shape half extents of (5, 10, 20), and a center translation of (100, 200, 300):
* Inputs: Outputs:
* ... ...
* (75, 150, 200) - (85, 170, 240) (-5, -10, -20) to (5, 10, 20) // forward mirror
* (85, 170, 240) - (95, 190, 280) (5, 10, 20) to (-5, -10, -20) // back mirror
* (95, 190, 280) - (105, 210, 320) (-5, -10, -20) to (5, 10, 20) // starting point
* (105, 210, 320) - (115, 230, 360) (5, 10, 20) to (-5, -10, -20) // back mirror
* (115, 230, 360) - (125, 250, 400) (-5, -10, -20) to (5, 10, 20) // forward mirror
* ... ...
* When below the starting point, both forward and back mirrors will be adjusted by UvEpsilon except for points that fall on the
* shape minimums.
* When above the starting point, only back mirrors will be adjusted by UvEpsilon.
*/
GradientTransformSetupData setup = { GradientSignal::WrappingType::Mirror };
GradientTransformTestData tests[] = {
// Test: Input exactly 2x below minimum bounds
// When landing exactly on the 2x boundary, we return the minumum shape bounds. There is no adjustment by epsilon
// on the minimum side of the bounds, even when we're in a mirror below the shape bounds.
{ { 75.0f, 150.0f, 200.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input within 2nd mirror repeat below minimum bounds
// The second mirror repeat should go forward in values, but will be adjusted by UvEpsilon since we're below the
// minimum bounds.
{ { 84.0f, 168.0f, 237.0f }, { 4.0f - UvEpsilon, 8.0f - UvEpsilon, 17.0f - UvEpsilon }, false },
// Test: Input exactly 1x below minimum bounds.
// When landing exactly on the 1x boundary, we return the maximum shape bounds minus epsilon.
{ { 85.0f, 170.0f, 240.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false },
// Test: Input within 1st mirror repeat below minimum bounds
// The first mirror repeat should go backwards in values, but will be adjusted by UvEpsilon since we're below the
// minimum bounds.
{ { 94.0f, 188.0f, 277.0f }, { -4.0f - UvEpsilon, -8.0f - UvEpsilon, -17.0f - UvEpsilon }, false },
// Test: Input inside shape bounds
// The translated input position is (1, 2, 3) is inside the shape bounds, so we should just get the translated
// position back as output.
{ { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
// Test: Input within 1st mirror repeat above maximum bounds
// The first mirror repeat should go backwards in values. We're above the maximum bounds, so the expected result
// is (4, 8, 17) minus an epsilon.
{ { 106.0f, 212.0f, 323.0f }, { 4.0f - UvEpsilon, 8.0f - UvEpsilon, 17.0f - UvEpsilon }, false },
// Test: Input exactly 2x above minimum bounds.
// When landing exactly on the 2x boundary, we return the exact minimum value again.
{ { 115.0f, 230.0f, 360.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input within 2nd mirror repeat above maximum bounds
// The second mirror repeat should go forwards in values. We're above the maximum bounds, so the expected result
// is (-4, -8, -17) with no epsilon.
{ { 116.0f, 232.0f, 363.0f }, { -4.0f, -8.0f, -17.0f }, false },
// Test: Input exactly 2x above maximum bounds
// When landing exactly on the 2x boundary, we return the maximum adjusted by the epsilon again.
{ { 125.0f, 250.0f, 400.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, false }
};
for (auto& test : tests)
{
TestGradientTransform(setup, test);
}
}
TEST_F(GradientSignalTransformTestsFixture, RepeatReturnsRepeatingValuesBasedOnShapeBounds)
{
/* Here's how the results are expected to work for various inputs when using Repeat wrapping.
* This assumes shape half extents of (5, 10, 20), and a center translation of (100, 200, 300):
* Inputs: Outputs:
* ... ...
* (75, 150, 200) - (85, 170, 240) (-5, -10, -20) to (5, 10, 20)
* (85, 170, 240) - (95, 190, 280) (-5, -10, -20) to (5, 10, 20)
* (95, 190, 280) - (105, 210, 320) (-5, -10, -20) to (5, 10, 20) // starting point
* (105, 210, 320) - (115, 230, 360) (-5, -10, -20) to (5, 10, 20)
* (115, 230, 360) - (125, 250, 400) (-5, -10, -20) to (5, 10, 20)
* ... ...
* Every shape min/max boundary point below the starting point will have the max shape value.
* Every shape min/max boundary point above the starting point with have the min shape value.
*/
GradientTransformSetupData setup = { GradientSignal::WrappingType::Repeat };
GradientTransformTestData tests[] = {
// Test: 2x below minimum shape bounds
// We're on a shape boundary below the minimum bounds, so it should return the maximum.
{ { 75.0f, 150.0f, 200.0f }, { 5.0f, 10.0f, 20.0f }, false },
// Test: Input within 2nd repeat below minimum shape bounds
// Every repeat should go forwards in values.
{ { 76.0f, 152.0f, 203.0f }, { -4.0f, -8.0f, -17.0f }, false },
// Test: 1x below minimum shape bounds
// We're on a shape boundary below the minimum bounds, so it should return the maximum.
{ { 85.0f, 170.0f, 240.0f }, { 5.0f, 10.0f, 20.0f }, false },
// Test: Input within 1st repeat below minimum shape bounds
// Every repeat should go forwards in values.
{ { 86.0f, 172.0f, 243.0f }, { -4.0f, -8.0f, -17.0f }, false },
// Test: Input exactly on minimum shape bounds
// This should return the actual minimum bounds.
{ { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input inside shape bounds
// This should return the mapped value.
{ { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
// Test: Input exactly on maximum shape bounds
// We're on a shape boundary above the minimum bounds, so it should return the minimum.
{ { 105.0f, 210.0f, 320.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input within 1st repeat above maximum shape bounds
// Every repeat should go forwards in values.
{ { 106.0f, 212.0f, 323.0f }, { -4.0f, -8.0f, -17.0f }, false },
// Test: 1x above maximum shape bounds
// We're on a shape boundary above the minimum bounds, so it should return the minimum.
{ { 105.0f, 210.0f, 320.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input within 2nd repeat above maximum shape bounds
// Every repeat should go forwards in values.
{ { 106.0f, 212.0f, 323.0f }, { -4.0f, -8.0f, -17.0f }, false },
};
for (auto& test : tests)
{
TestGradientTransform(setup, test);
}
}
TEST_F(GradientSignalTransformTestsFixture, ClampToZeroReturnsClampedValuesBasedOnShapeBounds)
{
GradientTransformSetupData setup = { GradientSignal::WrappingType::ClampToZero };
GradientTransformTestData tests[] = {
// Test: Input point far below minimum shape bounds
// Our input point is below the minimum of shape bounds, so the result should be the minimum corner of the shape.
// Points outside the shape bounds should return "true" for rejected.
{ { 0.0f, 0.0f, 0.0f }, { -5.0f, -10.0f, -20.0f }, true },
// Test: Input point directly on minimum shape bounds
// Our input point is directly on the minimum of shape bounds, so the result should be the minimum corner of the shape.
{ { 95.0f, 190.0f, 280.0f }, { -5.0f, -10.0f, -20.0f }, false },
// Test: Input point inside shape bounds
// Our input point is inside the shape bounds, so the result is just input - translation.
{ { 101.0f, 202.0f, 303.0f }, { 1.0f, 2.0f, 3.0f }, false },
// Test: Input point directly on maximum shape bounds
// On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
// expected results are the max shape corner - epsilon.
// Points outside the shape bounds (which includes the maximum edge of the shape bounds) should return "true" for rejected.
{ { 105.0f, 210.0f, 320.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, true },
// Test: Input point far above maximum shape bounds
// On the maximum side, GradientTransform clamps to "max - epsilon" for consistency with other wrapping types, so our
// expected results are the max shape corner - epsilon.
// Points outside the shape bounds should return "true" for rejected.
{ { 1000.0f, 1000.0f, 1000.0f }, { 5.0f - UvEpsilon, 10.0f - UvEpsilon, 20.0f - UvEpsilon }, true },
};
for (auto& test : tests)
{
TestGradientTransform(setup, test);
}
}
}
@@ -7,5 +7,6 @@
#
set(FILES
Tests/GradientSignalTestFixtures.cpp
Tests/EditorGradientSignalPreviewTests.cpp
)

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