@@ -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
|
||||
#
|
||||
#
|
||||
import os, traceback, binascii, sys, json, pathlib, logging
|
||||
import azlmbr.math
|
||||
import azlmbr.bus
|
||||
from scene_helpers import *
|
||||
|
||||
#
|
||||
# SceneAPI Processor
|
||||
#
|
||||
|
||||
def update_manifest(scene):
|
||||
import uuid
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
from scene_api import scene_data as sceneData
|
||||
|
||||
graph = sceneData.SceneGraph(scene.graph)
|
||||
# Get a list of all the mesh nodes, as well as all the nodes
|
||||
mesh_name_list, all_node_paths = get_mesh_node_names(graph)
|
||||
mesh_name_list.sort(key=lambda node: str.casefold(node.get_path()))
|
||||
scene_manifest = sceneData.SceneManifest()
|
||||
|
||||
clean_filename = scene.sourceFilename.replace('.', '_')
|
||||
|
||||
# Compute the filename of the scene file
|
||||
source_basepath = scene.watchFolder
|
||||
source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
|
||||
source_filename_only = os.path.basename(clean_filename)
|
||||
|
||||
created_entities = []
|
||||
previous_entity_id = azlmbr.entity.InvalidEntityId
|
||||
first_mesh = True
|
||||
|
||||
# Make a list of mesh node paths
|
||||
mesh_path_list = list(map(lambda node: node.get_path(), mesh_name_list))
|
||||
|
||||
# Assume the first mesh is the main mesh
|
||||
main_mesh = mesh_name_list[0]
|
||||
mesh_path = main_mesh.get_path()
|
||||
|
||||
# Create a unique mesh group name using the filename + node name
|
||||
mesh_group_name = '{}_{}'.format(source_filename_only, main_mesh.get_name())
|
||||
# Remove forbidden filename characters from the name since this will become a file on disk later
|
||||
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
|
||||
# Add the MeshGroup to the manifest and give it a unique ID
|
||||
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
|
||||
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
|
||||
# Set our current node as the only node that is included in this MeshGroup
|
||||
scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
|
||||
|
||||
# Explicitly remove all other nodes to prevent implicit inclusions
|
||||
for node in mesh_path_list:
|
||||
if node != mesh_path:
|
||||
scene_manifest.mesh_group_unselect_node(mesh_group, node)
|
||||
|
||||
# Create a LOD rule
|
||||
lod_rule = scene_manifest.mesh_group_add_lod_rule(mesh_group)
|
||||
|
||||
# Loop all the mesh nodes after the first
|
||||
for x in mesh_path_list[1:]:
|
||||
# Add a new LOD level
|
||||
lod = scene_manifest.lod_rule_add_lod(lod_rule)
|
||||
# Select the current mesh for this LOD level
|
||||
scene_manifest.lod_select_node(lod, x)
|
||||
|
||||
# Unselect every other mesh for this LOD level
|
||||
for y in mesh_path_list:
|
||||
if y != x:
|
||||
scene_manifest.lod_unselect_node(lod, y)
|
||||
|
||||
# Create an editor entity
|
||||
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
|
||||
# Add an EditorMeshComponent to the entity
|
||||
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
|
||||
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
|
||||
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
|
||||
# The assetHint will be converted to an AssetId later during prefab loading
|
||||
json_update = json.dumps({
|
||||
"Controller": { "Configuration": { "ModelAsset": {
|
||||
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
|
||||
});
|
||||
# Apply the JSON above to the component we created
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
|
||||
|
||||
create_prefab(scene_manifest, source_filename_only, [entity_id])
|
||||
|
||||
# Convert the manifest to a JSON string and return it
|
||||
new_manifest = scene_manifest.export()
|
||||
|
||||
return new_manifest
|
||||
|
||||
sceneJobHandler = None
|
||||
|
||||
def on_update_manifest(args):
|
||||
try:
|
||||
scene = args[0]
|
||||
return update_manifest(scene)
|
||||
except RuntimeError as err:
|
||||
print (f'ERROR - {err}')
|
||||
log_exception_traceback()
|
||||
except:
|
||||
log_exception_traceback()
|
||||
|
||||
global sceneJobHandler
|
||||
sceneJobHandler = None
|
||||
|
||||
# try to create SceneAPI handler for processing
|
||||
try:
|
||||
import azlmbr.scene as sceneApi
|
||||
if (sceneJobHandler == None):
|
||||
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
|
||||
sceneJobHandler.connect()
|
||||
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
|
||||
except:
|
||||
sceneJobHandler = None
|
||||
@@ -0,0 +1,95 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import traceback, logging, json
|
||||
from typing import Tuple, List
|
||||
|
||||
import azlmbr.bus
|
||||
from scene_api import scene_data as sceneData
|
||||
from scene_api.scene_data import SceneGraphName
|
||||
|
||||
|
||||
def log_exception_traceback():
|
||||
"""
|
||||
Outputs an exception stacktrace.
|
||||
"""
|
||||
data = traceback.format_exc()
|
||||
logger = logging.getLogger('python')
|
||||
logger.error(data)
|
||||
|
||||
|
||||
def sanitize_name_for_disk(name: str):
|
||||
"""
|
||||
Removes illegal filename characters from a string.
|
||||
|
||||
:param name: String to clean.
|
||||
:return: Name with illegal characters removed.
|
||||
"""
|
||||
return "".join(char for char in name if char not in "|<>:\"/?*\\")
|
||||
|
||||
|
||||
def get_mesh_node_names(scene_graph: sceneData.SceneGraph) -> Tuple[List[SceneGraphName], List[str]]:
|
||||
"""
|
||||
Returns a tuple of all the mesh nodes as well as all the node paths
|
||||
|
||||
:param scene_graph: Scene graph to search
|
||||
:return: Tuple of [Mesh Nodes, All Node Paths]
|
||||
"""
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
|
||||
mesh_data_list = []
|
||||
node = scene_graph.get_root()
|
||||
children = []
|
||||
paths = []
|
||||
|
||||
while node.IsValid():
|
||||
# store children to process after siblings
|
||||
if scene_graph.has_node_child(node):
|
||||
children.append(scene_graph.get_node_child(node))
|
||||
|
||||
node_name = sceneData.SceneGraphName(scene_graph.get_node_name(node))
|
||||
paths.append(node_name.get_path())
|
||||
|
||||
# store any node that has mesh data content
|
||||
node_content = scene_graph.get_node_content(node)
|
||||
if node_content.CastWithTypeName('MeshData'):
|
||||
if scene_graph.is_node_end_point(node) is False:
|
||||
if len(node_name.get_path()):
|
||||
mesh_data_list.append(sceneData.SceneGraphName(scene_graph.get_node_name(node)))
|
||||
|
||||
# advance to next node
|
||||
if scene_graph.has_node_sibling(node):
|
||||
node = scene_graph.get_node_sibling(node)
|
||||
elif children:
|
||||
node = children.pop()
|
||||
else:
|
||||
node = azlmbr.scene.graph.NodeIndex()
|
||||
|
||||
return mesh_data_list, paths
|
||||
|
||||
|
||||
def create_prefab(scene_manifest: sceneData.SceneManifest, prefab_name: str, entities: list) -> None:
|
||||
prefab_filename = prefab_name + ".prefab"
|
||||
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", entities,
|
||||
prefab_filename)
|
||||
|
||||
if created_template_id is None or created_template_id == azlmbr.prefab.InvalidTemplateId:
|
||||
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
|
||||
|
||||
# Convert the prefab to a JSON string
|
||||
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
|
||||
|
||||
if output is not None and output.IsSuccess():
|
||||
json_string = output.GetValue()
|
||||
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
|
||||
json_result = json.loads(json_string)
|
||||
# Add a PrefabGroup to the manifest and store the JSON on it
|
||||
scene_manifest.add_prefab_group(prefab_name, uuid, json_result)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
|
||||
@@ -5,55 +5,16 @@
|
||||
# SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
#
|
||||
#
|
||||
import os, traceback, binascii, sys, json, pathlib
|
||||
import azlmbr.math
|
||||
import azlmbr.bus
|
||||
import azlmbr.math
|
||||
|
||||
from scene_helpers import *
|
||||
|
||||
|
||||
#
|
||||
# SceneAPI Processor
|
||||
#
|
||||
|
||||
|
||||
def log_exception_traceback():
|
||||
exc_type, exc_value, exc_tb = sys.exc_info()
|
||||
data = traceback.format_exception(exc_type, exc_value, exc_tb)
|
||||
print(str(data))
|
||||
|
||||
def get_mesh_node_names(sceneGraph):
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
from scene_api import scene_data as sceneData
|
||||
|
||||
meshDataList = []
|
||||
node = sceneGraph.get_root()
|
||||
children = []
|
||||
paths = []
|
||||
|
||||
while node.IsValid():
|
||||
# store children to process after siblings
|
||||
if sceneGraph.has_node_child(node):
|
||||
children.append(sceneGraph.get_node_child(node))
|
||||
|
||||
nodeName = sceneData.SceneGraphName(sceneGraph.get_node_name(node))
|
||||
paths.append(nodeName.get_path())
|
||||
|
||||
# store any node that has mesh data content
|
||||
nodeContent = sceneGraph.get_node_content(node)
|
||||
if nodeContent.CastWithTypeName('MeshData'):
|
||||
if sceneGraph.is_node_end_point(node) is False:
|
||||
if (len(nodeName.get_path())):
|
||||
meshDataList.append(sceneData.SceneGraphName(sceneGraph.get_node_name(node)))
|
||||
|
||||
# advance to next node
|
||||
if sceneGraph.has_node_sibling(node):
|
||||
node = sceneGraph.get_node_sibling(node)
|
||||
elif children:
|
||||
node = children.pop()
|
||||
else:
|
||||
node = azlmbr.scene.graph.NodeIndex()
|
||||
|
||||
return meshDataList, paths
|
||||
|
||||
def add_material_component(entity_id):
|
||||
# Create an override AZ::Render::EditorMaterialComponent
|
||||
editor_material_component = azlmbr.entity.EntityUtilityBus(
|
||||
@@ -64,24 +25,24 @@ def add_material_component(entity_id):
|
||||
|
||||
# this fills out the material asset to a known product AZMaterial asset relative path
|
||||
json_update = json.dumps({
|
||||
"Controller": { "Configuration": { "materials": [
|
||||
{
|
||||
"Key": {},
|
||||
"Value": { "MaterialAsset":{
|
||||
"assetHint": "materials/basic_grey.azmaterial"
|
||||
}}
|
||||
}]
|
||||
}}
|
||||
});
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_material_component, json_update)
|
||||
"Controller": {"Configuration": {"materials": [
|
||||
{
|
||||
"Key": {},
|
||||
"Value": {"MaterialAsset": {
|
||||
"assetHint": "materials/basic_grey.azmaterial"
|
||||
}}
|
||||
}]
|
||||
}}
|
||||
})
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
|
||||
editor_material_component, json_update)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
|
||||
|
||||
|
||||
def update_manifest(scene):
|
||||
import json
|
||||
import uuid, os
|
||||
import azlmbr.scene as sceneApi
|
||||
import azlmbr.scene.graph
|
||||
from scene_api import scene_data as sceneData
|
||||
|
||||
@@ -89,9 +50,9 @@ def update_manifest(scene):
|
||||
# Get a list of all the mesh nodes, as well as all the nodes
|
||||
mesh_name_list, all_node_paths = get_mesh_node_names(graph)
|
||||
scene_manifest = sceneData.SceneManifest()
|
||||
|
||||
|
||||
clean_filename = scene.sourceFilename.replace('.', '_')
|
||||
|
||||
|
||||
# Compute the filename of the scene file
|
||||
source_basepath = scene.watchFolder
|
||||
source_relative_path = os.path.dirname(os.path.relpath(clean_filename, source_basepath))
|
||||
@@ -108,31 +69,39 @@ def update_manifest(scene):
|
||||
# Create a unique mesh group name using the filename + node name
|
||||
mesh_group_name = '{}_{}'.format(source_filename_only, mesh_name.get_name())
|
||||
# Remove forbidden filename characters from the name since this will become a file on disk later
|
||||
mesh_group_name = "".join(char for char in mesh_group_name if char not in "|<>:\"/?*\\")
|
||||
mesh_group_name = sanitize_name_for_disk(mesh_group_name)
|
||||
# Add the MeshGroup to the manifest and give it a unique ID
|
||||
mesh_group = scene_manifest.add_mesh_group(mesh_group_name)
|
||||
mesh_group['id'] = '{' + str(uuid.uuid5(uuid.NAMESPACE_DNS, source_filename_only + mesh_path)) + '}'
|
||||
# Set our current node as the only node that is included in this MeshGroup
|
||||
scene_manifest.mesh_group_select_node(mesh_group, mesh_path)
|
||||
scene_manifest.mesh_group_add_comment(mesh_group, "Hello World")
|
||||
|
||||
# Explicitly remove all other nodes to prevent implicit inclusions
|
||||
for node in all_node_paths:
|
||||
if node != mesh_path:
|
||||
scene_manifest.mesh_group_unselect_node(mesh_group, node)
|
||||
|
||||
scene_manifest.mesh_group_add_cloth_rule(mesh_group, mesh_path, "Col0", 1, "Col0", 2, "Col0", 2, 3)
|
||||
scene_manifest.mesh_group_add_advanced_mesh_rule(mesh_group, True, False, True, "Col0")
|
||||
scene_manifest.mesh_group_add_skin_rule(mesh_group, 3, 0.002)
|
||||
scene_manifest.mesh_group_add_tangent_rule(mesh_group, 1, 0)
|
||||
|
||||
# Create an editor entity
|
||||
entity_id = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "CreateEditorReadyEntity", mesh_group_name)
|
||||
# Add an EditorMeshComponent to the entity
|
||||
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "AZ::Render::EditorMeshComponent")
|
||||
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just created + the azmodel extension
|
||||
# The MeshGroup we created will be output as a product in the asset's path named mesh_group_name.azmodel
|
||||
# The assetHint will be converted to an AssetId later during prefab loading
|
||||
editor_mesh_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
|
||||
entity_id, "AZ::Render::EditorMeshComponent")
|
||||
# Set the ModelAsset assetHint to the relative path of the input asset + the name of the MeshGroup we just
|
||||
# created + the azmodel extension The MeshGroup we created will be output as a product in the asset's path
|
||||
# named mesh_group_name.azmodel The assetHint will be converted to an AssetId later during prefab loading
|
||||
json_update = json.dumps({
|
||||
"Controller": { "Configuration": { "ModelAsset": {
|
||||
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel" }}}
|
||||
});
|
||||
"Controller": {"Configuration": {"ModelAsset": {
|
||||
"assetHint": os.path.join(source_relative_path, mesh_group_name) + ".azmodel"}}}
|
||||
})
|
||||
# Apply the JSON above to the component we created
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, editor_mesh_component, json_update)
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
|
||||
editor_mesh_component, json_update)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
|
||||
@@ -143,17 +112,19 @@ def update_manifest(scene):
|
||||
add_material_component(entity_id)
|
||||
|
||||
# Get the transform component
|
||||
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName", entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
|
||||
transform_component = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "GetOrAddComponentByTypeName",
|
||||
entity_id, "27F1E1A1-8D9D-4C3B-BD3A-AFB9762449C0")
|
||||
|
||||
# Set this entity to be a child of the last entity we created
|
||||
# This is just an example of how to do parenting and isn't necessarily useful to parent everything like this
|
||||
if previous_entity_id is not None:
|
||||
transform_json = json.dumps({
|
||||
"Parent Entity" : previous_entity_id.to_json()
|
||||
});
|
||||
"Parent Entity": previous_entity_id.to_json()
|
||||
})
|
||||
|
||||
# Apply the JSON update
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id, transform_component, transform_json)
|
||||
result = azlmbr.entity.EntityUtilityBus(azlmbr.bus.Broadcast, "UpdateComponentForEntity", entity_id,
|
||||
transform_component, transform_json)
|
||||
|
||||
if not result:
|
||||
raise RuntimeError("UpdateComponentForEntity failed for Transform component")
|
||||
@@ -165,37 +136,23 @@ def update_manifest(scene):
|
||||
created_entities.append(entity_id)
|
||||
|
||||
# Create a prefab with all our entities
|
||||
prefab_filename = source_filename_only + ".prefab"
|
||||
created_template_id = azlmbr.prefab.PrefabSystemScriptingBus(azlmbr.bus.Broadcast, "CreatePrefab", created_entities, prefab_filename)
|
||||
|
||||
if created_template_id == azlmbr.prefab.InvalidTemplateId:
|
||||
raise RuntimeError("CreatePrefab {} failed".format(prefab_filename))
|
||||
|
||||
# Convert the prefab to a JSON string
|
||||
output = azlmbr.prefab.PrefabLoaderScriptingBus(azlmbr.bus.Broadcast, "SaveTemplateToString", created_template_id)
|
||||
|
||||
if output.IsSuccess():
|
||||
jsonString = output.GetValue()
|
||||
uuid = azlmbr.math.Uuid_CreateRandom().ToString()
|
||||
jsonResult = json.loads(jsonString)
|
||||
# Add a PrefabGroup to the manifest and store the JSON on it
|
||||
scene_manifest.add_prefab_group(source_filename_only, uuid, jsonResult)
|
||||
else:
|
||||
raise RuntimeError("SaveTemplateToString failed for template id {}, prefab {}".format(created_template_id, prefab_filename))
|
||||
create_prefab(scene_manifest, source_filename_only, created_entities)
|
||||
|
||||
# Convert the manifest to a JSON string and return it
|
||||
new_manifest = scene_manifest.export()
|
||||
|
||||
return new_manifest
|
||||
|
||||
|
||||
sceneJobHandler = None
|
||||
|
||||
|
||||
def on_update_manifest(args):
|
||||
try:
|
||||
scene = args[0]
|
||||
return update_manifest(scene)
|
||||
except RuntimeError as err:
|
||||
print (f'ERROR - {err}')
|
||||
print(f'ERROR - {err}')
|
||||
log_exception_traceback()
|
||||
except:
|
||||
log_exception_traceback()
|
||||
@@ -203,10 +160,12 @@ def on_update_manifest(args):
|
||||
global sceneJobHandler
|
||||
sceneJobHandler = None
|
||||
|
||||
|
||||
# try to create SceneAPI handler for processing
|
||||
try:
|
||||
import azlmbr.scene as sceneApi
|
||||
if (sceneJobHandler == None):
|
||||
|
||||
if sceneJobHandler is None:
|
||||
sceneJobHandler = sceneApi.ScriptBuildingNotificationBusHandler()
|
||||
sceneJobHandler.connect()
|
||||
sceneJobHandler.add_callback('OnUpdateManifest', on_update_manifest)
|
||||
|
||||
@@ -149,11 +149,14 @@ class AtomComponentProperties:
|
||||
def display_mapper(property: str = 'name') -> str:
|
||||
"""
|
||||
Display Mapper component properties.
|
||||
- 'LDR color Grading LUT' is the Low Definition Range (LDR) color grading for Look-up Textures (LUT) which is
|
||||
an Asset.id value corresponding to a lighting asset file.
|
||||
:param property: From the last element of the property tree path. Default 'name' for component name string.
|
||||
:return: Full property path OR component name if no property specified.
|
||||
"""
|
||||
properties = {
|
||||
'name': 'Display Mapper',
|
||||
'LDR color Grading LUT': 'Controller|Configuration|LDR color Grading LUT',
|
||||
}
|
||||
return properties[property]
|
||||
|
||||
|
||||
+26
-9
@@ -5,6 +5,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
|
||||
class Tests:
|
||||
camera_creation = (
|
||||
"Camera Entity successfully created",
|
||||
@@ -39,6 +40,9 @@ class Tests:
|
||||
is_hidden = (
|
||||
"Entity is hidden",
|
||||
"Entity was not hidden")
|
||||
ldr_color_grading_lut = (
|
||||
"LDR color Grading LUT asset set",
|
||||
"LDR color Grading LUT asset could not be set")
|
||||
entity_deleted = (
|
||||
"Entity deleted",
|
||||
"Entity was not deleted")
|
||||
@@ -71,16 +75,19 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
|
||||
5) Enter/Exit game mode.
|
||||
6) Test IsHidden.
|
||||
7) Test IsVisible.
|
||||
8) Delete Display Mapper entity.
|
||||
9) UNDO deletion.
|
||||
10) REDO deletion.
|
||||
11) Look for errors and asserts.
|
||||
8) Set LDR color Grading LUT asset.
|
||||
9) Delete Display Mapper entity.
|
||||
10) UNDO deletion.
|
||||
11) REDO deletion.
|
||||
12) Look for errors and asserts.
|
||||
|
||||
:return: None
|
||||
"""
|
||||
import os
|
||||
|
||||
import azlmbr.legacy.general as general
|
||||
|
||||
from editor_python_test_tools.asset_utils import Asset
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.utils import Report, Tracer, TestHelper
|
||||
from Atom.atom_utils.atom_constants import AtomComponentProperties
|
||||
@@ -97,7 +104,7 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
|
||||
Report.critical_result(Tests.display_mapper_creation, display_mapper_entity.exists())
|
||||
|
||||
# 2. Add Display Mapper component to Display Mapper entity.
|
||||
display_mapper_entity.add_component(AtomComponentProperties.display_mapper())
|
||||
display_mapper_component = display_mapper_entity.add_component(AtomComponentProperties.display_mapper())
|
||||
Report.critical_result(
|
||||
Tests.display_mapper_component,
|
||||
display_mapper_entity.has_component(AtomComponentProperties.display_mapper()))
|
||||
@@ -140,19 +147,29 @@ def AtomEditorComponents_DisplayMapper_AddedToEntity():
|
||||
general.idle_wait_frames(1)
|
||||
Report.result(Tests.is_visible, display_mapper_entity.is_visible() is True)
|
||||
|
||||
# 8. Delete Display Mapper entity.
|
||||
# 8. Set LDR color Grading LUT asset.
|
||||
display_mapper_asset_path = os.path.join("TestData", "test.lightingpreset.azasset")
|
||||
display_mapper_asset = Asset.find_asset_by_path(display_mapper_asset_path, False)
|
||||
display_mapper_component.set_component_property_value(
|
||||
AtomComponentProperties.display_mapper("LDR color Grading LUT"), display_mapper_asset.id)
|
||||
Report.result(
|
||||
Tests.ldr_color_grading_lut,
|
||||
display_mapper_component.get_component_property_value(
|
||||
AtomComponentProperties.display_mapper("LDR color Grading LUT")) == display_mapper_asset.id)
|
||||
|
||||
# 9. Delete Display Mapper entity.
|
||||
display_mapper_entity.delete()
|
||||
Report.result(Tests.entity_deleted, not display_mapper_entity.exists())
|
||||
|
||||
# 9. UNDO deletion.
|
||||
# 10. UNDO deletion.
|
||||
general.undo()
|
||||
Report.result(Tests.deletion_undo, display_mapper_entity.exists())
|
||||
|
||||
# 10. REDO deletion.
|
||||
# 11. REDO deletion.
|
||||
general.redo()
|
||||
Report.result(Tests.deletion_redo, not display_mapper_entity.exists())
|
||||
|
||||
# 11. Look for errors and asserts.
|
||||
# 12. Look for errors and asserts.
|
||||
TestHelper.wait_for_condition(lambda: error_tracer.has_errors or error_tracer.has_asserts, 1.0)
|
||||
for error_info in error_tracer.errors:
|
||||
Report.info(f"Error: {error_info.filename} {error_info.function} | {error_info.message}")
|
||||
|
||||
@@ -26,8 +26,8 @@ INSTALL
|
||||
It is recommended to set up these these tools with O3DE's CMake build commands.
|
||||
Assuming CMake is already setup on your operating system, below are some sample build commands:
|
||||
cd /path/to/od3e/
|
||||
mkdir windows_vs2019
|
||||
cd windows_vs2019
|
||||
mkdir windows
|
||||
cd windows
|
||||
cmake .. -G "Visual Studio 16 2019" -DLY_PROJECTS=AutomatedTesting
|
||||
|
||||
To manually install the project in development mode using your own installed Python interpreter:
|
||||
|
||||
+11
@@ -459,3 +459,14 @@ class EditorEntity:
|
||||
"""
|
||||
new_translation = convert_to_azvector3(new_translation)
|
||||
azlmbr.components.TransformBus(azlmbr.bus.Event, "SetLocalTranslation", self.id, new_translation)
|
||||
|
||||
# Use this only when prefab system is enabled as it will fail otherwise.
|
||||
def focus_on_owning_prefab(self) -> None:
|
||||
"""
|
||||
Focuses on the owning prefab instance of the given entity.
|
||||
:param entity: The entity used to fetch the owning prefab to focus on.
|
||||
"""
|
||||
|
||||
assert self.id.isValid(), "A valid entity id is required to focus on its owning prefab."
|
||||
focus_prefab_result = azlmbr.prefab.PrefabFocusPublicRequestBus(bus.Broadcast, "FocusOnOwningPrefab", self.id)
|
||||
assert focus_prefab_result.IsSuccess(), f"Prefab operation 'FocusOnOwningPrefab' failed. Error: {focus_prefab_result.GetError()}"
|
||||
|
||||
+32
@@ -34,6 +34,38 @@ class TestHelper:
|
||||
# JIRA: SPEC-2880
|
||||
# general.idle_wait_frames(1)
|
||||
|
||||
@staticmethod
|
||||
def create_level(level_name: str) -> bool:
|
||||
"""
|
||||
:param level_name: The name of the level to be created
|
||||
:return: True if ECreateLevelResult returns 0, False otherwise with logging to report reason
|
||||
"""
|
||||
Report.info(f"Creating level {level_name}")
|
||||
|
||||
# Use these hardcoded values to pass expected values for old terrain system until new create_level API is
|
||||
# available
|
||||
heightmap_resolution = 1024
|
||||
heightmap_meters_per_pixel = 1
|
||||
terrain_texture_resolution = 4096
|
||||
use_terrain = False
|
||||
|
||||
result = general.create_level_no_prompt(level_name, heightmap_resolution, heightmap_meters_per_pixel,
|
||||
terrain_texture_resolution, use_terrain)
|
||||
|
||||
# Result codes are ECreateLevelResult defined in CryEdit.h
|
||||
if result == 1:
|
||||
Report.info(f"{level_name} level already exists")
|
||||
elif result == 2:
|
||||
Report.info("Failed to create directory")
|
||||
elif result == 3:
|
||||
Report.info("Directory length is too long")
|
||||
elif result != 0:
|
||||
Report.info("Unknown error, failed to create level")
|
||||
else:
|
||||
Report.info(f"{level_name} level created successfully")
|
||||
|
||||
return result == 0
|
||||
|
||||
@staticmethod
|
||||
def open_level(directory : str, level : str):
|
||||
# type: (str, str) -> None
|
||||
|
||||
@@ -61,3 +61,11 @@ class TestAutomation(TestAutomationBase):
|
||||
def test_CreatePrefab_UnderAnotherPrefab(self, request, workspace, editor, launcher_platform):
|
||||
from Prefab.tests.create_prefab import CreatePrefab_UnderAnotherPrefab as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
|
||||
|
||||
def test_DeleteEntity_UnderAnotherPrefab(self, request, workspace, editor, launcher_platform):
|
||||
from Prefab.tests.delete_entity import DeleteEntity_UnderAnotherPrefab as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
|
||||
|
||||
def test_DeleteEntity_UnderLevelPrefab(self, request, workspace, editor, launcher_platform):
|
||||
from Prefab.tests.delete_entity import DeleteEntity_UnderLevelPrefab as test_module
|
||||
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)
|
||||
|
||||
+52
@@ -0,0 +1,52 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
def DeleteEntity_UnderAnotherPrefab():
|
||||
"""
|
||||
Test description:
|
||||
- Creates an entity.
|
||||
- Creates a prefab out of the above entity.
|
||||
- Focuses on the created prefab and destroys the entity within.
|
||||
Checks that the entity is correctly destroyed.
|
||||
"""
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
from editor_python_test_tools.prefab_utils import Prefab
|
||||
|
||||
import Prefab.tests.PrefabTestUtils as prefab_test_utils
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
PREFAB_FILE_NAME = 'some_prefab'
|
||||
|
||||
# Creates a new entity at the root level
|
||||
entity = EditorEntity.create_editor_entity()
|
||||
assert entity.id.IsValid(), "Couldn't create entity."
|
||||
|
||||
# Asserts if prefab creation doesn't succeed
|
||||
child_prefab, child_instance = Prefab.create_prefab([entity], PREFAB_FILE_NAME)
|
||||
child_entity_ids_inside_prefab = child_instance.get_direct_child_entities()
|
||||
assert len(
|
||||
child_entity_ids_inside_prefab) == 1, f"{len(child_entity_ids_inside_prefab)} entities found inside prefab" \
|
||||
f" when there should have been just 1 entity"
|
||||
|
||||
child_entity_inside_prefab = child_entity_ids_inside_prefab[0]
|
||||
child_entity_inside_prefab.focus_on_owning_prefab()
|
||||
|
||||
child_entity_inside_prefab.delete()
|
||||
|
||||
# Wait till prefab propagation finishes before validating entity deletion.
|
||||
azlmbr.legacy.general.idle_wait_frames(1)
|
||||
|
||||
child_entity_ids_inside_prefab = child_instance.get_direct_child_entities()
|
||||
assert len(
|
||||
child_entity_ids_inside_prefab) == 0, f"{len(child_entity_ids_inside_prefab)} entities found inside prefab" \
|
||||
f" when there should have been 0 entities"
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(DeleteEntity_UnderAnotherPrefab)
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
def DeleteEntity_UnderLevelPrefab():
|
||||
"""
|
||||
Test description:
|
||||
- Creates an entity.
|
||||
- Destroys the created entity.
|
||||
Checks that the entity is correctly destroyed.
|
||||
"""
|
||||
|
||||
from editor_python_test_tools.editor_entity_utils import EditorEntity
|
||||
import Prefab.tests.PrefabTestUtils as prefab_test_utils
|
||||
|
||||
prefab_test_utils.open_base_tests_level()
|
||||
|
||||
# Creates a new Entity at the root level
|
||||
# Asserts if creation didn't succeed
|
||||
entity = EditorEntity.create_editor_entity_at((100.0, 100.0, 100.0), name = "TestEntity")
|
||||
assert entity.id.IsValid(), "Couldn't create entity"
|
||||
|
||||
level_container_entity = EditorEntity(entity.get_parent_id())
|
||||
entity.delete()
|
||||
|
||||
# Wait till prefab propagation finishes before validating entity deletion.
|
||||
azlmbr.legacy.general.idle_wait_frames(1)
|
||||
level_container_child_entities_count = len(level_container_entity.get_children_ids())
|
||||
assert level_container_child_entities_count == 0, f"The level still has {level_container_child_entities_count}" \
|
||||
f" children when it should have 0."
|
||||
|
||||
if __name__ == "__main__":
|
||||
from editor_python_test_tools.utils import Report
|
||||
Report.start_test(DeleteEntity_UnderLevelPrefab)
|
||||
@@ -4,7 +4,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
A fixture for Setting Up Asset Processor Batch workspace for tests in lmbr_test
|
||||
A fixture for Setting Up Asset Processor Batch workspace for tests
|
||||
"""
|
||||
|
||||
# Import builtin libraries
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ For complete copyright and license terms please see the LICENSE at the root of t
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
|
||||
A fixture for using the Asset Processor in lmbr_test, this will stop the asset processor after every test via
|
||||
A fixture for using the Asset Processor, this will stop the asset processor after every test via
|
||||
the teardown. Using the fixture at class level will stop the asset processor after the suite completes.
|
||||
Using the fixture at test level will stop asset processor after the test completes. Calling this fixture as a test argument will still run the teardown to stop the Asset Processor.
|
||||
"""
|
||||
|
||||
@@ -103,6 +103,19 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS)
|
||||
AZ::AssetBundlerBatch
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AssetPipelineTests.BundleMode
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/bundle_mode_tests.py
|
||||
EXCLUDE_TEST_RUN_TARGET_FROM_IDE
|
||||
TEST_SERIAL
|
||||
TEST_SUITE periodic
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::AssetProcessor
|
||||
AZ::AssetBundlerBatch
|
||||
Legacy::Editor
|
||||
AutomatedTesting.Assets
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AssetPipelineTests.AssetBuilder
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/asset_builder_tests.py
|
||||
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
import azlmbr.bus
|
||||
import azlmbr.editor
|
||||
import azlmbr.legacy.general
|
||||
import sys
|
||||
|
||||
# Print out the passed in bundle_path, so the outer test can verify this was sent in correctly
|
||||
bundle_path = sys.argv[1]
|
||||
print('Bundle mode test running with path {}'.format(sys.argv[1]))
|
||||
|
||||
# Turn on bundle mode. This will trigger some printouts that the outer test logic will validate.
|
||||
azlmbr.legacy.general.set_cvar_integer("sys_report_files_not_found_in_paks", 1)
|
||||
azlmbr.legacy.general.run_console(f"loadbundles {bundle_path}")
|
||||
|
||||
azlmbr.editor.EditorToolsApplicationRequestBus(azlmbr.bus.Broadcast, 'ExitNoPrompt')
|
||||
+93
@@ -0,0 +1,93 @@
|
||||
"""
|
||||
Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
|
||||
SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
"""
|
||||
|
||||
import os
|
||||
import pytest
|
||||
import logging
|
||||
import sys
|
||||
import time
|
||||
pytest.importorskip('ly_test_tools')
|
||||
|
||||
import ly_test_tools.environment.file_system as fs
|
||||
import ly_test_tools.environment.waiter as waiter
|
||||
import ly_test_tools.log.log_monitor
|
||||
|
||||
from ..ap_fixtures.asset_processor_fixture import asset_processor as asset_processor
|
||||
from ..ap_fixtures.bundler_batch_setup_fixture import bundler_batch_setup_fixture as bundler_batch_helper
|
||||
from ..ap_fixtures.timeout_option_fixture import timeout_option_fixture as timeout
|
||||
|
||||
@pytest.mark.SUITE_periodic
|
||||
@pytest.mark.parametrize('launcher_platform', ['windows_editor'])
|
||||
@pytest.mark.parametrize('project', ['AutomatedTesting'])
|
||||
@pytest.mark.parametrize('level', ['auto_test'])
|
||||
class TestBundleMode(object):
|
||||
def test_bundle_mode_with_levels_mounts_bundles_correctly(self, request, editor, level, launcher_platform,
|
||||
asset_processor, workspace, bundler_batch_helper):
|
||||
level_pak = os.path.join("levels", level, "level.pak")
|
||||
|
||||
bundles_folder = os.path.join(workspace.paths.project(), "Bundles")
|
||||
bundle_request_path = os.path.join(bundles_folder, "bundle.pak")
|
||||
bundle_result_path = os.path.join(bundles_folder,
|
||||
bundler_batch_helper.platform_file_name(
|
||||
"bundle.pak", workspace.asset_processor_platform))
|
||||
|
||||
# Create target 'Bundles' folder if it doesn't exist
|
||||
if not os.path.exists(bundles_folder):
|
||||
os.mkdir(bundles_folder)
|
||||
# Delete target bundle file if it already exists
|
||||
if os.path.exists(bundle_result_path):
|
||||
fs.delete([bundle_result_path], True, False)
|
||||
|
||||
# Make asset list file to use in the bundle
|
||||
bundler_batch_helper.call_assetLists(
|
||||
addSeed=level_pak,
|
||||
assetListFile=bundler_batch_helper["asset_info_file_request"],
|
||||
)
|
||||
|
||||
# Make bundle in <project_folder>/Bundles
|
||||
bundler_batch_helper.call_bundles(
|
||||
assetListFile=bundler_batch_helper["asset_info_file_result"],
|
||||
outputBundlePath=bundle_request_path,
|
||||
maxSize="2048",
|
||||
)
|
||||
|
||||
# Ensure the bundle was created
|
||||
assert os.path.exists(bundle_result_path), f"Bundle was not created at location: {bundle_result_path}"
|
||||
|
||||
# The editor flips the slash direction in some of the printouts
|
||||
bundle_result_path_editor_separator = bundle_result_path.replace('\\', '/')
|
||||
|
||||
expected_lines = [
|
||||
# A beginning of test printout can help debug where failures occur, if this line is missing
|
||||
# then the Editor didn't launch, didn't run the Python test, or didn't pass in the right parameter
|
||||
f'Bundle mode test running with path {bundles_folder}',
|
||||
# These printouts happen in response to the loadbundles call, and verify this bundle is actually loaded
|
||||
f"[CONSOLE] Executing console command 'loadbundles {bundles_folder}'",
|
||||
f'(BundlingSystem) - Loading bundles from {bundles_folder} of type .pak',
|
||||
f'(Archive) - Opening archive file {bundle_result_path_editor_separator}',
|
||||
]
|
||||
unexpected_lines = []
|
||||
|
||||
timeout = 180
|
||||
halt_on_unexpected = False
|
||||
test_directory = os.path.join(os.path.dirname(__file__))
|
||||
test_file = os.path.join(test_directory, 'bundle_mode_in_editor_tests.py')
|
||||
editor.args.extend(['-NullRenderer', '-rhi=Null', "--skipWelcomeScreenDialog",
|
||||
"--autotest_mode", "--runpythontest", test_file, "--runpythonargs", bundles_folder])
|
||||
|
||||
with editor.start(launch_ap=True):
|
||||
editor_log_file = os.path.join(editor.workspace.paths.project_log(), 'Editor.log')
|
||||
log_monitor = ly_test_tools.log.log_monitor.LogMonitor(editor, editor_log_file)
|
||||
waiter.wait_for(
|
||||
lambda: editor.is_alive(),
|
||||
timeout,
|
||||
exc=("Log file '{}' was never opened by another process.".format(editor_log_file)),
|
||||
interval=1)
|
||||
log_monitor.monitor_log_for_lines(expected_lines, unexpected_lines, halt_on_unexpected, timeout)
|
||||
|
||||
# Delete the bundle created and used in this test
|
||||
fs.delete([bundle_result_path], True, False)
|
||||
@@ -7,60 +7,6 @@
|
||||
#
|
||||
|
||||
if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_FOUNDATION_TEST_SUPPORTED)
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Main
|
||||
TEST_SUITE main
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
|
||||
PYTEST_MARKS "not REQUIRES_gpu"
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Main_GPU
|
||||
TEST_SUITE main
|
||||
TEST_SERIAL
|
||||
TEST_REQUIRES gpu
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Main.py
|
||||
PYTEST_MARKS "REQUIRES_gpu"
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Periodic
|
||||
TEST_SUITE periodic
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Periodic.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Sandbox
|
||||
TEST_SUITE sandbox
|
||||
TEST_SERIAL
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/TestSuite_Sandbox.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
Legacy::Editor
|
||||
AZ::AssetProcessor
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
Editor
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::EditorTests_Main_Optimized
|
||||
|
||||
@@ -49,7 +49,6 @@ class TestAutomationNoAutoTestMode(EditorTestSuite):
|
||||
from .EditorScripts import AssetPicker_UI_UX as test_module
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
|
||||
@@ -11,24 +11,10 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
|
||||
## DynVeg ##
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::DynamicVegetationTests_Main
|
||||
NAME AutomatedTesting::DynamicVegetationTests_Main_Optimized
|
||||
TEST_SERIAL
|
||||
TEST_SUITE main
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::AssetProcessor
|
||||
Legacy::Editor
|
||||
AutomatedTesting.GameLauncher
|
||||
AutomatedTesting.Assets
|
||||
COMPONENT
|
||||
LargeWorlds
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::DynamicVegetationTests_Periodic
|
||||
TEST_SERIAL
|
||||
TEST_SUITE periodic
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Periodic.py
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main_Optimized.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::AssetProcessor
|
||||
Legacy::Editor
|
||||
@@ -37,7 +23,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
|
||||
COMPONENT
|
||||
LargeWorlds
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::DynamicVegetationTests_Periodic_Optimized
|
||||
TEST_SERIAL
|
||||
@@ -52,20 +37,6 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED AND PAL_TRAIT_BUILD_HOST_TOOLS AND PAL_TRAIT_
|
||||
LargeWorlds
|
||||
)
|
||||
|
||||
ly_add_pytest(
|
||||
NAME AutomatedTesting::DynamicVegetationTests_Main_Optimized
|
||||
TEST_SERIAL
|
||||
TEST_SUITE main
|
||||
PATH ${CMAKE_CURRENT_LIST_DIR}/dyn_veg/TestSuite_Main_Optimized.py
|
||||
RUNTIME_DEPENDENCIES
|
||||
AZ::AssetProcessor
|
||||
Legacy::Editor
|
||||
AutomatedTesting.Assets
|
||||
AutomatedTesting.GameLauncher
|
||||
COMPONENT
|
||||
LargeWorlds
|
||||
)
|
||||
|
||||
## LandscapeCanvas ##
|
||||
|
||||
ly_add_pytest(
|
||||
|
||||
+2
-2
@@ -72,9 +72,9 @@ def DynamicSliceInstanceSpawner_Embedded_E2E():
|
||||
# 1) Create a new, temporary level
|
||||
lvl_name = "tmp_level"
|
||||
helper.init_idle()
|
||||
level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
|
||||
level_created = helper.create_level(lvl_name)
|
||||
general.idle_wait(1.0)
|
||||
Report.critical_result(Tests.level_created, level_created == 0)
|
||||
Report.critical_result(Tests.level_created, level_created)
|
||||
general.set_current_view_position(512.0, 480.0, 38.0)
|
||||
|
||||
# 2) Create a new entity with required vegetation area components and Script Canvas component for launcher test
|
||||
|
||||
+2
-2
@@ -73,9 +73,9 @@ def DynamicSliceInstanceSpawner_External_E2E():
|
||||
# 1) Create a new, temporary level
|
||||
lvl_name = "tmp_level"
|
||||
helper.init_idle()
|
||||
level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
|
||||
level_created = helper.create_level(lvl_name)
|
||||
general.idle_wait(1.0)
|
||||
Report.critical_result(Tests.level_created, level_created == 0)
|
||||
Report.critical_result(Tests.level_created, level_created)
|
||||
general.set_current_view_position(512.0, 480.0, 38.0)
|
||||
|
||||
# 2) Create a new entity with required vegetation area components and switch the Vegetation Asset List Source
|
||||
|
||||
+2
-2
@@ -76,9 +76,9 @@ def LayerBlender_E2E_Editor():
|
||||
# 1) Create a new, temporary level
|
||||
lvl_name = "tmp_level"
|
||||
helper.init_idle()
|
||||
level_created = general.create_level_no_prompt(lvl_name, 1024, 1, 4096, False)
|
||||
level_created = helper.create_level(lvl_name)
|
||||
general.idle_wait(1.0)
|
||||
Report.critical_result(Tests.level_created, level_created == 0)
|
||||
Report.critical_result(Tests.level_created, level_created)
|
||||
|
||||
general.set_current_view_position(500.49, 498.69, 46.66)
|
||||
general.set_current_view_rotation(-42.05, 0.00, -36.33)
|
||||
|
||||
@@ -12,13 +12,23 @@ import ly_test_tools.environment.file_system as file_system
|
||||
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
|
||||
|
||||
|
||||
@pytest.mark.xfail(reason="Optimized tests are experimental, we will enable xfail and monitor them temporarily.")
|
||||
@pytest.mark.SUITE_main
|
||||
@pytest.mark.parametrize("launcher_platform", ['windows_editor'])
|
||||
@pytest.mark.parametrize("project", ["AutomatedTesting"])
|
||||
class TestAutomation(EditorTestSuite):
|
||||
|
||||
enable_prefab_system = False
|
||||
|
||||
# Helpers for test asset cleanup
|
||||
def cleanup_test_level(self, workspace):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
|
||||
def cleanup_test_slices(self, workspace):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
|
||||
"TestSlice_1.slice")], True, True)
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
|
||||
"TestSlice_2.slice")], True, True)
|
||||
|
||||
class test_DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks(EditorParallelTest):
|
||||
from .EditorScripts import DynamicSliceInstanceSpawner_DynamicSliceSpawnerWorks as test_module
|
||||
@@ -38,10 +48,7 @@ class TestAutomation(EditorTestSuite):
|
||||
class test_SpawnerSlices_SliceCreationAndVisibilityToggleWorks(EditorSingleTest):
|
||||
# Custom teardown to remove slice asset created during test
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
|
||||
"TestSlice_1.slice")], True, True)
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "slices",
|
||||
"TestSlice_2.slice")], True, True)
|
||||
TestAutomation.cleanup_test_slices(self, workspace)
|
||||
from .EditorScripts import SpawnerSlices_SliceCreationAndVisibilityToggleWorks as test_module
|
||||
|
||||
class test_AssetListCombiner_CombinedDescriptorsExpressInConfiguredArea(EditorParallelTest):
|
||||
@@ -152,23 +159,29 @@ class TestAutomation(EditorTestSuite):
|
||||
class test_DynamicSliceInstanceSpawner_Embedded_E2E_Editor(EditorSingleTest):
|
||||
from .EditorScripts import DynamicSliceInstanceSpawner_Embedded_E2E as test_module
|
||||
|
||||
# Custom teardown to remove test level created during test
|
||||
# Custom setup/teardown to remove test level created during test
|
||||
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
TestAutomation.cleanup_test_level(self, workspace)
|
||||
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
TestAutomation.cleanup_test_level(self, workspace)
|
||||
|
||||
class test_DynamicSliceInstanceSpawner_External_E2E_Editor(EditorSingleTest):
|
||||
from .EditorScripts import DynamicSliceInstanceSpawner_External_E2E as test_module
|
||||
|
||||
# Custom teardown to remove test level created during test
|
||||
# Custom setup/teardown to remove test level created during test
|
||||
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
TestAutomation.cleanup_test_level(self, workspace)
|
||||
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
|
||||
TestAutomation.cleanup_test_level(self, workspace)
|
||||
|
||||
class test_LayerBlender_E2E_Editor(EditorSingleTest):
|
||||
from .EditorScripts import LayerBlender_E2E_Editor as test_module
|
||||
|
||||
# Custom teardown to remove test level created during test
|
||||
# Custom setup/teardown to remove test level created during test
|
||||
def setup(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
TestAutomation.cleanup_test_level(self, workspace)
|
||||
|
||||
def teardown(self, request, workspace, editor, editor_test_results, launcher_platform):
|
||||
file_system.delete([os.path.join(workspace.paths.engine_root(), "AutomatedTesting", "Levels", "tmp_level")],
|
||||
True, True)
|
||||
TestAutomation.cleanup_test_level(self, workspace)
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:7e169277bca473325281d5fe043cffc9196bd3ef46f6bffbea6e0b5e3b7194a1
|
||||
size 62700
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"values": [
|
||||
{
|
||||
"$type": "ScriptProcessorRule",
|
||||
"scriptFilename": "Editor/Scripts/auto_lod.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -472,7 +472,7 @@ void EditorViewportWidget::Update()
|
||||
{
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
m_entityVisibilityQuery.UpdateVisibility(GetCameraState());
|
||||
m_entityVisibilityQuery.UpdateVisibility(m_renderViewport->GetCameraState());
|
||||
|
||||
if (ed_visibility_logTiming)
|
||||
{
|
||||
@@ -714,7 +714,7 @@ void EditorViewportWidget::RenderAll()
|
||||
|
||||
m_debugDisplay->DepthTestOff();
|
||||
m_manipulatorManager->DrawManipulators(
|
||||
*m_debugDisplay, GetCameraState(),
|
||||
*m_debugDisplay, m_renderViewport->GetCameraState(),
|
||||
BuildMouseInteractionInternal(
|
||||
AztfVi::MouseButtons(AztfVi::TranslateMouseButtons(QGuiApplication::mouseButtons())), keyboardModifiers,
|
||||
BuildMousePick(WidgetToViewport(mapFromGlobal(QCursor::pos())))));
|
||||
@@ -878,31 +878,11 @@ void EditorViewportWidget::OnMenuSelectCurrentCamera()
|
||||
}
|
||||
}
|
||||
|
||||
AzFramework::CameraState EditorViewportWidget::GetCameraState()
|
||||
{
|
||||
return m_renderViewport->GetCameraState();
|
||||
}
|
||||
|
||||
AZ::Vector3 EditorViewportWidget::PickTerrain(const AzFramework::ScreenPoint& point)
|
||||
{
|
||||
return LYVec3ToAZVec3(ViewToWorld(AzToolsFramework::ViewportInteraction::QPointFromScreenPoint(point), nullptr, true));
|
||||
}
|
||||
|
||||
float EditorViewportWidget::TerrainHeight(const AZ::Vector2& position)
|
||||
{
|
||||
return GetIEditor()->GetTerrainElevation(position.GetX(), position.GetY());
|
||||
}
|
||||
|
||||
void EditorViewportWidget::FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntitiesOut)
|
||||
{
|
||||
visibleEntitiesOut.assign(m_entityVisibilityQuery.Begin(), m_entityVisibilityQuery.End());
|
||||
}
|
||||
|
||||
AzFramework::ScreenPoint EditorViewportWidget::ViewportWorldToScreen(const AZ::Vector3& worldPosition)
|
||||
{
|
||||
return m_renderViewport->ViewportWorldToScreen(worldPosition);
|
||||
}
|
||||
|
||||
QWidget* EditorViewportWidget::GetWidgetForViewportContextMenu()
|
||||
{
|
||||
return this;
|
||||
@@ -2132,7 +2112,7 @@ bool EditorViewportWidget::GetActiveCameraState(AzFramework::CameraState& camera
|
||||
{
|
||||
if (m_pPrimaryViewport == this)
|
||||
{
|
||||
cameraState = GetCameraState();
|
||||
cameraState = m_renderViewport->GetCameraState();
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
@@ -205,8 +205,6 @@ private:
|
||||
void* GetSystemCursorConstraintWindow() const override;
|
||||
|
||||
// AzToolsFramework::MainEditorViewportInteractionRequestBus overrides ...
|
||||
AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) override;
|
||||
float TerrainHeight(const AZ::Vector2& position) override;
|
||||
bool ShowingWorldSpace() override;
|
||||
QWidget* GetWidgetForViewportContextMenu() override;
|
||||
|
||||
@@ -293,9 +291,6 @@ private:
|
||||
// This switches the active camera to the next one in the list of (default, all custom cams).
|
||||
void CycleCamera();
|
||||
|
||||
AzFramework::CameraState GetCameraState();
|
||||
AzFramework::ScreenPoint ViewportWorldToScreen(const AZ::Vector3& worldPosition);
|
||||
|
||||
QPoint WidgetToViewport(const QPoint& point) const;
|
||||
QPoint ViewportToWidget(const QPoint& point) const;
|
||||
QSize WidgetToViewport(const QSize& size) const;
|
||||
|
||||
@@ -1396,7 +1396,7 @@ void SandboxIntegrationManager::ContextMenu_NewEntity()
|
||||
{
|
||||
worldPosition = AzToolsFramework::FindClosestPickIntersection(
|
||||
view->GetViewportId(), AzFramework::ScreenPointFromVector2(m_contextMenuViewPoint), AzToolsFramework::EditorPickRayLength,
|
||||
GetDefaultEntityPlacementDistance());
|
||||
AzToolsFramework::GetDefaultEntityPlacementDistance());
|
||||
}
|
||||
|
||||
CreateNewEntityAtPosition(worldPosition);
|
||||
|
||||
@@ -75,17 +75,16 @@ void CImageEx::ReverseUpDown()
|
||||
}
|
||||
|
||||
uint32* pPixData = GetData();
|
||||
uint32* pReversePix = new uint32[GetWidth() * GetHeight()];
|
||||
|
||||
for (int i = GetHeight() - 1, i2 = 0; i >= 0; i--, i2++)
|
||||
const int height = GetHeight();
|
||||
const int width = GetWidth();
|
||||
for (int i = 0; i < height / 2; i++)
|
||||
{
|
||||
for (int k = 0; k < GetWidth(); k++)
|
||||
for (int j = 0; j < width; j++)
|
||||
{
|
||||
pReversePix[i2 * GetWidth() + k] = pPixData[i * GetWidth() + k];
|
||||
AZStd::swap(pPixData[i * width + j], pPixData[(height - 1 - i) * width + j]);
|
||||
}
|
||||
}
|
||||
|
||||
Attach(pReversePix, GetWidth(), GetHeight());
|
||||
}
|
||||
|
||||
void CImageEx::FillAlpha(unsigned char value)
|
||||
|
||||
@@ -14,14 +14,11 @@
|
||||
// Qt
|
||||
#include <QPainter>
|
||||
|
||||
// AzCore
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
|
||||
// AzQtComponents
|
||||
#include <AzQtComponents/DragAndDrop/ViewportDragAndDrop.h>
|
||||
|
||||
// AzToolsFramework
|
||||
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
|
||||
@@ -41,19 +38,6 @@
|
||||
#undef LoadCursor
|
||||
#endif
|
||||
|
||||
AZ_CVAR(
|
||||
float,
|
||||
ed_defaultEntityPlacementDistance,
|
||||
10.0f,
|
||||
nullptr,
|
||||
AZ::ConsoleFunctorFlags::Null,
|
||||
"The default distance to place an entity from the camera if no intersection is found");
|
||||
|
||||
float GetDefaultEntityPlacementDistance()
|
||||
{
|
||||
return ed_defaultEntityPlacementDistance;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
// Viewport drag and drop support
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -63,7 +47,7 @@ void QtViewport::BuildDragDropContext(
|
||||
{
|
||||
context.m_hitLocation = AzToolsFramework::FindClosestPickIntersection(
|
||||
viewportId, AzToolsFramework::ViewportInteraction::ScreenPointFromQPoint(point), AzToolsFramework::EditorPickRayLength,
|
||||
GetDefaultEntityPlacementDistance());
|
||||
AzToolsFramework::GetDefaultEntityPlacementDistance());
|
||||
}
|
||||
|
||||
void QtViewport::dragEnterEvent(QDragEnterEvent* event)
|
||||
|
||||
@@ -87,9 +87,6 @@ enum EStdCursor
|
||||
STD_CURSOR_LAST,
|
||||
};
|
||||
|
||||
//! The default distance an entity is placed from the camera if there is no intersection
|
||||
SANDBOX_API float GetDefaultEntityPlacementDistance();
|
||||
|
||||
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
|
||||
class SANDBOX_API CViewport
|
||||
: public IDisplayViewport
|
||||
|
||||
@@ -325,13 +325,13 @@ namespace AZ
|
||||
|
||||
T& operator*() const
|
||||
{
|
||||
AZ_Assert(m_assetData, "Asset is not loaded");
|
||||
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
|
||||
return *Get();
|
||||
}
|
||||
|
||||
T* operator->() const
|
||||
{
|
||||
AZ_Assert(m_assetData, "Asset is not loaded");
|
||||
AZ_Assert(m_assetData, "Asset %s (%s) is not loaded", m_assetId.ToString<AZStd::string>().c_str(), m_assetHint.c_str());
|
||||
return Get();
|
||||
}
|
||||
|
||||
|
||||
@@ -32,7 +32,16 @@ namespace AZ
|
||||
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
|
||||
inline void ConsoleDataWrapper<BASE_TYPE, THREAD_SAFETY>::operator =(const BASE_TYPE& rhs)
|
||||
{
|
||||
const BASE_TYPE currentValue = this->m_value;
|
||||
// Do the value assignment outside new value check.
|
||||
// Client code can supply a type for m_value that overrides the operator= function and trigger side effects
|
||||
// in the operator= function body. Doing the assignment outside the value change check avoids those side
|
||||
// effects not being triggered because AzCore believes the value wouldn't change.
|
||||
this->m_value = rhs;
|
||||
if (currentValue != rhs)
|
||||
{
|
||||
InvokeCallback();
|
||||
}
|
||||
}
|
||||
|
||||
template <typename BASE_TYPE, ThreadSafety THREAD_SAFETY>
|
||||
|
||||
@@ -78,7 +78,8 @@ namespace AZ::IO
|
||||
|
||||
// native format observers
|
||||
//! Returns string_view stored within the PathView
|
||||
constexpr AZStd::string_view Native() const noexcept;
|
||||
constexpr const AZStd::string_view& Native() const noexcept;
|
||||
constexpr AZStd::string_view& Native() noexcept;
|
||||
//! Conversion operator to retrieve string_view stored within the PathView
|
||||
constexpr explicit operator AZStd::string_view() const noexcept;
|
||||
|
||||
|
||||
@@ -101,7 +101,11 @@ namespace AZ::IO
|
||||
}
|
||||
|
||||
// native format observers
|
||||
constexpr auto PathView::Native() const noexcept -> AZStd::string_view
|
||||
constexpr auto PathView::Native() const noexcept -> const AZStd::string_view&
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
constexpr auto PathView::Native() noexcept -> AZStd::string_view&
|
||||
{
|
||||
return m_path;
|
||||
}
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
//
|
||||
// When AZ_CRC("My string") is used by default it will map to AZ::Crc32("My string").
|
||||
// We do have a pro-processor program which will precompute the crc for you and
|
||||
// transform that macro to AZ_CRC("My string",0xabcdef00) this will expand to just 0xabcdef00.
|
||||
// transform that macro to AZ_CRC("My string", 0x18fbd270) this will expand to just 0x18fbd270.
|
||||
// This will remove completely the "My string" from your executable, it will add it to a database and so on.
|
||||
// WHen you want to update the string, just change the string.
|
||||
// If you don't run the precompile step the code should still run fine, except it will be slower,
|
||||
@@ -24,7 +24,7 @@
|
||||
// a constant expression.
|
||||
// For example
|
||||
// switch(id) {
|
||||
// case AZ_CRC("My string",0xabcdef00): {} break; // this will compile fine
|
||||
// case AZ_CRC("My string",0x18fbd270): {} break; // this will compile fine
|
||||
// case AZ_CRC("My string"): {} break; // this will cause "error C2051: case expression not constant"
|
||||
// }
|
||||
// So it's you choice what you do, depending on your needs.
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Console/ConsoleFunctor.h>
|
||||
#include <AzCore/IO/ByteContainerStream.h>
|
||||
#include <AzCore/IO/Path/Path.h>
|
||||
#include <AzCore/Settings/SettingsRegistryConsoleUtils.h>
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/StringFunc/StringFunc.h>
|
||||
@@ -36,7 +37,7 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
combinedKeyValueCommand.c_str());
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", setOutput.c_str());
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleRemoveSettingsRegistryValue(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
@@ -57,7 +58,7 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", removeOutput.c_str());
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleDumpSettingsRegistryValue(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
@@ -88,13 +89,39 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
}
|
||||
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", outputString.c_str());
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleDumpAllSettingsRegistryValues(SettingsRegistryInterface& settingsRegistry,
|
||||
[[maybe_unused]] const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
ConsoleDumpSettingsRegistryValue(settingsRegistry, { "" });
|
||||
};
|
||||
}
|
||||
|
||||
static void ConsoleMergeFileToSettingsRegistry(SettingsRegistryInterface& settingsRegistry, const ConsoleCommandContainer& commandArgs)
|
||||
{
|
||||
if (commandArgs.empty())
|
||||
{
|
||||
AZ_Error("SettingsRegistryConsoleUtils", false, "Command %s requires a <file path> argument to locate json file to merge",
|
||||
SettingsRegistryMergeFile);
|
||||
return;
|
||||
}
|
||||
|
||||
auto commandArgumentsIter = commandArgs.begin();
|
||||
// Extract the JSON pointer path from the argument list
|
||||
AZStd::string_view filePath{ *commandArgumentsIter++ };
|
||||
AZ::SettingsRegistryInterface::FixedValueString jsonAnchorPath;
|
||||
AZ::StringFunc::Join(jsonAnchorPath, commandArgumentsIter, commandArgs.end(), ' ');
|
||||
|
||||
const auto mergeFormat = AZ::IO::PathView(filePath).Extension() != ".setregpatch" ? AZ::SettingsRegistryInterface::Format::JsonMergePatch : AZ::SettingsRegistryInterface::Format::JsonPatch;
|
||||
if (settingsRegistry.MergeSettingsFile(filePath, mergeFormat, jsonAnchorPath))
|
||||
{
|
||||
const auto mergeFileOutput = AZ::SettingsRegistryInterface::FixedValueString::format(
|
||||
R"(Merged json file "%*.s" anchored to json path "%s" into the global settings registry)" "\n",
|
||||
AZ_STRING_ARG(filePath), jsonAnchorPath.c_str());
|
||||
AZ::Debug::Trace::Output("SettingsRegistry", mergeFileOutput.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[[nodiscard]] ConsoleFunctorHandle RegisterAzConsoleCommands(SettingsRegistryInterface& registry, AZ::IConsole& azConsole)
|
||||
{
|
||||
@@ -115,6 +142,11 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
resultHandle.m_consoleFunctors.emplace_back(azConsole, SettingsRegistryDumpAll,
|
||||
R"(Dumps all values from the global settings registry)" "\n",
|
||||
ConsoleFunctorFlags::Null, AZ::TypeId::CreateNull(), registry, &ConsoleDumpAllSettingsRegistryValues);
|
||||
resultHandle.m_consoleFunctors.emplace_back(azConsole, SettingsRegistryMergeFile,
|
||||
R"(Merges File into the global settings registry)" "\n"
|
||||
R"(@param file-path - path to JSON formatted file to merge)" "\n"
|
||||
R"(@param anchor-path - JSON path to anchor merge operation. Defaults to "")" "\n",
|
||||
ConsoleFunctorFlags::Null, AZ::TypeId::CreateNull(), registry, &ConsoleMergeFileToSettingsRegistry);
|
||||
|
||||
return resultHandle;
|
||||
}
|
||||
|
||||
@@ -14,15 +14,16 @@
|
||||
|
||||
namespace AZ::SettingsRegistryConsoleUtils
|
||||
{
|
||||
//! Only 4 console command are registered for the settings registry
|
||||
//! "regset", "regremove", "regdump", "regdumpall"
|
||||
//! The following console command are registered for the settings registry
|
||||
//! "regset", "regremove", "regdump", "regdumpall", "regset-file"
|
||||
//! The value should be increased if more commands are needed
|
||||
inline constexpr size_t MaxSettingsRegistryConsoleFunctors = 4;
|
||||
inline constexpr size_t MaxSettingsRegistryConsoleFunctors = 5;
|
||||
|
||||
inline constexpr const char* SettingsRegistrySet = "sr_regset";
|
||||
inline constexpr const char* SettingsRegistryRemove = "sr_regremove";
|
||||
inline constexpr const char* SettingsRegistryDump = "sr_regdump";
|
||||
inline constexpr const char* SettingsRegistryDumpAll = "sr_regdumpall";
|
||||
inline constexpr const char* SettingsRegistryMergeFile = "sr_regset_file";
|
||||
|
||||
// RAII structure which owns the instances of the Settings Registry Console commands
|
||||
// registered with an AZ Console
|
||||
@@ -51,6 +52,10 @@ namespace AZ::SettingsRegistryConsoleUtils
|
||||
//!
|
||||
//! "sr_regdumpall" accepts 0 arguments and dumps the entire settings registry
|
||||
//! NOTE: this might result in a large amount of output to the console
|
||||
//!
|
||||
//! "sr_regset_file" accepts 1 or 2 arguments - <file-path> [<anchor json path>]
|
||||
//! Merges the json formatted file <file path> into the settings registry underneath the root anchor ""
|
||||
//! or <anchor json path> if supplied
|
||||
[[nodiscard]] ConsoleFunctorHandle RegisterAzConsoleCommands(SettingsRegistryInterface& registry, AZ::IConsole& azConsole);
|
||||
|
||||
}
|
||||
|
||||
@@ -19,9 +19,6 @@
|
||||
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
|
||||
#include <AzCore/Settings/CommandLine.h>
|
||||
#include <AzCore/std/string/conversions.h>
|
||||
#include <AzCore/std/string/wildcard.h>
|
||||
#include <AzCore/std/tuple.h>
|
||||
#include <AzCore/std/containers/variant.h>
|
||||
#include <AzCore/Utils/Utils.h>
|
||||
|
||||
#include <cinttypes>
|
||||
@@ -983,7 +980,7 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
// code in the loop makes calls that mutates the `commandLine` instance, invalidating the iterators. Making a copy
|
||||
// ensures that the iterators remain valid.
|
||||
// NOLINTNEXTLINE(performance-unnecessary-value-param)
|
||||
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeCommands)
|
||||
void MergeSettingsToRegistry_CommandLine(SettingsRegistryInterface& registry, AZ::CommandLine commandLine, bool executeRegdumpCommands)
|
||||
{
|
||||
// Iterate over all the command line options in order to parse the --regset and --regremove
|
||||
// arguments in the order they were supplied
|
||||
@@ -998,18 +995,44 @@ namespace AZ::SettingsRegistryMergeUtils
|
||||
continue;
|
||||
}
|
||||
}
|
||||
else if (commandArgument.m_option == "regset-file")
|
||||
{
|
||||
AZStd::string_view fileArg(commandArgument.m_value);
|
||||
AZStd::string_view jsonAnchorPath;
|
||||
// double colons is treated as the separator for an anchor path
|
||||
// single colon cannot be used as it is used in Windows paths
|
||||
if (auto anchorPathIndex = AZ::StringFunc::Find(fileArg, "::");
|
||||
anchorPathIndex != AZStd::string_view::npos)
|
||||
{
|
||||
jsonAnchorPath = fileArg.substr(anchorPathIndex + 2);
|
||||
fileArg = fileArg.substr(0, anchorPathIndex);
|
||||
}
|
||||
if (!fileArg.empty())
|
||||
{
|
||||
AZ::IO::PathView filePath(fileArg);
|
||||
const auto mergeFormat = filePath.Extension() != ".setregpatch"
|
||||
? AZ::SettingsRegistryInterface::Format::JsonMergePatch
|
||||
: AZ::SettingsRegistryInterface::Format::JsonPatch;
|
||||
if (!registry.MergeSettingsFile(filePath.Native(), mergeFormat, jsonAnchorPath))
|
||||
{
|
||||
AZ_Warning("SettingsRegistryMergeUtils", false, R"(Merging of file "%.*s" to the Settings Registry has failed at anchor "%.*s".)",
|
||||
AZ_STRING_ARG(filePath.Native()), AZ_STRING_ARG(jsonAnchorPath));
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (commandArgument.m_option == "regremove")
|
||||
{
|
||||
if (!registry.Remove(commandArgument.m_value))
|
||||
{
|
||||
AZ_Warning("SettingsRegistryMergeUtils", false, "Unable to remove value at JSON Pointer %s for --regremove.",
|
||||
commandArgument.m_value.data());
|
||||
commandArgument.m_value.c_str());
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (executeCommands)
|
||||
if (executeRegdumpCommands)
|
||||
{
|
||||
constexpr bool prettifyOutput = true;
|
||||
const size_t regdumpSwitchValues = commandLine.GetNumSwitchValues("regdump");
|
||||
|
||||
@@ -157,6 +157,22 @@ namespace AZ::Statistics
|
||||
}
|
||||
}
|
||||
|
||||
void GetAllStatistics(AZStd::vector<NamedRunningStatistic*>& stats)
|
||||
{
|
||||
for (auto& iter : m_profilers)
|
||||
{
|
||||
iter.second.m_profiler.GetStatsManager().GetAllStatistics(stats);
|
||||
}
|
||||
}
|
||||
|
||||
void GetAllStatisticsOfUnits(AZStd::vector<NamedRunningStatistic*>& stats, const char* units)
|
||||
{
|
||||
for (auto& iter : m_profilers)
|
||||
{
|
||||
iter.second.m_profiler.GetStatsManager().GetAllStatisticsOfUnits(stats, units);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
struct ProfilerInfo
|
||||
{
|
||||
|
||||
@@ -56,13 +56,25 @@ namespace AZ
|
||||
|
||||
void GetAllStatistics(AZStd::vector<NamedRunningStatistic*>& vector)
|
||||
{
|
||||
for (auto const& it : m_statistics)
|
||||
for (const auto& it : m_statistics)
|
||||
{
|
||||
NamedRunningStatistic* stat = it.second;
|
||||
vector.push_back(stat);
|
||||
}
|
||||
}
|
||||
|
||||
void GetAllStatisticsOfUnits(AZStd::vector<NamedRunningStatistic*>& vector, const char* units)
|
||||
{
|
||||
for (const auto& it : m_statistics)
|
||||
{
|
||||
NamedRunningStatistic* stat = it.second;
|
||||
if (stat->GetUnits() == units)
|
||||
{
|
||||
vector.push_back(stat);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//! Helper method to apply units to statistics with empty units string.
|
||||
AZ::u32 ApplyUnits(const AZStd::string& units)
|
||||
{
|
||||
|
||||
@@ -26,6 +26,7 @@
|
||||
#include <AzCore/std/functional.h>
|
||||
#include <AzCore/std/parallel/condition_variable.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
|
||||
#include <AZTestShared/Utils/Utils.h>
|
||||
#include <Streamer/IStreamerMock.h>
|
||||
#include <Tests/Asset/BaseAssetManagerTest.h>
|
||||
@@ -131,8 +132,8 @@ namespace UnitTest
|
||||
* This will test the aspect of the system where ObjectStreams and asset jobs loading dependent
|
||||
* assets will do the work in their own thread.
|
||||
*/
|
||||
class AssetJobsFloodTest
|
||||
: public BaseAssetManagerTest
|
||||
|
||||
class AssetJobsFloodTest : public DisklessAssetManagerBase
|
||||
{
|
||||
public:
|
||||
TestAssetManager* m_testAssetManager{ nullptr };
|
||||
@@ -183,15 +184,14 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BaseAssetManagerTest::SetUp();
|
||||
DisklessAssetManagerBase::SetUp();
|
||||
SetupTest();
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
{
|
||||
TearDownTest();
|
||||
AssetManager::Destroy();
|
||||
BaseAssetManagerTest::TearDown();
|
||||
DisklessAssetManagerBase::TearDown();
|
||||
}
|
||||
|
||||
void SetupAssets()
|
||||
@@ -257,9 +257,9 @@ namespace UnitTest
|
||||
AssetWithSerializedData ap2;
|
||||
AssetWithSerializedData ap3;
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &ap1, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &ap2, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &ap3, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &ap1, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &ap2, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &ap3, m_serializeContext));
|
||||
|
||||
AssetWithAssetReference assetWithPreload1;
|
||||
AssetWithAssetReference assetWithPreload2;
|
||||
@@ -273,11 +273,11 @@ namespace UnitTest
|
||||
noLoadAsset.m_asset = m_testAssetManager->CreateAsset<AssetWithSerializedData>(MyAsset2Id, AssetLoadBehavior::NoLoad);
|
||||
EXPECT_EQ(m_assetHandlerAndCatalog->m_numCreations, 4);
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &assetWithPreload1, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &assetWithPreload2, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &assetWithPreload3, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "DelayLoadAsset.txt", AZ::DataStream::ST_XML, &delayedAsset, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "NoLoadAsset.txt", AZ::DataStream::ST_XML, &noLoadAsset, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &assetWithPreload1, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &assetWithPreload2, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &assetWithPreload3, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("DelayLoadAsset.txt", &delayedAsset, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("NoLoadAsset.txt", &noLoadAsset, m_serializeContext));
|
||||
|
||||
AssetWithQueueAndPreLoadReferences preLoadRoot;
|
||||
AssetWithQueueAndPreLoadReferences preLoadA;
|
||||
@@ -297,16 +297,16 @@ namespace UnitTest
|
||||
preLoadBrokenA.m_preLoad = m_testAssetManager->CreateAsset<AssetWithAssetReference>(PreloadBrokenDepBId, AssetLoadBehavior::PreLoad);
|
||||
preLoadBrokenB.m_preLoad = m_testAssetManager->CreateAsset<AssetWithAssetReference>(PreloadAssetNoDataId, AssetLoadBehavior::PreLoad);
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadRoot.txt", AZ::DataStream::ST_XML, &preLoadRoot, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadA.txt", AZ::DataStream::ST_XML, &preLoadA, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadB.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadC.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "QueueLoadA.txt", AZ::DataStream::ST_XML, &queueLoadA, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "QueueLoadB.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "QueueLoadC.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadBrokenA.txt", AZ::DataStream::ST_XML, &preLoadBrokenA, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadBrokenB.txt", AZ::DataStream::ST_XML, &preLoadBrokenB, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "PreLoadNoData.txt", AZ::DataStream::ST_XML, &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadRoot.txt", &preLoadRoot, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadA.txt", &preLoadA, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadB.txt", &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadC.txt", &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("QueueLoadA.txt", &queueLoadA, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("QueueLoadB.txt", &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("QueueLoadC.txt", &noRefs, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadBrokenA.txt", &preLoadBrokenA, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadBrokenB.txt", &preLoadBrokenB, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("PreLoadNoData.txt", &noRefs, m_serializeContext));
|
||||
|
||||
AssetWithQueueAndPreLoadReferences circularA;
|
||||
AssetWithQueueAndPreLoadReferences circularB;
|
||||
@@ -318,43 +318,15 @@ namespace UnitTest
|
||||
circularC.m_preLoad = m_testAssetManager->CreateAsset<AssetWithAssetReference>(CircularBId, AssetLoadBehavior::PreLoad);
|
||||
circularD.m_preLoad = circularC.m_preLoad;
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularA.txt", AZ::DataStream::ST_XML, &circularA, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularB.txt", AZ::DataStream::ST_XML, &circularB, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularC.txt", AZ::DataStream::ST_XML, &circularC, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "CircularD.txt", AZ::DataStream::ST_XML, &circularD, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularA.txt", &circularA, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularB.txt", &circularB, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularC.txt", &circularC, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("CircularD.txt", &circularD, m_serializeContext));
|
||||
|
||||
m_assetHandlerAndCatalog->m_numCreations = 0;
|
||||
}
|
||||
}
|
||||
|
||||
void TearDownTest()
|
||||
{
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset4.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset5.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset6.txt");
|
||||
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset1.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset2.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "TestAsset3.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "DelayLoadAsset.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "NoLoadAsset.txt");
|
||||
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadRoot.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadA.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadB.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadC.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "QueueLoadA.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "QueueLoadB.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "QueueLoadC.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadBrokenA.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadBrokenB.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "PreLoadNoData.txt");
|
||||
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "CircularA.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "CircularB.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "CircularC.txt");
|
||||
DeleteAssetFromDisk(GetTestFolderPath() + "CircularD.txt");
|
||||
}
|
||||
|
||||
void CheckFinishedCreationsAndDestructions()
|
||||
{
|
||||
// Make sure asset jobs have finished before validating the number of destroyed assets, because it's possible that the asset job
|
||||
@@ -367,7 +339,7 @@ namespace UnitTest
|
||||
};
|
||||
|
||||
static constexpr AZStd::chrono::seconds MaxDispatchTimeoutSeconds = BaseAssetManagerTest::DefaultTimeoutSeconds * 12;
|
||||
|
||||
|
||||
template <typename Pred>
|
||||
bool DispatchEventsUntilCondition(AZ::Data::AssetManager& assetManager, Pred&& conditionPredicate,
|
||||
AZStd::chrono::seconds logIntervalSeconds = BaseAssetManagerTest::DefaultTimeoutSeconds,
|
||||
@@ -608,7 +580,7 @@ namespace UnitTest
|
||||
AZ::Data::AssetData::AssetStatus expected_base_status = AZ::Data::AssetData::AssetStatus::Ready;
|
||||
EXPECT_EQ(baseStatus, expected_base_status);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(AssetJobsFloodTest, RapidAcquireAndRelease)
|
||||
{
|
||||
auto assetUuids = {
|
||||
@@ -641,7 +613,7 @@ namespace UnitTest
|
||||
{
|
||||
Asset<AssetWithAssetReference> asset1 =
|
||||
m_testAssetManager->GetAsset(assetUuid, azrtti_typeid<AssetWithAssetReference>(), AZ::Data::AssetLoadBehavior::PreLoad);
|
||||
|
||||
|
||||
if (checkLoaded)
|
||||
{
|
||||
asset1.BlockUntilLoadComplete();
|
||||
@@ -714,8 +686,8 @@ namespace UnitTest
|
||||
|
||||
AssetWithSerializedData ap;
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "a.txt", AZ::DataStream::ST_XML, &ap, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "b.txt", AZ::DataStream::ST_XML, &ap, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("a.txt", &ap, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("b.txt", &ap, m_serializeContext));
|
||||
}
|
||||
|
||||
auto& assetManager = AssetManager::Instance();
|
||||
@@ -778,7 +750,7 @@ namespace UnitTest
|
||||
* Verify that loads without using the Asset Container still work correctly
|
||||
*/
|
||||
class AssetContainerDisableTest
|
||||
: public BaseAssetManagerTest
|
||||
: public DisklessAssetManagerBase
|
||||
{
|
||||
public:
|
||||
static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
|
||||
@@ -797,7 +769,7 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BaseAssetManagerTest::SetUp();
|
||||
DisklessAssetManagerBase::SetUp();
|
||||
SetupTest();
|
||||
|
||||
}
|
||||
@@ -807,7 +779,7 @@ namespace UnitTest
|
||||
AssetManager::Instance().UnregisterHandler(m_assetHandlerAndCatalog);
|
||||
delete m_assetHandlerAndCatalog;
|
||||
AssetManager::Destroy();
|
||||
BaseAssetManagerTest::TearDown();
|
||||
DisklessAssetManagerBase::TearDown();
|
||||
}
|
||||
|
||||
void SetupAssets()
|
||||
@@ -849,9 +821,9 @@ namespace UnitTest
|
||||
AssetWithSerializedData ap2;
|
||||
AssetWithSerializedData ap3;
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &ap1, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &ap2, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &ap3, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &ap1, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &ap2, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &ap3, m_serializeContext));
|
||||
|
||||
AssetWithAssetReference assetWithPreload1;
|
||||
AssetWithAssetReference assetWithPreload2;
|
||||
@@ -862,9 +834,9 @@ namespace UnitTest
|
||||
assetWithPreload3.m_asset = m_testAssetManager->CreateAsset<AssetWithSerializedData>(MyAsset6Id, AssetLoadBehavior::PreLoad);
|
||||
EXPECT_EQ(m_assetHandlerAndCatalog->m_numCreations, 3);
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &assetWithPreload1, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &assetWithPreload2, m_serializeContext));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &assetWithPreload3, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &assetWithPreload1, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &assetWithPreload2, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &assetWithPreload3, m_serializeContext));
|
||||
|
||||
m_assetHandlerAndCatalog->m_numCreations = 0;
|
||||
}
|
||||
@@ -2014,11 +1986,12 @@ namespace UnitTest
|
||||
CheckFinishedCreationsAndDestructions();
|
||||
m_assetHandlerAndCatalog->AssetCatalogRequestBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
/**
|
||||
* Run multiple threads that get and release assets simultaneously to test AssetManager's thread safety
|
||||
*/
|
||||
class AssetJobsMultithreadedTest
|
||||
: public BaseAssetManagerTest
|
||||
: public DisklessAssetManagerBase
|
||||
{
|
||||
public:
|
||||
static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
|
||||
@@ -2028,6 +2001,7 @@ namespace UnitTest
|
||||
static inline const AZ::Uuid MyAsset5Id{ "{D9CDAB04-D206-431E-BDC0-1DD615D56197}" };
|
||||
static inline const AZ::Uuid MyAsset6Id{ "{B2F139C3-5032-4B52-ADCA-D52A8F88E043}" };
|
||||
|
||||
|
||||
// Initialize the Job Manager with 2 threads for the Asset Manager to use.
|
||||
size_t GetNumJobManagerThreads() const override { return 2; }
|
||||
|
||||
@@ -2078,9 +2052,9 @@ namespace UnitTest
|
||||
AssetWithSerializedData ap2;
|
||||
AssetWithSerializedData ap3;
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &ap1, &context));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &ap2, &context));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &ap3, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &ap1, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &ap2, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &ap3, &context));
|
||||
|
||||
AssetWithAssetReference assetWithPreload1;
|
||||
AssetWithAssetReference assetWithPreload2;
|
||||
@@ -2089,9 +2063,9 @@ namespace UnitTest
|
||||
assetWithPreload2.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset5Id, AssetLoadBehavior::PreLoad);
|
||||
assetWithPreload3.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset6Id, AssetLoadBehavior::PreLoad);
|
||||
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &assetWithPreload1, &context));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &assetWithPreload2, &context));
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &assetWithPreload3, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &assetWithPreload1, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &assetWithPreload2, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &assetWithPreload3, &context));
|
||||
|
||||
EXPECT_TRUE(assetHandlerAndCatalog->m_numCreations == 3);
|
||||
assetHandlerAndCatalog->m_numCreations = 0;
|
||||
@@ -2191,22 +2165,22 @@ namespace UnitTest
|
||||
// A will be saved to disk with MyAsset1Id
|
||||
AssetWithAssetReference a;
|
||||
a.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset2Id);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &a, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &a, &context));
|
||||
AssetWithAssetReference b;
|
||||
b.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset3Id);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &b, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &b, &context));
|
||||
AssetWithAssetReference c;
|
||||
c.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset4Id);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &c, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &c, &context));
|
||||
AssetWithAssetReference d;
|
||||
d.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset5Id);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &d, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &d, &context));
|
||||
AssetWithAssetReference e;
|
||||
e.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset6Id);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset5.txt", AZ::DataStream::ST_XML, &e, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset5.txt", &e, &context));
|
||||
AssetWithAssetReference f;
|
||||
f.m_asset = AssetManager::Instance().CreateAsset<AssetWithSerializedData>(MyAsset1Id); // refer back to asset1
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset6.txt", AZ::DataStream::ST_XML, &f, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset6.txt", &f, &context));
|
||||
|
||||
EXPECT_TRUE(assetHandlerAndCatalog->m_numCreations == 6);
|
||||
assetHandlerAndCatalog->m_numCreations = 0;
|
||||
@@ -2347,26 +2321,26 @@ namespace UnitTest
|
||||
// AssetD is MYASSETD
|
||||
AssetWithSerializedData d;
|
||||
d.m_data = 42;
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset4.txt", AZ::DataStream::ST_XML, &d, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset4.txt", &d, &context));
|
||||
|
||||
// AssetC is MYASSETC
|
||||
AssetWithAssetReference c;
|
||||
c.m_asset = db.CreateAsset<AssetWithSerializedData>(AssetId(MyAssetDId)); // point at D
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset3.txt", AZ::DataStream::ST_XML, &c, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset3.txt", &c, &context));
|
||||
|
||||
// AssetB is MYASSETB
|
||||
AssetWithAssetReference b;
|
||||
b.m_asset = db.CreateAsset<AssetWithAssetReference>(AssetId(MyAssetCId)); // point at C
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset2.txt", AZ::DataStream::ST_XML, &b, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset2.txt", &b, &context));
|
||||
|
||||
// AssetA will be written to disk as MYASSETA
|
||||
AssetWithAssetReference a;
|
||||
a.m_asset = db.CreateAsset<AssetWithAssetReference>(AssetId(MyAssetBId)); // point at B
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "TestAsset1.txt", AZ::DataStream::ST_XML, &a, &context));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("TestAsset1.txt", &a, &context));
|
||||
}
|
||||
|
||||
const size_t numThreads = 4;
|
||||
AZStd::atomic_int threadCount(numThreads);
|
||||
constexpr size_t NumThreads = 4;
|
||||
AZStd::atomic_int threadCount(NumThreads);
|
||||
AZStd::condition_variable cv;
|
||||
AZStd::vector<AZStd::thread> threads;
|
||||
AZStd::atomic_bool keepDispatching(true);
|
||||
@@ -2381,7 +2355,7 @@ namespace UnitTest
|
||||
|
||||
AZStd::thread dispatchThread(dispatch);
|
||||
|
||||
for (size_t threadIdx = 0; threadIdx < numThreads; ++threadIdx)
|
||||
for (size_t threadIdx = 0; threadIdx < NumThreads; ++threadIdx)
|
||||
{
|
||||
threads.emplace_back([&threadCount, &db, &cv]()
|
||||
{
|
||||
@@ -2569,7 +2543,6 @@ namespace UnitTest
|
||||
#if AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
|
||||
TEST_F(AssetJobsMultithreadedTest, DISABLED_ParallelDeepAssetReferences)
|
||||
#else
|
||||
// temporarily disabled until sporadic failures can be root caused
|
||||
TEST_F(AssetJobsMultithreadedTest, ParallelDeepAssetReferences)
|
||||
#endif // AZ_TRAIT_DISABLE_FAILED_ASSET_MANAGER_TESTS
|
||||
{
|
||||
@@ -2577,7 +2550,7 @@ namespace UnitTest
|
||||
}
|
||||
|
||||
class AssetManagerTests
|
||||
: public BaseAssetManagerTest
|
||||
: public DisklessAssetManagerBase
|
||||
{
|
||||
protected:
|
||||
static inline const AZ::Uuid MyAsset1Id{ "{5B29FE2B-6B41-48C9-826A-C723951B0560}" };
|
||||
@@ -2592,7 +2565,7 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BaseAssetManagerTest::SetUp();
|
||||
DisklessAssetManagerBase::SetUp();
|
||||
|
||||
m_console = AZStd::make_unique<AZ::Console>();
|
||||
AZ::Interface<AZ::IConsole>::Register(m_console.get());
|
||||
@@ -2631,7 +2604,7 @@ namespace UnitTest
|
||||
AssetManager::Destroy();
|
||||
AZ::Interface<AZ::IConsole>::Unregister(m_console.get());
|
||||
m_console = nullptr;
|
||||
BaseAssetManagerTest::TearDown();
|
||||
DisklessAssetManagerBase::TearDown();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -2982,7 +2955,7 @@ namespace UnitTest
|
||||
* the middle of loading. The tests help ensure that assets can't get stuck in perpetual loading states.
|
||||
**/
|
||||
class AssetManagerClearAssetReferenceTests
|
||||
: public BaseAssetManagerTest
|
||||
: public DisklessAssetManagerBase
|
||||
{
|
||||
protected:
|
||||
static inline const AZ::Uuid RootAssetId{ "{AB13F568-C676-41FE-A7E9-341F71A78104}" };
|
||||
@@ -3001,7 +2974,7 @@ namespace UnitTest
|
||||
|
||||
void SetUp() override
|
||||
{
|
||||
BaseAssetManagerTest::SetUp();
|
||||
DisklessAssetManagerBase::SetUp();
|
||||
|
||||
// create the database
|
||||
AssetManager::Descriptor desc;
|
||||
@@ -3039,21 +3012,18 @@ namespace UnitTest
|
||||
|
||||
// Create and save the dependent asset first, so that we can get a reference to it.
|
||||
AssetWithSerializedData dependentBlockingAsset;
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "DependentPreloadBlockingAsset.txt",
|
||||
AZ::DataStream::ST_XML, &dependentBlockingAsset, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("DependentPreloadBlockingAsset.txt", &dependentBlockingAsset, m_serializeContext));
|
||||
|
||||
AssetWithAssetReference dependentAsset;
|
||||
dependentAsset.m_asset = AssetManager::Instance().CreateAsset<AssetWithAssetReference>(
|
||||
NestedDependentPreloadBlockingAssetId, AssetLoadBehavior::PreLoad);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "DependentPreloadAsset.txt",
|
||||
AZ::DataStream::ST_XML, &dependentAsset, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("DependentPreloadAsset.txt", &dependentAsset, m_serializeContext));
|
||||
|
||||
// Create and save the top-level asset.
|
||||
AssetWithAssetReference rootAsset;
|
||||
rootAsset.m_asset = AssetManager::Instance().CreateAsset<AssetWithAssetReference>(
|
||||
DependentPreloadAssetId, AssetLoadBehavior::PreLoad);
|
||||
EXPECT_TRUE(AZ::Utils::SaveObjectToFile(GetTestFolderPath() + "RootAsset.txt",
|
||||
AZ::DataStream::ST_XML, &rootAsset, m_serializeContext));
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile("RootAsset.txt", &rootAsset, m_serializeContext));
|
||||
}
|
||||
|
||||
void TearDown() override
|
||||
@@ -3065,7 +3035,7 @@ namespace UnitTest
|
||||
delete m_assetHandlerAndCatalog;
|
||||
|
||||
AssetManager::Destroy();
|
||||
BaseAssetManagerTest::TearDown();
|
||||
DisklessAssetManagerBase::TearDown();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -165,4 +165,254 @@ namespace UnitTest
|
||||
|
||||
EXPECT_FALSE(AssetManager::Instance().HasActiveJobsOrStreamerRequests());
|
||||
}
|
||||
|
||||
MemoryStreamerWrapper::MemoryStreamerWrapper()
|
||||
{
|
||||
using ::testing::_;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
|
||||
ON_CALL(m_mockStreamer, SuspendProcessing()).WillByDefault([this]()
|
||||
{
|
||||
m_suspended = true;
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, ResumeProcessing()).WillByDefault([this]()
|
||||
{
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
|
||||
m_suspended = false;
|
||||
|
||||
while (!m_processingQueue.empty())
|
||||
{
|
||||
FileRequestHandle requestHandle = m_processingQueue.front();
|
||||
m_processingQueue.pop();
|
||||
|
||||
const auto& onCompleteCallback = GetReadRequest(requestHandle)->m_callback;
|
||||
|
||||
if (onCompleteCallback)
|
||||
{
|
||||
onCompleteCallback(requestHandle);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, Read(_, ::testing::An<IStreamerTypes::RequestMemoryAllocator&>(), _, _, _, _))
|
||||
.WillByDefault(
|
||||
[this](
|
||||
[[maybe_unused]] AZStd::string_view relativePath, IStreamerTypes::RequestMemoryAllocator& allocator, size_t size,
|
||||
AZStd::chrono::microseconds deadline, IStreamerTypes::Priority priority, [[maybe_unused]] size_t offset)
|
||||
{
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
|
||||
ReadRequest request;
|
||||
|
||||
// Save off the requested deadline and priority
|
||||
request.m_deadline = deadline;
|
||||
request.m_priority = priority;
|
||||
request.m_data = allocator.Allocate(size, size, 8);
|
||||
|
||||
const auto* virtualFile = FindFile(relativePath);
|
||||
|
||||
AZ_Assert(
|
||||
virtualFile->size() == size, "Streamer read request size did not match size of saved file: %d vs %d (%.*s)",
|
||||
virtualFile->size(), size,
|
||||
relativePath.size(), relativePath.data());
|
||||
AZ_Assert(size > 0, "Size is zero %.*s", relativePath.size(), relativePath.data());
|
||||
|
||||
memcpy(request.m_data.m_address, virtualFile->data(), size);
|
||||
|
||||
// Create a real file request result and return it
|
||||
request.m_request = m_context.GetNewExternalRequest();
|
||||
|
||||
m_readRequests.push_back(request);
|
||||
|
||||
return request.m_request;
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, SetRequestCompleteCallback(_, _))
|
||||
.WillByDefault([this](FileRequestPtr& request, AZ::IO::IStreamer::OnCompleteCallback callback) -> FileRequestPtr&
|
||||
{
|
||||
// Save off the callback just so that we can call it when the request is "done"
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
ReadRequest* readRequest = GetReadRequest(request);
|
||||
readRequest->m_callback = callback;
|
||||
|
||||
return request;
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, QueueRequest(_))
|
||||
.WillByDefault([this](const auto& fileRequest)
|
||||
{
|
||||
if (!m_suspended)
|
||||
{
|
||||
decltype(ReadRequest::m_callback) onCompleteCallback;
|
||||
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
ReadRequest* readRequest = GetReadRequest(fileRequest);
|
||||
onCompleteCallback = readRequest->m_callback;
|
||||
|
||||
if (onCompleteCallback)
|
||||
{
|
||||
onCompleteCallback(fileRequest);
|
||||
|
||||
m_readRequests.erase(readRequest);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
|
||||
m_processingQueue.push(fileRequest);
|
||||
}
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, GetRequestStatus(_))
|
||||
.WillByDefault([]([[maybe_unused]] FileRequestHandle request)
|
||||
{
|
||||
// Return whatever request status has been set in this class
|
||||
return IO::IStreamerTypes::RequestStatus::Completed;
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, GetReadRequestResult(_, _, _, _))
|
||||
.WillByDefault([this](
|
||||
[[maybe_unused]] FileRequestHandle request, void*& buffer, AZ::u64& numBytesRead,
|
||||
IStreamerTypes::ClaimMemory claimMemory)
|
||||
{
|
||||
// Make sure the requestor plans to free the data buffer we allocated.
|
||||
EXPECT_EQ(claimMemory, IStreamerTypes::ClaimMemory::Yes);
|
||||
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
|
||||
ReadRequest* readRequest = GetReadRequest(request);
|
||||
|
||||
// Provide valid data buffer results.
|
||||
numBytesRead = readRequest->m_data.m_size;
|
||||
buffer = readRequest->m_data.m_address;
|
||||
|
||||
return true;
|
||||
});
|
||||
|
||||
ON_CALL(m_mockStreamer, RescheduleRequest(_, _, _))
|
||||
.WillByDefault([this](IO::FileRequestPtr target, AZStd::chrono::microseconds newDeadline, IO::IStreamerTypes::Priority newPriority)
|
||||
{
|
||||
AZStd::unique_lock lock(m_mutex);
|
||||
ReadRequest* readRequest = GetReadRequest(target);
|
||||
|
||||
readRequest->m_deadline = newDeadline;
|
||||
readRequest->m_priority = newPriority;
|
||||
|
||||
return target;
|
||||
});
|
||||
}
|
||||
|
||||
ReadRequest* MemoryStreamerWrapper::GetReadRequest(FileRequestHandle request)
|
||||
{
|
||||
auto itr = AZStd::find_if(
|
||||
m_readRequests.begin(), m_readRequests.end(),
|
||||
[request](const ReadRequest& searchItem) -> bool
|
||||
{
|
||||
return (searchItem.m_request == request);
|
||||
});
|
||||
|
||||
return itr;
|
||||
}
|
||||
|
||||
AZStd::vector<char>* MemoryStreamerWrapper::FindFile(AZStd::string_view path)
|
||||
{
|
||||
auto itr = m_virtualFiles.find(path);
|
||||
|
||||
if (itr == m_virtualFiles.end())
|
||||
{
|
||||
// Path didn't work as-is, does it have the test folder prefixed? If so try removing it
|
||||
if (AZ::StringFunc::StartsWith(path, GetTestFolderPath()))
|
||||
{
|
||||
AZStd::string_view pathWithoutFolder = path;
|
||||
|
||||
pathWithoutFolder = AZ::StringFunc::LStrip(pathWithoutFolder, GetTestFolderPath().c_str());
|
||||
itr = m_virtualFiles.find(pathWithoutFolder);
|
||||
}
|
||||
else // Path isn't prefixed, so try adding it
|
||||
{
|
||||
itr = m_virtualFiles.find(GetTestFolderPath().append(path));
|
||||
}
|
||||
}
|
||||
|
||||
if (itr != m_virtualFiles.end())
|
||||
{
|
||||
return &itr->second;
|
||||
}
|
||||
|
||||
// Currently no test expects a file not to exist so we assert to make it easy to quickly find where something went wrong
|
||||
// If we ever need to test for a non-existent file this assert should just be conditionally disabled for that specific test
|
||||
AZ_Assert(false, "Failed to find virtual file %*.s", path.size(), path.data())
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
void DisklessAssetManagerBase::SetUp()
|
||||
{
|
||||
using ::testing::_;
|
||||
using ::testing::NiceMock;
|
||||
using ::testing::Return;
|
||||
|
||||
BaseAssetManagerTest::SetUp();
|
||||
|
||||
ON_CALL(m_fileIO, Size(::testing::Matcher<const char*>(::testing::_), _))
|
||||
.WillByDefault(
|
||||
[this](const char* path, u64& size)
|
||||
{
|
||||
AZStd::scoped_lock lock(m_streamerWrapper->m_mutex);
|
||||
|
||||
const auto* file = m_streamerWrapper->FindFile(path);
|
||||
|
||||
if (file)
|
||||
{
|
||||
size = file->size();
|
||||
return ResultCode::Success;
|
||||
}
|
||||
|
||||
AZ_Error("DisklessAssetManagerBase", false, "Failed to find virtual file %.*s", path);
|
||||
|
||||
return ResultCode::Error;
|
||||
});
|
||||
|
||||
m_prevFileIO = IO::FileIOBase::GetInstance();
|
||||
IO::FileIOBase::SetInstance(nullptr);
|
||||
IO::FileIOBase::SetInstance(&m_fileIO);
|
||||
}
|
||||
|
||||
void DisklessAssetManagerBase::TearDown()
|
||||
{
|
||||
IO::FileIOBase::SetInstance(nullptr);
|
||||
IO::FileIOBase::SetInstance(m_prevFileIO);
|
||||
|
||||
BaseAssetManagerTest::TearDown();
|
||||
}
|
||||
|
||||
IO::IStreamer* DisklessAssetManagerBase::CreateStreamer()
|
||||
{
|
||||
m_streamerWrapper = AZStd::make_unique<MemoryStreamerWrapper>();
|
||||
|
||||
return &(m_streamerWrapper->m_mockStreamer);
|
||||
}
|
||||
|
||||
void DisklessAssetManagerBase::DestroyStreamer(IO::IStreamer*)
|
||||
{
|
||||
m_streamerWrapper = nullptr;
|
||||
}
|
||||
|
||||
void DisklessAssetManagerBase::WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string&)
|
||||
{
|
||||
AZStd::string assetFileName = GetTestFolderPath() + assetName;
|
||||
|
||||
AssetWithCustomData asset;
|
||||
|
||||
EXPECT_TRUE(m_streamerWrapper->WriteMemoryFile(assetFileName, &asset, m_serializeContext));
|
||||
}
|
||||
|
||||
void DisklessAssetManagerBase::DeleteAssetFromDisk(const AZStd::string&)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -20,7 +20,8 @@
|
||||
#include <Tests/Asset/TestAssetTypes.h>
|
||||
#include <Tests/SerializeContextFixture.h>
|
||||
#include <Tests/TestCatalog.h>
|
||||
|
||||
#include <AzCore/UnitTest/Mocks/MockFileIOBase.h>
|
||||
#include <Streamer/IStreamerMock.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
@@ -58,7 +59,11 @@ namespace UnitTest
|
||||
|
||||
// Subclasses can optionally override the streamer creation and destruction
|
||||
virtual IO::IStreamer* CreateStreamer() { return aznew IO::Streamer(AZStd::thread_desc{}, StreamerComponent::CreateStreamerStack()); }
|
||||
virtual void DestroyStreamer(IO::IStreamer* streamer) { delete streamer; }
|
||||
virtual void DestroyStreamer(IO::IStreamer* streamer)
|
||||
{
|
||||
delete streamer;
|
||||
streamer = nullptr;
|
||||
}
|
||||
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
@@ -66,8 +71,8 @@ namespace UnitTest
|
||||
static void SuppressTraceOutput(bool suppress);
|
||||
|
||||
// Helper methods to create and destroy actual assets on the disk for true end-to-end asset loading.
|
||||
void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid);
|
||||
void DeleteAssetFromDisk(const AZStd::string& assetName);
|
||||
virtual void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid);
|
||||
virtual void DeleteAssetFromDisk(const AZStd::string& assetName);
|
||||
|
||||
void BlockUntilAssetJobsAreComplete();
|
||||
|
||||
@@ -82,4 +87,57 @@ namespace UnitTest
|
||||
AZStd::vector<AZStd::string> m_assetsWritten;
|
||||
};
|
||||
|
||||
|
||||
struct ReadRequest
|
||||
{
|
||||
AZStd::chrono::milliseconds m_deadline{};
|
||||
AZ::IO::IStreamerTypes::Priority m_priority{};
|
||||
IO::IStreamerTypes::RequestMemoryAllocatorResult m_data{ nullptr, 0, IO::IStreamerTypes::MemoryType::ReadWrite };
|
||||
AZ::IO::IStreamer::OnCompleteCallback m_callback;
|
||||
IO::FileRequestPtr m_request;
|
||||
};
|
||||
|
||||
struct MemoryStreamerWrapper
|
||||
{
|
||||
MemoryStreamerWrapper();
|
||||
~MemoryStreamerWrapper() = default;
|
||||
|
||||
ReadRequest* GetReadRequest(IO::FileRequestHandle request);
|
||||
|
||||
template<typename TObject>
|
||||
bool WriteMemoryFile(const AZStd::string& filePath, TObject* object, AZ::SerializeContext* context)
|
||||
{
|
||||
auto& buffer = m_virtualFiles[filePath];
|
||||
ByteContainerStream stream(&buffer);
|
||||
|
||||
return AZ::Utils::SaveObjectToStream(stream, DataStream::StreamType::ST_XML, object, context);
|
||||
}
|
||||
|
||||
AZStd::vector<char>* FindFile(AZStd::string_view path);
|
||||
|
||||
::testing::NiceMock<StreamerMock> m_mockStreamer;
|
||||
IO::StreamerContext m_context;
|
||||
AZStd::atomic_bool m_suspended{ false };
|
||||
|
||||
AZStd::recursive_mutex m_mutex;
|
||||
AZStd::queue<FileRequestHandle> m_processingQueue; // Keeps tracks of requests that have been queued while processing is suspended
|
||||
AZStd::vector<ReadRequest> m_readRequests;
|
||||
AZStd::unordered_map<AZStd::string, AZStd::vector<char>> m_virtualFiles;
|
||||
};
|
||||
|
||||
struct DisklessAssetManagerBase : BaseAssetManagerTest
|
||||
{
|
||||
void SetUp() override;
|
||||
void TearDown() override;
|
||||
IO::IStreamer* CreateStreamer() override;
|
||||
void DestroyStreamer(IO::IStreamer*) override;
|
||||
|
||||
void WriteAssetToDisk(const AZStd::string& assetName, const AZStd::string& assetIdGuid) override;
|
||||
void DeleteAssetFromDisk(const AZStd::string& assetName) override;
|
||||
|
||||
AZStd::unique_ptr<MemoryStreamerWrapper> m_streamerWrapper;
|
||||
::testing::NiceMock<MockFileIOBase> m_fileIO;
|
||||
IO::FileIOBase* m_prevFileIO{};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
@@ -539,6 +539,22 @@ tags=tools,renderer,metal)"
|
||||
EXPECT_STREQ("Bat", commandLine.GetMiscValue(2).c_str());
|
||||
}
|
||||
|
||||
TEST_F(SettingsRegistryMergeUtilsCommandLineFixture, RegsetFileArgument_DoesNotMergeNUL)
|
||||
{
|
||||
AZStd::string regsetFile = AZ::IO::SystemFile::GetNullFilename();
|
||||
AZ::CommandLine commandLine;
|
||||
commandLine.Parse({ "--regset-file", regsetFile });
|
||||
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_registry, commandLine, false);
|
||||
|
||||
// Add a settings path to anchor loaded settings underneath
|
||||
regsetFile = AZStd::string::format("%s::/AnchorPath/Of/Settings", AZ::IO::SystemFile::GetNullFilename());
|
||||
commandLine.Parse({ "--regset-file", regsetFile });
|
||||
|
||||
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_CommandLine(*m_registry, commandLine, false);
|
||||
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::NoType, m_registry->GetType("/AnchorPath/Of/Settings"));
|
||||
}
|
||||
|
||||
using SettingsRegistryAncestorDescendantOrEqualPathFixture = SettingsRegistryMergeUtilsCommandLineFixture;
|
||||
|
||||
TEST_F(SettingsRegistryAncestorDescendantOrEqualPathFixture, ValidateThatAncestorOrDescendantOrPathWithTheSameValue_Succeeds)
|
||||
|
||||
@@ -167,7 +167,8 @@ namespace UnitTest
|
||||
if (!info.m_streamName.empty())
|
||||
{
|
||||
AZStd::string fullName = GetTestFolderPath() + info.m_streamName;
|
||||
info.m_dataLen = static_cast<size_t>(IO::SystemFile::Length(fullName.c_str()));
|
||||
IO::FileIOBase* io = IO::FileIOBase::GetInstance();
|
||||
io->Size(fullName.c_str(), info.m_dataLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -187,8 +188,11 @@ namespace UnitTest
|
||||
|
||||
if (!info.m_streamName.empty())
|
||||
{
|
||||
IO::FileIOBase* io = AZ::IO::FileIOBase::GetInstance();
|
||||
|
||||
AZStd::string fullName = GetTestFolderPath() + info.m_streamName;
|
||||
info.m_dataLen = static_cast<size_t>(IO::SystemFile::Length(fullName.c_str()));
|
||||
|
||||
io->Size(fullName.c_str(), info.m_dataLen);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
||||
+7
-6
@@ -6,16 +6,17 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "CommunicatorTracePrinter.h"
|
||||
#include "ProcessCommunicatorTracePrinter.h"
|
||||
|
||||
CommunicatorTracePrinter::CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) :
|
||||
|
||||
ProcessCommunicatorTracePrinter::ProcessCommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window) :
|
||||
m_communicator(communicator),
|
||||
m_window(window)
|
||||
{
|
||||
m_stringBeingConcatenated.reserve(1024);
|
||||
}
|
||||
|
||||
CommunicatorTracePrinter::~CommunicatorTracePrinter()
|
||||
ProcessCommunicatorTracePrinter::~ProcessCommunicatorTracePrinter()
|
||||
{
|
||||
// flush stdout
|
||||
WriteCurrentString(false);
|
||||
@@ -24,7 +25,7 @@ CommunicatorTracePrinter::~CommunicatorTracePrinter()
|
||||
WriteCurrentString(true);
|
||||
}
|
||||
|
||||
void CommunicatorTracePrinter::Pump()
|
||||
void ProcessCommunicatorTracePrinter::Pump()
|
||||
{
|
||||
if (m_communicator->IsValid())
|
||||
{
|
||||
@@ -42,7 +43,7 @@ void CommunicatorTracePrinter::Pump()
|
||||
}
|
||||
}
|
||||
|
||||
void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr)
|
||||
void ProcessCommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr)
|
||||
{
|
||||
if (readSize > AZ_ARRAY_SIZE(m_streamBuffer))
|
||||
{
|
||||
@@ -67,7 +68,7 @@ void CommunicatorTracePrinter::ParseDataBuffer(AZ::u32 readSize, bool isFromStdE
|
||||
}
|
||||
}
|
||||
|
||||
void CommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr)
|
||||
void ProcessCommunicatorTracePrinter::WriteCurrentString(bool isFromStdErr)
|
||||
{
|
||||
AZStd::string& bufferToUse = isFromStdErr ? m_errorStringBeingConcatenated : m_stringBeingConcatenated;
|
||||
|
||||
+7
-6
@@ -10,20 +10,21 @@
|
||||
|
||||
#include <AzFramework/Process/ProcessCommunicator.h>
|
||||
|
||||
//! CommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system
|
||||
//! ProcessCommunicatorTracePrinter listens to stderr and stdout of a running process and writes its output to the AZ_Trace system
|
||||
//! Importantly, it does not do any blocking operations.
|
||||
class CommunicatorTracePrinter
|
||||
class ProcessCommunicatorTracePrinter
|
||||
{
|
||||
public:
|
||||
CommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window);
|
||||
~CommunicatorTracePrinter();
|
||||
ProcessCommunicatorTracePrinter(AzFramework::ProcessCommunicator* communicator, const char* window);
|
||||
~ProcessCommunicatorTracePrinter();
|
||||
|
||||
// call this periodically to drain the buffers and write them.
|
||||
//! Call this periodically to drain the buffers and write them.
|
||||
void Pump();
|
||||
|
||||
// drains the buffer into the string thats being built, then traces the string when it hits a newline.
|
||||
//! Drains the buffer into the string that's being built, then traces the string when it hits a newline.
|
||||
void ParseDataBuffer(AZ::u32 readSize, bool isFromStdErr);
|
||||
|
||||
//! Prints the current buffer to AZ_Error or AZ_TracePrintf so that it can be picked up by AZ::Debug::Trace
|
||||
void WriteCurrentString(bool isFromStdError);
|
||||
|
||||
private:
|
||||
@@ -26,10 +26,11 @@ namespace AzFramework::Terrain
|
||||
->Event("GetSurfaceWeights", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfaceWeights)
|
||||
->Event("GetSurfaceWeightsFromVector2",
|
||||
&AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfaceWeightsFromVector2)
|
||||
->Event("GetIsHole", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetIsHole)
|
||||
->Event("GetIsHoleFromFloats", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetIsHoleFromFloats)
|
||||
->Event("GetSurfacePoint", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfacePoint)
|
||||
->Event("GetSurfacePoint", &AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetSurfacePoint)
|
||||
->Event("GetSurfacePointFromVector2",
|
||||
&AzFramework::Terrain::TerrainDataRequestBus::Events::GetSurfacePointFromVector2)
|
||||
&AzFramework::Terrain::TerrainDataRequestBus::Events::BehaviorContextGetSurfacePointFromVector2)
|
||||
->Event("GetTerrainAabb", &AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainAabb)
|
||||
->Event("GetTerrainHeightQueryResolution",
|
||||
&AzFramework::Terrain::TerrainDataRequestBus::Events::GetTerrainHeightQueryResolution)
|
||||
|
||||
@@ -52,8 +52,8 @@ namespace AzFramework
|
||||
virtual void SetTerrainAabb(const AZ::Aabb& worldBounds) = 0;
|
||||
|
||||
//! Returns terrains height in meters at location x,y.
|
||||
//! @terrainExistsPtr: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will become false,
|
||||
//! otherwise *terrainExistsPtr will become true.
|
||||
//! @terrainExistsPtr: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside
|
||||
//! a terrain HOLE then *terrainExistsPtr will become false, otherwise *terrainExistsPtr will become true.
|
||||
virtual float GetHeight(const AZ::Vector3& position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
virtual float GetHeightFromVector2(
|
||||
const AZ::Vector2& position, Sampler sampler = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
@@ -68,8 +68,7 @@ namespace AzFramework
|
||||
|
||||
// Given an XY coordinate, return the surface normal.
|
||||
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a
|
||||
//! terrain HOLE then *terrainExistsPtr will be set to false,
|
||||
//! otherwise *terrainExistsPtr will be set to true.
|
||||
//! terrain HOLE then *terrainExistsPtr will be set to false, otherwise *terrainExistsPtr will be set to true.
|
||||
virtual AZ::Vector3 GetNormal(
|
||||
const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
virtual AZ::Vector3 GetNormalFromVector2(
|
||||
@@ -78,8 +77,8 @@ namespace AzFramework
|
||||
float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
|
||||
//! Given an XY coordinate, return the max surface type and weight.
|
||||
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside a terrain HOLE then *terrainExistsPtr will be set to false,
|
||||
//! otherwise *terrainExistsPtr will be set to true.
|
||||
//! @terrainExists: Can be nullptr. If != nullptr then, if there's no terrain at location x,y or location x,y is inside
|
||||
//! a terrain HOLE then *terrainExistsPtr will be set to false, otherwise *terrainExistsPtr will be set to true.
|
||||
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeight(
|
||||
const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromVector2(
|
||||
@@ -87,8 +86,8 @@ namespace AzFramework
|
||||
virtual SurfaceData::SurfaceTagWeight GetMaxSurfaceWeightFromFloats(
|
||||
float x, float y, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
|
||||
//! Given an XY coordinate, return the set of surface types and weights. The Vector3 input position version is defined to ignore
|
||||
//! the input Z value.
|
||||
//! Given an XY coordinate, return the set of surface types and weights. The Vector3 input position version is defined to
|
||||
//! ignore the input Z value.
|
||||
virtual void GetSurfaceWeights(
|
||||
const AZ::Vector3& inPosition,
|
||||
SurfaceData::SurfaceTagWeightList& outSurfaceWeights,
|
||||
@@ -106,13 +105,14 @@ namespace AzFramework
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
bool* terrainExistsPtr = nullptr) const = 0;
|
||||
|
||||
//! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats.
|
||||
//! Convenience function for low level systems that can't do a reverse lookup from Crc to string. Everyone else should use
|
||||
//! GetMaxSurfaceWeight or GetMaxSurfaceWeightFromFloats.
|
||||
//! Not available in the behavior context.
|
||||
//! Returns nullptr if the position is inside a hole or outside of the terrain boundaries.
|
||||
virtual const char* GetMaxSurfaceName(
|
||||
const AZ::Vector3& position, Sampler sampleFilter = Sampler::BILINEAR, bool* terrainExistsPtr = nullptr) const = 0;
|
||||
|
||||
//! Given an XY coordinate, return all terrain information at that location. The Vector3 input position version is defined
|
||||
//! Given an XY coordinate, return all terrain information at that location. The Vector3 input position version is defined
|
||||
//! to ignore the input Z value.
|
||||
virtual void GetSurfacePoint(
|
||||
const AZ::Vector3& inPosition,
|
||||
@@ -130,6 +130,27 @@ namespace AzFramework
|
||||
SurfaceData::SurfacePoint& outSurfacePoint,
|
||||
Sampler sampleFilter = Sampler::DEFAULT,
|
||||
bool* terrainExistsPtr = nullptr) const = 0;
|
||||
|
||||
private:
|
||||
// Private variations of the GetSurfacePoint API exposed to BehaviorContext that returns a value instead of
|
||||
// using an "out" parameter. The "out" parameter is useful for reusing memory allocated in SurfacePoint when
|
||||
// using the public API, but can't easily be used from Script Canvas.
|
||||
SurfaceData::SurfacePoint BehaviorContextGetSurfacePoint(
|
||||
const AZ::Vector3& inPosition,
|
||||
Sampler sampleFilter = Sampler::DEFAULT) const
|
||||
{
|
||||
SurfaceData::SurfacePoint result;
|
||||
GetSurfacePoint(inPosition, result, sampleFilter);
|
||||
return result;
|
||||
}
|
||||
SurfaceData::SurfacePoint BehaviorContextGetSurfacePointFromVector2(
|
||||
const AZ::Vector2& inPosition,
|
||||
Sampler sampleFilter = Sampler::DEFAULT) const
|
||||
{
|
||||
SurfaceData::SurfacePoint result;
|
||||
GetSurfacePointFromVector2(inPosition, result, sampleFilter);
|
||||
return result;
|
||||
}
|
||||
};
|
||||
using TerrainDataRequestBus = AZ::EBus<TerrainDataRequests>;
|
||||
|
||||
|
||||
@@ -143,6 +143,13 @@ namespace AzFramework
|
||||
return vsync_interval;
|
||||
}
|
||||
|
||||
bool NativeWindow::SetSyncInterval(uint32_t newSyncInterval)
|
||||
{
|
||||
vsync_interval = newSyncInterval;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/*static*/ bool NativeWindow::GetFullScreenStateOfDefaultWindow()
|
||||
{
|
||||
NativeWindowHandle defaultWindowHandle = nullptr;
|
||||
|
||||
@@ -132,6 +132,7 @@ namespace AzFramework
|
||||
void ToggleFullScreenState() override;
|
||||
float GetDpiScaleFactor() const override;
|
||||
uint32_t GetSyncInterval() const override;
|
||||
bool SetSyncInterval(uint32_t newSyncInterval) override;
|
||||
uint32_t GetDisplayRefreshRate() const override;
|
||||
|
||||
//! Get the full screen state of the default window.
|
||||
|
||||
@@ -78,6 +78,10 @@ namespace AzFramework
|
||||
//! Returns the sync interval which tells the drivers the number of v-blanks to synchronize with
|
||||
virtual uint32_t GetSyncInterval() const = 0;
|
||||
|
||||
//! Sets the sync interval which tells the drivers the number of v-blanks to synchronize with
|
||||
//! Returns if the sync interval was succesfully set
|
||||
virtual bool SetSyncInterval(uint32_t newSyncInterval) = 0;
|
||||
|
||||
//! Returns the refresh rate of the main display
|
||||
virtual uint32_t GetDisplayRefreshRate() const = 0;
|
||||
};
|
||||
|
||||
@@ -277,6 +277,8 @@ set(FILES
|
||||
Process/ProcessWatcher.cpp
|
||||
Process/ProcessWatcher.h
|
||||
Process/ProcessCommon_fwd.h
|
||||
Process/ProcessCommunicatorTracePrinter.cpp
|
||||
Process/ProcessCommunicatorTracePrinter.h
|
||||
ProjectManager/ProjectManager.h
|
||||
ProjectManager/ProjectManager.cpp
|
||||
Render/GameIntersectorComponent.h
|
||||
|
||||
@@ -36,6 +36,7 @@ namespace UnitTest
|
||||
MOCK_METHOD0(ToggleFullScreenState, void());
|
||||
MOCK_CONST_METHOD0(GetDpiScaleFactor, float());
|
||||
MOCK_CONST_METHOD0(GetSyncInterval, uint32_t());
|
||||
MOCK_METHOD1(SetSyncInterval, bool(uint32_t));
|
||||
MOCK_CONST_METHOD0(GetDisplayRefreshRate, uint32_t());
|
||||
};
|
||||
} // namespace UnitTest
|
||||
|
||||
+22
@@ -39,6 +39,10 @@ namespace AzManipulatorTestFramework
|
||||
DerivedDispatcherT* MouseLButtonDown();
|
||||
//! Set the left mouse button up.
|
||||
DerivedDispatcherT* MouseLButtonUp();
|
||||
//! Set the middle mouse button down.
|
||||
DerivedDispatcherT* MouseMButtonDown();
|
||||
//! Set the middle mouse button up.
|
||||
DerivedDispatcherT* MouseMButtonUp();
|
||||
//! Send a double click event.
|
||||
DerivedDispatcherT* MouseLButtonDoubleClick();
|
||||
//! Set the keyboard modifier button down.
|
||||
@@ -73,6 +77,8 @@ namespace AzManipulatorTestFramework
|
||||
virtual void CameraStateImpl(const AzFramework::CameraState& cameraState) = 0;
|
||||
virtual void MouseLButtonDownImpl() = 0;
|
||||
virtual void MouseLButtonUpImpl() = 0;
|
||||
virtual void MouseMButtonDownImpl() = 0;
|
||||
virtual void MouseMButtonUpImpl() = 0;
|
||||
virtual void MouseLButtonDoubleClickImpl() = 0;
|
||||
virtual void MousePositionImpl(const AzFramework::ScreenPoint& position) = 0;
|
||||
virtual void KeyboardModifierDownImpl(const AzToolsFramework::ViewportInteraction::KeyboardModifier& keyModifier) = 0;
|
||||
@@ -183,6 +189,22 @@ namespace AzManipulatorTestFramework
|
||||
return static_cast<DerivedDispatcherT*>(this);
|
||||
}
|
||||
|
||||
template<typename DerivedDispatcherT>
|
||||
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::MouseMButtonDown()
|
||||
{
|
||||
Log("Mouse middle button down");
|
||||
MouseMButtonDownImpl();
|
||||
return static_cast<DerivedDispatcherT*>(this);
|
||||
}
|
||||
|
||||
template<typename DerivedDispatcherT>
|
||||
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::MouseMButtonUp()
|
||||
{
|
||||
Log("Mouse middle button up");
|
||||
MouseMButtonUpImpl();
|
||||
return static_cast<DerivedDispatcherT*>(this);
|
||||
}
|
||||
|
||||
template<typename DerivedDispatcherT>
|
||||
DerivedDispatcherT* ActionDispatcher<DerivedDispatcherT>::MouseLButtonDoubleClick()
|
||||
{
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorBus.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
@@ -40,7 +41,7 @@ namespace AzManipulatorTestFramework
|
||||
//! Set the angular step.
|
||||
virtual void SetAngularStep(float step) = 0;
|
||||
//! Get the viewport id.
|
||||
virtual int GetViewportId() const = 0;
|
||||
virtual AzFramework::ViewportId GetViewportId() const = 0;
|
||||
//! Updates the visibility state.
|
||||
//! Updates which entities are currently visible given the current camera state.
|
||||
virtual void UpdateVisibility() = 0;
|
||||
|
||||
+2
-2
@@ -30,7 +30,7 @@ namespace AzManipulatorTestFramework
|
||||
|
||||
private:
|
||||
AZStd::shared_ptr<CustomManipulatorManager> m_customManager;
|
||||
std::unique_ptr<ViewportInteraction> m_viewportInteraction;
|
||||
std::unique_ptr<DirectCallManipulatorManager> m_manipulatorManager;
|
||||
AZStd::unique_ptr<ViewportInteraction> m_viewportInteraction;
|
||||
AZStd::unique_ptr<DirectCallManipulatorManager> m_manipulatorManager;
|
||||
};
|
||||
} // namespace AzManipulatorTestFramework
|
||||
|
||||
+5
-3
@@ -25,7 +25,7 @@ namespace AzManipulatorTestFramework
|
||||
using MouseInteractionEvent = AzToolsFramework::ViewportInteraction::MouseInteractionEvent;
|
||||
|
||||
public:
|
||||
explicit ImmediateModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction);
|
||||
explicit ImmediateModeActionDispatcher(ManipulatorViewportInteraction& manipulatorViewportInteraction);
|
||||
~ImmediateModeActionDispatcher();
|
||||
|
||||
//! Clear the current event state.
|
||||
@@ -55,13 +55,15 @@ namespace AzManipulatorTestFramework
|
||||
AZStd::chrono::milliseconds EditorViewportInputTimeNow() override;
|
||||
|
||||
protected:
|
||||
// ActionDispatcher ...
|
||||
// ActionDispatcher overrides ...
|
||||
void SetSnapToGridImpl(bool enabled) override;
|
||||
void SetStickySelectImpl(bool enabled) override;
|
||||
void GridSizeImpl(float size) override;
|
||||
void CameraStateImpl(const AzFramework::CameraState& cameraState) override;
|
||||
void MouseLButtonDownImpl() override;
|
||||
void MouseLButtonUpImpl() override;
|
||||
void MouseMButtonDownImpl() override;
|
||||
void MouseMButtonUpImpl() override;
|
||||
void MouseLButtonDoubleClickImpl() override;
|
||||
void MousePositionImpl(const AzFramework::ScreenPoint& position) override;
|
||||
void KeyboardModifierDownImpl(const KeyboardModifier& keyModifier) override;
|
||||
@@ -82,7 +84,7 @@ namespace AzManipulatorTestFramework
|
||||
const MouseInteractionEvent* GetMouseInteractionEvent() const;
|
||||
|
||||
mutable AZStd::unique_ptr<MouseInteractionEvent> m_event;
|
||||
ManipulatorViewportInteraction& m_viewportManipulatorInteraction;
|
||||
ManipulatorViewportInteraction& m_manipulatorViewportInteraction;
|
||||
|
||||
//! Current time that ticks up after each call to EditorViewportInputTimeNow.
|
||||
AZStd::chrono::milliseconds m_timeNow = AZStd::chrono::milliseconds(0);
|
||||
|
||||
+3
-2
@@ -33,7 +33,7 @@ namespace AzManipulatorTestFramework
|
||||
void SetAngularSnapping(bool enabled) override;
|
||||
void SetGridSize(float size) override;
|
||||
void SetAngularStep(float step) override;
|
||||
int GetViewportId() const override;
|
||||
AzFramework::ViewportId GetViewportId() const override;
|
||||
void UpdateVisibility() override;
|
||||
void SetStickySelect(bool enabled) override;
|
||||
AZ::Vector3 DefaultEditorCameraPosition() const override;
|
||||
@@ -60,9 +60,10 @@ namespace AzManipulatorTestFramework
|
||||
void FindVisibleEntities(AZStd::vector<AZ::EntityId>& visibleEntities) override;
|
||||
|
||||
private:
|
||||
static constexpr AzFramework::ViewportId m_viewportId = 1234; //!< Arbitrary viewport id for manipulator tests.
|
||||
|
||||
AzFramework::EntityVisibilityQuery m_entityVisibilityQuery;
|
||||
AZStd::unique_ptr<NullDebugDisplayRequests> m_nullDebugDisplayRequests;
|
||||
const int m_viewportId = 1234; // Arbitrary viewport id for manipulator tests
|
||||
AzFramework::CameraState m_cameraState;
|
||||
bool m_gridSnapping = false;
|
||||
bool m_angularSnapping = false;
|
||||
|
||||
+33
-15
@@ -29,8 +29,8 @@ namespace AzManipulatorTestFramework
|
||||
using KeyboardModifier = AzToolsFramework::ViewportInteraction::KeyboardModifier;
|
||||
using MouseInteractionEvent = AzToolsFramework::ViewportInteraction::MouseInteractionEvent;
|
||||
|
||||
ImmediateModeActionDispatcher::ImmediateModeActionDispatcher(ManipulatorViewportInteraction& viewportManipulatorInteraction)
|
||||
: m_viewportManipulatorInteraction(viewportManipulatorInteraction)
|
||||
ImmediateModeActionDispatcher::ImmediateModeActionDispatcher(ManipulatorViewportInteraction& manipulatorViewportInteraction)
|
||||
: m_manipulatorViewportInteraction(manipulatorViewportInteraction)
|
||||
{
|
||||
AzToolsFramework::ViewportInteraction::EditorModifierKeyRequestBus::Handler::BusConnect();
|
||||
AzToolsFramework::ViewportInteraction::EditorViewportInputTimeNowRequestBus::Handler::BusConnect();
|
||||
@@ -48,34 +48,34 @@ namespace AzManipulatorTestFramework
|
||||
// mouse down and mouse up event, to match the editor behavior we insert this event
|
||||
// to ensure the tests are simulating the same environment as the editor
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move;
|
||||
m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::SetSnapToGridImpl(const bool enabled)
|
||||
{
|
||||
m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSnapping(enabled);
|
||||
m_manipulatorViewportInteraction.GetViewportInteraction().SetGridSnapping(enabled);
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::SetStickySelectImpl(const bool enabled)
|
||||
{
|
||||
m_viewportManipulatorInteraction.GetViewportInteraction().SetStickySelect(enabled);
|
||||
m_manipulatorViewportInteraction.GetViewportInteraction().SetStickySelect(enabled);
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::GridSizeImpl(const float size)
|
||||
{
|
||||
m_viewportManipulatorInteraction.GetViewportInteraction().SetGridSize(size);
|
||||
m_manipulatorViewportInteraction.GetViewportInteraction().SetGridSize(size);
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::CameraStateImpl(const AzFramework::CameraState& cameraState)
|
||||
{
|
||||
m_viewportManipulatorInteraction.GetViewportInteraction().SetCameraState(cameraState);
|
||||
m_manipulatorViewportInteraction.GetViewportInteraction().SetCameraState(cameraState);
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::MouseLButtonDownImpl()
|
||||
{
|
||||
ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left);
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Down;
|
||||
m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
// the mouse position will be the same as the previous event, thus the delta will be 0
|
||||
MouseMoveAfterButton();
|
||||
}
|
||||
@@ -83,17 +83,35 @@ namespace AzManipulatorTestFramework
|
||||
void ImmediateModeActionDispatcher::MouseLButtonUpImpl()
|
||||
{
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up;
|
||||
m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left);
|
||||
// the mouse position will be the same as the previous event, thus the delta will be 0
|
||||
MouseMoveAfterButton();
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::MouseMButtonDownImpl()
|
||||
{
|
||||
ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Middle);
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Down;
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
// the mouse position will be the same as the previous event, thus the delta will be 0
|
||||
MouseMoveAfterButton();
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::MouseMButtonUpImpl()
|
||||
{
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Up;
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Middle);
|
||||
// the mouse position will be the same as the previous event, thus the delta will be 0
|
||||
MouseMoveAfterButton();
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::MouseLButtonDoubleClickImpl()
|
||||
{
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::DoubleClick;
|
||||
ToggleOn(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left);
|
||||
m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
ToggleOff(GetMouseInteractionEvent()->m_mouseInteraction.m_mouseButtons.m_mouseButtons, MouseButton::Left);
|
||||
// the mouse position will be the same as the previous event, thus the delta will be 0
|
||||
MouseMoveAfterButton();
|
||||
@@ -101,10 +119,10 @@ namespace AzManipulatorTestFramework
|
||||
|
||||
void ImmediateModeActionDispatcher::MousePositionImpl(const AzFramework::ScreenPoint& position)
|
||||
{
|
||||
const auto cameraState = m_viewportManipulatorInteraction.GetViewportInteraction().GetCameraState();
|
||||
const auto cameraState = m_manipulatorViewportInteraction.GetViewportInteraction().GetCameraState();
|
||||
GetMouseInteractionEvent()->m_mouseInteraction.m_mousePick = BuildMousePick(position, cameraState);
|
||||
GetMouseInteractionEvent()->m_mouseEvent = AzToolsFramework::ViewportInteraction::MouseEvent::Move;
|
||||
m_viewportManipulatorInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
m_manipulatorViewportInteraction.GetManipulatorManager().ConsumeMouseInteractionEvent(*m_event);
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::KeyboardModifierDownImpl(const KeyboardModifier& keyModifier)
|
||||
@@ -144,7 +162,7 @@ namespace AzManipulatorTestFramework
|
||||
{
|
||||
m_event = AZStd::unique_ptr<MouseInteractionEvent>(AZStd::make_unique<MouseInteractionEvent>());
|
||||
m_event->m_mouseInteraction.m_interactionId.m_viewportId =
|
||||
m_viewportManipulatorInteraction.GetViewportInteraction().GetViewportId();
|
||||
m_manipulatorViewportInteraction.GetViewportInteraction().GetViewportId();
|
||||
}
|
||||
|
||||
return m_event.get();
|
||||
@@ -178,12 +196,12 @@ namespace AzManipulatorTestFramework
|
||||
|
||||
void ImmediateModeActionDispatcher::ExpectManipulatorBeingInteractedImpl()
|
||||
{
|
||||
EXPECT_TRUE(m_viewportManipulatorInteraction.GetManipulatorManager().ManipulatorBeingInteracted());
|
||||
EXPECT_TRUE(m_manipulatorViewportInteraction.GetManipulatorManager().ManipulatorBeingInteracted());
|
||||
}
|
||||
|
||||
void ImmediateModeActionDispatcher::ExpectManipulatorNotBeingInteractedImpl()
|
||||
{
|
||||
EXPECT_FALSE(m_viewportManipulatorInteraction.GetManipulatorManager().ManipulatorBeingInteracted());
|
||||
EXPECT_FALSE(m_manipulatorViewportInteraction.GetManipulatorManager().ManipulatorBeingInteracted());
|
||||
}
|
||||
|
||||
ImmediateModeActionDispatcher* ImmediateModeActionDispatcher::ResetEvent()
|
||||
|
||||
@@ -135,20 +135,20 @@ namespace AzManipulatorTestFramework
|
||||
m_angularStep = step;
|
||||
}
|
||||
|
||||
int ViewportInteraction::GetViewportId() const
|
||||
AzFramework::ViewportId ViewportInteraction::GetViewportId() const
|
||||
{
|
||||
return m_viewportId;
|
||||
}
|
||||
|
||||
AZ::Vector3 ViewportInteraction::ViewportScreenToWorld([[maybe_unused]] const AzFramework::ScreenPoint& screenPosition)
|
||||
{
|
||||
return AZ::Vector3::CreateZero();
|
||||
return AzFramework::ScreenToWorld(screenPosition, m_cameraState);
|
||||
}
|
||||
|
||||
AzToolsFramework::ViewportInteraction::ProjectedViewportRay ViewportInteraction::ViewportScreenToWorldRay(
|
||||
[[maybe_unused]] const AzFramework::ScreenPoint& screenPosition)
|
||||
{
|
||||
return {};
|
||||
return AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, screenPosition);
|
||||
}
|
||||
|
||||
float ViewportInteraction::DeviceScalingFactor()
|
||||
|
||||
@@ -53,7 +53,6 @@ static void OptimizedSetParent(QWidget* widget, QWidget* parent)
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
static const FancyDockingDropZoneConstants g_FancyDockingConstants;
|
||||
|
||||
// Constant for the threshold in pixels for snapping to edges while dragging for docking
|
||||
static const int g_snapThresholdInPixels = 15;
|
||||
@@ -155,7 +154,7 @@ namespace AzQtComponents
|
||||
|
||||
// Timer for updating our hovered drop zone opacity
|
||||
QObject::connect(m_dropZoneHoverFadeInTimer, &QTimer::timeout, this, &FancyDocking::onDropZoneHoverFadeInUpdate);
|
||||
m_dropZoneHoverFadeInTimer->setInterval(g_FancyDockingConstants.dropZoneHoverFadeUpdateIntervalMS);
|
||||
m_dropZoneHoverFadeInTimer->setInterval(FancyDockingDropZoneConstants::dropZoneHoverFadeUpdateIntervalMS);
|
||||
QIcon dragIcon = QIcon(QStringLiteral(":/Cursors/Grabbing.svg"));
|
||||
m_dragCursor = QCursor(dragIcon.pixmap(16), 5, 2);
|
||||
}
|
||||
@@ -333,13 +332,13 @@ namespace AzQtComponents
|
||||
*/
|
||||
void FancyDocking::onDropZoneHoverFadeInUpdate()
|
||||
{
|
||||
const qreal dropZoneHoverOpacity = g_FancyDockingConstants.dropZoneHoverFadeIncrement + m_dropZoneState.dropZoneHoverOpacity();
|
||||
const qreal dropZoneHoverOpacity = FancyDockingDropZoneConstants::dropZoneHoverFadeIncrement + m_dropZoneState.dropZoneHoverOpacity();
|
||||
|
||||
// Once we've reached the full drop zone opacity, cut it off in case we
|
||||
// went over and stop the timer
|
||||
if (dropZoneHoverOpacity >= g_FancyDockingConstants.dropZoneOpacity)
|
||||
if (dropZoneHoverOpacity >= FancyDockingDropZoneConstants::dropZoneOpacity)
|
||||
{
|
||||
m_dropZoneState.setDropZoneHoverOpacity(g_FancyDockingConstants.dropZoneOpacity);
|
||||
m_dropZoneState.setDropZoneHoverOpacity(FancyDockingDropZoneConstants::dropZoneOpacity);
|
||||
m_dropZoneHoverFadeInTimer->stop();
|
||||
}
|
||||
else
|
||||
@@ -792,12 +791,12 @@ namespace AzQtComponents
|
||||
QPoint mainWindowTopLeft = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.topLeft()));
|
||||
QPoint mainWindowTopRight = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.topRight()));
|
||||
QPoint mainWindowBottomLeft = multiscreenMapFromGlobal(mainWindow->mapToGlobal(mainWindowRect.bottomLeft()));
|
||||
QSize absoluteLeftRightSize(g_FancyDockingConstants.absoluteDropZoneSizeInPixels, mainWindowRect.height());
|
||||
QSize absoluteLeftRightSize(FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels, mainWindowRect.height());
|
||||
QRect absoluteLeftDropZone(mainWindowTopLeft, absoluteLeftRightSize);
|
||||
QRect absoluteRightDropZone(mainWindowTopRight - QPoint(g_FancyDockingConstants.absoluteDropZoneSizeInPixels, 0), absoluteLeftRightSize);
|
||||
QSize absoluteTopBottomSize(mainWindowRect.width(), g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
||||
QRect absoluteRightDropZone(mainWindowTopRight - QPoint(FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels, 0), absoluteLeftRightSize);
|
||||
QSize absoluteTopBottomSize(mainWindowRect.width(), FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels);
|
||||
QRect absoluteTopDropZone(mainWindowTopLeft, absoluteTopBottomSize);
|
||||
QRect absoluteBottomDropZone(mainWindowBottomLeft - QPoint(0, g_FancyDockingConstants.absoluteDropZoneSizeInPixels), absoluteTopBottomSize);
|
||||
QRect absoluteBottomDropZone(mainWindowBottomLeft - QPoint(0, FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels), absoluteTopBottomSize);
|
||||
|
||||
// If the drop target is a main window, then we will only show the absolute
|
||||
// drop zone if the cursor is in that zone already
|
||||
@@ -986,16 +985,16 @@ namespace AzQtComponents
|
||||
switch (m_dropZoneState.absoluteDropZoneArea())
|
||||
{
|
||||
case Qt::LeftDockWidgetArea:
|
||||
dockRect.setX(dockRect.x() + g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
||||
dockRect.setX(dockRect.x() + FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels);
|
||||
break;
|
||||
case Qt::RightDockWidgetArea:
|
||||
dockRect.setWidth(dockRect.width() - g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
||||
dockRect.setWidth(dockRect.width() - FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels);
|
||||
break;
|
||||
case Qt::TopDockWidgetArea:
|
||||
dockRect.setY(dockRect.y() + g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
||||
dockRect.setY(dockRect.y() + FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels);
|
||||
break;
|
||||
case Qt::BottomDockWidgetArea:
|
||||
dockRect.setHeight(dockRect.height() - g_FancyDockingConstants.absoluteDropZoneSizeInPixels);
|
||||
dockRect.setHeight(dockRect.height() - FancyDockingDropZoneConstants::absoluteDropZoneSizeInPixels);
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -1034,15 +1033,15 @@ namespace AzQtComponents
|
||||
// Set the drop zone width/height to the default, but if the dock widget
|
||||
// width and/or height is below the threshold, then switch to scaling them
|
||||
// down accordingly
|
||||
int dropZoneWidth = g_FancyDockingConstants.dropZoneSizeInPixels;
|
||||
if (dockWidth < g_FancyDockingConstants.minDockSizeBeforeDropZoneScalingInPixels)
|
||||
int dropZoneWidth = FancyDockingDropZoneConstants::dropZoneSizeInPixels;
|
||||
if (dockWidth < FancyDockingDropZoneConstants::minDockSizeBeforeDropZoneScalingInPixels)
|
||||
{
|
||||
dropZoneWidth = aznumeric_cast<int>(dockWidth * g_FancyDockingConstants.dropZoneScaleFactor);
|
||||
dropZoneWidth = aznumeric_cast<int>(dockWidth * FancyDockingDropZoneConstants::dropZoneScaleFactor);
|
||||
}
|
||||
int dropZoneHeight = g_FancyDockingConstants.dropZoneSizeInPixels;
|
||||
if (dockHeight < g_FancyDockingConstants.minDockSizeBeforeDropZoneScalingInPixels)
|
||||
int dropZoneHeight = FancyDockingDropZoneConstants::dropZoneSizeInPixels;
|
||||
if (dockHeight < FancyDockingDropZoneConstants::minDockSizeBeforeDropZoneScalingInPixels)
|
||||
{
|
||||
dropZoneHeight = aznumeric_cast<int>(dockHeight * g_FancyDockingConstants.dropZoneScaleFactor);
|
||||
dropZoneHeight = aznumeric_cast<int>(dockHeight * FancyDockingDropZoneConstants::dropZoneScaleFactor);
|
||||
}
|
||||
|
||||
// Calculate the inner corners to be used when constructing the drop zone polygons
|
||||
@@ -1078,7 +1077,7 @@ namespace AzQtComponents
|
||||
int innerDropZoneWidth = m_dropZoneState.innerDropZoneRect().width();
|
||||
int innerDropZoneHeight = m_dropZoneState.innerDropZoneRect().height();
|
||||
int centerDropZoneDiameter = (innerDropZoneWidth < innerDropZoneHeight) ? innerDropZoneWidth : innerDropZoneHeight;
|
||||
centerDropZoneDiameter = aznumeric_cast<int>(centerDropZoneDiameter * g_FancyDockingConstants.centerTabDropZoneScale);
|
||||
centerDropZoneDiameter = aznumeric_cast<int>(centerDropZoneDiameter * FancyDockingDropZoneConstants::centerTabDropZoneScale);
|
||||
|
||||
// Setup our center tab drop zone
|
||||
const QSize centerDropZoneSize(centerDropZoneDiameter, centerDropZoneDiameter);
|
||||
@@ -1986,7 +1985,7 @@ namespace AzQtComponents
|
||||
// hasn't faded in all the way yet, then ignore the drop zone area
|
||||
// which will make the widget floating
|
||||
bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey();
|
||||
if (modifiedKeyPressed || m_dropZoneState.dropZoneHoverOpacity() != g_FancyDockingConstants.dropZoneOpacity)
|
||||
if (modifiedKeyPressed || m_dropZoneState.dropZoneHoverOpacity() != FancyDockingDropZoneConstants::dropZoneOpacity)
|
||||
{
|
||||
area = Qt::NoDockWidgetArea;
|
||||
}
|
||||
@@ -3026,7 +3025,7 @@ namespace AzQtComponents
|
||||
{
|
||||
bool modifiedKeyPressed = FancyDockingDropZoneWidget::CheckModifierKey();
|
||||
|
||||
m_ghostWidget->setWindowOpacity(modifiedKeyPressed ? 1.0f : g_FancyDockingConstants.draggingDockWidgetOpacity);
|
||||
m_ghostWidget->setWindowOpacity(modifiedKeyPressed ? 1.0f : FancyDockingDropZoneConstants::draggingDockWidgetOpacity);
|
||||
m_ghostWidget->setPixmap(m_state.dockWidgetScreenGrab.screenGrab, m_state.placeholder(), m_state.placeholderScreen());
|
||||
}
|
||||
}
|
||||
|
||||
+7
-27
@@ -19,26 +19,6 @@
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
static const FancyDockingDropZoneConstants g_Constants;
|
||||
|
||||
FancyDockingDropZoneConstants::FancyDockingDropZoneConstants()
|
||||
{
|
||||
draggingDockWidgetOpacity = 0.6;
|
||||
dropZoneOpacity = 0.4;
|
||||
dropZoneSizeInPixels = 40;
|
||||
minDockSizeBeforeDropZoneScalingInPixels = dropZoneSizeInPixels * 3;
|
||||
dropZoneScaleFactor = 0.25;
|
||||
centerTabDropZoneScale = 0.5;
|
||||
centerTabIconScale = 0.5;
|
||||
dropZoneColor = QColor(155, 155, 155);
|
||||
dropZoneBorderColor = Qt::black;
|
||||
dropZoneBorderInPixels = 1;
|
||||
absoluteDropZoneSizeInPixels = 25;
|
||||
dockingTargetDelayMS = 110;
|
||||
dropZoneHoverFadeUpdateIntervalMS = 20;
|
||||
dropZoneHoverFadeIncrement = dropZoneOpacity / (dockingTargetDelayMS / dropZoneHoverFadeUpdateIntervalMS);
|
||||
centerDropZoneIconPath = QString(":/stylesheet/img/UI20/docking/tabs_icon.svg");
|
||||
}
|
||||
|
||||
FancyDockingDropZoneWidget::FancyDockingDropZoneWidget(QMainWindow* mainWindow, QWidget* coordinatesRelativeTo, QScreen* screen, FancyDockingDropZoneState* dropZoneState)
|
||||
// NOTE: this will not work with multiple monitors if this widget has a parent. The floating drop zone
|
||||
@@ -154,7 +134,7 @@ namespace AzQtComponents
|
||||
|
||||
// Draw all of the normal drop zones if they exist (if a dock widget is hovered over)
|
||||
painter.setPen(Qt::NoPen);
|
||||
painter.setOpacity(g_Constants.dropZoneOpacity);
|
||||
painter.setOpacity(FancyDockingDropZoneConstants::dropZoneOpacity);
|
||||
auto dropZones = m_dropZoneState->dropZones();
|
||||
for (auto it = dropZones.cbegin(); it != dropZones.cend(); ++it)
|
||||
{
|
||||
@@ -189,7 +169,7 @@ namespace AzQtComponents
|
||||
// Otherwise, set the normal color
|
||||
else
|
||||
{
|
||||
painter.setBrush(g_Constants.dropZoneColor);
|
||||
painter.setBrush(FancyDockingDropZoneConstants::dropZoneColor);
|
||||
}
|
||||
|
||||
// negate the window position to offset everything by that much
|
||||
@@ -214,8 +194,8 @@ namespace AzQtComponents
|
||||
// Scale the tabs icon based on the drop zone size and our specified offset
|
||||
// Doing this through QIcon to make sure that SVG is rendered already in desired resolution
|
||||
const QSize& dropZoneSize = dropZoneRect.size();
|
||||
const QSize requestedIconSize = dropZoneSize * g_Constants.centerTabIconScale;
|
||||
const QIcon dropZoneIcon = QIcon(g_Constants.centerDropZoneIconPath);
|
||||
const QSize requestedIconSize = dropZoneSize * FancyDockingDropZoneConstants::centerTabIconScale;
|
||||
const QIcon dropZoneIcon = QIcon(FancyDockingDropZoneConstants::centerDropZoneIconPath);
|
||||
const QPixmap dropZonePixmap = dropZoneIcon.pixmap(requestedIconSize);
|
||||
const QSize receivedIconSize = dropZoneIcon.actualSize(requestedIconSize);
|
||||
|
||||
@@ -264,7 +244,7 @@ namespace AzQtComponents
|
||||
}
|
||||
else
|
||||
{
|
||||
painter.setBrush(g_Constants.dropZoneColor);
|
||||
painter.setBrush(FancyDockingDropZoneConstants::dropZoneColor);
|
||||
}
|
||||
painter.drawRect(absoluteDropZoneRect);
|
||||
|
||||
@@ -313,8 +293,8 @@ namespace AzQtComponents
|
||||
const QPoint innerBottomRight = innerDropZoneRect.bottomRight();
|
||||
|
||||
// Draw the lines using the appropriate pen
|
||||
QPen dropZoneBorderPen(g_Constants.dropZoneBorderColor);
|
||||
dropZoneBorderPen.setWidth(g_Constants.dropZoneBorderInPixels);
|
||||
QPen dropZoneBorderPen(FancyDockingDropZoneConstants::dropZoneBorderColor);
|
||||
dropZoneBorderPen.setWidth(FancyDockingDropZoneConstants::dropZoneBorderInPixels);
|
||||
painter.setPen(dropZoneBorderPen);
|
||||
painter.setOpacity(1);
|
||||
painter.drawLine(topLeft, innerTopLeft);
|
||||
|
||||
+16
-21
@@ -28,63 +28,58 @@ class QPainter;
|
||||
|
||||
namespace AzQtComponents
|
||||
{
|
||||
struct AZ_QT_COMPONENTS_API FancyDockingDropZoneConstants
|
||||
namespace FancyDockingDropZoneConstants
|
||||
{
|
||||
// Constant for the opacity of the screen grab for the dock widget being dragged
|
||||
qreal draggingDockWidgetOpacity;
|
||||
static constexpr qreal draggingDockWidgetOpacity = 0.6;
|
||||
|
||||
// Constant for the opacity of the normal drop zones
|
||||
qreal dropZoneOpacity;
|
||||
static constexpr qreal dropZoneOpacity = 0.4;
|
||||
|
||||
// Constant for the default drop zone size (in pixels)
|
||||
int dropZoneSizeInPixels;
|
||||
static constexpr int dropZoneSizeInPixels = 40;
|
||||
|
||||
// Constant for the dock width/height size (in pixels) before we need to start
|
||||
// scaling down the drop zone sizes, or else they will overlap with the center
|
||||
// tab icon or each other
|
||||
int minDockSizeBeforeDropZoneScalingInPixels;
|
||||
static constexpr int minDockSizeBeforeDropZoneScalingInPixels = dropZoneSizeInPixels * 3;
|
||||
|
||||
// Constant for the factor by which we must scale down the drop zone sizes once
|
||||
// the dock width/height size is too small
|
||||
qreal dropZoneScaleFactor;
|
||||
static constexpr qreal dropZoneScaleFactor = 0.25;
|
||||
|
||||
// Constant for the percentage to scale down the inner drop zone rectangle for the center tab drop zone
|
||||
qreal centerTabDropZoneScale;
|
||||
static constexpr qreal centerTabDropZoneScale = 0.5;
|
||||
|
||||
// Constant for the percentage to scale down the center tab drop zone for the center tab icon
|
||||
qreal centerTabIconScale;
|
||||
static constexpr qreal centerTabIconScale = 0.5;
|
||||
|
||||
// Constant for the drop zone hotspot default color
|
||||
QColor dropZoneColor;
|
||||
static const QColor dropZoneColor = QColor(155, 155, 155);
|
||||
|
||||
// Constant for the drop zone border color
|
||||
QColor dropZoneBorderColor;
|
||||
static const QColor dropZoneBorderColor = Qt::black;
|
||||
|
||||
// Constant for the border width in pixels separating the drop zones
|
||||
int dropZoneBorderInPixels;
|
||||
static constexpr int dropZoneBorderInPixels = 1;
|
||||
|
||||
// Constant for the border width in pixels separating the drop zones
|
||||
int absoluteDropZoneSizeInPixels;
|
||||
static constexpr int absoluteDropZoneSizeInPixels = 25;
|
||||
|
||||
// Constant for the delay (in milliseconds) before a drop zone becomes active
|
||||
// once it is hovered over
|
||||
int dockingTargetDelayMS;
|
||||
static constexpr int dockingTargetDelayMS = 110;
|
||||
|
||||
// Constant for the rate at which we will update (fade in) the drop zone opacity
|
||||
// when hovered over (in milliseconds)
|
||||
int dropZoneHoverFadeUpdateIntervalMS;
|
||||
static constexpr int dropZoneHoverFadeUpdateIntervalMS = 20;
|
||||
|
||||
// Constant for the incremental opacity increase for the hovered drop zone
|
||||
// that will fade in to the full drop zone opacity in the desired time
|
||||
qreal dropZoneHoverFadeIncrement;
|
||||
static constexpr qreal dropZoneHoverFadeIncrement = dropZoneOpacity / (dockingTargetDelayMS / dropZoneHoverFadeUpdateIntervalMS);
|
||||
|
||||
// Constant for the path to the center drop zone tabs icon
|
||||
QString centerDropZoneIconPath;
|
||||
|
||||
FancyDockingDropZoneConstants();
|
||||
|
||||
FancyDockingDropZoneConstants(const FancyDockingDropZoneConstants&) = delete;
|
||||
FancyDockingDropZoneConstants& operator=(const FancyDockingDropZoneConstants&) = delete;
|
||||
static const QString centerDropZoneIconPath = QStringLiteral(":/stylesheet/img/UI20/docking/tabs_icon.svg");
|
||||
};
|
||||
|
||||
class FancyDockingDropZoneState
|
||||
|
||||
@@ -13,8 +13,6 @@
|
||||
#define AZ_TRAIT_UNIT_TEST_ENTITY_ID_GEN_TEST_COUNT 10000
|
||||
#define AZ_TRAIT_UNIT_TEST_DILLER_TRIGGER_EVENT_COUNT 100000
|
||||
|
||||
#define AZ_TRAIT_DISABLE_ALL_SAVE_DATA_TESTS true
|
||||
|
||||
#define AZ_TRAIT_DISABLE_FAILED_AP_CONNECTION_TESTS true
|
||||
#define AZ_TRAIT_DISABLE_FAILED_ASSET_LOAD_TESTS true
|
||||
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include <AzToolsFramework/ContainerEntity/ContainerEntitySystemComponent.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextComponent.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
|
||||
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
|
||||
#include <AzToolsFramework/Slice/SliceMetadataEntityContextComponent.h>
|
||||
#include <AzToolsFramework/Prefab/PrefabSystemComponent.h>
|
||||
@@ -268,6 +269,7 @@ namespace AzToolsFramework
|
||||
azrtti_typeid<Components::EditorEntityUiSystemComponent>(),
|
||||
azrtti_typeid<FocusModeSystemComponent>(),
|
||||
azrtti_typeid<ContainerEntitySystemComponent>(),
|
||||
azrtti_typeid<ReadOnlyEntitySystemComponent>(),
|
||||
azrtti_typeid<SliceMetadataEntityContextComponent>(),
|
||||
azrtti_typeid<Prefab::PrefabSystemComponent>(),
|
||||
azrtti_typeid<EditorEntityFixupComponent>(),
|
||||
|
||||
+1
@@ -30,6 +30,7 @@ namespace AzToolsFramework
|
||||
connect(m_filterModel, &QAbstractItemModel::rowsRemoved, this, &AssetBrowserTableModel::UpdateTableModelMaps);
|
||||
connect(m_filterModel, &QAbstractItemModel::layoutChanged, this, &AssetBrowserTableModel::UpdateTableModelMaps);
|
||||
connect(m_filterModel, &AssetBrowserFilterModel::filterChanged, this, &AssetBrowserTableModel::beginResetModel);
|
||||
connect(m_filterModel, &AssetBrowserFilterModel::filterChanged, this, &AssetBrowserTableModel::UpdateTableModelMaps);
|
||||
connect(m_filterModel, &QAbstractItemModel::dataChanged, this, &AssetBrowserTableModel::SourceDataChanged);
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,7 @@
|
||||
#include <AzToolsFramework/Entity/EditorEntityModelComponent.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntitySearchComponent.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntitySortComponent.h>
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
|
||||
#include <AzToolsFramework/FocusMode/FocusModeSystemComponent.h>
|
||||
#include <AzToolsFramework/PropertyTreeEditor/PropertyTreeEditorComponent.h>
|
||||
#include <AzToolsFramework/Render/EditorIntersectorComponent.h>
|
||||
@@ -75,6 +76,7 @@ namespace AzToolsFramework
|
||||
EditorEntityFixupComponent::CreateDescriptor(),
|
||||
EntityUtilityComponent::CreateDescriptor(),
|
||||
ContainerEntitySystemComponent::CreateDescriptor(),
|
||||
ReadOnlyEntitySystemComponent::CreateDescriptor(),
|
||||
FocusModeSystemComponent::CreateDescriptor(),
|
||||
SliceMetadataEntityContextComponent::CreateDescriptor(),
|
||||
SliceRequestComponent::CreateDescriptor(),
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/EntityId.h>
|
||||
#include <AzCore/EBus/EBus.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContext.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
//! Used to notify changes of state for read-only entities.
|
||||
class ReadOnlyEntityPublicNotifications
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = AzFramework::EntityContextId;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//! Triggered when an entity's read-only status changes.
|
||||
//! @param entityId The entity whose status has changed.
|
||||
//! @param readOnly The read-only state the container was changed to.
|
||||
virtual void OnReadOnlyEntityStatusChanged([[maybe_unused]] const AZ::EntityId& entityId, [[maybe_unused]] bool readOnly) {}
|
||||
|
||||
protected:
|
||||
~ReadOnlyEntityPublicNotifications() = default;
|
||||
};
|
||||
using ReadOnlyEntityPublicNotificationBus = AZ::EBus<ReadOnlyEntityPublicNotifications>;
|
||||
|
||||
//! Used by the ReadOnlyEntitySystemComponent to query the read-only state of entities as set by systems using the API.
|
||||
class ReadOnlyEntityQueryRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// EBusTraits overrides
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
|
||||
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
|
||||
using BusIdType = AzFramework::EntityContextId;
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
//! Triggered when an entity's read-only status is queried.
|
||||
//! Allows multiple systems to weigh in on the read-only status of an entity.
|
||||
//! @param entityId The entity whose status has changed.
|
||||
//! @param[out] isReadOnly The output of the query. Should only be changed to true, and left untouched if false.
|
||||
virtual void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) = 0;
|
||||
|
||||
protected:
|
||||
~ReadOnlyEntityQueryRequests() = default;
|
||||
};
|
||||
using ReadOnlyEntityQueryRequestBus = AZ::EBus<ReadOnlyEntityQueryRequests>;
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
+43
@@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
|
||||
#include <AzFramework/Entity/EntityContextBus.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
//! An entity registered as read-only cannot be altered in the editor.
|
||||
class ReadOnlyEntityPublicInterface
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ReadOnlyEntityPublicInterface, "{921FE15B-6EBD-47F0-8238-BC63318DEDEA}");
|
||||
|
||||
//! Returns whether the entity id provided is registered as read-only.
|
||||
virtual bool IsReadOnly(const AZ::EntityId& entityId) = 0;
|
||||
};
|
||||
|
||||
//! An entity registered as read-only cannot be altered in the editor.
|
||||
class ReadOnlyEntityQueryInterface
|
||||
{
|
||||
public:
|
||||
AZ_RTTI(ReadOnlyEntityQueryInterface, "{2ACD63C5-1F3E-4DE8-880E-8115F857D329}");
|
||||
|
||||
//! Refreshes the cached read-only status for the entities provided.
|
||||
//! @param entityIds The entityIds whose read-only state will be queried again.
|
||||
virtual void RefreshReadOnlyState(const EntityIdList& entityIds) = 0;
|
||||
|
||||
//! Refreshes the cached read-only status for all entities.
|
||||
//! Useful when disconnecting a handler at runtime.
|
||||
virtual void RefreshReadOnlyStateForAllEntities() = 0;
|
||||
};
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
+99
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntitySystemComponent.h>
|
||||
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h>
|
||||
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
void ReadOnlyEntitySystemComponent::Activate()
|
||||
{
|
||||
AZ::Interface<ReadOnlyEntityQueryInterface>::Register(this);
|
||||
AZ::Interface<ReadOnlyEntityPublicInterface>::Register(this);
|
||||
EditorEntityContextNotificationBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::Deactivate()
|
||||
{
|
||||
EditorEntityContextNotificationBus::Handler::BusDisconnect();
|
||||
AZ::Interface<ReadOnlyEntityPublicInterface>::Unregister(this);
|
||||
AZ::Interface<ReadOnlyEntityQueryInterface>::Unregister(this);
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<ReadOnlyEntitySystemComponent, AZ::Component>()->Version(1);
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
|
||||
{
|
||||
provided.push_back(AZ_CRC_CE("ReadOnlyEntityService"));
|
||||
}
|
||||
|
||||
bool ReadOnlyEntitySystemComponent::IsReadOnly(const AZ::EntityId& entityId)
|
||||
{
|
||||
if (!m_readOnlystates.contains(entityId))
|
||||
{
|
||||
QueryReadOnlyStateForEntity(entityId);
|
||||
}
|
||||
|
||||
return m_readOnlystates[entityId];
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::RefreshReadOnlyState(const EntityIdList& entityIds)
|
||||
{
|
||||
for (const AZ::EntityId entityId : entityIds)
|
||||
{
|
||||
bool wasReadOnly = m_readOnlystates[entityId];
|
||||
QueryReadOnlyStateForEntity(entityId);
|
||||
|
||||
if (bool isReadOnly = m_readOnlystates[entityId]; wasReadOnly != isReadOnly)
|
||||
{
|
||||
ReadOnlyEntityPublicNotificationBus::Broadcast(
|
||||
&ReadOnlyEntityPublicNotificationBus::Events::OnReadOnlyEntityStatusChanged, entityId, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::RefreshReadOnlyStateForAllEntities()
|
||||
{
|
||||
for (auto elem : m_readOnlystates)
|
||||
{
|
||||
AZ::EntityId entityId = elem.first;
|
||||
bool wasReadOnly = m_readOnlystates[entityId];
|
||||
QueryReadOnlyStateForEntity(entityId);
|
||||
|
||||
if (bool isReadOnly = m_readOnlystates[entityId]; wasReadOnly != isReadOnly)
|
||||
{
|
||||
ReadOnlyEntityPublicNotificationBus::Broadcast(
|
||||
&ReadOnlyEntityPublicNotificationBus::Events::OnReadOnlyEntityStatusChanged, entityId, isReadOnly);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::OnContextReset()
|
||||
{
|
||||
m_readOnlystates.clear();
|
||||
}
|
||||
|
||||
void ReadOnlyEntitySystemComponent::QueryReadOnlyStateForEntity(const AZ::EntityId& entityId)
|
||||
{
|
||||
bool isReadOnly = false;
|
||||
|
||||
ReadOnlyEntityQueryRequestBus::Broadcast(
|
||||
&ReadOnlyEntityQueryRequestBus::Events::IsReadOnly, entityId, isReadOnly);
|
||||
|
||||
m_readOnlystates[entityId] = isReadOnly;
|
||||
}
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/Component.h>
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
//! System Component to track read-only entity registration.
|
||||
//! An entity registered as ReadOnly cannot be altered in the Editor.
|
||||
class ReadOnlyEntitySystemComponent final
|
||||
: public AZ::Component
|
||||
, private ReadOnlyEntityPublicInterface
|
||||
, private ReadOnlyEntityQueryInterface
|
||||
, private EditorEntityContextNotificationBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_COMPONENT(ReadOnlyEntitySystemComponent, "{B32EB03F-D88F-4B3A-9C16-071AF04DA646}");
|
||||
|
||||
ReadOnlyEntitySystemComponent() = default;
|
||||
virtual ~ReadOnlyEntitySystemComponent() = default;
|
||||
|
||||
// AZ::Component overrides ...
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
|
||||
|
||||
// ReadOnlyEntityPublicNotifications overrides ...
|
||||
bool IsReadOnly(const AZ::EntityId& entityId) override;
|
||||
|
||||
// ReadOnlyEntityQueryInterface overrides ...
|
||||
void RefreshReadOnlyState(const EntityIdList& entityIds) override;
|
||||
void RefreshReadOnlyStateForAllEntities() override;
|
||||
|
||||
// EditorEntityContextNotificationBus overrides ...
|
||||
void OnContextReset() override;
|
||||
|
||||
private:
|
||||
void QueryReadOnlyStateForEntity(const AZ::EntityId& entityId);
|
||||
|
||||
AZStd::unordered_map<AZ::EntityId, bool> m_readOnlystates;
|
||||
};
|
||||
|
||||
} // namespace AzToolsFramework
|
||||
+9
-15
@@ -84,12 +84,12 @@ namespace AzToolsFramework
|
||||
break;
|
||||
case State::Translating:
|
||||
{
|
||||
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Left() &&
|
||||
if (mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() &&
|
||||
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Shift() &&
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl())
|
||||
{
|
||||
SnapVerticesToTerrain(mouseInteraction);
|
||||
SnapVerticesToSurface(mouseInteraction);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -109,29 +109,23 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
template<typename Vertex>
|
||||
void EditorVertexSelectionBase<Vertex>::SnapVerticesToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
void EditorVertexSelectionBase<Vertex>::SnapVerticesToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
{
|
||||
ScopedUndoBatch surfaceSnapUndo("Snap to Surface");
|
||||
ScopedUndoBatch::MarkEntityDirty(GetEntityId());
|
||||
|
||||
const int viewportId = mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId;
|
||||
// get unsnapped terrain position (world space)
|
||||
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
|
||||
;
|
||||
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
|
||||
worldSurfacePosition, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
|
||||
mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates);
|
||||
// get unsnapped surface position (world space)
|
||||
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(
|
||||
viewportId, mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates, EditorPickRayLength,
|
||||
GetDefaultEntityPlacementDistance());
|
||||
|
||||
AZ::Transform worldFromLocal;
|
||||
AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
|
||||
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
|
||||
|
||||
// convert to local space - snap if enabled
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
|
||||
const AZ::Vector3 localFinalSurfacePosition = gridSnapParams.m_gridSnap
|
||||
? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocal, viewportId, gridSnapParams.m_gridSize)
|
||||
: localFromWorld.TransformPoint(worldSurfacePosition);
|
||||
|
||||
// convert to local space
|
||||
const AZ::Vector3 localFinalSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
|
||||
SetSelectedPosition(localFinalSurfacePosition);
|
||||
|
||||
OnEntityComponentPropertyChanged(GetEntityComponentIdPair());
|
||||
|
||||
+1
-1
@@ -171,7 +171,7 @@ namespace AzToolsFramework
|
||||
//! Snap the selected vertices to the terrain.
|
||||
//! Note: With a multi-selection the manipulator will be translated to the picked
|
||||
//! terrain position with all vertices moved relative to it.
|
||||
void SnapVerticesToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
|
||||
void SnapVerticesToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
|
||||
|
||||
//! The Actions provided by the EditorVertexSelection while it is active.
|
||||
//! e.g. Vertex deletion, duplication etc.
|
||||
|
||||
@@ -110,30 +110,6 @@ namespace AzToolsFramework
|
||||
return unsnappedPosition + CalculateSnappedOffset(unsnappedPosition, snapAxes, snapAxesCount, size);
|
||||
}
|
||||
|
||||
AZ::Vector3 CalculateSnappedTerrainPosition(
|
||||
const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal, const int viewportId, const float size)
|
||||
{
|
||||
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
|
||||
const AZ::Vector3 localSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
|
||||
|
||||
// snap in xy plane
|
||||
AZ::Vector3 localSnappedSurfacePosition = localSurfacePosition +
|
||||
CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisX(), size) +
|
||||
CalculateSnappedOffset(localSurfacePosition, AZ::Vector3::CreateAxisY(), size);
|
||||
|
||||
// find terrain height at xy snapped location
|
||||
float terrainHeight = 0.0f;
|
||||
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
|
||||
terrainHeight, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::TerrainHeight,
|
||||
Vector3ToVector2(worldFromLocal.TransformPoint(localSnappedSurfacePosition)));
|
||||
|
||||
// set snapped z value to terrain height
|
||||
AZ::Vector3 localTerrainHeight = localFromWorld.TransformPoint(AZ::Vector3(0.0f, 0.0f, terrainHeight));
|
||||
localSnappedSurfacePosition.SetZ(localTerrainHeight.GetZ());
|
||||
|
||||
return localSnappedSurfacePosition;
|
||||
}
|
||||
|
||||
bool GridSnapping(const int viewportId)
|
||||
{
|
||||
bool snapping = false;
|
||||
|
||||
@@ -63,11 +63,6 @@ namespace AzToolsFramework
|
||||
AZ::Vector3 CalculateSnappedPosition(
|
||||
const AZ::Vector3& unsnappedPosition, const AZ::Vector3* snapAxes, size_t snapAxesCount, float size);
|
||||
|
||||
//! For a given point on the terrain, calculate the closest xy position snapped to the grid
|
||||
//! (z position is aligned to terrain height, not snapped to z grid)
|
||||
AZ::Vector3 CalculateSnappedTerrainPosition(
|
||||
const AZ::Vector3& worldSurfacePosition, const AZ::Transform& worldFromLocal, int viewportId, float size);
|
||||
|
||||
//! Wrapper for grid snapping and grid size bus calls.
|
||||
GridSnapParameters GridSnapSettings(int viewportId);
|
||||
|
||||
|
||||
+51
-53
@@ -10,6 +10,7 @@
|
||||
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorSnapping.h>
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorView.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
@@ -17,28 +18,16 @@ namespace AzToolsFramework
|
||||
const AZ::Transform& worldFromLocal,
|
||||
const AZ::Vector3& worldSurfacePosition,
|
||||
const AZ::Vector3& localStartPosition,
|
||||
const bool snapping,
|
||||
const float gridSize,
|
||||
const int viewportId)
|
||||
[[maybe_unused]] const bool snapping,
|
||||
[[maybe_unused]] const float gridSize,
|
||||
[[maybe_unused]] const int viewportId)
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
|
||||
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
|
||||
|
||||
const AZ::Vector3 localFinalSurfacePosition = snapping
|
||||
// note: gridSize is not scaled by scaleRecip here as localStartPosition is
|
||||
// unscaled itself so the position returned by CalculateSnappedTerrainPosition
|
||||
// must be in the same space (if localStartPosition were also scaled, gridSize
|
||||
// would need to be multiplied by scaleRecip)
|
||||
? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize)
|
||||
: localFromWorldUniform.TransformPoint(worldSurfacePosition);
|
||||
|
||||
// delta/offset between initial vertex position and terrain pick position
|
||||
const AZ::Vector3 localSurfaceOffset = localFinalSurfacePosition - localStartPosition;
|
||||
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
|
||||
|
||||
StartInternal startInternal;
|
||||
startInternal.m_snapOffset = localSurfaceOffset;
|
||||
startInternal.m_localPosition = localStartPosition + localSurfaceOffset;
|
||||
startInternal.m_localHitPosition = localFromWorldUniform.TransformVector(worldSurfacePosition);
|
||||
startInternal.m_snapOffset = AZ::Vector3::CreateZero();
|
||||
startInternal.m_localPosition = localStartPosition;
|
||||
startInternal.m_localHitPosition = localFromWorld.TransformPoint(worldSurfacePosition);
|
||||
return startInternal;
|
||||
}
|
||||
|
||||
@@ -46,26 +35,19 @@ namespace AzToolsFramework
|
||||
const StartInternal& startInternal,
|
||||
const AZ::Transform& worldFromLocal,
|
||||
const AZ::Vector3& worldSurfacePosition,
|
||||
const bool snapping,
|
||||
const float gridSize,
|
||||
[[maybe_unused]] const bool snapping,
|
||||
[[maybe_unused]] const float gridSize,
|
||||
const ViewportInteraction::KeyboardModifiers keyboardModifiers,
|
||||
const int viewportId)
|
||||
[[maybe_unused]] const int viewportId)
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniform = AzToolsFramework::TransformUniformScale(worldFromLocal);
|
||||
const AZ::Transform localFromWorldUniform = worldFromLocalUniform.GetInverse();
|
||||
|
||||
const float scaleRecip = ScaleReciprocal(worldFromLocalUniform);
|
||||
|
||||
const AZ::Vector3 localFinalSurfacePosition = snapping
|
||||
? CalculateSnappedTerrainPosition(worldSurfacePosition, worldFromLocalUniform, viewportId, gridSize * scaleRecip)
|
||||
: localFromWorldUniform.TransformPoint(worldSurfacePosition);
|
||||
const AZ::Transform localFromWorld = worldFromLocal.GetInverse();
|
||||
const AZ::Vector3 localFinalSurfacePosition = localFromWorld.TransformPoint(worldSurfacePosition);
|
||||
|
||||
Action action;
|
||||
action.m_start.m_localPosition = startInternal.m_localPosition;
|
||||
action.m_start.m_snapOffset = startInternal.m_snapOffset;
|
||||
action.m_start.m_snapOffset = AZ::Vector3::CreateZero();
|
||||
action.m_current.m_localOffset = localFinalSurfacePosition - startInternal.m_localPosition;
|
||||
// record what modifier keys are held during this action
|
||||
action.m_modifiers = keyboardModifiers;
|
||||
action.m_modifiers = keyboardModifiers; // record what modifier keys are held during this action
|
||||
return action;
|
||||
}
|
||||
|
||||
@@ -78,12 +60,16 @@ namespace AzToolsFramework
|
||||
{
|
||||
SetSpace(worldFromLocal);
|
||||
AttachLeftMouseDownImpl();
|
||||
|
||||
// only cast rays against objects (entities/meshes etc.) we can actually see
|
||||
m_rayRequest.m_onlyVisible = true;
|
||||
}
|
||||
|
||||
void SurfaceManipulator::InstallLeftMouseDownCallback(const MouseActionCallback& onMouseDownCallback)
|
||||
{
|
||||
m_onLeftMouseDownCallback = onMouseDownCallback;
|
||||
}
|
||||
|
||||
void SurfaceManipulator::InstallLeftMouseUpCallback(const MouseActionCallback& onMouseUpCallback)
|
||||
{
|
||||
m_onLeftMouseUpCallback = onMouseUpCallback;
|
||||
@@ -95,17 +81,30 @@ namespace AzToolsFramework
|
||||
}
|
||||
|
||||
void SurfaceManipulator::OnLeftMouseDownImpl(
|
||||
const ViewportInteraction::MouseInteraction& interaction, float /*rayIntersectionDistance*/)
|
||||
const ViewportInteraction::MouseInteraction& interaction, [[maybe_unused]] float rayIntersectionDistance)
|
||||
{
|
||||
const AZ::Transform worldFromLocalUniformScale = TransformUniformScale(GetSpace());
|
||||
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
|
||||
|
||||
AZ::Vector3 worldSurfacePosition;
|
||||
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
|
||||
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
|
||||
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
|
||||
interaction.m_mousePick.m_screenCoordinates);
|
||||
const auto& entityComponentIdPairs = EntityComponentIdPairs();
|
||||
m_rayRequest.m_entityFilter.m_ignoreEntities.clear();
|
||||
m_rayRequest.m_entityFilter.m_ignoreEntities.reserve(entityComponentIdPairs.size());
|
||||
AZStd::transform(
|
||||
entityComponentIdPairs.begin(), entityComponentIdPairs.end(),
|
||||
AZStd::inserter(m_rayRequest.m_entityFilter.m_ignoreEntities, m_rayRequest.m_entityFilter.m_ignoreEntities.begin()),
|
||||
[](const AZ::EntityComponentIdPair& entityComponentIdPair)
|
||||
{
|
||||
return entityComponentIdPair.GetEntityId();
|
||||
});
|
||||
|
||||
// calculate the start and end of the ray
|
||||
RefreshRayRequest(
|
||||
m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates),
|
||||
EditorPickRayLength);
|
||||
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
|
||||
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance());
|
||||
|
||||
m_startInternal = CalculateManipulationDataStart(
|
||||
worldFromLocalUniformScale, worldSurfacePosition, GetLocalPosition(), gridSnapParams.m_gridSnap, gridSnapParams.m_gridSize,
|
||||
@@ -123,17 +122,14 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (m_onLeftMouseUpCallback)
|
||||
{
|
||||
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
|
||||
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
|
||||
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
|
||||
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
|
||||
interaction.m_mousePick.m_screenCoordinates);
|
||||
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
|
||||
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
|
||||
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance());
|
||||
|
||||
m_onLeftMouseUpCallback(CalculateManipulationDataAction(
|
||||
m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition, gridSnapParams.m_gridSnap,
|
||||
gridSnapParams.m_gridSize, interaction.m_keyboardModifiers, interaction.m_interactionId.m_viewportId));
|
||||
gridSnapParams.m_gridSize, interaction.m_keyboardModifiers, viewportId));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -141,13 +137,15 @@ namespace AzToolsFramework
|
||||
{
|
||||
if (m_onMouseMoveCallback)
|
||||
{
|
||||
AZ::Vector3 worldSurfacePosition = AZ::Vector3::CreateZero();
|
||||
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
|
||||
worldSurfacePosition, interaction.m_interactionId.m_viewportId,
|
||||
&ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
|
||||
interaction.m_mousePick.m_screenCoordinates);
|
||||
const AzFramework::ViewportId viewportId = interaction.m_interactionId.m_viewportId;
|
||||
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(interaction.m_interactionId.m_viewportId);
|
||||
// update the start and end of the ray
|
||||
RefreshRayRequest(
|
||||
m_rayRequest, ViewportInteraction::ViewportScreenToWorldRay(viewportId, interaction.m_mousePick.m_screenCoordinates),
|
||||
EditorPickRayLength);
|
||||
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
|
||||
const AZ::Vector3 worldSurfacePosition = FindClosestPickIntersection(m_rayRequest, GetDefaultEntityPlacementDistance());
|
||||
|
||||
m_onMouseMoveCallback(CalculateManipulationDataAction(
|
||||
m_startInternal, TransformUniformScale(GetSpace()), worldSurfacePosition, gridSnapParams.m_gridSnap,
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
#include "BaseManipulator.h"
|
||||
|
||||
#include <AzCore/Memory/SystemAllocator.h>
|
||||
#include <AzFramework/Render/GeometryIntersectionStructures.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
@@ -58,10 +59,12 @@ namespace AzToolsFramework
|
||||
Start m_start;
|
||||
Current m_current;
|
||||
ViewportInteraction::KeyboardModifiers m_modifiers;
|
||||
|
||||
AZ::Vector3 LocalPosition() const
|
||||
{
|
||||
return m_start.m_localPosition + m_current.m_localOffset;
|
||||
}
|
||||
|
||||
AZ::Vector3 LocalPositionOffset() const
|
||||
{
|
||||
return m_current.m_localOffset;
|
||||
@@ -106,6 +109,9 @@ namespace AzToolsFramework
|
||||
MouseActionCallback m_onLeftMouseUpCallback = nullptr;
|
||||
MouseActionCallback m_onMouseMoveCallback = nullptr;
|
||||
|
||||
//! Cached ray request initialized at mouse down and updated during mouse move.
|
||||
AzFramework::RenderGeometry::RayRequest m_rayRequest;
|
||||
|
||||
static StartInternal CalculateManipulationDataStart(
|
||||
const AZ::Transform& worldFromLocal,
|
||||
const AZ::Vector3& worldSurfacePosition,
|
||||
|
||||
@@ -34,10 +34,12 @@ namespace AzToolsFramework::Prefab
|
||||
PrefabPublicNotificationBus::Handler::BusConnect();
|
||||
AZ::Interface<PrefabFocusInterface>::Register(this);
|
||||
AZ::Interface<PrefabFocusPublicInterface>::Register(this);
|
||||
PrefabFocusPublicRequestBus::Handler::BusConnect();
|
||||
}
|
||||
|
||||
PrefabFocusHandler::~PrefabFocusHandler()
|
||||
{
|
||||
PrefabFocusPublicRequestBus::Handler::BusDisconnect();
|
||||
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
|
||||
AZ::Interface<PrefabFocusInterface>::Unregister(this);
|
||||
PrefabPublicNotificationBus::Handler::BusDisconnect();
|
||||
@@ -45,6 +47,18 @@ namespace AzToolsFramework::Prefab
|
||||
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (AZ::BehaviorContext* behaviorContext = azrtti_cast<AZ::BehaviorContext*>(context); behaviorContext)
|
||||
{
|
||||
behaviorContext->EBus<PrefabFocusPublicRequestBus>("PrefabFocusPublicRequestBus")
|
||||
->Attribute(AZ::Script::Attributes::Scope, AZ::Script::Attributes::ScopeFlags::Automation)
|
||||
->Attribute(AZ::Script::Attributes::Category, "Prefab")
|
||||
->Attribute(AZ::Script::Attributes::Module, "prefab")
|
||||
->Event("FocusOnOwningPrefab", &PrefabFocusPublicInterface::FocusOnOwningPrefab);
|
||||
}
|
||||
}
|
||||
|
||||
void PrefabFocusHandler::InitializeEditorInterfaces()
|
||||
{
|
||||
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
|
||||
|
||||
@@ -30,8 +30,8 @@ namespace AzToolsFramework::Prefab
|
||||
|
||||
//! Handles Prefab Focus mode, determining which prefab file entity changes will target.
|
||||
class PrefabFocusHandler final
|
||||
: private PrefabFocusInterface
|
||||
, private PrefabFocusPublicInterface
|
||||
: public PrefabFocusPublicRequestBus::Handler
|
||||
, private PrefabFocusInterface
|
||||
, private PrefabPublicNotificationBus::Handler
|
||||
, private EditorEntityContextNotificationBus::Handler
|
||||
, private EditorEntityInfoNotificationBus::Handler
|
||||
@@ -42,13 +42,15 @@ namespace AzToolsFramework::Prefab
|
||||
PrefabFocusHandler();
|
||||
~PrefabFocusHandler();
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
// PrefabFocusInterface overrides ...
|
||||
void InitializeEditorInterfaces() override;
|
||||
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
|
||||
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
|
||||
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
|
||||
|
||||
// PrefabFocusPublicInterface overrides ...
|
||||
// PrefabFocusPublicInterface and PrefabFocusPublicRequestBus overrides ...
|
||||
PrefabFocusOperationResult FocusOnOwningPrefab(AZ::EntityId entityId) override;
|
||||
PrefabFocusOperationResult FocusOnParentOfFocusedPrefab(AzFramework::EntityContextId entityContextId) override;
|
||||
PrefabFocusOperationResult FocusOnPathIndex(AzFramework::EntityContextId entityContextId, int index) override;
|
||||
|
||||
@@ -58,4 +58,18 @@ namespace AzToolsFramework::Prefab
|
||||
virtual const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const = 0;
|
||||
};
|
||||
|
||||
/**
|
||||
* The primary purpose of this bus is to facilitate writing automated tests for prefab focus mode.
|
||||
* If you would like to integrate prefabs focus mode into your system, please call PrefabFocusPublicInterface
|
||||
* for better performance.
|
||||
*/
|
||||
class PrefabFocusPublicRequests
|
||||
: public AZ::EBusTraits
|
||||
{
|
||||
public:
|
||||
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Single;
|
||||
};
|
||||
|
||||
using PrefabFocusPublicRequestBus = AZ::EBus<PrefabFocusPublicInterface, PrefabFocusPublicRequests>;
|
||||
|
||||
} // namespace AzToolsFramework::Prefab
|
||||
|
||||
@@ -60,6 +60,7 @@ namespace AzToolsFramework
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::PrefabCatchmentProcessor::Reflect(context);
|
||||
AzToolsFramework::Prefab::PrefabConversionUtils::EditorInfoRemover::Reflect(context);
|
||||
PrefabPublicRequestHandler::Reflect(context);
|
||||
PrefabFocusHandler::Reflect(context);
|
||||
PrefabLoader::Reflect(context);
|
||||
PrefabSystemScriptingHandler::Reflect(context);
|
||||
|
||||
|
||||
+4
@@ -169,6 +169,10 @@ namespace AzToolsFramework
|
||||
{
|
||||
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&PropertyEditorGUIMessages::Bus::Events::RequestWrite, newCtrl);
|
||||
});
|
||||
this->connect(newCtrl, &PropertyControl::editingFinished, this, [newCtrl]()
|
||||
{
|
||||
AzToolsFramework::PropertyEditorGUIMessages::Bus::Broadcast(&PropertyEditorGUIMessages::Bus::Handler::OnEditingFinished, newCtrl);
|
||||
});
|
||||
// note: Qt automatically disconnects objects from each other when either end is destroyed, no need to worry about delete.
|
||||
|
||||
// Set the value range to that of ValueType as clamped to the range of QtWidgetValueType
|
||||
|
||||
@@ -23,11 +23,11 @@ namespace AzToolsFramework
|
||||
/// @name Reverse URLs.
|
||||
/// Used to identify common actions and override them when necessary.
|
||||
//@{
|
||||
static const AZ::Crc32 s_backAction = AZ_CRC("com.o3de.action.common.back", 0xd772a2af);
|
||||
static const AZ::Crc32 s_deleteAction = AZ_CRC("com.o3de.action.common.delete", 0x5731f6cb);
|
||||
static const AZ::Crc32 s_duplicateAction = AZ_CRC("com.o3de.action.common.duplicate", 0x08ccf461);
|
||||
static const AZ::Crc32 s_nextComponentMode = AZ_CRC("com.o3de.action.common.nextComponentMode", 0xcc26094f);
|
||||
static const AZ::Crc32 s_previousComponentMode = AZ_CRC("com.o3de.action.common.previousComponentMode", 0x0d18ff39);
|
||||
static const AZ::Crc32 s_backAction = AZ_CRC("com.o3de.action.common.back", 0x80c3030f);
|
||||
static const AZ::Crc32 s_deleteAction = AZ_CRC("com.o3de.action.common.delete", 0x58e78eed);
|
||||
static const AZ::Crc32 s_duplicateAction = AZ_CRC("com.o3de.action.common.duplicate", 0xbc5a4a23);
|
||||
static const AZ::Crc32 s_nextComponentMode = AZ_CRC("com.o3de.action.common.nextComponentMode", 0xf9aca3a8);
|
||||
static const AZ::Crc32 s_previousComponentMode = AZ_CRC("com.o3de.action.common.previousComponentMode", 0x0580eaec);
|
||||
//@}
|
||||
|
||||
/// Specific Action properties to be sent to a type implementing
|
||||
|
||||
@@ -64,23 +64,12 @@ namespace AzToolsFramework
|
||||
return circleBoundWidth;
|
||||
}
|
||||
|
||||
AZ::Vector3 FindClosestPickIntersection(
|
||||
AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, const float rayLength, const float defaultDistance)
|
||||
AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, const float defaultDistance)
|
||||
{
|
||||
using AzToolsFramework::ViewportInteraction::ViewportInteractionRequestBus;
|
||||
AzToolsFramework::ViewportInteraction::ProjectedViewportRay viewportRay{};
|
||||
ViewportInteractionRequestBus::EventResult(
|
||||
viewportRay, viewportId, &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint);
|
||||
|
||||
AzFramework::RenderGeometry::RayRequest ray;
|
||||
ray.m_startWorldPosition = viewportRay.origin;
|
||||
ray.m_endWorldPosition = viewportRay.origin + viewportRay.direction * rayLength;
|
||||
ray.m_onlyVisible = true;
|
||||
|
||||
AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult;
|
||||
AzFramework::RenderGeometry::IntersectorBus::EventResult(
|
||||
renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(),
|
||||
&AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, ray);
|
||||
&AzFramework::RenderGeometry::IntersectorBus::Events::RayIntersect, rayRequest);
|
||||
|
||||
// attempt a ray intersection with any visible mesh and return the intersection position if successful
|
||||
if (renderGeometryIntersectionResult)
|
||||
@@ -89,7 +78,32 @@ namespace AzToolsFramework
|
||||
}
|
||||
else
|
||||
{
|
||||
return viewportRay.origin + viewportRay.direction * defaultDistance;
|
||||
const AZ::Vector3 rayDirection = (rayRequest.m_endWorldPosition - rayRequest.m_startWorldPosition).GetNormalized();
|
||||
return rayRequest.m_startWorldPosition + rayDirection * defaultDistance;
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshRayRequest(
|
||||
AzFramework::RenderGeometry::RayRequest& rayRequest,
|
||||
const ViewportInteraction::ProjectedViewportRay& viewportRay,
|
||||
const float rayLength)
|
||||
{
|
||||
AZ_Assert(rayLength > 0.0f, "Invalid ray length passed to RefreshRayRequest");
|
||||
rayRequest.m_startWorldPosition = viewportRay.origin;
|
||||
rayRequest.m_endWorldPosition = viewportRay.origin + viewportRay.direction * rayLength;
|
||||
}
|
||||
|
||||
AZ::Vector3 FindClosestPickIntersection(
|
||||
const AzFramework::ViewportId viewportId,
|
||||
const AzFramework::ScreenPoint& screenPoint,
|
||||
const float rayLength,
|
||||
const float defaultDistance)
|
||||
{
|
||||
AzFramework::RenderGeometry::RayRequest ray;
|
||||
ray.m_onlyVisible = true; // only consider visible objects
|
||||
|
||||
RefreshRayRequest(ray, ViewportInteraction::ViewportScreenToWorldRay(viewportId, screenPoint), rayLength);
|
||||
|
||||
return FindClosestPickIntersection(ray, defaultDistance);
|
||||
}
|
||||
} // namespace AzToolsFramework
|
||||
|
||||
@@ -15,13 +15,19 @@
|
||||
#include <AzFramework/Viewport/CameraState.h>
|
||||
#include <AzFramework/Viewport/ClickDetector.h>
|
||||
#include <AzFramework/Viewport/ViewportId.h>
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityContextBus.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportTypes.h>
|
||||
|
||||
namespace AzFramework
|
||||
{
|
||||
struct ScreenPoint;
|
||||
}
|
||||
|
||||
namespace RenderGeometry
|
||||
{
|
||||
struct RayRequest;
|
||||
}
|
||||
} // namespace AzFramework
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
@@ -177,6 +183,26 @@ namespace AzToolsFramework
|
||||
//! Type to inherit to implement ViewportInteractionRequests.
|
||||
using ViewportInteractionRequestBus = AZ::EBus<ViewportInteractionRequests, ViewportEBusTraits>;
|
||||
|
||||
//! Utility function to return a viewport ray.
|
||||
inline ProjectedViewportRay ViewportScreenToWorldRay(
|
||||
const AzFramework::CameraState& cameraState, const AzFramework::ScreenPoint& screenPoint)
|
||||
{
|
||||
const AZ::Vector3 rayOrigin = AzFramework::ScreenToWorld(screenPoint, cameraState);
|
||||
const AZ::Vector3 rayDirection = (rayOrigin - cameraState.m_position).GetNormalized();
|
||||
return AzToolsFramework::ViewportInteraction::ProjectedViewportRay{ rayOrigin, rayDirection };
|
||||
}
|
||||
|
||||
//! Utility function to return a viewport ray using the ViewportInteractionRequestBus.
|
||||
inline ProjectedViewportRay ViewportScreenToWorldRay(
|
||||
const AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint)
|
||||
{
|
||||
ProjectedViewportRay viewportRay{};
|
||||
ViewportInteractionRequestBus::EventResult(
|
||||
viewportRay, viewportId, &ViewportInteractionRequestBus::Events::ViewportScreenToWorldRay, screenPoint);
|
||||
|
||||
return viewportRay;
|
||||
}
|
||||
|
||||
//! Interface to return only viewport specific settings (e.g. snapping).
|
||||
class ViewportSettingsRequests
|
||||
{
|
||||
@@ -228,10 +254,6 @@ namespace AzToolsFramework
|
||||
class MainEditorViewportInteractionRequests
|
||||
{
|
||||
public:
|
||||
//! Given a point in screen space, return the terrain position in world space.
|
||||
virtual AZ::Vector3 PickTerrain(const AzFramework::ScreenPoint& point) = 0;
|
||||
//! Return the terrain height given a world position in 2d (xy plane).
|
||||
virtual float TerrainHeight(const AZ::Vector2& position) = 0;
|
||||
//! Is the user holding a modifier key to move the manipulator space from local to world.
|
||||
virtual bool ShowingWorldSpace() = 0;
|
||||
//! Return the widget to use as the parent for the viewport context menu.
|
||||
@@ -337,9 +359,18 @@ namespace AzToolsFramework
|
||||
//! Performs an intersection test against meshes in the scene, if there is a hit (the ray intersects
|
||||
//! a mesh), that position is returned, otherwise a point projected defaultDistance from the
|
||||
//! origin of the ray will be returned.
|
||||
//! @note The intersection will only consider visible objects.
|
||||
AZ::Vector3 FindClosestPickIntersection(
|
||||
AzFramework::ViewportId viewportId, const AzFramework::ScreenPoint& screenPoint, float rayLength, float defaultDistance);
|
||||
|
||||
//! Overload of FindClosestPickIntersection taking a RenderGeometry::RayRequest directly.
|
||||
//! @note rayRequest must contain a valid ray/line segment (start/endWorldPosition must not be at the same position).
|
||||
AZ::Vector3 FindClosestPickIntersection(const AzFramework::RenderGeometry::RayRequest& rayRequest, float defaultDistance);
|
||||
|
||||
//! Update the in/out parameter rayRequest based on the latest viewport ray.
|
||||
void RefreshRayRequest(
|
||||
AzFramework::RenderGeometry::RayRequest& rayRequest, const ViewportInteraction::ProjectedViewportRay& viewportRay, float rayLength);
|
||||
|
||||
//! Maps a mouse interaction event to a ClickDetector event.
|
||||
//! @note Function only cares about up or down events, all other events are mapped to Nil (ignored).
|
||||
AzFramework::ClickDetector::ClickEvent ClickDetectorEventFromViewportInteraction(
|
||||
|
||||
+14
@@ -9,6 +9,7 @@
|
||||
#include "EditorSelectionUtil.h"
|
||||
|
||||
#include <AzCore/Component/ComponentApplicationBus.h>
|
||||
#include <AzCore/Console/IConsole.h>
|
||||
#include <AzCore/Interface/Interface.h>
|
||||
#include <AzCore/Math/Aabb.h>
|
||||
#include <AzCore/Math/IntersectSegment.h>
|
||||
@@ -16,8 +17,21 @@
|
||||
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
|
||||
#include <AzToolsFramework/Viewport/ViewportMessages.h>
|
||||
|
||||
AZ_CVAR(
|
||||
float,
|
||||
ed_defaultEntityPlacementDistance,
|
||||
10.0f,
|
||||
nullptr,
|
||||
AZ::ConsoleFunctorFlags::Null,
|
||||
"The default distance to place an entity from the camera if no intersection is found");
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
float GetDefaultEntityPlacementDistance()
|
||||
{
|
||||
return ed_defaultEntityPlacementDistance;
|
||||
}
|
||||
|
||||
AZ::Vector3 CalculateCenterOffset(const AZ::EntityId entityId, const EditorTransformComponentSelectionRequests::Pivot pivot)
|
||||
{
|
||||
if (Centered(pivot))
|
||||
|
||||
+3
@@ -60,6 +60,9 @@ namespace AzToolsFramework
|
||||
//! Wrapper for EBus call to return the DPI scaling for a given viewport.
|
||||
float GetScreenDisplayScaling(int viewportId);
|
||||
|
||||
//! The default distance an entity is placed from the camera if there is no intersection.
|
||||
float GetDefaultEntityPlacementDistance();
|
||||
|
||||
//! A utility to return the center of several points.
|
||||
//! Take several positions and store the min and max of each in
|
||||
//! turn - when all points have been added return the center/midpoint.
|
||||
|
||||
+56
-40
@@ -231,8 +231,8 @@ namespace AzToolsFramework
|
||||
{
|
||||
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() &&
|
||||
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl() &&
|
||||
!mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt();
|
||||
!mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt() &&
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl();
|
||||
}
|
||||
|
||||
static bool IndividualDitto(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
@@ -243,12 +243,12 @@ namespace AzToolsFramework
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl();
|
||||
}
|
||||
|
||||
static bool SnapTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
static bool SnapSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
{
|
||||
return mouseInteraction.m_mouseInteraction.m_mouseButtons.Middle() &&
|
||||
mouseInteraction.m_mouseEvent == ViewportInteraction::MouseEvent::Down &&
|
||||
(mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Alt() ||
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl());
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Shift() &&
|
||||
mouseInteraction.m_mouseInteraction.m_keyboardModifiers.Ctrl();
|
||||
}
|
||||
|
||||
static bool ManipulatorDitto(
|
||||
@@ -923,26 +923,6 @@ namespace AzToolsFramework
|
||||
}
|
||||
}
|
||||
|
||||
static AZ::Vector3 PickTerrainPosition(const ViewportInteraction::MouseInteraction& mouseInteraction)
|
||||
{
|
||||
AZ_PROFILE_FUNCTION(AzToolsFramework);
|
||||
|
||||
const int viewportId = mouseInteraction.m_interactionId.m_viewportId;
|
||||
// get unsnapped terrain position (world space)
|
||||
AZ::Vector3 worldSurfacePosition;
|
||||
ViewportInteraction::MainEditorViewportInteractionRequestBus::EventResult(
|
||||
worldSurfacePosition, viewportId, &ViewportInteraction::MainEditorViewportInteractionRequestBus::Events::PickTerrain,
|
||||
mouseInteraction.m_mousePick.m_screenCoordinates);
|
||||
|
||||
// convert to local space - snap if enabled
|
||||
const GridSnapParameters gridSnapParams = GridSnapSettings(viewportId);
|
||||
const AZ::Vector3 finalSurfacePosition = gridSnapParams.m_gridSnap
|
||||
? CalculateSnappedTerrainPosition(worldSurfacePosition, AZ::Transform::CreateIdentity(), viewportId, gridSnapParams.m_gridSize)
|
||||
: worldSurfacePosition;
|
||||
|
||||
return finalSurfacePosition;
|
||||
}
|
||||
|
||||
// is the passed entity id contained with in the entity id list
|
||||
template<typename EntityIdContainer>
|
||||
static bool IsEntitySelectedInternal(AZ::EntityId entityId, const EntityIdContainer& selectedEntityIds)
|
||||
@@ -1286,7 +1266,7 @@ namespace AzToolsFramework
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
|
||||
|
||||
// [ref 1.]
|
||||
// see comment [ref 1.] above
|
||||
BeginRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
@@ -1321,7 +1301,7 @@ namespace AzToolsFramework
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
|
||||
|
||||
// [ref 1.]
|
||||
// see comment [ref 1.] above
|
||||
BeginRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
@@ -1343,6 +1323,39 @@ namespace AzToolsFramework
|
||||
EndRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
// surface
|
||||
translationManipulators->InstallSurfaceManipulatorMouseDownCallback(
|
||||
[this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action)
|
||||
{
|
||||
BuildSortedEntityIdVectorFromEntityIdMap(m_entityIdManipulators.m_lookups, manipulatorEntityIds->m_entityIds);
|
||||
|
||||
InitializeTranslationLookup(m_entityIdManipulators);
|
||||
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
|
||||
|
||||
// see comment [ref 1.] above
|
||||
BeginRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
translationManipulators->InstallSurfaceManipulatorMouseMoveCallback(
|
||||
[this, prevModifiers, manipulatorEntityIds](const SurfaceManipulator::Action& action) mutable
|
||||
{
|
||||
UpdateTranslationManipulator(
|
||||
action, manipulatorEntityIds->m_entityIds, m_entityIdManipulators, m_pivotOverrideFrame, prevModifiers,
|
||||
m_transformChangedInternally, m_spaceCluster.m_spaceLock);
|
||||
});
|
||||
|
||||
translationManipulators->InstallSurfaceManipulatorMouseUpCallback(
|
||||
[this, manipulatorEntityIds]([[maybe_unused]] const SurfaceManipulator::Action& action)
|
||||
{
|
||||
AzToolsFramework::EditorTransformChangeNotificationBus::Broadcast(
|
||||
&AzToolsFramework::EditorTransformChangeNotificationBus::Events::OnEntityTransformChanged,
|
||||
manipulatorEntityIds->m_entityIds);
|
||||
|
||||
EndRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
// transfer ownership
|
||||
m_entityIdManipulators.m_manipulators = AZStd::move(translationManipulators);
|
||||
}
|
||||
@@ -1395,7 +1408,7 @@ namespace AzToolsFramework
|
||||
m_axisPreview.m_translation = m_entityIdManipulators.m_manipulators->GetLocalTransform().GetTranslation();
|
||||
m_axisPreview.m_orientation = QuaternionFromTransformNoScaling(m_entityIdManipulators.m_manipulators->GetLocalTransform());
|
||||
|
||||
// [ref 1.]
|
||||
// see comment [ref 1.] above
|
||||
BeginRecordManipulatorCommand();
|
||||
});
|
||||
|
||||
@@ -1867,6 +1880,13 @@ namespace AzToolsFramework
|
||||
|
||||
if (!m_selectedEntityIds.empty())
|
||||
{
|
||||
// try snapping to a surface (mesh) if in Translation mode
|
||||
if (Input::SnapSurface(mouseInteraction))
|
||||
{
|
||||
PerformSnapToSurface(mouseInteraction);
|
||||
return false;
|
||||
}
|
||||
|
||||
// group copying/alignment to specific entity - 'ditto' position/orientation for group
|
||||
if (Input::GroupDitto(mouseInteraction) && PerformGroupDitto(entityIdUnderCursor))
|
||||
{
|
||||
@@ -1879,13 +1899,6 @@ namespace AzToolsFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
// try snapping to the terrain (if in Translation mode) and entity wasn't picked
|
||||
if (Input::SnapTerrain(mouseInteraction))
|
||||
{
|
||||
PerformSnapToTerrain(mouseInteraction);
|
||||
return false;
|
||||
}
|
||||
|
||||
// set manipulator pivot override translation or orientation (update manipulators)
|
||||
if (Input::ManipulatorDitto(clickOutcome, mouseInteraction))
|
||||
{
|
||||
@@ -1973,25 +1986,28 @@ namespace AzToolsFramework
|
||||
return false;
|
||||
}
|
||||
|
||||
void EditorTransformComponentSelection::PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
void EditorTransformComponentSelection::PerformSnapToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction)
|
||||
{
|
||||
for (AZ::EntityId entityId : m_selectedEntityIds)
|
||||
for (const AZ::EntityId& entityId : m_selectedEntityIds)
|
||||
{
|
||||
ScopedUndoBatch::MarkEntityDirty(entityId);
|
||||
}
|
||||
|
||||
if (m_mode == Mode::Translation)
|
||||
{
|
||||
const AZ::Vector3 finalSurfacePosition = PickTerrainPosition(mouseInteraction.m_mouseInteraction);
|
||||
const AZ::Vector3 worldPosition = FindClosestPickIntersection(
|
||||
mouseInteraction.m_mouseInteraction.m_interactionId.m_viewportId,
|
||||
mouseInteraction.m_mouseInteraction.m_mousePick.m_screenCoordinates, AzToolsFramework::EditorPickRayLength,
|
||||
GetDefaultEntityPlacementDistance());
|
||||
|
||||
// handle modifier alternatives
|
||||
if (Input::IndividualDitto(mouseInteraction))
|
||||
{
|
||||
CopyTranslationToSelectedEntitiesIndividual(finalSurfacePosition);
|
||||
CopyTranslationToSelectedEntitiesIndividual(worldPosition);
|
||||
}
|
||||
else if (Input::GroupDitto(mouseInteraction))
|
||||
{
|
||||
CopyTranslationToSelectedEntitiesGroup(finalSurfacePosition);
|
||||
CopyTranslationToSelectedEntitiesGroup(worldPosition);
|
||||
}
|
||||
}
|
||||
else if (m_mode == Mode::Rotation)
|
||||
|
||||
+1
-1
@@ -308,7 +308,7 @@ namespace AzToolsFramework
|
||||
bool PerformGroupDitto(AZ::EntityId entityId);
|
||||
bool PerformIndividualDitto(AZ::EntityId entityId);
|
||||
void PerformManipulatorDitto(AZ::EntityId entityId);
|
||||
void PerformSnapToTerrain(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
|
||||
void PerformSnapToSurface(const ViewportInteraction::MouseInteractionEvent& mouseInteraction);
|
||||
|
||||
//! Responsible for keeping the space cluster in sync with the current reference frame.
|
||||
void UpdateSpaceCluster(ReferenceFrame referenceFrame);
|
||||
|
||||
@@ -159,6 +159,10 @@ set(FILES
|
||||
Entity/SliceEditorEntityOwnershipServiceBus.h
|
||||
Entity/EntityUtilityComponent.h
|
||||
Entity/EntityUtilityComponent.cpp
|
||||
Entity/ReadOnly/ReadOnlyEntityInterface.h
|
||||
Entity/ReadOnly/ReadOnlyEntityBus.h
|
||||
Entity/ReadOnly/ReadOnlyEntitySystemComponent.cpp
|
||||
Entity/ReadOnly/ReadOnlyEntitySystemComponent.h
|
||||
Fingerprinting/TypeFingerprinter.h
|
||||
Fingerprinting/TypeFingerprinter.cpp
|
||||
FocusMode/FocusModeInterface.h
|
||||
|
||||
@@ -8,6 +8,8 @@
|
||||
|
||||
#include <Tests/BoundsTestComponent.h>
|
||||
|
||||
#include <AzCore/Math/Obb.h>
|
||||
#include <AzCore/Math/IntersectSegment.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorSelectionUtil.h>
|
||||
|
||||
namespace UnitTest
|
||||
@@ -62,4 +64,51 @@ namespace UnitTest
|
||||
{
|
||||
return m_localBounds;
|
||||
}
|
||||
|
||||
void RenderGeometryIntersectionTestComponent::Reflect(AZ::ReflectContext* context)
|
||||
{
|
||||
if (auto serializeContext = azrtti_cast<AZ::SerializeContext*>(context))
|
||||
{
|
||||
serializeContext->Class<RenderGeometryIntersectionTestComponent, BoundsTestComponent>()->Version(1);
|
||||
}
|
||||
}
|
||||
|
||||
void RenderGeometryIntersectionTestComponent::Activate()
|
||||
{
|
||||
BoundsTestComponent::Activate();
|
||||
|
||||
const AZ::EntityId entityId = GetEntityId();
|
||||
AzFramework::EntityContextId contextId = AzFramework::EntityContextId::CreateNull();
|
||||
AzFramework::EntityIdContextQueryBus::EventResult(contextId, entityId, &AzFramework::EntityIdContextQueries::GetOwningContextId);
|
||||
AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusConnect({entityId, contextId});
|
||||
}
|
||||
|
||||
void RenderGeometryIntersectionTestComponent::Deactivate()
|
||||
{
|
||||
AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusDisconnect();
|
||||
BoundsTestComponent::Deactivate();
|
||||
}
|
||||
|
||||
AzFramework::RenderGeometry::RayResult RenderGeometryIntersectionTestComponent::RenderGeometryIntersect(
|
||||
const AzFramework::RenderGeometry::RayRequest& ray)
|
||||
{
|
||||
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
|
||||
AZ::TransformBus::EventResult(worldFromLocal, GetEntityId(), &AZ::TransformBus::Events::GetWorldTM);
|
||||
|
||||
AzFramework::RenderGeometry::RayResult rayResult;
|
||||
|
||||
float t = 0.0f;
|
||||
const AZ::Obb obb = GetLocalBounds().GetTransformedObb(worldFromLocal);
|
||||
const AZ::Vector3 rayDirection = ray.m_endWorldPosition - ray.m_startWorldPosition;
|
||||
if (AZ::Intersect::IntersectRayObb(ray.m_startWorldPosition, rayDirection, obb, t))
|
||||
{
|
||||
rayResult.m_worldPosition = ray.m_startWorldPosition + rayDirection * t;
|
||||
rayResult.m_entityAndComponent = AZ::EntityComponentIdPair(GetEntityId(), GetId());
|
||||
rayResult.m_distance = t;
|
||||
rayResult.m_uv = AZ::Vector2::CreateZero();
|
||||
rayResult.m_worldNormal = AZ::Vector3::CreateZero();
|
||||
}
|
||||
|
||||
return rayResult;
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzFramework/Render/GeometryIntersectionBus.h>
|
||||
#include <AzFramework/Visibility/BoundsBus.h>
|
||||
#include <AzToolsFramework/API/ComponentEntitySelectionBus.h>
|
||||
#include <AzToolsFramework/ToolsComponents/EditorComponentBase.h>
|
||||
@@ -44,4 +45,21 @@ namespace UnitTest
|
||||
|
||||
AZ::Aabb m_localBounds; //!< Local bounds that can be modified for certain tests (defaults to unit cube).
|
||||
};
|
||||
|
||||
class RenderGeometryIntersectionTestComponent
|
||||
: public BoundsTestComponent
|
||||
, public AzFramework::RenderGeometry::IntersectionRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
AZ_EDITOR_COMPONENT(RenderGeometryIntersectionTestComponent, "{6F46B5BF-60DF-4BDD-9BA7-9658E85B99C2}", BoundsTestComponent);
|
||||
|
||||
static void Reflect(AZ::ReflectContext* context);
|
||||
|
||||
// AZ::Component overrides ...
|
||||
void Activate() override;
|
||||
void Deactivate() override;
|
||||
|
||||
// IntersectionRequestBus overrides ...
|
||||
AzFramework::RenderGeometry::RayResult RenderGeometryIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override;
|
||||
};
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -1663,7 +1663,7 @@ namespace UnitTest
|
||||
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityId1);
|
||||
|
||||
// ensure final world positions match
|
||||
EXPECT_TRUE(finalEntityTransform.IsClose(finalTransformWorld, 0.01f));
|
||||
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
|
||||
}
|
||||
|
||||
TEST_F(EditorTransformComponentSelectionManipulatorTestFixture, TranslatingEntityWithLinearManipulatorNotifiesOnEntityTransformChanged)
|
||||
@@ -2973,4 +2973,176 @@ namespace UnitTest
|
||||
EXPECT_THAT(hoveredEntityIdAccentRemoved, IsTrue());
|
||||
EXPECT_THAT(hoveredEntityEntityId, Eq(AZ::EntityId(12345)));
|
||||
}
|
||||
|
||||
class EditorTransformComponentSelectionRenderGeometryIntersectionFixture : public ToolsApplicationFixture
|
||||
{
|
||||
public:
|
||||
void SetUpEditorFixtureImpl() override
|
||||
{
|
||||
auto* app = GetApplication();
|
||||
// register a simple component implementing BoundsRequestBus and EditorComponentSelectionRequestsBus
|
||||
app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor());
|
||||
// register a component implementing RenderGeometry::IntersectionRequestBus
|
||||
app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor());
|
||||
|
||||
auto createEntityWithGeometryIntersectionFn = [](const char* entityName)
|
||||
{
|
||||
AZ::Entity* entity = nullptr;
|
||||
AZ::EntityId entityId = CreateDefaultEditorEntity(entityName, &entity);
|
||||
|
||||
entity->Deactivate();
|
||||
entity->CreateComponent<RenderGeometryIntersectionTestComponent>();
|
||||
entity->Activate();
|
||||
|
||||
return entityId;
|
||||
};
|
||||
|
||||
m_entityIdGround = createEntityWithGeometryIntersectionFn("Entity1");
|
||||
m_entityIdBox = createEntityWithGeometryIntersectionFn("Entity2");
|
||||
|
||||
if (auto* ground = AzToolsFramework::GetEntityById(m_entityIdGround)->FindComponent<RenderGeometryIntersectionTestComponent>())
|
||||
{
|
||||
ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f));
|
||||
}
|
||||
|
||||
AzToolsFramework::SetWorldTransform(m_entityIdGround, AZ::Transform::CreateTranslation(AZ::Vector3(0.0f, 10.0f, 5.0f)));
|
||||
|
||||
if (auto* box = AzToolsFramework::GetEntityById(m_entityIdBox)->FindComponent<RenderGeometryIntersectionTestComponent>())
|
||||
{
|
||||
box->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-0.5f), AZ::Vector3(0.5f));
|
||||
}
|
||||
|
||||
AzToolsFramework::SetWorldTransform(
|
||||
m_entityIdBox,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(45.0f)), AZ::Vector3(0.0f, 10.0f, 7.0f)));
|
||||
}
|
||||
|
||||
AZ::EntityId m_entityIdGround;
|
||||
AZ::EntityId m_entityIdBox;
|
||||
};
|
||||
|
||||
using EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture =
|
||||
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorTransformComponentSelectionRenderGeometryIntersectionFixture>;
|
||||
|
||||
TEST_F(
|
||||
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture, BoxCanBePlacedOnMeshSurfaceUsingSurfaceManipulator)
|
||||
{
|
||||
// camera (go to position format) - 0.00, 20.00, 12.00, -35.00, -180.00
|
||||
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
|
||||
AzFramework::SetCameraTransform(
|
||||
m_cameraState,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-180.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-35.0f)),
|
||||
AZ::Vector3(0.0f, 20.0f, 12.0f)));
|
||||
|
||||
// the initial starting position of the entity
|
||||
const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox);
|
||||
// where the entity should end up (snapped to the larger ground surface)
|
||||
const auto finalTransformWorld =
|
||||
AZ::Transform::CreateFromQuaternionAndTranslation(initialTransformWorld.GetRotation(), AZ::Vector3(2.5f, 12.5f, 5.5f));
|
||||
|
||||
// calculate the position in screen space of the initial position of the entity
|
||||
const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState);
|
||||
// calculate the position in screen space of the final position of the entity
|
||||
const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState);
|
||||
|
||||
// select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection)
|
||||
AzToolsFramework::SelectEntity(m_entityIdBox);
|
||||
|
||||
// press and drag the mouse (starting where the surface manipulator is)
|
||||
m_actionDispatcher->CameraState(m_cameraState)
|
||||
->MousePosition(initialPositionScreen)
|
||||
->MouseLButtonDown()
|
||||
->MousePosition(finalPositionScreen)
|
||||
->MouseLButtonUp();
|
||||
|
||||
// read back the position of the entity now
|
||||
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
|
||||
|
||||
// ensure final world positions match
|
||||
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture,
|
||||
SurfaceManipulatorFollowsMouseAtDefaultEditorDistanceFromCameraWhenNoMeshIntersection)
|
||||
{
|
||||
// camera (go to position format) - 0.00, 25.00, 12.00, 0.00, -180.00
|
||||
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
|
||||
AzFramework::SetCameraTransform(
|
||||
m_cameraState,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-180.0f)), AZ::Vector3(0.0f, 25.0f, 12.0f)));
|
||||
|
||||
// the initial starting position of the entity
|
||||
const auto initialTransformWorld = AzToolsFramework::GetWorldTransform(m_entityIdBox);
|
||||
// where the entity should end up (default distance away from the camera/near clip under where the mouse is)
|
||||
const auto finalTransformWorld =
|
||||
AZ::Transform::CreateFromQuaternionAndTranslation(initialTransformWorld.GetRotation(), AZ::Vector3(0.0f, 14.9f, 12.0f));
|
||||
|
||||
// calculate the position in screen space of the initial position of the entity
|
||||
const auto initialPositionScreen = AzFramework::WorldToScreen(initialTransformWorld.GetTranslation(), m_cameraState);
|
||||
// calculate the position in screen space of the final position of the entity
|
||||
const auto finalPositionScreen = AzFramework::WorldToScreen(finalTransformWorld.GetTranslation(), m_cameraState);
|
||||
|
||||
// select the entity (this will cause the manipulators to appear in EditorTransformComponentSelection)
|
||||
AzToolsFramework::SelectEntity(m_entityIdBox);
|
||||
|
||||
// press and drag the mouse (starting where the surface manipulator is)
|
||||
m_actionDispatcher->CameraState(m_cameraState)
|
||||
->MousePosition(initialPositionScreen)
|
||||
->MouseLButtonDown()
|
||||
->MousePosition(finalPositionScreen)
|
||||
->MouseLButtonUp();
|
||||
|
||||
// read back the position of the entity now
|
||||
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
|
||||
|
||||
const auto viewportRay = AzToolsFramework::ViewportInteraction::ViewportScreenToWorldRay(m_cameraState, initialPositionScreen);
|
||||
const auto distanceAway = (finalEntityTransform.GetTranslation() - viewportRay.origin).GetLength();
|
||||
|
||||
// ensure final world positions match
|
||||
EXPECT_THAT(finalEntityTransform, IsCloseTolerance(finalTransformWorld, 0.01f));
|
||||
// ensure distance away is what we expect
|
||||
EXPECT_NEAR(distanceAway, AzToolsFramework::GetDefaultEntityPlacementDistance(), 0.001f);
|
||||
}
|
||||
|
||||
TEST_F(
|
||||
EditorTransformComponentSelectionRenderGeometryIntersectionManipulatorFixture,
|
||||
MiddleMouseButtonWithShiftAndCtrlHeldOnMeshSurfaceWillSnapSelectedEntityToIntersectionPoint)
|
||||
{
|
||||
// camera (go to position format) - 21.00, 8.00, 11.00, -22.00, 150.00
|
||||
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
|
||||
AzFramework::SetCameraTransform(
|
||||
m_cameraState,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(150.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-22.0f)),
|
||||
AZ::Vector3(21.0f, 8.0f, 11.0f)));
|
||||
|
||||
// position the ground entity
|
||||
AzToolsFramework::SetWorldTransform(
|
||||
m_entityIdGround,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(40.0f)) * AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)),
|
||||
AZ::Vector3(14.0f, -6.0f, 5.0f)));
|
||||
|
||||
// select the other entity (a 1x1x1 box)
|
||||
AzToolsFramework::SelectEntity(m_entityIdBox);
|
||||
|
||||
// expected world position (value taken from editor scenario)
|
||||
const auto expectedWorldPosition = AZ::Vector3(13.606657f, -2.6753534f, 5.9827675f);
|
||||
const auto screenPosition = AzFramework::WorldToScreen(expectedWorldPosition, m_cameraState);
|
||||
|
||||
// perform snap action
|
||||
m_actionDispatcher->CameraState(m_cameraState)
|
||||
->MousePosition(screenPosition)
|
||||
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Control)
|
||||
->KeyboardModifierDown(AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift)
|
||||
->MouseMButtonDown();
|
||||
|
||||
// read back the current entity transform after placement
|
||||
const AZ::Transform finalEntityTransform = AzToolsFramework::GetWorldTransform(m_entityIdBox);
|
||||
EXPECT_THAT(finalEntityTransform.GetTranslation(), IsCloseTolerance(expectedWorldPosition, 0.01f));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -10,22 +10,23 @@
|
||||
#include <AzCore/Serialization/SerializeContext.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
#include <AzFramework/Viewport/ViewportScreen.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
|
||||
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
|
||||
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
|
||||
#include <AzTest/AzTest.h>
|
||||
#include <AzToolsFramework/Application/ToolsApplication.h>
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
#include <AzToolsFramework/Manipulators/EditorVertexSelection.h>
|
||||
#include <AzToolsFramework/Manipulators/HoverSelection.h>
|
||||
#include <AzToolsFramework/Manipulators/ManipulatorManager.h>
|
||||
#include <AzToolsFramework/Manipulators/TranslationManipulators.h>
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzToolsFramework/ViewportSelection/EditorInteractionSystemViewportSelectionRequestBus.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFramework.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkUtils.h>
|
||||
#include <AzManipulatorTestFramework/AzManipulatorTestFrameworkTestHelpers.h>
|
||||
#include <AzManipulatorTestFramework/IndirectManipulatorViewportInteraction.h>
|
||||
#include <AzManipulatorTestFramework/ImmediateModeActionDispatcher.h>
|
||||
#include <Tests/Utils/Printers.h>
|
||||
|
||||
using namespace AzToolsFramework;
|
||||
#include <Tests/BoundsTestComponent.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
@@ -42,20 +43,65 @@ namespace UnitTest
|
||||
void Disconnect();
|
||||
|
||||
// FixedVerticesRequestBus/VariableVerticesRequestBus ...
|
||||
bool GetVertex(size_t index, AZ::Vector3& vertex) const override { return m_vertexContainer.GetVertex(index, vertex); }
|
||||
bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.UpdateVertex(index, vertex); };
|
||||
void AddVertex(const AZ::Vector3& vertex) override { m_vertexContainer.AddVertex(vertex); }
|
||||
bool InsertVertex(size_t index, const AZ::Vector3& vertex) override { return m_vertexContainer.InsertVertex(index, vertex); }
|
||||
bool RemoveVertex(size_t index) override { return m_vertexContainer.RemoveVertex(index); }
|
||||
void SetVertices(const AZStd::vector<AZ::Vector3>& vertices) override { m_vertexContainer.SetVertices(vertices); };
|
||||
void ClearVertices() override { m_vertexContainer.Clear(); }
|
||||
size_t Size() const override { return m_vertexContainer.Size(); }
|
||||
bool Empty() const override { return m_vertexContainer.Empty(); }
|
||||
bool GetVertex(size_t index, AZ::Vector3& vertex) const override;
|
||||
bool UpdateVertex(size_t index, const AZ::Vector3& vertex) override;
|
||||
void AddVertex(const AZ::Vector3& vertex) override;
|
||||
bool InsertVertex(size_t index, const AZ::Vector3& vertex) override;
|
||||
bool RemoveVertex(size_t index) override;
|
||||
void SetVertices(const AZStd::vector<AZ::Vector3>& vertices) override;
|
||||
void ClearVertices() override;
|
||||
size_t Size() const override;
|
||||
bool Empty() const override;
|
||||
|
||||
private:
|
||||
AZ::VertexContainer<AZ::Vector3> m_vertexContainer;
|
||||
};
|
||||
|
||||
bool TestVariableVerticesVertexContainer::GetVertex(size_t index, AZ::Vector3& vertex) const
|
||||
{
|
||||
return m_vertexContainer.GetVertex(index, vertex);
|
||||
}
|
||||
|
||||
bool TestVariableVerticesVertexContainer::UpdateVertex(size_t index, const AZ::Vector3& vertex)
|
||||
{
|
||||
return m_vertexContainer.UpdateVertex(index, vertex);
|
||||
}
|
||||
|
||||
void TestVariableVerticesVertexContainer::AddVertex(const AZ::Vector3& vertex)
|
||||
{
|
||||
m_vertexContainer.AddVertex(vertex);
|
||||
}
|
||||
|
||||
bool TestVariableVerticesVertexContainer::InsertVertex(size_t index, const AZ::Vector3& vertex)
|
||||
{
|
||||
return m_vertexContainer.InsertVertex(index, vertex);
|
||||
}
|
||||
|
||||
bool TestVariableVerticesVertexContainer::RemoveVertex(size_t index)
|
||||
{
|
||||
return m_vertexContainer.RemoveVertex(index);
|
||||
}
|
||||
|
||||
void TestVariableVerticesVertexContainer::SetVertices(const AZStd::vector<AZ::Vector3>& vertices)
|
||||
{
|
||||
m_vertexContainer.SetVertices(vertices);
|
||||
}
|
||||
|
||||
void TestVariableVerticesVertexContainer::ClearVertices()
|
||||
{
|
||||
m_vertexContainer.Clear();
|
||||
}
|
||||
|
||||
size_t TestVariableVerticesVertexContainer::Size() const
|
||||
{
|
||||
return m_vertexContainer.Size();
|
||||
}
|
||||
|
||||
bool TestVariableVerticesVertexContainer::Empty() const
|
||||
{
|
||||
return m_vertexContainer.Empty();
|
||||
}
|
||||
|
||||
void TestVariableVerticesVertexContainer::Connect(const AZ::EntityId entityId)
|
||||
{
|
||||
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusConnect(entityId);
|
||||
@@ -68,17 +114,18 @@ namespace UnitTest
|
||||
AZ::VariableVerticesRequestBus<AZ::Vector3>::Handler::BusDisconnect();
|
||||
}
|
||||
|
||||
class TestEditorVertexSelectionVariable
|
||||
: public EditorVertexSelectionVariable<AZ::Vector3>
|
||||
class TestEditorVertexSelectionVariable : public AzToolsFramework::EditorVertexSelectionVariable<AZ::Vector3>
|
||||
{
|
||||
public:
|
||||
AZ_CLASS_ALLOCATOR(TestEditorVertexSelectionVariable, AZ::SystemAllocator, 0)
|
||||
|
||||
void ShowVertexDeletionWarning() override { /*noop*/ }
|
||||
void ShowVertexDeletionWarning() override
|
||||
{
|
||||
// noop
|
||||
}
|
||||
};
|
||||
|
||||
class EditorVertexSelectionFixture
|
||||
: public ToolsApplicationFixture
|
||||
class EditorVertexSelectionFixture : public ToolsApplicationFixture
|
||||
{
|
||||
public:
|
||||
void SetUpEditorFixtureImpl() override
|
||||
@@ -112,26 +159,25 @@ namespace UnitTest
|
||||
|
||||
void EditorVertexSelectionFixture::RecreateVertexSelection()
|
||||
{
|
||||
namespace aztf = AzToolsFramework;
|
||||
m_vertexSelection.Create(
|
||||
AZ::EntityComponentIdPair(m_entityId, TestComponentId),
|
||||
g_mainManipulatorManagerId, AZStd::make_unique<NullHoverSelection>(),
|
||||
TranslationManipulators::Dimensions::Three, ConfigureTranslationManipulatorAppearance3d);
|
||||
AZ::EntityComponentIdPair(m_entityId, TestComponentId), aztf::g_mainManipulatorManagerId,
|
||||
AZStd::make_unique<aztf::NullHoverSelection>(), aztf::TranslationManipulators::Dimensions::Three,
|
||||
aztf::ConfigureTranslationManipulatorAppearance3d);
|
||||
}
|
||||
|
||||
void EditorVertexSelectionFixture::PopulateVertices()
|
||||
{
|
||||
for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex)
|
||||
{
|
||||
InsertVertexAfter(
|
||||
AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0, AZ::Vector3::CreateZero());
|
||||
AzToolsFramework::InsertVertexAfter(AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0, AZ::Vector3::CreateZero());
|
||||
}
|
||||
}
|
||||
void EditorVertexSelectionFixture::ClearVertices()
|
||||
{
|
||||
for (size_t vertIndex = 0; vertIndex < EditorVertexSelectionFixture::VertexCount; ++vertIndex)
|
||||
{
|
||||
SafeRemoveVertex<AZ::Vector3>(
|
||||
AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0);
|
||||
AzToolsFramework::SafeRemoveVertex<AZ::Vector3>(AZ::EntityComponentIdPair(m_entityId, TestComponentId), 0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -188,7 +234,7 @@ namespace UnitTest
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
// When
|
||||
// just provide a placeholder mouse interaction event in this case
|
||||
m_vertexSelection.SnapVerticesToTerrain(ViewportInteraction::MouseInteractionEvent{});
|
||||
m_vertexSelection.SnapVerticesToSurface(AzToolsFramework::ViewportInteraction::MouseInteractionEvent{});
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -197,8 +243,7 @@ namespace UnitTest
|
||||
///////////////////////////////////////////////////////////////////////////////////////////////////////////////
|
||||
}
|
||||
|
||||
using EditorVertexSelectionManipulatorFixture =
|
||||
IndirectCallManipulatorViewportInteractionFixtureMixin<EditorVertexSelectionFixture>;
|
||||
using EditorVertexSelectionManipulatorFixture = IndirectCallManipulatorViewportInteractionFixtureMixin<EditorVertexSelectionFixture>;
|
||||
|
||||
TEST_F(EditorVertexSelectionManipulatorFixture, CannotDeleteAllVertices)
|
||||
{
|
||||
@@ -206,25 +251,23 @@ namespace UnitTest
|
||||
|
||||
const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, TestComponentId);
|
||||
|
||||
const float horizontalPositions[] = {-1.5f, -0.5f, 0.5f, 1.5f};
|
||||
for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex)
|
||||
const float horizontalPositions[] = { -1.5f, -0.5f, 0.5f, 1.5f };
|
||||
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
|
||||
{
|
||||
InsertVertexAfter(
|
||||
entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f));
|
||||
AzToolsFramework::InsertVertexAfter(entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 5.0f, 0.0f));
|
||||
}
|
||||
|
||||
// rebuild the vertex selection after adding the new verts
|
||||
// rebuild the vertex selection after adding the new vertices
|
||||
RecreateVertexSelection();
|
||||
|
||||
// build a vector of the vertex positions in screen space
|
||||
AZStd::vector<AzFramework::ScreenPoint> vertexScreenPositions;
|
||||
for (size_t vertIndex = 0; vertIndex < std::size(horizontalPositions); ++vertIndex)
|
||||
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
|
||||
{
|
||||
AZ::Vector3 localVertex;
|
||||
AZ::Vector3 localVertex = AZ::Vector3::CreateZero();
|
||||
bool found = false;
|
||||
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
|
||||
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex,
|
||||
vertIndex, localVertex);
|
||||
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex, vertIndex, localVertex);
|
||||
|
||||
if (found)
|
||||
{
|
||||
@@ -267,9 +310,9 @@ namespace UnitTest
|
||||
const auto entityComponentIdPair = AZ::EntityComponentIdPair(m_entityId, TestComponentId);
|
||||
|
||||
// add a single vertex (in front of the camera)
|
||||
InsertVertexAfter(entityComponentIdPair, 0, AZ::Vector3::CreateAxisY(5.0f));
|
||||
AzToolsFramework::InsertVertexAfter(entityComponentIdPair, 0, AZ::Vector3::CreateAxisY(5.0f));
|
||||
|
||||
// rebuild the vertex selection after adding the new verts
|
||||
// rebuild the vertex selection after adding the new vertices
|
||||
RecreateVertexSelection();
|
||||
|
||||
AzFramework::ScreenPoint vertexScreenPosition;
|
||||
@@ -300,4 +343,132 @@ namespace UnitTest
|
||||
// deleting the last vertex through a manipulator is disallowed - size should remain the same
|
||||
EXPECT_THAT(vertexCountAfter, Eq(1));
|
||||
}
|
||||
|
||||
static AZ::EntityId CreateEntityForVertexIntersectionPlacement(EditorVertexSelectionManipulatorFixture& fixture)
|
||||
{
|
||||
auto* app = fixture.GetApplication();
|
||||
app->RegisterComponentDescriptor(BoundsTestComponent::CreateDescriptor());
|
||||
app->RegisterComponentDescriptor(RenderGeometryIntersectionTestComponent::CreateDescriptor());
|
||||
|
||||
AZ::Entity* entityGround = nullptr;
|
||||
AZ::EntityId entityIdGround = CreateDefaultEditorEntity("EntityGround", &entityGround);
|
||||
|
||||
entityGround->Deactivate();
|
||||
auto ground = entityGround->CreateComponent<RenderGeometryIntersectionTestComponent>();
|
||||
entityGround->Activate();
|
||||
|
||||
ground->m_localBounds = AZ::Aabb::CreateFromMinMax(AZ::Vector3(-10.0f, -10.0f, -0.5f), AZ::Vector3(10.0f, 10.0f, 0.5f));
|
||||
|
||||
return entityIdGround;
|
||||
}
|
||||
|
||||
static AZStd::vector<AzFramework::ScreenPoint> SetupVertices(
|
||||
const AZ::EntityId entityId, EditorVertexSelectionManipulatorFixture& fixture)
|
||||
{
|
||||
const auto entityComponentIdPair = AZ::EntityComponentIdPair(entityId, TestComponentId);
|
||||
const float horizontalPositions[] = { -3.0f, -1.0f, 1.0f, 3.0f };
|
||||
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
|
||||
{
|
||||
AzToolsFramework::InsertVertexAfter(entityComponentIdPair, vertIndex, AZ::Vector3(horizontalPositions[vertIndex], 0.0f, 0.0f));
|
||||
}
|
||||
|
||||
// rebuild the vertex selection after adding the new vertices
|
||||
fixture.RecreateVertexSelection();
|
||||
|
||||
// build a vector of the vertex positions in screen space
|
||||
AZStd::vector<AzFramework::ScreenPoint> vertexScreenPositions;
|
||||
for (size_t vertIndex = 0; vertIndex < AZStd::size(horizontalPositions); ++vertIndex)
|
||||
{
|
||||
AZ::Vector3 localVertex;
|
||||
bool found = false;
|
||||
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
|
||||
found, entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex, vertIndex, localVertex);
|
||||
|
||||
if (found)
|
||||
{
|
||||
const AZ::Vector3 worldVertex = AzToolsFramework::GetWorldTransform(entityId).TransformPoint(localVertex);
|
||||
vertexScreenPositions.push_back(AzFramework::WorldToScreen(worldVertex, fixture.m_cameraState));
|
||||
}
|
||||
}
|
||||
|
||||
return vertexScreenPositions;
|
||||
}
|
||||
|
||||
AzToolsFramework::ViewportInteraction::MouseInteractionEvent BuildMiddleMouseDownEvent(
|
||||
const AzFramework::ScreenPoint& screenPosition, const AzFramework::ViewportId viewportId)
|
||||
{
|
||||
AzToolsFramework::ViewportInteraction::MousePick mousePick;
|
||||
mousePick.m_screenCoordinates = screenPosition;
|
||||
|
||||
AzToolsFramework::ViewportInteraction::MouseInteraction mouseInteraction;
|
||||
mouseInteraction.m_interactionId.m_cameraId = AZ::EntityId();
|
||||
mouseInteraction.m_interactionId.m_viewportId = viewportId;
|
||||
mouseInteraction.m_mouseButtons =
|
||||
AzToolsFramework::ViewportInteraction::MouseButtonsFromButton(AzToolsFramework::ViewportInteraction::MouseButton::Middle);
|
||||
mouseInteraction.m_mousePick = mousePick;
|
||||
mouseInteraction.m_keyboardModifiers = AzToolsFramework::ViewportInteraction::KeyboardModifiers(
|
||||
static_cast<AZ::u32>(AzToolsFramework::ViewportInteraction::KeyboardModifier::Shift) |
|
||||
static_cast<AZ::u32>(AzToolsFramework::ViewportInteraction::KeyboardModifier::Ctrl));
|
||||
|
||||
return AzToolsFramework::ViewportInteraction::MouseInteractionEvent(
|
||||
mouseInteraction, AzToolsFramework::ViewportInteraction::MouseEvent::Down, /*captured=*/false);
|
||||
}
|
||||
|
||||
TEST_F(EditorVertexSelectionManipulatorFixture, VertexPlacedWhereIntersectionPointIsFoundWithCustomReferenceSpace)
|
||||
{
|
||||
const AZ::EntityId entityIdGround = CreateEntityForVertexIntersectionPlacement(*this);
|
||||
|
||||
// position ground
|
||||
AzToolsFramework::SetWorldTransform(
|
||||
entityIdGround,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-20.0f)) * AZ::Matrix3x3::CreateRotationY(AZ::DegToRad(-40.0f)) *
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(60.0f)),
|
||||
AZ::Vector3(14.0f, -6.0f, 5.0f)));
|
||||
|
||||
// camera (go to position format) - 12.00, 18.00, 16.00, -38.00, -175.00
|
||||
m_cameraState.m_viewportSize = AZ::Vector2(1280.0f, 720.0f);
|
||||
AzFramework::SetCameraTransform(
|
||||
m_cameraState,
|
||||
AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(-175.0f)) * AZ::Matrix3x3::CreateRotationX(AZ::DegToRad(-38.0f)),
|
||||
AZ::Vector3(12.0f, 18.0f, 16.0f)));
|
||||
|
||||
// create orientated and scaled transform for vertex selection entity transform
|
||||
auto vertexSelectionTransform = AZ::Transform::CreateFromMatrix3x3AndTranslation(
|
||||
AZ::Matrix3x3::CreateRotationZ(AZ::DegToRad(45.0f)), AZ::Vector3(14.0f, 7.0f, 5.0f));
|
||||
vertexSelectionTransform.MultiplyByUniformScale(3.0f);
|
||||
|
||||
// set the initial starting position of the vertex selection
|
||||
AzToolsFramework::SetWorldTransform(m_entityId, vertexSelectionTransform);
|
||||
|
||||
auto vertexScreenPositions = SetupVertices(m_entityId, *this);
|
||||
|
||||
// press and drag the mouse (starting where the surface manipulator is)
|
||||
// select each vertex (by holding ctrl)
|
||||
m_actionDispatcher->CameraState(m_cameraState)->MousePosition(vertexScreenPositions[0])->MouseLButtonDown()->MouseLButtonUp();
|
||||
|
||||
const auto finalPositionWorld = AZ::Vector3(14.3573294f, -8.94695091f, 7.08627319f);
|
||||
// calculate the position in screen space of the final position of the entity
|
||||
const auto finalPositionScreen = AzFramework::WorldToScreen(finalPositionWorld, m_cameraState);
|
||||
|
||||
auto middleMouseDownEvent =
|
||||
BuildMiddleMouseDownEvent(finalPositionScreen, m_viewportManipulatorInteraction->GetViewportInteraction().GetViewportId());
|
||||
|
||||
// explicitly handle mouse event in vertex selection instance
|
||||
m_vertexSelection.HandleMouse(middleMouseDownEvent);
|
||||
|
||||
// read back the position of the vertex now
|
||||
AZ::Vector3 localVertex = AZ::Vector3::CreateZero();
|
||||
bool found = false;
|
||||
AZ::FixedVerticesRequestBus<AZ::Vector3>::EventResult(
|
||||
found, m_entityId, &AZ::FixedVerticesRequestBus<AZ::Vector3>::Handler::GetVertex, 0, localVertex);
|
||||
|
||||
// transform to world space
|
||||
const AZ::Vector3 worldVertex = vertexSelectionTransform.TransformPoint(localVertex);
|
||||
|
||||
EXPECT_THAT(found, ::testing::IsTrue());
|
||||
// ensure final world positions match
|
||||
EXPECT_THAT(worldVertex, IsCloseTolerance(finalPositionWorld, 0.01f));
|
||||
}
|
||||
} // namespace UnitTest
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
/*
|
||||
* 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/Entity/ReadOnly/ReadOnlyEntityFixture.h>
|
||||
|
||||
#include <AzToolsFramework/Entity/EditorEntityHelpers.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
void ReadOnlyEntityFixture::SetUpEditorFixtureImpl()
|
||||
{
|
||||
// Without this, the user settings component would attempt to save on finalize/shutdown. Since the file is
|
||||
// shared across the whole engine, if multiple tests are run in parallel, the saving could cause a crash
|
||||
// in the unit tests.
|
||||
AZ::UserSettingsComponentRequestBus::Broadcast(&AZ::UserSettingsComponentRequests::DisableSaveOnFinalize);
|
||||
|
||||
m_readOnlyEntityPublicInterface = AZ::Interface<ReadOnlyEntityPublicInterface>::Get();
|
||||
ASSERT_TRUE(m_readOnlyEntityPublicInterface != nullptr);
|
||||
|
||||
GenerateTestHierarchy();
|
||||
}
|
||||
|
||||
void ReadOnlyEntityFixture::TearDownEditorFixtureImpl()
|
||||
{
|
||||
}
|
||||
|
||||
void ReadOnlyEntityFixture::GenerateTestHierarchy()
|
||||
{
|
||||
/*
|
||||
* Root
|
||||
* |_ Child
|
||||
* |_ GrandChild1
|
||||
* |_ GrandChild2
|
||||
*/
|
||||
|
||||
m_entityMap[RootEntityName] = CreateEditorEntity(RootEntityName, AZ::EntityId());
|
||||
m_entityMap[ChildEntityName] = CreateEditorEntity(ChildEntityName, m_entityMap[RootEntityName]);
|
||||
m_entityMap[GrandChild1EntityName] = CreateEditorEntity(GrandChild1EntityName, m_entityMap[ChildEntityName]);
|
||||
m_entityMap[GrandChild2EntityName] = CreateEditorEntity(GrandChild2EntityName, m_entityMap[ChildEntityName]);
|
||||
}
|
||||
|
||||
AZ::EntityId ReadOnlyEntityFixture::CreateEditorEntity(const char* name, AZ::EntityId parentId)
|
||||
{
|
||||
AZ::Entity* entity = nullptr;
|
||||
UnitTest::CreateDefaultEditorEntity(name, &entity);
|
||||
|
||||
// Parent
|
||||
AZ::TransformBus::Event(entity->GetId(), &AZ::TransformInterface::SetParent, parentId);
|
||||
|
||||
return entity->GetId();
|
||||
}
|
||||
|
||||
ReadOnlyHandlerAlwaysTrue::ReadOnlyHandlerAlwaysTrue()
|
||||
{
|
||||
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
|
||||
}
|
||||
|
||||
ReadOnlyHandlerAlwaysTrue::~ReadOnlyHandlerAlwaysTrue()
|
||||
{
|
||||
ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
|
||||
|
||||
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
|
||||
{
|
||||
readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyHandlerAlwaysTrue::IsReadOnly([[maybe_unused]] const AZ::EntityId& entityId, bool& isReadOnly)
|
||||
{
|
||||
isReadOnly = true;
|
||||
}
|
||||
|
||||
ReadOnlyHandlerAlwaysFalse::ReadOnlyHandlerAlwaysFalse()
|
||||
{
|
||||
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
|
||||
}
|
||||
|
||||
ReadOnlyHandlerAlwaysFalse::~ReadOnlyHandlerAlwaysFalse()
|
||||
{
|
||||
ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
|
||||
|
||||
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
|
||||
{
|
||||
readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
|
||||
}
|
||||
}
|
||||
|
||||
ReadOnlyHandlerEntityId::ReadOnlyHandlerEntityId(AZ::EntityId entityId)
|
||||
: m_entityId(entityId)
|
||||
{
|
||||
auto editorEntityContextId = AzFramework::EntityContextId::CreateNull();
|
||||
EditorEntityContextRequestBus::BroadcastResult(editorEntityContextId, &EditorEntityContextRequests::GetEditorEntityContextId);
|
||||
|
||||
ReadOnlyEntityQueryRequestBus::Handler::BusConnect(editorEntityContextId);
|
||||
}
|
||||
|
||||
ReadOnlyHandlerEntityId::~ReadOnlyHandlerEntityId()
|
||||
{
|
||||
ReadOnlyEntityQueryRequestBus::Handler::BusDisconnect();
|
||||
|
||||
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
|
||||
{
|
||||
readOnlyEntityQueryInterface->RefreshReadOnlyStateForAllEntities();
|
||||
}
|
||||
}
|
||||
|
||||
void ReadOnlyHandlerEntityId::IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly)
|
||||
{
|
||||
if (entityId == m_entityId)
|
||||
{
|
||||
isReadOnly = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) Contributors to the Open 3D Engine Project.
|
||||
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
|
||||
*
|
||||
* SPDX-License-Identifier: Apache-2.0 OR MIT
|
||||
*
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <AzCore/Component/TransformBus.h>
|
||||
#include <AzCore/UnitTest/TestTypes.h>
|
||||
|
||||
#include <AzTest/AzTest.h>
|
||||
|
||||
#include <AzToolsFramework/UnitTest/AzToolsFrameworkTestHelpers.h>
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityBus.h>
|
||||
#include <AzToolsFramework/Entity/ReadOnly/ReadOnlyEntityInterface.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
class ReadOnlyEntityFixture
|
||||
: public UnitTest::ToolsApplicationFixture
|
||||
{
|
||||
protected:
|
||||
void SetUpEditorFixtureImpl() override;
|
||||
void TearDownEditorFixtureImpl() override;
|
||||
|
||||
void GenerateTestHierarchy();
|
||||
AZ::EntityId CreateEditorEntity(const char* name, AZ::EntityId parentId);
|
||||
|
||||
AZStd::unordered_map<AZStd::string, AZ::EntityId> m_entityMap;
|
||||
|
||||
ReadOnlyEntityPublicInterface* m_readOnlyEntityPublicInterface = nullptr;
|
||||
|
||||
public:
|
||||
inline static const char* RootEntityName = "Root";
|
||||
inline static const char* ChildEntityName = "Child";
|
||||
inline static const char* GrandChild1EntityName = "GrandChild1";
|
||||
inline static const char* GrandChild2EntityName = "GrandChild2";
|
||||
};
|
||||
|
||||
class ReadOnlyHandlerAlwaysTrue
|
||||
: public ReadOnlyEntityQueryRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
ReadOnlyHandlerAlwaysTrue();
|
||||
~ReadOnlyHandlerAlwaysTrue();
|
||||
|
||||
// ReadOnlyEntityQueryNotificationBus overrides ...
|
||||
void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override;
|
||||
};
|
||||
|
||||
class ReadOnlyHandlerAlwaysFalse
|
||||
: public ReadOnlyEntityQueryRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
ReadOnlyHandlerAlwaysFalse();
|
||||
~ReadOnlyHandlerAlwaysFalse();
|
||||
|
||||
// ReadOnlyEntityQueryNotificationBus overrides ...
|
||||
void IsReadOnly([[maybe_unused]] const AZ::EntityId& entityId, [[maybe_unused]] bool& isReadOnly) override {}
|
||||
};
|
||||
|
||||
class ReadOnlyHandlerEntityId
|
||||
: public ReadOnlyEntityQueryRequestBus::Handler
|
||||
{
|
||||
public:
|
||||
ReadOnlyHandlerEntityId(AZ::EntityId entityId);
|
||||
~ReadOnlyHandlerEntityId();
|
||||
|
||||
// ReadOnlyEntityQueryNotificationBus overrides ...
|
||||
void IsReadOnly(const AZ::EntityId& entityId, bool& isReadOnly) override;
|
||||
|
||||
private:
|
||||
AZ::EntityId m_entityId;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
/*
|
||||
* 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/Entity/ReadOnly/ReadOnlyEntityFixture.h>
|
||||
|
||||
namespace AzToolsFramework
|
||||
{
|
||||
TEST_F(ReadOnlyEntityFixture, NoHandlerEntityIsNotReadOnlyByDefault)
|
||||
{
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
}
|
||||
|
||||
TEST_F(ReadOnlyEntityFixture, SingleHandlerEntityIsReadOnly)
|
||||
{
|
||||
// Create a handler that sets all entities to read-only.
|
||||
ReadOnlyHandlerAlwaysTrue alwaysTrueHandler;
|
||||
|
||||
// All entities should be marked read-only now.
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
|
||||
}
|
||||
|
||||
TEST_F(ReadOnlyEntityFixture, SingleHandlerEntityIsNotReadOnly)
|
||||
{
|
||||
// Create a handler that sets all entities to read-only.
|
||||
ReadOnlyHandlerAlwaysFalse alwaysFalseHandler;
|
||||
|
||||
// All entities should not be marked read-only now.
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
|
||||
}
|
||||
|
||||
TEST_F(ReadOnlyEntityFixture, SingleHandlerWithLogic)
|
||||
{
|
||||
// Create a handler that sets just the child entity to read-only.
|
||||
ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
|
||||
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
|
||||
}
|
||||
|
||||
TEST_F(ReadOnlyEntityFixture, TwoHandlersCanOverlap)
|
||||
{
|
||||
// Create two handlers that set different entities to read-only.
|
||||
ReadOnlyHandlerEntityId entityIdHandler1(m_entityMap[ChildEntityName]);
|
||||
ReadOnlyHandlerEntityId entityIdHandler2(m_entityMap[GrandChild2EntityName]);
|
||||
|
||||
// Both entities should be marked as read-only, while others aren't.
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[RootEntityName]));
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild1EntityName]));
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[GrandChild2EntityName]));
|
||||
}
|
||||
|
||||
TEST_F(ReadOnlyEntityFixture, EnsureCacheIsRefreshedCorrectly)
|
||||
{
|
||||
// Verify the child entity is not marked as read-only
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
|
||||
// Create a handler that sets the child entity to read-only.
|
||||
ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
|
||||
|
||||
// Communicate to the ReadOnlyEntitySystemComponent that the read-only state for the child entity may have changed.
|
||||
// Note that this operation would usually be executed by the handler, hence the Query interface call.
|
||||
if (auto readOnlyEntityQueryInterface = AZ::Interface<ReadOnlyEntityQueryInterface>::Get())
|
||||
{
|
||||
readOnlyEntityQueryInterface->RefreshReadOnlyState({ m_entityMap[ChildEntityName] });
|
||||
}
|
||||
|
||||
// Verify the child entity is marked as read-only
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
}
|
||||
|
||||
TEST_F(ReadOnlyEntityFixture, EnsureCacheIsClearedCorrectly)
|
||||
{
|
||||
{
|
||||
// Create a handler that sets the child entity to read-only.
|
||||
ReadOnlyHandlerEntityId entityIdHandler(m_entityMap[ChildEntityName]);
|
||||
|
||||
// Verify the child entity is marked as read-only
|
||||
EXPECT_TRUE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
}
|
||||
// When the handler goes out of scope, it calls RefreshReadOnlyStateForAllEntities and refreshes the cache.
|
||||
|
||||
// Verify the child entity is no longer marked as read-only
|
||||
EXPECT_FALSE(m_readOnlyEntityPublicInterface->IsReadOnly(m_entityMap[ChildEntityName]));
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,9 @@
|
||||
*
|
||||
*/
|
||||
|
||||
#include "IntegerPrimtitiveTestConfig.h"
|
||||
#include <AzToolsFramework/UI/PropertyEditor/PropertyIntCtrlCommon.h>
|
||||
#include <AzToolsFramework/UI/PropertyEditor/QtWidgetLimits.h>
|
||||
#include <Tests/IntegerPrimtitiveTestConfig.h>
|
||||
|
||||
namespace UnitTest
|
||||
{
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user