Merge remote-tracking branch 'upstream/development' into Atom/rbarrand/MaterialVersionUpdate

monroegm-disable-blank-issue-2
santorac 4 years ago
commit bf2997f960

@ -54,6 +54,30 @@ def get_mesh_node_names(sceneGraph):
return meshDataList, paths return meshDataList, paths
def add_material_component(entity_id):
# Create an override AZ::Render::EditorMaterialComponent
editor_material_component = azlmbr.entity.EntityUtilityBus(
azlmbr.bus.Broadcast,
"GetOrAddComponentByTypeName",
entity_id,
"EditorMaterialComponent")
# 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)
if not result:
raise RuntimeError("UpdateComponentForEntity for editor_material_component failed")
def update_manifest(scene): def update_manifest(scene):
import json import json
import uuid, os import uuid, os
@ -75,6 +99,7 @@ def update_manifest(scene):
created_entities = [] created_entities = []
previous_entity_id = azlmbr.entity.InvalidEntityId previous_entity_id = azlmbr.entity.InvalidEntityId
first_mesh = True
# Loop every mesh node in the scene # Loop every mesh node in the scene
for activeMeshIndex in range(len(mesh_name_list)): for activeMeshIndex in range(len(mesh_name_list)):
@ -112,6 +137,11 @@ def update_manifest(scene):
if not result: if not result:
raise RuntimeError("UpdateComponentForEntity failed for Mesh component") raise RuntimeError("UpdateComponentForEntity failed for Mesh component")
# an example of adding a material component to override the default material
if previous_entity_id is not None and first_mesh:
first_mesh = False
add_material_component(entity_id)
# Get the transform component # 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")

@ -54,4 +54,5 @@ set(ENABLED_GEMS
AWSMetrics AWSMetrics
PrefabBuilder PrefabBuilder
AudioSystem AudioSystem
Profiler
) )

@ -204,7 +204,7 @@ namespace PythonCoverage
return coveringModuleOutputNames; return coveringModuleOutputNames;
} }
void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest(AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args) void PythonCoverageEditorSystemComponent::OnStartExecuteByFilenameAsTest([[maybe_unused]]AZStd::string_view filename, AZStd::string_view testCase, [[maybe_unused]] const AZStd::vector<AZStd::string_view>& args)
{ {
if (m_coverageState == CoverageState::Disabled) if (m_coverageState == CoverageState::Disabled)
{ {
@ -226,8 +226,7 @@ namespace PythonCoverage
return; return;
} }
const AZStd::string scriptName = AZ::IO::Path(filename).Stem().Native(); const auto coverageFile = m_coverageDir / AZStd::string::format("%.*s.pycoverage", AZ_STRING_ARG(testCase));
const auto coverageFile = m_coverageDir / AZStd::string::format("%s.pycoverage", scriptName.c_str());
// If this is a different python script we clear the existing entity components and start afresh // If this is a different python script we clear the existing entity components and start afresh
if (m_coverageFile != coverageFile) if (m_coverageFile != coverageFile)

@ -253,73 +253,3 @@ class TestAtomEditorComponentsMain(object):
) )
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@pytest.mark.system
class TestMaterialEditorBasicTests(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project):
def delete_files():
file_system.delete(
[
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
],
True,
True,
)
# Cleanup our newly created materials
delete_files()
def teardown():
# Cleanup our newly created materials
delete_files()
request.addfinalizer(teardown)
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
@pytest.mark.test_case_id("C34448118") # Saving Material.
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
def test_MaterialEditorBasicTests(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
expected_lines = [
"Material opened: True",
"Test asset doesn't exist initially: True",
"New asset created: True",
"New Material opened: True",
"Material closed: True",
"All documents closed: True",
"Close All Except Selected worked as expected: True",
"Actual Document saved with changes: True",
"Document saved as copy is saved with changes: True",
"Document saved as child is saved with changes: True",
"Save All worked as expected: True",
]
unexpected_lines = [
# "Trace::Assert",
# "Trace::Error",
"Traceback (most recent call last):"
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
generic_launcher,
"hydra_AtomMaterialEditor_BasicTests.py",
run_python="--runpython",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
log_file_name="MaterialEditor.log",
)

@ -70,3 +70,75 @@ class TestAtomEditorComponentsSandbox(object):
null_renderer=True, null_renderer=True,
cfg_args=cfg_args, cfg_args=cfg_args,
) )
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("launcher_platform", ['windows_generic'])
@pytest.mark.system
class TestMaterialEditorBasicTests(object):
@pytest.fixture(autouse=True)
def setup_teardown(self, request, workspace, project):
def delete_files():
file_system.delete(
[
os.path.join(workspace.paths.project(), "Materials", "test_material.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_1.material"),
os.path.join(workspace.paths.project(), "Materials", "test_material_2.material"),
],
True,
True,
)
# Cleanup our newly created materials
delete_files()
def teardown():
# Cleanup our newly created materials
delete_files()
request.addfinalizer(teardown)
@pytest.mark.parametrize("exe_file_name", ["MaterialEditor"])
@pytest.mark.test_case_id("C34448113") # Creating a New Asset.
@pytest.mark.test_case_id("C34448114") # Opening an Existing Asset.
@pytest.mark.test_case_id("C34448115") # Closing Selected Material.
@pytest.mark.test_case_id("C34448116") # Closing All Materials.
@pytest.mark.test_case_id("C34448117") # Closing all but Selected Material.
@pytest.mark.test_case_id("C34448118") # Saving Material.
@pytest.mark.test_case_id("C34448119") # Saving as a New Material.
@pytest.mark.test_case_id("C34448120") # Saving as a Child Material.
@pytest.mark.test_case_id("C34448121") # Saving all Open Materials.
def test_MaterialEditorBasicTests(
self, request, workspace, project, launcher_platform, generic_launcher, exe_file_name):
expected_lines = [
"Material opened: True",
"Test asset doesn't exist initially: True",
"New asset created: True",
"New Material opened: True",
"Material closed: True",
"All documents closed: True",
"Close All Except Selected worked as expected: True",
"Actual Document saved with changes: True",
"Document saved as copy is saved with changes: True",
"Document saved as child is saved with changes: True",
"Save All worked as expected: True",
]
unexpected_lines = [
# "Trace::Assert",
# "Trace::Error",
"Traceback (most recent call last):"
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
generic_launcher,
"hydra_AtomMaterialEditor_BasicTests.py",
run_python="--runpython",
timeout=120,
expected_lines=expected_lines,
unexpected_lines=unexpected_lines,
halt_on_unexpected=True,
null_renderer=True,
log_file_name="MaterialEditor.log",
)

@ -33,7 +33,7 @@ add_subdirectory(WhiteBox)
add_subdirectory(NvCloth) add_subdirectory(NvCloth)
## Prefab ## ## Prefab ##
add_subdirectory(prefab) add_subdirectory(Prefab)
## Editor Python Bindings ## ## Editor Python Bindings ##
add_subdirectory(EditorPythonBindings) add_subdirectory(EditorPythonBindings)

@ -122,17 +122,32 @@ class EditorEntity:
# Creation functions # Creation functions
@classmethod @classmethod
def find_editor_entity(cls, entity_name: str) -> EditorEntity: def find_editor_entity(cls, entity_name: str, must_be_unique : bool = False) -> EditorEntity:
""" """
Given Entity name, outputs entity object Given Entity name, outputs entity object
:param entity_name: Name of entity to find :param entity_name: Name of entity to find
:return: EditorEntity class object :return: EditorEntity class object
""" """
entity_id = general.find_editor_entity(entity_name) entities = cls.find_editor_entities([entity_name])
assert entity_id.IsValid(), f"Failure: Couldn't find entity with name: '{entity_name}'" assert len(entities) != 0, f"Failure: Couldn't find entity with name: '{entity_name}'"
entity = cls(entity_id) if must_be_unique:
assert len(entities) == 1, f"Failure: Multiple entities with name: '{entity_name}' when expected only one"
entity = cls(entities[0])
return entity return entity
@classmethod
def find_editor_entities(cls, entity_names: List[str]) -> EditorEntity:
"""
Given Entities names, returns a list of EditorEntity
:param entity_name: Name of entity to find
:return: List[EditorEntity] class object
"""
searchFilter = azlmbr.entity.SearchFilter()
searchFilter.names = entity_names
ids = azlmbr.entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
return [cls(id) for id in ids]
@classmethod @classmethod
def create_editor_entity(cls, name: str = None, parent_id=None) -> EditorEntity: def create_editor_entity(cls, name: str = None, parent_id=None) -> EditorEntity:
""" """
@ -157,8 +172,7 @@ class EditorEntity:
cls, cls,
entity_position: Union[List, Tuple, math.Vector3], entity_position: Union[List, Tuple, math.Vector3],
name: str = None, name: str = None,
parent_id: azlmbr.entity.EntityId = None, parent_id: azlmbr.entity.EntityId = None) -> EditorEntity:
) -> EditorEntity:
""" """
Used to create entity at position using 'CreateNewEntityAtPosition' Bus. Used to create entity at position using 'CreateNewEntityAtPosition' Bus.
:param entity_position: World Position(X, Y, Z) of entity in viewport. :param entity_position: World Position(X, Y, Z) of entity in viewport.
@ -227,6 +241,12 @@ class EditorEntity:
""" """
return editor.EditorEntityInfoRequestBus(bus.Event, "GetChildren", self.id) return editor.EditorEntityInfoRequestBus(bus.Event, "GetChildren", self.id)
def get_children(self) -> List[EditorEntity]:
"""
:return: List of EditorEntity children. Type: [EditorEntity]
"""
return [EditorEntity(child_id) for child_id in self.get_children_ids()]
def add_component(self, component_name: str) -> EditorComponent: def add_component(self, component_name: str) -> EditorComponent:
""" """
Used to add new component to Entity. Used to add new component to Entity.

@ -51,7 +51,7 @@ def launch_and_validate_results(request, test_directory, editor, editor_script,
logger.debug("Running automated test: {}".format(editor_script)) logger.debug("Running automated test: {}".format(editor_script))
editor.args.extend(["--skipWelcomeScreenDialog", "--regset=/Amazon/Settings/EnableSourceControl=false", editor.args.extend(["--skipWelcomeScreenDialog", "--regset=/Amazon/Settings/EnableSourceControl=false",
"--regset=/Amazon/Preferences/EnablePrefabSystem=false", run_python, test_case, "--regset=/Amazon/Preferences/EnablePrefabSystem=false", run_python, test_case,
f"--pythontestcase={request.node.originalname}", "--runpythonargs", " ".join(cfg_args)]) f"--pythontestcase={request.node.name}", "--runpythonargs", " ".join(cfg_args)])
if auto_test_mode: if auto_test_mode:
editor.args.extend(["--autotest_mode"]) editor.args.extend(["--autotest_mode"])
if null_renderer: if null_renderer:

@ -12,20 +12,39 @@ from os import path
from PySide2 import QtWidgets from PySide2 import QtWidgets
import azlmbr.legacy.general as general
from azlmbr.entity import EntityId from azlmbr.entity import EntityId
from azlmbr.math import Vector3 from azlmbr.math import Vector3
from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import Report
import azlmbr.entity as entity
import azlmbr.bus as bus import azlmbr.bus as bus
import azlmbr.prefab as prefab import azlmbr.prefab as prefab
import editor_python_test_tools.pyside_utils as pyside_utils import editor_python_test_tools.pyside_utils as pyside_utils
import prefab.Prefab_Test_Utils as prefab_test_utils
def get_prefab_file_path(prefab_path):
if not path.isabs(prefab_path):
prefab_path = path.join(general.get_file_alias("@projectroot@"), prefab_path)
# Append prefab if it doesn't contain .prefab on it
name, ext = path.splitext(prefab_path)
if ext != ".prefab":
prefab_path = name + ".prefab"
return prefab_path
def get_all_entity_ids():
return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
def wait_for_propagation():
general.idle_wait_frames(1)
# This is a helper class which contains some of the useful information about a prefab instance. # This is a helper class which contains some of the useful information about a prefab instance.
class PrefabInstance: class PrefabInstance:
def __init__(self, prefab_file_name: str=None, container_entity: EditorEntity=EntityId()): def __init__(self, prefab_file_name: str = None, container_entity: EditorEntity = None):
self.prefab_file_name: str = prefab_file_name self.prefab_file_name: str = prefab_file_name
self.container_entity: EditorEntity = container_entity self.container_entity: EditorEntity = container_entity
@ -42,7 +61,7 @@ class PrefabInstance:
See if this instance is valid to be used with other prefab operations. See if this instance is valid to be used with other prefab operations.
:return: Whether the target instance is valid or not. :return: Whether the target instance is valid or not.
""" """
def is_valid() -> bool: def is_valid(self) -> bool:
return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs return self.container_entity.id.IsValid() and self.prefab_file_name in Prefab.existing_prefabs
""" """
@ -60,7 +79,7 @@ class PrefabInstance:
new_parent_before_reparent_children_ids = set(new_parent.get_children_ids()) new_parent_before_reparent_children_ids = set(new_parent.get_children_ids())
pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id)) pyside_utils.run_soon(lambda: self.container_entity.set_parent_entity(parent_entity_id))
pyside_utils.run_soon(lambda: prefab_test_utils.wait_for_propagation()) pyside_utils.run_soon(lambda: wait_for_propagation())
try: try:
active_modal_widget = await pyside_utils.wait_for_modal_widget() active_modal_widget = await pyside_utils.wait_for_modal_widget()
@ -94,19 +113,18 @@ class Prefab:
existing_prefabs = {} existing_prefabs = {}
def __init__(self, file_name: str): def __init__(self, file_path: str):
self.file_name:str = file_name self.file_path: str = get_prefab_file_path(file_path)
self.file_path: str = prefab_test_utils.get_prefab_file_path(file_name)
self.instances: set[PrefabInstance] = set() self.instances: set[PrefabInstance] = set()
""" """
Check if a prefab is ready to be used to generate its instances. Check if a prefab is ready to be used to generate its instances.
:param file_name: A unique file name of the target prefab. :param file_path: A unique file path of the target prefab.
:return: Whether the target prefab is loaded or not. :return: Whether the target prefab is loaded or not.
""" """
@classmethod @classmethod
def is_prefab_loaded(cls, file_name: str) -> bool: def is_prefab_loaded(cls, file_path: str) -> bool:
return file_name in Prefab.existing_prefabs return file_path in Prefab.existing_prefabs
""" """
Check if a prefab exists in the directory for files of prefab tests. Check if a prefab exists in the directory for files of prefab tests.
@ -114,9 +132,8 @@ class Prefab:
:return: Whether the target prefab exists or not. :return: Whether the target prefab exists or not.
""" """
@classmethod @classmethod
def prefab_exists(cls, file_name: str) -> bool: def prefab_exists(cls, file_path: str) -> bool:
file_path = prefab_test_utils.get_prefab_file_path(file_name) return path.exists(get_prefab_file_path(file_path))
return path.exists(file_path)
""" """
Return a prefab which can be used immediately. Return a prefab which can be used immediately.
@ -125,10 +142,11 @@ class Prefab:
""" """
@classmethod @classmethod
def get_prefab(cls, file_name: str) -> Prefab: def get_prefab(cls, file_name: str) -> Prefab:
assert file_name, "Received an empty file_name"
if Prefab.is_prefab_loaded(file_name): if Prefab.is_prefab_loaded(file_name):
return Prefab.existing_prefabs[file_name] return Prefab.existing_prefabs[file_name]
else: else:
assert Prefab.prefab_exists(file_name), f"Attempted to get a prefab {file_name} that doesn't exist" assert Prefab.prefab_exists(file_name), f"Attempted to get a prefab \"{file_name}\" that doesn't exist"
new_prefab = Prefab(file_name) new_prefab = Prefab(file_name)
Prefab.existing_prefabs[file_name] = Prefab(file_name) Prefab.existing_prefabs[file_name] = Prefab(file_name)
return new_prefab return new_prefab
@ -141,7 +159,7 @@ class Prefab:
:return: Created Prefab object and the very first PrefabInstance object owned by the prefab. :return: Created Prefab object and the very first PrefabInstance object owned by the prefab.
""" """
@classmethod @classmethod
def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> (Prefab, PrefabInstance): def create_prefab(cls, entities: list[EditorEntity], file_name: str, prefab_instance_name: str=None) -> tuple(Prefab, PrefabInstance):
assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists" assert not Prefab.is_prefab_loaded(file_name), f"Can't create Prefab '{file_name}' since the prefab already exists"
new_prefab = Prefab(file_name) new_prefab = Prefab(file_name)
@ -155,7 +173,7 @@ class Prefab:
if prefab_instance_name: if prefab_instance_name:
container_entity.set_name(prefab_instance_name) container_entity.set_name(prefab_instance_name)
prefab_test_utils.wait_for_propagation() wait_for_propagation()
new_prefab_instance = PrefabInstance(file_name, EditorEntity(container_entity_id)) new_prefab_instance = PrefabInstance(file_name, EditorEntity(container_entity_id))
new_prefab.instances.add(new_prefab_instance) new_prefab.instances.add(new_prefab_instance)
@ -182,12 +200,13 @@ class Prefab:
delete_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DeleteEntitiesAndAllDescendantsInInstance', container_entity_ids) delete_prefab_result = prefab.PrefabPublicRequestBus(bus.Broadcast, 'DeleteEntitiesAndAllDescendantsInInstance', container_entity_ids)
assert delete_prefab_result.IsSuccess(), f"Prefab operation 'DeleteEntitiesAndAllDescendantsInInstance' failed. Error: {delete_prefab_result.GetError()}" assert delete_prefab_result.IsSuccess(), f"Prefab operation 'DeleteEntitiesAndAllDescendantsInInstance' failed. Error: {delete_prefab_result.GetError()}"
prefab_test_utils.wait_for_propagation() wait_for_propagation()
entity_ids_after_delete = set(prefab_test_utils.get_all_entities()) entity_ids_after_delete = set(get_all_entity_ids())
for entity_id_removed in entity_ids_to_remove: for entity_id_removed in entity_ids_to_remove:
if entity_id_removed in entity_ids_after_delete: if entity_id_removed in entity_ids_after_delete:
assert prefab_entities_deleted, "Not all entities and descendants in target prefabs are deleted." assert False, "Not all entities and descendants in target prefabs are deleted."
for instance in prefab_instances: for instance in prefab_instances:
instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name) instance_deleted_prefab = Prefab.get_prefab(instance.prefab_file_name)
@ -215,12 +234,10 @@ class Prefab:
if name: if name:
container_entity.set_name(name) container_entity.set_name(name)
prefab_test_utils.wait_for_propagation() wait_for_propagation()
new_prefab_instance = PrefabInstance(self.file_name, EditorEntity(container_entity_id)) new_prefab_instance = PrefabInstance(self.file_path, EditorEntity(container_entity_id))
assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation." assert not new_prefab_instance in self.instances, "This prefab instance is already existed before this instantiation."
self.instances.add(new_prefab_instance) self.instances.add(new_prefab_instance)
prefab_test_utils.check_entity_at_position(container_entity_id, prefab_position)
return new_prefab_instance return new_prefab_instance

@ -126,7 +126,7 @@ def ForceRegion_LinearDampingForceOnRigidBodies():
# Constants # Constants
CLOSE_ENOUGH = 0.001 CLOSE_ENOUGH = 0.001
TIME_OUT = 3.0 TIME_OUT = 10.0
INITIAL_VELOCITY = azmath.Vector3(0.0, 0.0, -10.0) INITIAL_VELOCITY = azmath.Vector3(0.0, 0.0, -10.0)
# 1) Open level / Enter game mode # 1) Open level / Enter game mode

@ -29,21 +29,21 @@ class TestAutomation(TestAutomationBase):
autotest_mode=autotest_mode) autotest_mode=autotest_mode)
def test_PrefabLevel_OpensLevelWithEntities(self, request, workspace, editor, launcher_platform): def test_PrefabLevel_OpensLevelWithEntities(self, request, workspace, editor, launcher_platform):
from . import PrefabLevel_OpensLevelWithEntities as test_module from .tests import PrefabLevel_OpensLevelWithEntities as test_module
self._run_prefab_test(request, workspace, editor, test_module) self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_CreatePrefab(self, request, workspace, editor, launcher_platform): def test_PrefabBasicWorkflow_CreatePrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_CreatePrefab as test_module from .tests import PrefabBasicWorkflow_CreatePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module) self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_InstantiatePrefab(self, request, workspace, editor, launcher_platform): def test_PrefabBasicWorkflow_InstantiatePrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_InstantiatePrefab as test_module from .tests import PrefabBasicWorkflow_InstantiatePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module) self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_CreateAndDeletePrefab(self, request, workspace, editor, launcher_platform): def test_PrefabBasicWorkflow_CreateAndDeletePrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_CreateAndDeletePrefab as test_module from .tests import PrefabBasicWorkflow_CreateAndDeletePrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module) self._run_prefab_test(request, workspace, editor, test_module)
def test_Prefab_BasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform): def test_PrefabBasicWorkflow_CreateAndReparentPrefab(self, request, workspace, editor, launcher_platform):
from . import Prefab_BasicWorkflow_CreateAndReparentPrefab as test_module from .tests import PrefabBasicWorkflow_CreateAndReparentPrefab as test_module
self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False) self._run_prefab_test(request, workspace, editor, test_module, autotest_mode=False)

@ -5,14 +5,14 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
def Prefab_BasicWorkflow_CreateAndDeletePrefab(): def PrefabBasicWorkflow_CreateAndDeletePrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab' CAR_PREFAB_FILE_NAME = 'car_prefab'
from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.editor_entity_utils import EditorEntity
from prefab.Prefab import Prefab from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level() prefab_test_utils.open_base_tests_level()
@ -21,13 +21,13 @@ def Prefab_BasicWorkflow_CreateAndDeletePrefab():
car_entity = EditorEntity.create_editor_entity() car_entity = EditorEntity.create_editor_entity()
car_prefab_entities = [car_entity] car_prefab_entities = [car_entity]
# Checks for prefab creation passed or not # Asserts if prefab creation doesn't succeeds
_, car = Prefab.create_prefab( _, car = Prefab.create_prefab(
car_prefab_entities, CAR_PREFAB_FILE_NAME) car_prefab_entities, CAR_PREFAB_FILE_NAME)
# Checks for prefab deletion passed or not # Asserts if prefab deletion fails
Prefab.remove_prefabs([car]) Prefab.remove_prefabs([car])
if __name__ == "__main__": if __name__ == "__main__":
from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_CreateAndDeletePrefab) Report.start_test(PrefabBasicWorkflow_CreateAndDeletePrefab)

@ -5,7 +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 SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
def Prefab_BasicWorkflow_CreateAndReparentPrefab(): def PrefabBasicWorkflow_CreateAndReparentPrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab' CAR_PREFAB_FILE_NAME = 'car_prefab'
WHEEL_PREFAB_FILE_NAME = 'wheel_prefab' WHEEL_PREFAB_FILE_NAME = 'wheel_prefab'
@ -16,9 +16,9 @@ def Prefab_BasicWorkflow_CreateAndReparentPrefab():
async def run_test(): async def run_test():
from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.editor_entity_utils import EditorEntity
from prefab.Prefab import Prefab from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level() prefab_test_utils.open_base_tests_level()
@ -46,4 +46,4 @@ def Prefab_BasicWorkflow_CreateAndReparentPrefab():
if __name__ == "__main__": if __name__ == "__main__":
from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_CreateAndReparentPrefab) Report.start_test(PrefabBasicWorkflow_CreateAndReparentPrefab)

@ -5,15 +5,15 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
def Prefab_BasicWorkflow_CreatePrefab(): def PrefabBasicWorkflow_CreatePrefab():
CAR_PREFAB_FILE_NAME = 'car_prefab' CAR_PREFAB_FILE_NAME = 'car_prefab'
from editor_python_test_tools.editor_entity_utils import EditorEntity from editor_python_test_tools.editor_entity_utils import EditorEntity
from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import Report
from prefab.Prefab import Prefab from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level() prefab_test_utils.open_base_tests_level()
@ -27,4 +27,4 @@ def Prefab_BasicWorkflow_CreatePrefab():
if __name__ == "__main__": if __name__ == "__main__":
from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_CreatePrefab) Report.start_test(PrefabBasicWorkflow_CreatePrefab)

@ -5,17 +5,17 @@ For complete copyright and license terms please see the LICENSE at the root of t
SPDX-License-Identifier: Apache-2.0 OR MIT SPDX-License-Identifier: Apache-2.0 OR MIT
""" """
def Prefab_BasicWorkflow_InstantiatePrefab(): def PrefabBasicWorkflow_InstantiatePrefab():
from azlmbr.math import Vector3 from azlmbr.math import Vector3
EXISTING_TEST_PREFAB_FILE_NAME = "Test" EXISTING_TEST_PREFAB_FILE_NAME = "Gem/PythonTests/Prefab/data/Test.prefab"
INSTANTIATED_TEST_PREFAB_POSITION = Vector3(10.00, 20.0, 30.0) INSTANTIATED_TEST_PREFAB_POSITION = Vector3(10.00, 20.0, 30.0)
EXPECTED_TEST_PREFAB_CHILDREN_COUNT = 1 EXPECTED_TEST_PREFAB_CHILDREN_COUNT = 1
from prefab.Prefab import Prefab from editor_python_test_tools.prefab_utils import Prefab
import prefab.Prefab_Test_Utils as prefab_test_utils import PrefabTestUtils as prefab_test_utils
prefab_test_utils.open_base_tests_level() prefab_test_utils.open_base_tests_level()
@ -31,4 +31,4 @@ def Prefab_BasicWorkflow_InstantiatePrefab():
if __name__ == "__main__": if __name__ == "__main__":
from editor_python_test_tools.utils import Report from editor_python_test_tools.utils import Report
Report.start_test(Prefab_BasicWorkflow_InstantiatePrefab) Report.start_test(PrefabBasicWorkflow_InstantiatePrefab)

@ -7,9 +7,9 @@ SPDX-License-Identifier: Apache-2.0 OR MIT
# fmt:off # fmt:off
class Tests(): class Tests():
find_empty_entity = ("Entity: 'EmptyEntity' found", "Entity: 'EmptyEntity' *not* found in level") find_empty_entity = ("Entity: 'EmptyEntity' found", "Entity: 'EmptyEntity' *not* found in level")
empty_entity_pos = ("'EmptyEntity' position is at the expected position", "'EmptyEntity' position is *not* at the expected position") empty_entity_pos = ("'EmptyEntity' position is at the expected position", "'EmptyEntity' position is *not* at the expected position")
find_pxentity = ("Entity: 'EntityWithPxCollider' found", "Entity: 'EntityWithPxCollider' *not* found in level") find_pxentity = ("Entity: 'EntityWithPxCollider' found", "Entity: 'EntityWithPxCollider' *not* found in level")
pxentity_component = ("Entity: 'EntityWithPxCollider' has a Physx Collider", "Entity: 'EntityWithPxCollider' does *not* have a Physx Collider") pxentity_component = ("Entity: 'EntityWithPxCollider' has a Physx Collider", "Entity: 'EntityWithPxCollider' does *not* have a Physx Collider")
# fmt:on # fmt:on

@ -18,20 +18,6 @@ import azlmbr.components as components
import azlmbr.entity as entity import azlmbr.entity as entity
import azlmbr.legacy.general as general import azlmbr.legacy.general as general
def get_prefab_file_name(prefab_name):
return prefab_name + ".prefab"
def get_prefab_file_path(prefab_name):
return os.path.join(os.path.dirname(os.path.abspath(__file__)), get_prefab_file_name(prefab_name))
def find_entities_by_name(entity_name):
searchFilter = entity.SearchFilter()
searchFilter.names = [entity_name]
return entity.SearchBus(bus.Broadcast, 'SearchEntities', searchFilter)
def get_all_entities():
return entity.SearchBus(bus.Broadcast, 'SearchEntities', entity.SearchFilter())
def check_entity_at_position(entity_id, expected_entity_position): def check_entity_at_position(entity_id, expected_entity_position):
entity_at_expected_position_result = ( entity_at_expected_position_result = (
"entity is at expected position", "entity is at expected position",
@ -74,9 +60,6 @@ def get_children_ids_by_name(entity_id, entity_name):
return result return result
def wait_for_propagation():
general.idle_wait_frames(1)
def open_base_tests_level(): def open_base_tests_level():
helper.init_idle() helper.init_idle()
helper.open_level("Prefab", "Base") helper.open_level("Prefab", "Base")

@ -0,0 +1,3 @@
version https://git-lfs.github.com/spec/v1
oid sha256:c3384e88cd0f47ab8ec81eb75101b02ff9675c76dc070c726a9f3f39f1b2b2df
size 4994448

@ -1,16 +1,34 @@
ProductName: single_mesh_multiple_materials.dbgsg ProductName: single_mesh_multiple_materials.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
single_mesh_multiple_materials single_mesh_multiple_materials
Node Name: Torus Node Name: RootNode
Node Path: RootNode.Torus Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Torus_1
Node Path: RootNode.Torus.Torus_1
Node Type: MeshData Node Type: MeshData
Positions: Count 2304. Hash: 12560656679477605282 Positions: Count 2304. Hash: 12560656679477605282
Normals: Count 2304. Hash: 14915939258818888021 Normals: Count 2304. Hash: 14915939258818888021
FaceList: Count 1152. Hash: 3035560221708475304 FaceList: Count 1152. Hash: 3035560221708475304
FaceMaterialIds: Count 1152. Hash: 2033667258170256242 FaceMaterialIds: Count 1152. Hash: 2033667258170256242
Node Name: Torus_optimized Node Name: Torus_2
Node Path: RootNode.Torus_optimized Node Path: RootNode.Torus.Torus_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Torus_1_optimized
Node Path: RootNode.Torus.Torus_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 2304. Hash: 12560656679477605282 Positions: Count 2304. Hash: 12560656679477605282
Normals: Count 2304. Hash: 14915939258818888021 Normals: Count 2304. Hash: 14915939258818888021
@ -18,7 +36,7 @@ Node Type: MeshData
FaceMaterialIds: Count 1152. Hash: 2033667258170256242 FaceMaterialIds: Count 1152. Hash: 2033667258170256242
Node Name: transform Node Name: transform
Node Path: RootNode.Torus.transform Node Path: RootNode.Torus.Torus_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -27,13 +45,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UV0 Node Name: UV0
Node Path: RootNode.Torus.UV0 Node Path: RootNode.Torus.Torus_1.UV0
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 2304. Hash: 6069930558565069665 UVs: Count 2304. Hash: 6069930558565069665
UVCustomName: UV0 UVCustomName: UV0
Node Name: OrangeMaterial Node Name: OrangeMaterial
Node Path: RootNode.Torus.OrangeMaterial Node Path: RootNode.Torus.Torus_1.OrangeMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: OrangeMaterial MaterialName: OrangeMaterial
UniqueId: 10937477720113828524 UniqueId: 10937477720113828524
@ -63,7 +81,7 @@ Node Type: MaterialData
BaseColorTexture: BaseColorTexture:
Node Name: SecondTextureMaterial Node Name: SecondTextureMaterial
Node Path: RootNode.Torus.SecondTextureMaterial Node Path: RootNode.Torus.Torus_1.SecondTextureMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SecondTextureMaterial MaterialName: SecondTextureMaterial
UniqueId: 16601413836225607467 UniqueId: 16601413836225607467
@ -93,7 +111,7 @@ Node Type: MaterialData
BaseColorTexture: OneMeshMultipleMaterials/FBXSecondTestTexture.png BaseColorTexture: OneMeshMultipleMaterials/FBXSecondTestTexture.png
Node Name: FirstTextureMaterial Node Name: FirstTextureMaterial
Node Path: RootNode.Torus.FirstTextureMaterial Node Path: RootNode.Torus.Torus_1.FirstTextureMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: FirstTextureMaterial MaterialName: FirstTextureMaterial
UniqueId: 2580020563915538382 UniqueId: 2580020563915538382
@ -122,40 +140,145 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: OneMeshMultipleMaterials/FBXTestTexture.png BaseColorTexture: OneMeshMultipleMaterials/FBXTestTexture.png
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Torus.TangentSet_MikkT_0 Node Path: RootNode.Torus.Torus_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 2304. Hash: 17641066831235827929 Tangents: Count 2304. Hash: 17641066831235827929
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Torus.BitangentSet_MikkT_0 Node Path: RootNode.Torus.Torus_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 2304. Hash: 6274616552656695154 Bitangents: Count 2304. Hash: 6274616552656695154
TangentSpace: 1 GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Torus.Torus_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UV0
Node Path: RootNode.Torus.Torus_2.UV0
Node Type: MeshVertexUVData
UVs: Count 2304. Hash: 6069930558565069665
UVCustomName: UV0
Node Name: OrangeMaterial
Node Path: RootNode.Torus.Torus_2.OrangeMaterial
Node Type: MaterialData
MaterialName: OrangeMaterial
UniqueId: 10937477720113828524
IsNoDraw: false
DiffuseColor: < 0.800000, 0.113346, 0.000000>
SpecularColor: < 0.800000, 0.113346, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: SecondTextureMaterial
Node Path: RootNode.Torus.Torus_2.SecondTextureMaterial
Node Type: MaterialData
MaterialName: SecondTextureMaterial
UniqueId: 16601413836225607467
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: OneMeshMultipleMaterials/FBXSecondTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: OneMeshMultipleMaterials/FBXSecondTestTexture.png
Node Name: FirstTextureMaterial
Node Path: RootNode.Torus.Torus_2.FirstTextureMaterial
Node Type: MaterialData
MaterialName: FirstTextureMaterial
UniqueId: 2580020563915538382
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: OneMeshMultipleMaterials/FBXTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: OneMeshMultipleMaterials/FBXTestTexture.png
Node Name: UV0 Node Name: UV0
Node Path: RootNode.Torus_optimized.UV0 Node Path: RootNode.Torus.Torus_1_optimized.UV0
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 2304. Hash: 6069930558565069665 UVs: Count 2304. Hash: 6069930558565069665
UVCustomName: UV0 UVCustomName: UV0
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Torus_optimized.TangentSet_MikkT_0 Node Path: RootNode.Torus.Torus_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 2304. Hash: 17641066831235827929 Tangents: Count 2304. Hash: 17641066831235827929
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Torus_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Torus.Torus_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 2304. Hash: 6274616552656695154 Bitangents: Count 2304. Hash: 6274616552656695154
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Torus_optimized.transform Node Path: RootNode.Torus.Torus_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -164,7 +287,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: OrangeMaterial Node Name: OrangeMaterial
Node Path: RootNode.Torus_optimized.OrangeMaterial Node Path: RootNode.Torus.Torus_1_optimized.OrangeMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: OrangeMaterial MaterialName: OrangeMaterial
UniqueId: 10937477720113828524 UniqueId: 10937477720113828524
@ -194,7 +317,7 @@ Node Type: MaterialData
BaseColorTexture: BaseColorTexture:
Node Name: SecondTextureMaterial Node Name: SecondTextureMaterial
Node Path: RootNode.Torus_optimized.SecondTextureMaterial Node Path: RootNode.Torus.Torus_1_optimized.SecondTextureMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SecondTextureMaterial MaterialName: SecondTextureMaterial
UniqueId: 16601413836225607467 UniqueId: 16601413836225607467
@ -224,7 +347,7 @@ Node Type: MaterialData
BaseColorTexture: OneMeshMultipleMaterials/FBXSecondTestTexture.png BaseColorTexture: OneMeshMultipleMaterials/FBXSecondTestTexture.png
Node Name: FirstTextureMaterial Node Name: FirstTextureMaterial
Node Path: RootNode.Torus_optimized.FirstTextureMaterial Node Path: RootNode.Torus.Torus_1_optimized.FirstTextureMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: FirstTextureMaterial MaterialName: FirstTextureMaterial
UniqueId: 2580020563915538382 UniqueId: 2580020563915538382
@ -252,4 +375,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: OneMeshMultipleMaterials/FBXTestTexture.png BaseColorTexture: OneMeshMultipleMaterials/FBXTestTexture.png

@ -1,16 +1,34 @@
ProductName: OneMeshOneMaterial.dbgsg ProductName: OneMeshOneMaterial.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
OneMeshOneMaterial OneMeshOneMaterial
Node Name: Cube Node Name: RootNode
Node Path: RootNode.Cube Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1
Node Path: RootNode.Cube.Cube_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cube_optimized Node Name: Cube_2
Node Path: RootNode.Cube_optimized Node Path: RootNode.Cube.Cube_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1_optimized
Node Path: RootNode.Cube.Cube_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
@ -18,7 +36,7 @@ Node Type: MeshData
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: transform Node Name: transform
Node Path: RootNode.Cube.transform Node Path: RootNode.Cube.Cube_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -27,13 +45,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube.UVMap Node Path: RootNode.Cube.Cube_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: CubeMaterial Node Name: CubeMaterial
Node Path: RootNode.Cube.CubeMaterial Node Path: RootNode.Cube.Cube_1.CubeMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: CubeMaterial MaterialName: CubeMaterial
UniqueId: 973942033197978066 UniqueId: 973942033197978066
@ -62,40 +80,85 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: OneMeshOneMaterial/FBXTestTexture.png BaseColorTexture: OneMeshOneMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Cube.Cube_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.Cube.Cube_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap
Node Name: CubeMaterial
Node Path: RootNode.Cube.Cube_2.CubeMaterial
Node Type: MaterialData
MaterialName: CubeMaterial
UniqueId: 973942033197978066
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 36.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: OneMeshOneMaterial/FBXTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: OneMeshOneMaterial/FBXTestTexture.png
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube_optimized.UVMap Node Path: RootNode.Cube.Cube_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_optimized.transform Node Path: RootNode.Cube.Cube_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -104,7 +167,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: CubeMaterial Node Name: CubeMaterial
Node Path: RootNode.Cube_optimized.CubeMaterial Node Path: RootNode.Cube.Cube_1_optimized.CubeMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: CubeMaterial MaterialName: CubeMaterial
UniqueId: 973942033197978066 UniqueId: 973942033197978066

@ -1,64 +1,109 @@
ProductName: lodtest.dbgsg ProductName: lodtest.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
lodtest lodtest
Node Name: lodtest Node Name: RootNode
Node Path: RootNode.lodtest Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: lodtest_1
Node Path: RootNode.lodtest.lodtest_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: lodtest_lod3 Node Name: lodtest_2
Node Path: RootNode.lodtest_lod3 Node Path: RootNode.lodtest.lodtest_2
Node Type: MeshData Node Type: BoneData
Positions: Count 1984. Hash: 6600975913707260286 WorldTransform:
Normals: Count 1984. Hash: 2708036977889843831 BasisX: < 100.000000, 0.000000, 0.000000>
FaceList: Count 960. Hash: 10390417165025722786 BasisY: < 0.000000, -0.000016, 100.000000>
FaceMaterialIds: Count 960. Hash: 12510609185544665964 BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: lodtest_lod2
Node Path: RootNode.lodtest_lod2
Node Type: MeshData
Positions: Count 240. Hash: 219362421205407416
Normals: Count 240. Hash: 11195242321181199939
FaceList: Count 80. Hash: 11130917988116538993
FaceMaterialIds: Count 80. Hash: 4190892684086530065
Node Name: lodtest_lod1
Node Path: RootNode.lodtest_lod1
Node Type: MeshData
Positions: Count 192. Hash: 1283526254311745349
Normals: Count 192. Hash: 1873340970602844856
FaceList: Count 124. Hash: 3728991722746136013
FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: lodtest_optimized Node Name: lodtest_1_optimized
Node Path: RootNode.lodtest_optimized Node Path: RootNode.lodtest.lodtest_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: lodtest_lod3_optimized Node Name: lodtest_lod3_1
Node Path: RootNode.lodtest_lod3_optimized Node Path: RootNode.lodtest_lod3.lodtest_lod3_1
Node Type: MeshData Node Type: MeshData
Positions: Count 1984. Hash: 6600975913707260286 Positions: Count 1984. Hash: 6600975913707260286
Normals: Count 1984. Hash: 2708036977889843831 Normals: Count 1984. Hash: 2708036977889843831
FaceList: Count 960. Hash: 10390417165025722786 FaceList: Count 960. Hash: 10390417165025722786
FaceMaterialIds: Count 960. Hash: 12510609185544665964 FaceMaterialIds: Count 960. Hash: 12510609185544665964
Node Name: lodtest_lod2_optimized Node Name: lodtest_lod3_2
Node Path: RootNode.lodtest_lod2_optimized Node Path: RootNode.lodtest_lod3.lodtest_lod3_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 2.298166, 0.000000>
Node Name: lodtest_lod3_1_optimized
Node Path: RootNode.lodtest_lod3.lodtest_lod3_1_optimized
Node Type: MeshData
Positions: Count 1984. Hash: 6600975913707260286
Normals: Count 1984. Hash: 2708036977889843831
FaceList: Count 960. Hash: 10390417165025722786
FaceMaterialIds: Count 960. Hash: 12510609185544665964
Node Name: lodtest_lod2_1
Node Path: RootNode.lodtest_lod2.lodtest_lod2_1
Node Type: MeshData Node Type: MeshData
Positions: Count 240. Hash: 219362421205407416 Positions: Count 240. Hash: 219362421205407416
Normals: Count 240. Hash: 11195242321181199939 Normals: Count 240. Hash: 11195242321181199939
FaceList: Count 80. Hash: 11130917988116538993 FaceList: Count 80. Hash: 11130917988116538993
FaceMaterialIds: Count 80. Hash: 4190892684086530065 FaceMaterialIds: Count 80. Hash: 4190892684086530065
Node Name: lodtest_lod1_optimized Node Name: lodtest_lod2_2
Node Path: RootNode.lodtest_lod1_optimized Node Path: RootNode.lodtest_lod2.lodtest_lod2_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, -0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, -2.211498, 0.000000>
Node Name: lodtest_lod2_1_optimized
Node Path: RootNode.lodtest_lod2.lodtest_lod2_1_optimized
Node Type: MeshData
Positions: Count 240. Hash: 219362421205407416
Normals: Count 240. Hash: 11195242321181199939
FaceList: Count 80. Hash: 11130917988116538993
FaceMaterialIds: Count 80. Hash: 4190892684086530065
Node Name: lodtest_lod1_1
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1
Node Type: MeshData
Positions: Count 192. Hash: 1283526254311745349
Normals: Count 192. Hash: 1873340970602844856
FaceList: Count 124. Hash: 3728991722746136013
FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: lodtest_lod1_2
Node Path: RootNode.lodtest_lod1.lodtest_lod1_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 2.410331, 0.000000, 0.000000>
Node Name: lodtest_lod1_1_optimized
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 192. Hash: 7921557352486854444 Positions: Count 192. Hash: 7921557352486854444
Normals: Count 192. Hash: 1873340970602844856 Normals: Count 192. Hash: 1873340970602844856
@ -66,7 +111,7 @@ Node Type: MeshData
FaceMaterialIds: Count 124. Hash: 2372486708814455910 FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest.transform Node Path: RootNode.lodtest.lodtest_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -75,13 +120,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest.UVMap Node Path: RootNode.lodtest.lodtest_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: Material Node Name: Material
Node Path: RootNode.lodtest.Material Node Path: RootNode.lodtest.lodtest_1.Material
Node Type: MaterialData Node Type: MaterialData
MaterialName: Material MaterialName: Material
UniqueId: 11127505492038345244 UniqueId: 11127505492038345244
@ -110,45 +155,45 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.lodtest.TangentSet_MikkT_0 Node Path: RootNode.lodtest.lodtest_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest.BitangentSet_MikkT_0 Node Path: RootNode.lodtest.lodtest_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_lod3.transform Node Path: RootNode.lodtest.lodtest_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 2.298166, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_lod3.UVMap Node Path: RootNode.lodtest.lodtest_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 1984. Hash: 14119273880200542497 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: Material
Node Path: RootNode.lodtest_lod3.DefaultMaterial Node Path: RootNode.lodtest.lodtest_2.Material
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: Material
UniqueId: 3809502407269006983 UniqueId: 11127505492038345244
IsNoDraw: false IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000> DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.000000, 0.000000, 0.000000> SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000> EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000 Opacity: 1.000000
Shininess: 0.000000 Shininess: 36.000000
UseColorMap: Not set UseColorMap: Not set
BaseColor: Not set BaseColor: Not set
UseMetallicMap: Not set UseMetallicMap: Not set
@ -168,36 +213,81 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: UVMap
Node Path: RootNode.lodtest_lod3.TangentSet_MikkT_0 Node Path: RootNode.lodtest.lodtest_1_optimized.UVMap
Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap
Node Name: TangentSet_0
Node Path: RootNode.lodtest.lodtest_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 1984. Hash: 5664494957869921957 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest_lod3.BitangentSet_MikkT_0 Node Path: RootNode.lodtest.lodtest_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 1984. Hash: 5048878728906162461 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_lod2.transform Node Path: RootNode.lodtest.lodtest_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, -0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, -2.211498, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Material
Node Path: RootNode.lodtest.lodtest_1_optimized.Material
Node Type: MaterialData
MaterialName: Material
UniqueId: 11127505492038345244
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 36.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: transform
Node Path: RootNode.lodtest_lod3.lodtest_lod3_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 2.298166, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_lod2.UVMap Node Path: RootNode.lodtest_lod3.lodtest_lod3_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 240. Hash: 13702273589593616598 UVs: Count 1984. Hash: 14119273880200542497
UVCustomName: UVMap UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod2.DefaultMaterial Node Path: RootNode.lodtest_lod3.lodtest_lod3_1.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -226,36 +316,36 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.lodtest_lod2.TangentSet_MikkT_0 Node Path: RootNode.lodtest_lod3.lodtest_lod3_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 240. Hash: 1390901212717410749 Tangents: Count 1984. Hash: 5664494957869921957
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest_lod2.BitangentSet_MikkT_0 Node Path: RootNode.lodtest_lod3.lodtest_lod3_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 240. Hash: 1379238632949267281 Bitangents: Count 1984. Hash: 5048878728906162461
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_lod1.transform Node Path: RootNode.lodtest_lod3.lodtest_lod3_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 2.410331, 0.000000, 0.000000> Transl: < 0.000000, 2.298166, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_lod1.UVMap Node Path: RootNode.lodtest_lod3.lodtest_lod3_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681 UVs: Count 1984. Hash: 14119273880200542497
UVCustomName: UVMap UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod1.DefaultMaterial Node Path: RootNode.lodtest_lod3.lodtest_lod3_2.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -284,58 +374,45 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0
Node Path: RootNode.lodtest_lod1.TangentSet_MikkT_0
Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141
TangentSpace: 1
SetIndex: 0
Node Name: BitangentSet_MikkT_0
Node Path: RootNode.lodtest_lod1.BitangentSet_MikkT_0
Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536
TangentSpace: 1
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_optimized.UVMap Node Path: RootNode.lodtest_lod3.lodtest_lod3_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 1984. Hash: 14119273880200542497
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.lodtest_optimized.TangentSet_MikkT_0 Node Path: RootNode.lodtest_lod3.lodtest_lod3_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 1984. Hash: 5664494957869921957
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest_optimized.BitangentSet_MikkT_0 Node Path: RootNode.lodtest_lod3.lodtest_lod3_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 1984. Hash: 5048878728906162461
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_optimized.transform Node Path: RootNode.lodtest_lod3.lodtest_lod3_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 2.298166, 0.000000>
Node Name: Material Node Name: DefaultMaterial
Node Path: RootNode.lodtest_optimized.Material Node Path: RootNode.lodtest_lod3.lodtest_lod3_1_optimized.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: Material MaterialName: DefaultMaterial
UniqueId: 11127505492038345244 UniqueId: 3809502407269006983
IsNoDraw: false IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000> DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000> SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000> EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000 Opacity: 1.000000
Shininess: 36.000000 Shininess: 0.000000
UseColorMap: Not set UseColorMap: Not set
BaseColor: Not set BaseColor: Not set
UseMetallicMap: Not set UseMetallicMap: Not set
@ -355,36 +432,81 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: transform
Node Path: RootNode.lodtest_lod2.lodtest_lod2_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, -0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, -2.211498, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_lod3_optimized.UVMap Node Path: RootNode.lodtest_lod2.lodtest_lod2_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 1984. Hash: 14119273880200542497 UVs: Count 240. Hash: 13702273589593616598
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod3_optimized.TangentSet_MikkT_0 Node Path: RootNode.lodtest_lod2.lodtest_lod2_1.DefaultMaterial
Node Type: MaterialData
MaterialName: DefaultMaterial
UniqueId: 3809502407269006983
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 0.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: TangentSet_0
Node Path: RootNode.lodtest_lod2.lodtest_lod2_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 1984. Hash: 5664494957869921957 Tangents: Count 240. Hash: 1390901212717410749
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest_lod3_optimized.BitangentSet_MikkT_0 Node Path: RootNode.lodtest_lod2.lodtest_lod2_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 1984. Hash: 5048878728906162461 Bitangents: Count 240. Hash: 1379238632949267281
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_lod3_optimized.transform Node Path: RootNode.lodtest_lod2.lodtest_lod2_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, -0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 2.298166, 0.000000> Transl: < 0.000000, -2.211498, 0.000000>
Node Name: UVMap
Node Path: RootNode.lodtest_lod2.lodtest_lod2_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 240. Hash: 13702273589593616598
UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod3_optimized.DefaultMaterial Node Path: RootNode.lodtest_lod2.lodtest_lod2_2.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -414,26 +536,26 @@ Node Type: MaterialData
BaseColorTexture: BaseColorTexture:
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_lod2_optimized.UVMap Node Path: RootNode.lodtest_lod2.lodtest_lod2_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 240. Hash: 13702273589593616598 UVs: Count 240. Hash: 13702273589593616598
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.lodtest_lod2_optimized.TangentSet_MikkT_0 Node Path: RootNode.lodtest_lod2.lodtest_lod2_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 240. Hash: 1390901212717410749 Tangents: Count 240. Hash: 1390901212717410749
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest_lod2_optimized.BitangentSet_MikkT_0 Node Path: RootNode.lodtest_lod2.lodtest_lod2_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 240. Hash: 1379238632949267281 Bitangents: Count 240. Hash: 1379238632949267281
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_lod2_optimized.transform Node Path: RootNode.lodtest_lod2.lodtest_lod2_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, -0.000000, 0.000000> BasisX: < 100.000000, -0.000000, 0.000000>
@ -442,7 +564,7 @@ Node Type: TransformData
Transl: < 0.000000, -2.211498, 0.000000> Transl: < 0.000000, -2.211498, 0.000000>
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod2_optimized.DefaultMaterial Node Path: RootNode.lodtest_lod2.lodtest_lod2_1_optimized.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -471,27 +593,130 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: transform
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 2.410331, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.lodtest_lod1_optimized.UVMap Node Path: RootNode.lodtest_lod1.lodtest_lod1_1.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1.DefaultMaterial
Node Type: MaterialData
MaterialName: DefaultMaterial
UniqueId: 3809502407269006983
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 0.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: TangentSet_0
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1.TangentSet_0
Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141
GenerationMethod: 1
SetIndex: 0
Node Name: BitangentSet_0
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1.BitangentSet_0
Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536
GenerationMethod: 1
Node Name: transform
Node Path: RootNode.lodtest_lod1.lodtest_lod1_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 2.410331, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.lodtest_lod1.lodtest_lod1_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod1.lodtest_lod1_2.DefaultMaterial
Node Type: MaterialData
MaterialName: DefaultMaterial
UniqueId: 3809502407269006983
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 0.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: UVMap
Node Path: RootNode.lodtest_lod1.lodtest_lod1_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 13790301632763350589 UVs: Count 192. Hash: 13790301632763350589
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.lodtest_lod1_optimized.TangentSet_MikkT_0 Node Path: RootNode.lodtest_lod1.lodtest_lod1_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 7293001660047850407 Tangents: Count 192. Hash: 7293001660047850407
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.lodtest_lod1_optimized.BitangentSet_MikkT_0 Node Path: RootNode.lodtest_lod1.lodtest_lod1_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 2874689498270494796 Bitangents: Count 192. Hash: 2874689498270494796
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.lodtest_lod1_optimized.transform Node Path: RootNode.lodtest_lod1.lodtest_lod1_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -500,7 +725,7 @@ Node Type: TransformData
Transl: < 2.410331, 0.000000, 0.000000> Transl: < 2.410331, 0.000000, 0.000000>
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.lodtest_lod1_optimized.DefaultMaterial Node Path: RootNode.lodtest_lod1.lodtest_lod1_1_optimized.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -528,4 +753,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:

@ -1,32 +1,59 @@
ProductName: physicstest.dbgsg ProductName: physicstest.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
physicstest physicstest
Node Name: Cone Node Name: RootNode
Node Path: RootNode.Cone Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cone_1
Node Path: RootNode.Cone.Cone_1
Node Type: MeshData Node Type: MeshData
Positions: Count 128. Hash: 7714223793259938211 Positions: Count 128. Hash: 7714223793259938211
Normals: Count 128. Hash: 2352668179264002707 Normals: Count 128. Hash: 2352668179264002707
FaceList: Count 62. Hash: 14563017593520122982 FaceList: Count 62. Hash: 14563017593520122982
FaceMaterialIds: Count 62. Hash: 12234218120113875284 FaceMaterialIds: Count 62. Hash: 12234218120113875284
Node Name: Cube_phys Node Name: Cone_2
Node Path: RootNode.Cube_phys Node Path: RootNode.Cone.Cone_2
Node Type: MeshData Node Type: BoneData
Positions: Count 24. Hash: 3478903613105670818 WorldTransform:
Normals: Count 24. Hash: 7251512570672401149 BasisX: < 100.000000, 0.000000, 0.000000>
FaceList: Count 12. Hash: 9888799799190757436 BasisY: < 0.000000, -0.000016, 100.000000>
FaceMaterialIds: Count 12. Hash: 7110546404675862471 BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cone_optimized Node Name: Cone_1_optimized
Node Path: RootNode.Cone_optimized Node Path: RootNode.Cone.Cone_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 128. Hash: 10174710861731544050 Positions: Count 128. Hash: 10174710861731544050
Normals: Count 128. Hash: 2352668179264002707 Normals: Count 128. Hash: 2352668179264002707
FaceList: Count 62. Hash: 11332459830831720586 FaceList: Count 62. Hash: 11332459830831720586
FaceMaterialIds: Count 62. Hash: 12234218120113875284 FaceMaterialIds: Count 62. Hash: 12234218120113875284
Node Name: Cube_phys_1
Node Path: RootNode.Cube_phys.Cube_phys_1
Node Type: MeshData
Positions: Count 24. Hash: 3478903613105670818
Normals: Count 24. Hash: 7251512570672401149
FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cube_phys_2
Node Path: RootNode.Cube_phys.Cube_phys_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: transform Node Name: transform
Node Path: RootNode.Cone.transform Node Path: RootNode.Cone.Cone_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -35,13 +62,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cone.UVMap Node Path: RootNode.Cone.Cone_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 128. Hash: 10171083346831193808 UVs: Count 128. Hash: 10171083346831193808
UVCustomName: UVMap UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.Cone.DefaultMaterial Node Path: RootNode.Cone.Cone_1.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -70,21 +97,21 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cone.TangentSet_MikkT_0 Node Path: RootNode.Cone.Cone_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 128. Hash: 14351734474754285313 Tangents: Count 128. Hash: 14351734474754285313
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cone.BitangentSet_MikkT_0 Node Path: RootNode.Cone.Cone_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 128. Hash: 15997251922861304891 Bitangents: Count 128. Hash: 15997251922861304891
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_phys.transform Node Path: RootNode.Cone.Cone_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -93,13 +120,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube_phys.UVMap Node Path: RootNode.Cone.Cone_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 13623018071435219250 UVs: Count 128. Hash: 10171083346831193808
UVCustomName: UVMap UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.Cube_phys.DefaultMaterial Node Path: RootNode.Cone.Cone_2.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -128,40 +155,124 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: UVMap
Node Path: RootNode.Cube_phys.TangentSet_MikkT_0 Node Path: RootNode.Cone.Cone_1_optimized.UVMap
Node Type: MeshVertexUVData
UVs: Count 128. Hash: 7873368003484215433
UVCustomName: UVMap
Node Name: TangentSet_0
Node Path: RootNode.Cone.Cone_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 11965897353301448436 Tangents: Count 128. Hash: 12937806066914201637
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_phys.BitangentSet_MikkT_0 Node Path: RootNode.Cone.Cone_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 17515781720544086759 Bitangents: Count 128. Hash: 873786942732834087
TangentSpace: 1 GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Cone.Cone_1_optimized.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: DefaultMaterial
Node Path: RootNode.Cone.Cone_1_optimized.DefaultMaterial
Node Type: MaterialData
MaterialName: DefaultMaterial
UniqueId: 3809502407269006983
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 0.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: transform
Node Path: RootNode.Cube_phys.Cube_phys_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cone_optimized.UVMap Node Path: RootNode.Cube_phys.Cube_phys_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 128. Hash: 7873368003484215433 UVs: Count 24. Hash: 13623018071435219250
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: DefaultMaterial
Node Path: RootNode.Cone_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cube_phys.Cube_phys_1.DefaultMaterial
Node Type: MaterialData
MaterialName: DefaultMaterial
UniqueId: 3809502407269006983
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 0.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: TangentSet_0
Node Path: RootNode.Cube_phys.Cube_phys_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 128. Hash: 12937806066914201637 Tangents: Count 24. Hash: 11965897353301448436
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cone_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cube_phys.Cube_phys_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 128. Hash: 873786942732834087 Bitangents: Count 24. Hash: 17515781720544086759
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cone_optimized.transform Node Path: RootNode.Cube_phys.Cube_phys_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -169,8 +280,14 @@ Node Type: TransformData
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.Cube_phys.Cube_phys_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 24. Hash: 13623018071435219250
UVCustomName: UVMap
Node Name: DefaultMaterial Node Name: DefaultMaterial
Node Path: RootNode.Cone_optimized.DefaultMaterial Node Path: RootNode.Cube_phys.Cube_phys_2.DefaultMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: DefaultMaterial MaterialName: DefaultMaterial
UniqueId: 3809502407269006983 UniqueId: 3809502407269006983
@ -198,4 +315,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:

@ -1,32 +1,59 @@
ProductName: multiple_mesh_linked_materials.dbgsg ProductName: multiple_mesh_linked_materials.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
multiple_mesh_linked_materials multiple_mesh_linked_materials
Node Name: Cube Node Name: RootNode
Node Path: RootNode.Cube Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1
Node Path: RootNode.Cube.Cube_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7113802799051126666 FaceMaterialIds: Count 12. Hash: 7113802799051126666
Node Name: Cone Node Name: Cube_2
Node Path: RootNode.Cone Node Path: RootNode.Cube.Cube_2
Node Type: MeshData Node Type: BoneData
Positions: Count 128. Hash: 12506421592104186200 WorldTransform:
Normals: Count 128. Hash: 367461522682321485 BasisX: < 100.000000, 0.000000, 0.000000>
FaceList: Count 62. Hash: 13208951979626973193 BasisY: < 0.000000, -0.000016, 100.000000>
FaceMaterialIds: Count 62. Hash: 15454348664434923102 BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_optimized Node Name: Cube_1_optimized
Node Path: RootNode.Cube_optimized Node Path: RootNode.Cube.Cube_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7113802799051126666 FaceMaterialIds: Count 12. Hash: 7113802799051126666
Node Name: Cone_optimized Node Name: Cone_1
Node Path: RootNode.Cone_optimized Node Path: RootNode.Cone.Cone_1
Node Type: MeshData
Positions: Count 128. Hash: 12506421592104186200
Normals: Count 128. Hash: 367461522682321485
FaceList: Count 62. Hash: 13208951979626973193
FaceMaterialIds: Count 62. Hash: 15454348664434923102
Node Name: Cone_2
Node Path: RootNode.Cone.Cone_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 2.000000>
Node Name: Cone_1_optimized
Node Path: RootNode.Cone.Cone_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 128. Hash: 14946490408303214595 Positions: Count 128. Hash: 14946490408303214595
Normals: Count 128. Hash: 367461522682321485 Normals: Count 128. Hash: 367461522682321485
@ -34,7 +61,7 @@ Node Type: MeshData
FaceMaterialIds: Count 62. Hash: 15454348664434923102 FaceMaterialIds: Count 62. Hash: 15454348664434923102
Node Name: transform Node Name: transform
Node Path: RootNode.Cube.transform Node Path: RootNode.Cube.Cube_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -43,13 +70,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UV0 Node Name: UV0
Node Path: RootNode.Cube.UV0 Node Path: RootNode.Cube.Cube_1.UV0
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UV0 UVCustomName: UV0
Node Name: SharedBlack Node Name: SharedBlack
Node Path: RootNode.Cube.SharedBlack Node Path: RootNode.Cube.Cube_1.SharedBlack
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedBlack MaterialName: SharedBlack
UniqueId: 5248829540156873090 UniqueId: 5248829540156873090
@ -79,7 +106,7 @@ Node Type: MaterialData
BaseColorTexture: BaseColorTexture:
Node Name: SharedOrange Node Name: SharedOrange
Node Path: RootNode.Cube.SharedOrange Node Path: RootNode.Cube.Cube_1.SharedOrange
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedOrange MaterialName: SharedOrange
UniqueId: 9470651048605569128 UniqueId: 9470651048605569128
@ -108,42 +135,42 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cone.transform Node Path: RootNode.Cube.Cube_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 2.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UV0 Node Name: UV0
Node Path: RootNode.Cone.UV0 Node Path: RootNode.Cube.Cube_2.UV0
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 128. Hash: 10291654057525777310 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UV0 UVCustomName: UV0
Node Name: SharedOrange Node Name: SharedBlack
Node Path: RootNode.Cone.SharedOrange Node Path: RootNode.Cube.Cube_2.SharedBlack
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedOrange MaterialName: SharedBlack
UniqueId: 9470651048605569128 UniqueId: 5248829540156873090
IsNoDraw: false IsNoDraw: false
DiffuseColor: < 0.800000, 0.139382, 0.014429> DiffuseColor: < 0.000000, 0.000000, 0.000000>
SpecularColor: < 0.800000, 0.139382, 0.014429> SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000> EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000 Opacity: 1.000000
Shininess: 25.000000 Shininess: 25.000000
@ -166,14 +193,14 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: SharedBlack Node Name: SharedOrange
Node Path: RootNode.Cone.SharedBlack Node Path: RootNode.Cube.Cube_2.SharedOrange
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedBlack MaterialName: SharedOrange
UniqueId: 5248829540156873090 UniqueId: 9470651048605569128
IsNoDraw: false IsNoDraw: false
DiffuseColor: < 0.000000, 0.000000, 0.000000> DiffuseColor: < 0.800000, 0.139382, 0.014429>
SpecularColor: < 0.000000, 0.000000, 0.000000> SpecularColor: < 0.800000, 0.139382, 0.014429>
EmissiveColor: < 0.000000, 0.000000, 0.000000> EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000 Opacity: 1.000000
Shininess: 25.000000 Shininess: 25.000000
@ -196,40 +223,27 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0
Node Path: RootNode.Cone.TangentSet_MikkT_0
Node Type: MeshVertexTangentData
Tangents: Count 128. Hash: 12695232913942738512
TangentSpace: 1
SetIndex: 0
Node Name: BitangentSet_MikkT_0
Node Path: RootNode.Cone.BitangentSet_MikkT_0
Node Type: MeshVertexBitangentData
Bitangents: Count 128. Hash: 9034210764777745751
TangentSpace: 1
Node Name: UV0 Node Name: UV0
Node Path: RootNode.Cube_optimized.UV0 Node Path: RootNode.Cube.Cube_1_optimized.UV0
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UV0 UVCustomName: UV0
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_optimized.transform Node Path: RootNode.Cube.Cube_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -238,7 +252,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: SharedBlack Node Name: SharedBlack
Node Path: RootNode.Cube_optimized.SharedBlack Node Path: RootNode.Cube.Cube_1_optimized.SharedBlack
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedBlack MaterialName: SharedBlack
UniqueId: 5248829540156873090 UniqueId: 5248829540156873090
@ -268,7 +282,7 @@ Node Type: MaterialData
BaseColorTexture: BaseColorTexture:
Node Name: SharedOrange Node Name: SharedOrange
Node Path: RootNode.Cube_optimized.SharedOrange Node Path: RootNode.Cube.Cube_1_optimized.SharedOrange
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedOrange MaterialName: SharedOrange
UniqueId: 9470651048605569128 UniqueId: 9470651048605569128
@ -297,27 +311,190 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: transform
Node Path: RootNode.Cone.Cone_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 2.000000>
Node Name: UV0 Node Name: UV0
Node Path: RootNode.Cone_optimized.UV0 Node Path: RootNode.Cone.Cone_1.UV0
Node Type: MeshVertexUVData
UVs: Count 128. Hash: 10291654057525777310
UVCustomName: UV0
Node Name: SharedOrange
Node Path: RootNode.Cone.Cone_1.SharedOrange
Node Type: MaterialData
MaterialName: SharedOrange
UniqueId: 9470651048605569128
IsNoDraw: false
DiffuseColor: < 0.800000, 0.139382, 0.014429>
SpecularColor: < 0.800000, 0.139382, 0.014429>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: SharedBlack
Node Path: RootNode.Cone.Cone_1.SharedBlack
Node Type: MaterialData
MaterialName: SharedBlack
UniqueId: 5248829540156873090
IsNoDraw: false
DiffuseColor: < 0.000000, 0.000000, 0.000000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: TangentSet_0
Node Path: RootNode.Cone.Cone_1.TangentSet_0
Node Type: MeshVertexTangentData
Tangents: Count 128. Hash: 12695232913942738512
GenerationMethod: 1
SetIndex: 0
Node Name: BitangentSet_0
Node Path: RootNode.Cone.Cone_1.BitangentSet_0
Node Type: MeshVertexBitangentData
Bitangents: Count 128. Hash: 9034210764777745751
GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Cone.Cone_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 2.000000>
Node Name: UV0
Node Path: RootNode.Cone.Cone_2.UV0
Node Type: MeshVertexUVData
UVs: Count 128. Hash: 10291654057525777310
UVCustomName: UV0
Node Name: SharedOrange
Node Path: RootNode.Cone.Cone_2.SharedOrange
Node Type: MaterialData
MaterialName: SharedOrange
UniqueId: 9470651048605569128
IsNoDraw: false
DiffuseColor: < 0.800000, 0.139382, 0.014429>
SpecularColor: < 0.800000, 0.139382, 0.014429>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: SharedBlack
Node Path: RootNode.Cone.Cone_2.SharedBlack
Node Type: MaterialData
MaterialName: SharedBlack
UniqueId: 5248829540156873090
IsNoDraw: false
DiffuseColor: < 0.000000, 0.000000, 0.000000>
SpecularColor: < 0.000000, 0.000000, 0.000000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: UV0
Node Path: RootNode.Cone.Cone_1_optimized.UV0
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 128. Hash: 7173974213247584731 UVs: Count 128. Hash: 7173974213247584731
UVCustomName: UV0 UVCustomName: UV0
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cone_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cone.Cone_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 128. Hash: 10740776669168782230 Tangents: Count 128. Hash: 10740776669168782230
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cone_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cone.Cone_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 128. Hash: 6990068477421150065 Bitangents: Count 128. Hash: 6990068477421150065
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cone_optimized.transform Node Path: RootNode.Cone.Cone_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -326,7 +503,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 2.000000> Transl: < 0.000000, 0.000000, 2.000000>
Node Name: SharedOrange Node Name: SharedOrange
Node Path: RootNode.Cone_optimized.SharedOrange Node Path: RootNode.Cone.Cone_1_optimized.SharedOrange
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedOrange MaterialName: SharedOrange
UniqueId: 9470651048605569128 UniqueId: 9470651048605569128
@ -356,7 +533,7 @@ Node Type: MaterialData
BaseColorTexture: BaseColorTexture:
Node Name: SharedBlack Node Name: SharedBlack
Node Path: RootNode.Cone_optimized.SharedBlack Node Path: RootNode.Cone.Cone_1_optimized.SharedBlack
Node Type: MaterialData Node Type: MaterialData
MaterialName: SharedBlack MaterialName: SharedBlack
UniqueId: 5248829540156873090 UniqueId: 5248829540156873090
@ -384,4 +561,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:

@ -1,32 +1,59 @@
ProductName: multiple_mesh_one_material.dbgsg ProductName: multiple_mesh_one_material.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
multiple_mesh_one_material multiple_mesh_one_material
Node Name: Cube Node Name: RootNode
Node Path: RootNode.Cube Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1
Node Path: RootNode.Cube.Cube_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cylinder Node Name: Cube_2
Node Path: RootNode.Cylinder Node Path: RootNode.Cube.Cube_2
Node Type: MeshData Node Type: BoneData
Positions: Count 192. Hash: 1283526254311745349 WorldTransform:
Normals: Count 192. Hash: 1873340970602844856 BasisX: < 100.000000, 0.000000, 0.000000>
FaceList: Count 124. Hash: 3728991722746136013 BasisY: < 0.000000, -0.000016, 100.000000>
FaceMaterialIds: Count 124. Hash: 2372486708814455910 BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_optimized Node Name: Cube_1_optimized
Node Path: RootNode.Cube_optimized Node Path: RootNode.Cube.Cube_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cylinder_optimized Node Name: Cylinder_1
Node Path: RootNode.Cylinder_optimized Node Path: RootNode.Cylinder.Cylinder_1
Node Type: MeshData
Positions: Count 192. Hash: 1283526254311745349
Normals: Count 192. Hash: 1873340970602844856
FaceList: Count 124. Hash: 3728991722746136013
FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: Cylinder_2
Node Path: RootNode.Cylinder.Cylinder_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: Cylinder_1_optimized
Node Path: RootNode.Cylinder.Cylinder_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 192. Hash: 7921557352486854444 Positions: Count 192. Hash: 7921557352486854444
Normals: Count 192. Hash: 1873340970602844856 Normals: Count 192. Hash: 1873340970602844856
@ -34,7 +61,7 @@ Node Type: MeshData
FaceMaterialIds: Count 124. Hash: 2372486708814455910 FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: transform Node Name: transform
Node Path: RootNode.Cube.transform Node Path: RootNode.Cube.Cube_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -43,13 +70,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube.UVMap Node Path: RootNode.Cube.Cube_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cube.SingleMaterial Node Path: RootNode.Cube.Cube_1.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -78,36 +105,36 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cylinder.transform Node Path: RootNode.Cube.Cube_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cylinder.UVMap Node Path: RootNode.Cube.Cube_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cylinder.SingleMaterial Node Path: RootNode.Cube.Cube_2.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -136,49 +163,139 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0 Node Name: UVMap
Node Path: RootNode.Cylinder.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.UVMap
Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap
Node Name: TangentSet_0
Node Path: RootNode.Cube.Cube_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cylinder.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Cube.Cube_1_optimized.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: SingleMaterial
Node Path: RootNode.Cube.Cube_1_optimized.SingleMaterial
Node Type: MaterialData
MaterialName: SingleMaterial
UniqueId: 14432700632681398127
IsNoDraw: false
DiffuseColor: < 0.814049, 0.814049, 0.814049>
SpecularColor: < 0.814049, 0.814049, 0.814049>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: TwoMeshOneMaterial/FBXTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png
Node Name: transform
Node Path: RootNode.Cylinder.Cylinder_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube_optimized.UVMap Node Path: RootNode.Cylinder.Cylinder_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: SingleMaterial
Node Path: RootNode.Cube_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cylinder.Cylinder_1.SingleMaterial
Node Type: MaterialData
MaterialName: SingleMaterial
UniqueId: 14432700632681398127
IsNoDraw: false
DiffuseColor: < 0.814049, 0.814049, 0.814049>
SpecularColor: < 0.814049, 0.814049, 0.814049>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: TwoMeshOneMaterial/FBXTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png
Node Name: TangentSet_0
Node Path: RootNode.Cylinder.Cylinder_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 192. Hash: 11165448242141781141
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cylinder.Cylinder_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 192. Hash: 7987814487334449536
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_optimized.transform Node Path: RootNode.Cylinder.Cylinder_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000> Transl: <-4.388482, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.Cylinder.Cylinder_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cube_optimized.SingleMaterial Node Path: RootNode.Cylinder.Cylinder_2.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -208,26 +325,26 @@ Node Type: MaterialData
BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cylinder_optimized.UVMap Node Path: RootNode.Cylinder.Cylinder_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 13790301632763350589 UVs: Count 192. Hash: 13790301632763350589
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cylinder_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cylinder.Cylinder_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 7293001660047850407 Tangents: Count 192. Hash: 7293001660047850407
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cylinder_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cylinder.Cylinder_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 2874689498270494796 Bitangents: Count 192. Hash: 2874689498270494796
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cylinder_optimized.transform Node Path: RootNode.Cylinder.Cylinder_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -236,7 +353,7 @@ Node Type: TransformData
Transl: <-4.388482, 0.000000, 0.000000> Transl: <-4.388482, 0.000000, 0.000000>
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cylinder_optimized.SingleMaterial Node Path: RootNode.Cylinder.Cylinder_1_optimized.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -264,4 +381,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshOneMaterial/FBXTestTexture.png

@ -1,32 +1,59 @@
ProductName: multiple_mesh_multiple_material.dbgsg ProductName: multiple_mesh_multiple_material.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
multiple_mesh_multiple_material multiple_mesh_multiple_material
Node Name: Cube Node Name: RootNode
Node Path: RootNode.Cube Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1
Node Path: RootNode.Cube.Cube_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cylinder Node Name: Cube_2
Node Path: RootNode.Cylinder Node Path: RootNode.Cube.Cube_2
Node Type: MeshData Node Type: BoneData
Positions: Count 192. Hash: 1283526254311745349 WorldTransform:
Normals: Count 192. Hash: 1873340970602844856 BasisX: < 100.000000, 0.000000, 0.000000>
FaceList: Count 124. Hash: 3728991722746136013 BasisY: < 0.000000, -0.000016, 100.000000>
FaceMaterialIds: Count 124. Hash: 2372486708814455910 BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_optimized Node Name: Cube_1_optimized
Node Path: RootNode.Cube_optimized Node Path: RootNode.Cube.Cube_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cylinder_optimized Node Name: Cylinder_1
Node Path: RootNode.Cylinder_optimized Node Path: RootNode.Cylinder.Cylinder_1
Node Type: MeshData
Positions: Count 192. Hash: 1283526254311745349
Normals: Count 192. Hash: 1873340970602844856
FaceList: Count 124. Hash: 3728991722746136013
FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: Cylinder_2
Node Path: RootNode.Cylinder.Cylinder_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: Cylinder_1_optimized
Node Path: RootNode.Cylinder.Cylinder_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 192. Hash: 7921557352486854444 Positions: Count 192. Hash: 7921557352486854444
Normals: Count 192. Hash: 1873340970602844856 Normals: Count 192. Hash: 1873340970602844856
@ -34,7 +61,7 @@ Node Type: MeshData
FaceMaterialIds: Count 124. Hash: 2372486708814455910 FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: transform Node Name: transform
Node Path: RootNode.Cube.transform Node Path: RootNode.Cube.Cube_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -43,13 +70,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube.UVMap Node Path: RootNode.Cube.Cube_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cube.SingleMaterial Node Path: RootNode.Cube.Cube_1.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -78,42 +105,42 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cylinder.transform Node Path: RootNode.Cube.Cube_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cylinder.UVMap Node Path: RootNode.Cube.Cube_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: SecondMaterial Node Name: SingleMaterial
Node Path: RootNode.Cylinder.SecondMaterial Node Path: RootNode.Cube.Cube_2.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SecondMaterial MaterialName: SingleMaterial
UniqueId: 5229255358802505087 UniqueId: 14432700632681398127
IsNoDraw: false IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000> DiffuseColor: < 0.814049, 0.814049, 0.814049>
SpecularColor: < 0.800000, 0.800000, 0.800000> SpecularColor: < 0.814049, 0.814049, 0.814049>
EmissiveColor: < 0.000000, 0.000000, 0.000000> EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000 Opacity: 1.000000
Shininess: 25.000000 Shininess: 25.000000
@ -126,7 +153,7 @@ Node Type: MaterialData
UseEmissiveMap: Not set UseEmissiveMap: Not set
EmissiveIntensity: Not set EmissiveIntensity: Not set
UseAOMap: Not set UseAOMap: Not set
DiffuseTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png DiffuseTexture: TwoMeshTwoMaterial/FBXTestTexture.png
SpecularTexture: SpecularTexture:
BumpTexture: BumpTexture:
NormalTexture: NormalTexture:
@ -134,42 +161,29 @@ Node Type: MaterialData
RoughnessTexture: RoughnessTexture:
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0
Node Path: RootNode.Cylinder.TangentSet_MikkT_0
Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141
TangentSpace: 1
SetIndex: 0
Node Name: BitangentSet_MikkT_0
Node Path: RootNode.Cylinder.BitangentSet_MikkT_0
Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536
TangentSpace: 1
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube_optimized.UVMap Node Path: RootNode.Cube.Cube_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_optimized.transform Node Path: RootNode.Cube.Cube_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -178,7 +192,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cube_optimized.SingleMaterial Node Path: RootNode.Cube.Cube_1_optimized.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -207,27 +221,130 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png
Node Name: transform
Node Path: RootNode.Cylinder.Cylinder_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cylinder_optimized.UVMap Node Path: RootNode.Cylinder.Cylinder_1.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: SecondMaterial
Node Path: RootNode.Cylinder.Cylinder_1.SecondMaterial
Node Type: MaterialData
MaterialName: SecondMaterial
UniqueId: 5229255358802505087
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
Node Name: TangentSet_0
Node Path: RootNode.Cylinder.Cylinder_1.TangentSet_0
Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141
GenerationMethod: 1
SetIndex: 0
Node Name: BitangentSet_0
Node Path: RootNode.Cylinder.Cylinder_1.BitangentSet_0
Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536
GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Cylinder.Cylinder_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.Cylinder.Cylinder_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: SecondMaterial
Node Path: RootNode.Cylinder.Cylinder_2.SecondMaterial
Node Type: MaterialData
MaterialName: SecondMaterial
UniqueId: 5229255358802505087
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
Node Name: UVMap
Node Path: RootNode.Cylinder.Cylinder_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 13790301632763350589 UVs: Count 192. Hash: 13790301632763350589
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cylinder_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cylinder.Cylinder_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 7293001660047850407 Tangents: Count 192. Hash: 7293001660047850407
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cylinder_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cylinder.Cylinder_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 2874689498270494796 Bitangents: Count 192. Hash: 2874689498270494796
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cylinder_optimized.transform Node Path: RootNode.Cylinder.Cylinder_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -236,7 +353,7 @@ Node Type: TransformData
Transl: <-4.388482, 0.000000, 0.000000> Transl: <-4.388482, 0.000000, 0.000000>
Node Name: SecondMaterial Node Name: SecondMaterial
Node Path: RootNode.Cylinder_optimized.SecondMaterial Node Path: RootNode.Cylinder.Cylinder_1_optimized.SecondMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SecondMaterial MaterialName: SecondMaterial
UniqueId: 5229255358802505087 UniqueId: 5229255358802505087
@ -264,4 +381,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png

@ -1,32 +1,59 @@
ProductName: multiple_mesh_multiple_material.dbgsg ProductName: multiple_mesh_multiple_material.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
multiple_mesh_multiple_material multiple_mesh_multiple_material
Node Name: Cube Node Name: RootNode
Node Path: RootNode.Cube Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1
Node Path: RootNode.Cube.Cube_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cylinder Node Name: Cube_2
Node Path: RootNode.Cylinder Node Path: RootNode.Cube.Cube_2
Node Type: MeshData Node Type: BoneData
Positions: Count 192. Hash: 1283526254311745349 WorldTransform:
Normals: Count 192. Hash: 1873340970602844856 BasisX: < 100.000000, 0.000000, 0.000000>
FaceList: Count 124. Hash: 3728991722746136013 BasisY: < 0.000000, -0.000016, 100.000000>
FaceMaterialIds: Count 124. Hash: 2372486708814455910 BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_optimized Node Name: Cube_1_optimized
Node Path: RootNode.Cube_optimized Node Path: RootNode.Cube.Cube_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24. Hash: 8661923109306356285 Positions: Count 24. Hash: 8661923109306356285
Normals: Count 24. Hash: 5807525742165000561 Normals: Count 24. Hash: 5807525742165000561
FaceList: Count 12. Hash: 9888799799190757436 FaceList: Count 12. Hash: 9888799799190757436
FaceMaterialIds: Count 12. Hash: 7110546404675862471 FaceMaterialIds: Count 12. Hash: 7110546404675862471
Node Name: Cylinder_1
Node Path: RootNode.Cylinder.Cylinder_1
Node Type: MeshData
Positions: Count 192. Hash: 1283526254311745349
Normals: Count 192. Hash: 1873340970602844856
FaceList: Count 124. Hash: 3728991722746136013
FaceMaterialIds: Count 124. Hash: 2372486708814455910
Node Name: Cylinder_2
Node Path: RootNode.Cylinder.Cylinder_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: transform Node Name: transform
Node Path: RootNode.Cube.transform Node Path: RootNode.Cube.Cube_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -35,13 +62,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube.UVMap Node Path: RootNode.Cube.Cube_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cube.SingleMaterial Node Path: RootNode.Cube.Cube_1.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -70,42 +97,42 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cylinder.transform Node Path: RootNode.Cube.Cube_2.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000> BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016> BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cylinder.UVMap Node Path: RootNode.Cube.Cube_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: SecondMaterial Node Name: SingleMaterial
Node Path: RootNode.Cylinder.SecondMaterial Node Path: RootNode.Cube.Cube_2.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SecondMaterial MaterialName: SingleMaterial
UniqueId: 5229255358802505087 UniqueId: 14432700632681398127
IsNoDraw: false IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000> DiffuseColor: < 0.814049, 0.814049, 0.814049>
SpecularColor: < 0.800000, 0.800000, 0.800000> SpecularColor: < 0.814049, 0.814049, 0.814049>
EmissiveColor: < 0.000000, 0.000000, 0.000000> EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000 Opacity: 1.000000
Shininess: 25.000000 Shininess: 25.000000
@ -118,7 +145,7 @@ Node Type: MaterialData
UseEmissiveMap: Not set UseEmissiveMap: Not set
EmissiveIntensity: Not set EmissiveIntensity: Not set
UseAOMap: Not set UseAOMap: Not set
DiffuseTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png DiffuseTexture: TwoMeshTwoMaterial/FBXTestTexture.png
SpecularTexture: SpecularTexture:
BumpTexture: BumpTexture:
NormalTexture: NormalTexture:
@ -126,42 +153,29 @@ Node Type: MaterialData
RoughnessTexture: RoughnessTexture:
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png
Node Name: TangentSet_MikkT_0
Node Path: RootNode.Cylinder.TangentSet_MikkT_0
Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141
TangentSpace: 1
SetIndex: 0
Node Name: BitangentSet_MikkT_0
Node Path: RootNode.Cylinder.BitangentSet_MikkT_0
Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536
TangentSpace: 1
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube_optimized.UVMap Node Path: RootNode.Cube.Cube_1_optimized.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24. Hash: 1622169145591646736 UVs: Count 24. Hash: 1622169145591646736
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24. Hash: 13438447437797057049 Tangents: Count 24. Hash: 13438447437797057049
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24. Hash: 11372562338897179017 Bitangents: Count 24. Hash: 11372562338897179017
TangentSpace: 1 GenerationMethod: 1
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_optimized.transform Node Path: RootNode.Cube.Cube_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -170,7 +184,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: SingleMaterial Node Name: SingleMaterial
Node Path: RootNode.Cube_optimized.SingleMaterial Node Path: RootNode.Cube.Cube_1_optimized.SingleMaterial
Node Type: MaterialData Node Type: MaterialData
MaterialName: SingleMaterial MaterialName: SingleMaterial
UniqueId: 14432700632681398127 UniqueId: 14432700632681398127
@ -199,3 +213,105 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png BaseColorTexture: TwoMeshTwoMaterial/FBXTestTexture.png
Node Name: transform
Node Path: RootNode.Cylinder.Cylinder_1.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.Cylinder.Cylinder_1.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: SecondMaterial
Node Path: RootNode.Cylinder.Cylinder_1.SecondMaterial
Node Type: MaterialData
MaterialName: SecondMaterial
UniqueId: 5229255358802505087
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
Node Name: TangentSet_0
Node Path: RootNode.Cylinder.Cylinder_1.TangentSet_0
Node Type: MeshVertexTangentData
Tangents: Count 192. Hash: 11165448242141781141
GenerationMethod: 1
SetIndex: 0
Node Name: BitangentSet_0
Node Path: RootNode.Cylinder.Cylinder_1.BitangentSet_0
Node Type: MeshVertexBitangentData
Bitangents: Count 192. Hash: 7987814487334449536
GenerationMethod: 1
Node Name: transform
Node Path: RootNode.Cylinder.Cylinder_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: <-4.388482, 0.000000, 0.000000>
Node Name: UVMap
Node Path: RootNode.Cylinder.Cylinder_2.UVMap
Node Type: MeshVertexUVData
UVs: Count 192. Hash: 27253578623892681
UVCustomName: UVMap
Node Name: SecondMaterial
Node Path: RootNode.Cylinder.Cylinder_2.SecondMaterial
Node Type: MaterialData
MaterialName: SecondMaterial
UniqueId: 5229255358802505087
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 25.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture: TwoMeshTwoMaterial/FBXSecondTestTexture.png

@ -1,30 +1,48 @@
ProductName: vertexcolor.dbgsg ProductName: vertexcolor.dbgsg
debugSceneGraphVersion: 1 debugSceneGraphVersion: 1
vertexcolor vertexcolor
Node Name: Cube Node Name: RootNode
Node Path: RootNode.Cube Node Path: RootNode
Node Type: RootBoneData
WorldTransform:
BasisX: < 1.000000, 0.000000, 0.000000>
BasisY: < 0.000000, 1.000000, 0.000000>
BasisZ: < 0.000000, 0.000000, 1.000000>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1
Node Path: RootNode.Cube.Cube_1
Node Type: MeshData Node Type: MeshData
Positions: Count 24576. Hash: 7031773714680283213 Positions: Count 24576. Hash: 7031773714680283213
Normals: Count 24576. Hash: 8968157737282745201 Normals: Count 24576. Hash: 8968157737282745201
FaceList: Count 12288. Hash: 13183441914179219962 FaceList: Count 12288. Hash: 13183441914179219962
FaceMaterialIds: Count 12288. Hash: 12545154121625736090 FaceMaterialIds: Count 12288. Hash: 12545154121625736090
Node Name: Cube_optimized Node Name: Cube_2
Node Path: RootNode.Cube_optimized Node Path: RootNode.Cube.Cube_2
Node Type: BoneData
WorldTransform:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Cube_1_optimized
Node Path: RootNode.Cube.Cube_1_optimized
Node Type: MeshData Node Type: MeshData
Positions: Count 24576. Hash: 7031773714680283213 Positions: Count 6376. Hash: 10806296444120211070
Normals: Count 24576. Hash: 8968157737282745201 Normals: Count 6376. Hash: 3814626075063770280
FaceList: Count 12288. Hash: 13183441914179219962 FaceList: Count 12288. Hash: 15242182080304859208
FaceMaterialIds: Count 12288. Hash: 12545154121625736090 FaceMaterialIds: Count 12288. Hash: 12545154121625736090
Node Name: Col0 Node Name: Col0
Node Path: RootNode.Cube.Col0 Node Path: RootNode.Cube.Cube_1.Col0
Node Type: MeshVertexColorData Node Type: MeshVertexColorData
Colors: Count 24576. Hash: 17169952715183318502 Colors: Count 24576. Hash: 17169952715183318502
ColorsCustomName: ColorsCustomName:
Node Name: transform Node Name: transform
Node Path: RootNode.Cube.transform Node Path: RootNode.Cube.Cube_1.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -33,13 +51,13 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube.UVMap Node Path: RootNode.Cube.Cube_1.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24576. Hash: 4554678369329207802 UVs: Count 24576. Hash: 4554678369329207802
UVCustomName: UVMap UVCustomName: UVMap
Node Name: Material Node Name: Material
Node Path: RootNode.Cube.Material Node Path: RootNode.Cube.Cube_1.Material
Node Type: MaterialData Node Type: MaterialData
MaterialName: Material MaterialName: Material
UniqueId: 11127505492038345244 UniqueId: 11127505492038345244
@ -68,46 +86,97 @@ Node Type: MaterialData
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:
Node Name: TangentSet_MikkT_0 Node Name: TangentSet_0
Node Path: RootNode.Cube.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24576. Hash: 13321090379606717973 Tangents: Count 24576. Hash: 13321090379606717973
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24576. Hash: 17217515414004886507 Bitangents: Count 24576. Hash: 17217515414004886507
TangentSpace: 1 GenerationMethod: 1
Node Name: Col0
Node Path: RootNode.Cube.Cube_2.Col0
Node Type: MeshVertexColorData
Colors: Count 24576. Hash: 17169952715183318502
ColorsCustomName:
Node Name: transform
Node Path: RootNode.Cube.Cube_2.transform
Node Type: TransformData
Matrix:
BasisX: < 100.000000, 0.000000, 0.000000>
BasisY: < 0.000000, -0.000016, 100.000000>
BasisZ: < 0.000000, -100.000000, -0.000016>
Transl: < 0.000000, 0.000000, 0.000000>
Node Name: UVMap Node Name: UVMap
Node Path: RootNode.Cube_optimized.UVMap Node Path: RootNode.Cube.Cube_2.UVMap
Node Type: MeshVertexUVData Node Type: MeshVertexUVData
UVs: Count 24576. Hash: 4554678369329207802 UVs: Count 24576. Hash: 4554678369329207802
UVCustomName: UVMap UVCustomName: UVMap
Node Name: TangentSet_MikkT_0 Node Name: Material
Node Path: RootNode.Cube_optimized.TangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_2.Material
Node Type: MaterialData
MaterialName: Material
UniqueId: 11127505492038345244
IsNoDraw: false
DiffuseColor: < 0.800000, 0.800000, 0.800000>
SpecularColor: < 0.800000, 0.800000, 0.800000>
EmissiveColor: < 0.000000, 0.000000, 0.000000>
Opacity: 1.000000
Shininess: 36.000000
UseColorMap: Not set
BaseColor: Not set
UseMetallicMap: Not set
MetallicFactor: Not set
UseRoughnessMap: Not set
RoughnessFactor: Not set
UseEmissiveMap: Not set
EmissiveIntensity: Not set
UseAOMap: Not set
DiffuseTexture:
SpecularTexture:
BumpTexture:
NormalTexture:
MetallicTexture:
RoughnessTexture:
AmbientOcclusionTexture:
EmissiveTexture:
BaseColorTexture:
Node Name: UVMap
Node Path: RootNode.Cube.Cube_1_optimized.UVMap
Node Type: MeshVertexUVData
UVs: Count 6376. Hash: 12957930967905951851
UVCustomName: UVMap
Node Name: TangentSet_0
Node Path: RootNode.Cube.Cube_1_optimized.TangentSet_0
Node Type: MeshVertexTangentData Node Type: MeshVertexTangentData
Tangents: Count 24576. Hash: 13321090379606717973 Tangents: Count 6376. Hash: 7712841033379094373
TangentSpace: 1 GenerationMethod: 1
SetIndex: 0 SetIndex: 0
Node Name: BitangentSet_MikkT_0 Node Name: BitangentSet_0
Node Path: RootNode.Cube_optimized.BitangentSet_MikkT_0 Node Path: RootNode.Cube.Cube_1_optimized.BitangentSet_0
Node Type: MeshVertexBitangentData Node Type: MeshVertexBitangentData
Bitangents: Count 24576. Hash: 17217515414004886507 Bitangents: Count 6376. Hash: 12547048737213169362
TangentSpace: 1 GenerationMethod: 1
Node Name: Col0 Node Name: Col0
Node Path: RootNode.Cube_optimized.Col0 Node Path: RootNode.Cube.Cube_1_optimized.Col0
Node Type: MeshVertexColorData Node Type: MeshVertexColorData
Colors: Count 24576. Hash: 17169952715183318502 Colors: Count 6376. Hash: 8761962599807935159
ColorsCustomName: ColorsCustomName:
Node Name: transform Node Name: transform
Node Path: RootNode.Cube_optimized.transform Node Path: RootNode.Cube.Cube_1_optimized.transform
Node Type: TransformData Node Type: TransformData
Matrix: Matrix:
BasisX: < 100.000000, 0.000000, 0.000000> BasisX: < 100.000000, 0.000000, 0.000000>
@ -116,7 +185,7 @@ Node Type: TransformData
Transl: < 0.000000, 0.000000, 0.000000> Transl: < 0.000000, 0.000000, 0.000000>
Node Name: Material Node Name: Material
Node Path: RootNode.Cube_optimized.Material Node Path: RootNode.Cube.Cube_1_optimized.Material
Node Type: MaterialData Node Type: MaterialData
MaterialName: Material MaterialName: Material
UniqueId: 11127505492038345244 UniqueId: 11127505492038345244
@ -144,4 +213,3 @@ Node Type: MaterialData
AmbientOcclusionTexture: AmbientOcclusionTexture:
EmissiveTexture: EmissiveTexture:
BaseColorTexture: BaseColorTexture:

@ -67,7 +67,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=1,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='onemeshonematerial/onemeshonematerial.dbgsg', product_name='onemeshonematerial/onemeshonematerial.dbgsg',
@ -99,7 +99,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=9, warning_count=22,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='softnaminglod/lodtest.dbgsg', product_name='softnaminglod/lodtest.dbgsg',
@ -131,7 +131,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=6, warning_count=14,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='softnamingphysics/physicstest.dbgsg', product_name='softnamingphysics/physicstest.dbgsg',
@ -165,7 +165,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=2,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='twomeshonematerial/multiple_mesh_one_material.dbgsg', product_name='twomeshonematerial/multiple_mesh_one_material.dbgsg',
@ -197,7 +197,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=2,
products= [ products= [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='twomeshlinkedmaterials/multiple_mesh_linked_materials.dbgsg', product_name='twomeshlinkedmaterials/multiple_mesh_linked_materials.dbgsg',
@ -230,7 +230,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=1,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='onemeshmultiplematerials/single_mesh_multiple_materials.dbgsg', product_name='onemeshmultiplematerials/single_mesh_multiple_materials.dbgsg',
@ -260,7 +260,7 @@ blackbox_fbx_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=1,
products=[ products=[
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='vertexcolor/vertexcolor.dbgsg', product_name='vertexcolor/vertexcolor.dbgsg',
@ -275,6 +275,38 @@ blackbox_fbx_tests = [
id="35796285", id="35796285",
marks=pytest.mark.test_case_id("C35796285"), marks=pytest.mark.test_case_id("C35796285"),
), ),
pytest.param(
BlackboxAssetTest(
test_name= "MotionTest_RunAP_SuccessWithMatchingProducts",
asset_folder= "Motion",
scene_debug_file="Jack_Idle_Aim_ZUp.dbgsg",
assets = [
asset_db_utils.DBSourceAsset(
source_file_name = "Jack_Idle_Aim_ZUp.fbx",
uuid = b"eda904ae0e145f8b973d57fc5809918b",
jobs = [
asset_db_utils.DBJob(
job_key= "Scene compilation",
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4,
error_count=0,
warning_count=0,
products = [
asset_db_utils.DBProduct(
product_name='motion/jack_idle_aim_zup.dbgsg',
sub_id=-517610290,
asset_type=b'07f289d14dc74c4094b40a53bbcb9f0b'),
asset_db_utils.DBProduct(
product_name='motion/jack_idle_aim_zup.motion',
sub_id=186392073,
asset_type=b'00494b8e75784ba28b28272e90680787')
]
),
]
)
]
),
),
] ]
@ -296,7 +328,7 @@ blackbox_fbx_special_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=2,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg', product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg',
@ -317,7 +349,7 @@ blackbox_fbx_special_tests = [
builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3", builder_guid=b"bd8bf65894854fe3830e8ec3a23c35f3",
status=4, status=4,
error_count=0, error_count=0,
warning_count=0, warning_count=2,
products = [ products = [
asset_db_utils.DBProduct( asset_db_utils.DBProduct(
product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg', product_name='twomeshtwomaterial/multiple_mesh_multiple_material.dbgsg',
@ -387,7 +419,6 @@ class TestsFBX_AllPlatforms(object):
self.run_fbx_test(workspace, ap_setup_fixture, self.run_fbx_test(workspace, ap_setup_fixture,
asset_processor, project, blackbox_param, True) asset_processor, project, blackbox_param, True)
def populateAssetInfo(self, workspace, project, assets): def populateAssetInfo(self, workspace, project, assets):
# Check that each given source asset resulted in the expected jobs and products. # Check that each given source asset resulted in the expected jobs and products.
@ -398,7 +429,6 @@ class TestsFBX_AllPlatforms(object):
product.product_name = job.platform + "/" \ product.product_name = job.platform + "/" \
+ product.product_name + product.product_name
def run_fbx_test(self, workspace, ap_setup_fixture, asset_processor, def run_fbx_test(self, workspace, ap_setup_fixture, asset_processor,
project, blackbox_params: BlackboxAssetTest, overrideAsset = False): project, blackbox_params: BlackboxAssetTest, overrideAsset = False):
""" """

@ -90,7 +90,7 @@ class TestAutomationBase:
editor_starttime = time.time() editor_starttime = time.time()
self.logger.debug("Running automated test") self.logger.debug("Running automated test")
testcase_module_filepath = self._get_testcase_module_filepath(testcase_module) testcase_module_filepath = self._get_testcase_module_filepath(testcase_module)
pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.originalname}"] pycmd = ["--runpythontest", testcase_module_filepath, f"-pythontestcase={request.node.name}"]
if use_null_renderer: if use_null_renderer:
pycmd += ["-rhi=null"] pycmd += ["-rhi=null"]
if batch_mode: if batch_mode:

@ -9,6 +9,7 @@ import os
import pytest import pytest
import ly_test_tools.environment.file_system as file_system import ly_test_tools.environment.file_system as file_system
import ly_test_tools._internal.pytest_plugin as internal_plugin
from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite from ly_test_tools.o3de.editor_test import EditorSingleTest, EditorSharedTest, EditorParallelTest, EditorTestSuite
@ -79,6 +80,8 @@ class TestAutomation(EditorTestSuite):
class test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(EditorSharedTest): class test_LandscapeCanvas_GradientNodes_EntityRemovedOnNodeDelete(EditorSharedTest):
from .EditorScripts import GradientNodes_EntityRemovedOnNodeDelete as test_module from .EditorScripts import GradientNodes_EntityRemovedOnNodeDelete as test_module
@pytest.mark.skipif("debug" == os.path.basename(internal_plugin.build_directory),
reason="https://github.com/o3de/o3de/issues/4872")
class test_LandscapeCanvas_GraphUpdates_UpdateComponents(EditorSharedTest): class test_LandscapeCanvas_GraphUpdates_UpdateComponents(EditorSharedTest):
from .EditorScripts import GraphUpdates_UpdateComponents as test_module from .EditorScripts import GraphUpdates_UpdateComponents as test_module

@ -1,44 +0,0 @@
"""
Copyright (c) Contributors to the Open 3D Engine Project.
For complete copyright and license terms please see the LICENSE at the root of this distribution.
SPDX-License-Identifier: Apache-2.0 OR MIT
UI Apps: AutomatedTesting.GameLauncher
Launch AutomatedTesting.GameLauncher with Simple level
Test should run in both gpu and non gpu
"""
import pytest
import psutil
import ly_test_tools.environment.waiter as waiter
import editor_python_test_tools.hydra_test_utils as editor_test_utils
from ly_remote_console.remote_console_commands import RemoteConsole as RemoteConsole
from ly_remote_console.remote_console_commands import (
send_command_and_expect_response as send_command_and_expect_response,
)
@pytest.mark.parametrize("launcher_platform", ["windows"])
@pytest.mark.parametrize("project", ["AutomatedTesting"])
@pytest.mark.parametrize("level", ["Simple"])
@pytest.mark.SUITE_smoke
class TestRemoteConsoleLoadLevelWorks(object):
@pytest.fixture
def remote_console_instance(self, request):
console = RemoteConsole()
def teardown():
if console.connected:
console.stop()
request.addfinalizer(teardown)
return console
def test_RemoteConsole_LoadLevel_Works(self, launcher, level, remote_console_instance, launcher_platform):
expected_lines = ['Level system is loading "Simple"']
editor_test_utils.launch_and_validate_results_launcher(launcher, level, remote_console_instance, expected_lines, null_renderer=True)

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e4937547ca4c486ef59656314401933217e0e0401fec103e1fb91c25ec60a177 oid sha256:a5f9e27e0f22c31ca61d866fb594c6fde5b8ceb891e17dda075fa1e0033ec2b9
size 2806 size 1666

@ -17,13 +17,6 @@
"Gems/PhysicsEntities" "Gems/PhysicsEntities"
] ]
}, },
"PhysXSamples":
{
"SourcePaths":
[
"Gems/PhysXSamples"
]
},
"PrimitiveAssets": "PrimitiveAssets":
{ {
"SourcePaths": "SourcePaths":

@ -287,21 +287,22 @@ bool CCryDocManager::DoPromptFileName(QString& fileName, [[maybe_unused]] UINT n
return false; return false;
} }
CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* lpszFileName, bool bAddToMRU) CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* filename, bool addToMostRecentFileList, COpenSameLevelOptions openSameLevelOptions)
{ {
assert(lpszFileName != nullptr); assert(filename != nullptr);
const bool reopenIfSame = openSameLevelOptions == COpenSameLevelOptions::ReopenLevelIfSame;
// find the highest confidence // find the highest confidence
auto pos = m_templateList.begin(); auto pos = m_templateList.begin();
CCrySingleDocTemplate::Confidence bestMatch = CCrySingleDocTemplate::noAttempt; CCrySingleDocTemplate::Confidence bestMatch = CCrySingleDocTemplate::noAttempt;
CCrySingleDocTemplate* pBestTemplate = nullptr; CCrySingleDocTemplate* pBestTemplate = nullptr;
CCryEditDoc* pOpenDocument = nullptr; CCryEditDoc* pOpenDocument = nullptr;
if (lpszFileName[0] == '\"') if (filename[0] == '\"')
{ {
++lpszFileName; ++filename;
} }
QString szPath = QString::fromUtf8(lpszFileName); QString szPath = QString::fromUtf8(filename);
if (szPath.endsWith('"')) if (szPath.endsWith('"'))
{ {
szPath.remove(szPath.length() - 1, 1); szPath.remove(szPath.length() - 1, 1);
@ -325,7 +326,7 @@ CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* lpszFileName, bool bAd
} }
} }
if (pOpenDocument != nullptr) if (!reopenIfSame && pOpenDocument != nullptr)
{ {
return pOpenDocument; return pOpenDocument;
} }
@ -336,7 +337,7 @@ CCryEditDoc* CCryDocManager::OpenDocumentFile(const char* lpszFileName, bool bAd
return nullptr; return nullptr;
} }
return pBestTemplate->OpenDocumentFile(szPath.toUtf8().data(), bAddToMRU, false); return pBestTemplate->OpenDocumentFile(szPath.toUtf8().data(), addToMostRecentFileList, false);
} }
////////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////////
@ -513,7 +514,7 @@ public:
QString m_appRoot; QString m_appRoot;
QString m_logFile; QString m_logFile;
QString m_pythonArgs; QString m_pythonArgs;
QString m_pythontTestCase; QString m_pythonTestCase;
QString m_execFile; QString m_execFile;
QString m_execLineCmd; QString m_execLineCmd;
@ -562,7 +563,7 @@ public:
const std::vector<std::pair<CommandLineStringOption, QString&> > stringOptions = { const std::vector<std::pair<CommandLineStringOption, QString&> > stringOptions = {
{{"logfile", "File name of the log file to write out to.", "logfile"}, m_logFile}, {{"logfile", "File name of the log file to write out to.", "logfile"}, m_logFile},
{{"runpythonargs", "Command-line argument string to pass to the python script if --runpython or --runpythontest was used.", "runpythonargs"}, m_pythonArgs}, {{"runpythonargs", "Command-line argument string to pass to the python script if --runpython or --runpythontest was used.", "runpythonargs"}, m_pythonArgs},
{{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythontTestCase}, {{"pythontestcase", "Test case name of python test script if --runpythontest was used.", "pythontestcase"}, m_pythonTestCase},
{{"exec", "cfg file to run on startup, used for systems like automation", "exec"}, m_execFile}, {{"exec", "cfg file to run on startup, used for systems like automation", "exec"}, m_execFile},
{{"rhi", "Command-line argument to force which rhi to use", "dummyString"}, dummyString }, {{"rhi", "Command-line argument to force which rhi to use", "dummyString"}, dummyString },
{{"rhi-device-validation", "Command-line argument to configure rhi validation", "dummyString"}, dummyString }, {{"rhi-device-validation", "Command-line argument to configure rhi validation", "dummyString"}, dummyString },
@ -818,7 +819,7 @@ CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, b
return OpenDocumentFile(lpszPathName, true, bMakeVisible); return OpenDocumentFile(lpszPathName, true, bMakeVisible);
} }
CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, bool bAddToMRU, [[maybe_unused]] bool bMakeVisible) CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, bool addToMostRecentFileList, [[maybe_unused]] bool bMakeVisible)
{ {
CCryEditDoc* pCurDoc = GetIEditor()->GetDocument(); CCryEditDoc* pCurDoc = GetIEditor()->GetDocument();
@ -848,7 +849,7 @@ CCryEditDoc* CCrySingleDocTemplate::OpenDocumentFile(const char* lpszPathName, b
{ {
pCurDoc->OnOpenDocument(lpszPathName); pCurDoc->OnOpenDocument(lpszPathName);
pCurDoc->SetPathName(lpszPathName); pCurDoc->SetPathName(lpszPathName);
if (bAddToMRU) if (addToMostRecentFileList)
{ {
CCryEditApp::instance()->AddToRecentFileList(lpszPathName); CCryEditApp::instance()->AddToRecentFileList(lpszPathName);
} }
@ -1535,11 +1536,12 @@ void CCryEditApp::RunInitPythonScript(CEditCommandLineInfo& cmdInfo)
{ {
// Multiple testcases can be specified them with ';', these should match the files to run // Multiple testcases can be specified them with ';', these should match the files to run
AZStd::vector<AZStd::string_view> testcaseList; AZStd::vector<AZStd::string_view> testcaseList;
QByteArray pythonTestCase = cmdInfo.m_pythonTestCase.toUtf8();
testcaseList.resize(fileList.size()); testcaseList.resize(fileList.size());
{ {
int i = 0; int i = 0;
AzFramework::StringFunc::TokenizeVisitor( AzFramework::StringFunc::TokenizeVisitor(
fileStr.constData(), pythonTestCase.constData(),
[&i, &testcaseList](AZStd::string_view elem) [&i, &testcaseList](AZStd::string_view elem)
{ {
testcaseList[i++] = (elem); testcaseList[i++] = (elem);
@ -2630,7 +2632,7 @@ void CCryEditApp::OnShowHelpers()
void CCryEditApp::OnEditLevelData() void CCryEditApp::OnEditLevelData()
{ {
auto dir = QFileInfo(GetIEditor()->GetDocument()->GetLevelPathName()).dir(); auto dir = QFileInfo(GetIEditor()->GetDocument()->GetLevelPathName()).dir();
CFileUtil::EditTextFile(dir.absoluteFilePath("LevelData.xml").toUtf8().data()); CFileUtil::EditTextFile(dir.absoluteFilePath("leveldata.xml").toUtf8().data());
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
@ -3364,7 +3366,7 @@ void CCryEditApp::OnOpenSlice()
} }
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* lpszFileName) CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* filename, bool addToMostRecentFileList, COpenSameLevelOptions openSameLevelOptions)
{ {
if (m_openingLevel) if (m_openingLevel)
{ {
@ -3404,9 +3406,9 @@ CCryEditDoc* CCryEditApp::OpenDocumentFile(const char* lpszFileName)
openDocTraceHandler.SetShowWindow(false); openDocTraceHandler.SetShowWindow(false);
} }
// in this case, we set bAddToMRU to always be true because adding files to the MRU list // in this case, we set addToMostRecentFileList to always be true because adding files to the MRU list
// automatically culls duplicate and normalizes paths anyway // automatically culls duplicate and normalizes paths anyway
m_pDocManager->OpenDocumentFile(lpszFileName, true); m_pDocManager->OpenDocumentFile(filename, addToMostRecentFileList, openSameLevelOptions);
if (openDocTraceHandler.HasAnyErrors()) if (openDocTraceHandler.HasAnyErrors())
{ {

@ -85,6 +85,12 @@ public:
using EditorIdleProcessingBus = AZ::EBus<EditorIdleProcessing>; using EditorIdleProcessingBus = AZ::EBus<EditorIdleProcessing>;
enum class COpenSameLevelOptions
{
ReopenLevelIfSame,
NotReopenIfSame
};
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
class SANDBOX_API CCryEditApp class SANDBOX_API CCryEditApp
@ -174,7 +180,9 @@ public:
virtual bool InitInstance(); virtual bool InitInstance();
virtual int ExitInstance(int exitCode = 0); virtual int ExitInstance(int exitCode = 0);
virtual bool OnIdle(LONG lCount); virtual bool OnIdle(LONG lCount);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszFileName); virtual CCryEditDoc* OpenDocumentFile(const char* filename,
bool addToMostRecentFileList=true,
COpenSameLevelOptions openSameLevelOptions = COpenSameLevelOptions::NotReopenIfSame);
CCryDocManager* GetDocManager() { return m_pDocManager; } CCryDocManager* GetDocManager() { return m_pDocManager; }
@ -448,7 +456,7 @@ public:
~CCrySingleDocTemplate() {}; ~CCrySingleDocTemplate() {};
// avoid creating another CMainFrame // avoid creating another CMainFrame
// close other type docs before opening any things // close other type docs before opening any things
virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool bAddToMRU, bool bMakeVisible); virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool addToMostRecentFileList, bool bMakeVisible);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool bMakeVisible = TRUE); virtual CCryEditDoc* OpenDocumentFile(const char* lpszPathName, bool bMakeVisible = TRUE);
virtual Confidence MatchDocType(const char* lpszPathName, CCryEditDoc*& rpDocMatch); virtual Confidence MatchDocType(const char* lpszPathName, CCryEditDoc*& rpDocMatch);
@ -468,7 +476,7 @@ public:
virtual void OnFileNew(); virtual void OnFileNew();
virtual bool DoPromptFileName(QString& fileName, UINT nIDSTitle, virtual bool DoPromptFileName(QString& fileName, UINT nIDSTitle,
DWORD lFlags, bool bOpenFileDialog, CDocTemplate* pTemplate); DWORD lFlags, bool bOpenFileDialog, CDocTemplate* pTemplate);
virtual CCryEditDoc* OpenDocumentFile(const char* lpszFileName, bool bAddToMRU); virtual CCryEditDoc* OpenDocumentFile(const char* filename, bool addToMostRecentFileList, COpenSameLevelOptions openSameLevelOptions = COpenSameLevelOptions::NotReopenIfSame);
QVector<CCrySingleDocTemplate*> m_templateList; QVector<CCrySingleDocTemplate*> m_templateList;
}; };

@ -143,20 +143,11 @@ namespace
return false; return false;
} }
} }
const bool addToMostRecentFileList = false;
auto newDocument = CCryEditApp::instance()->OpenDocumentFile(levelPath.toUtf8().data(),
addToMostRecentFileList, COpenSameLevelOptions::ReopenLevelIfSame);
auto previousDocument = GetIEditor()->GetDocument(); return newDocument != nullptr && !newDocument->IsLevelLoadFailed();
QString previousPathName = (previousDocument != nullptr) ? previousDocument->GetLevelPathName() : "";
auto newDocument = CCryEditApp::instance()->OpenDocumentFile(levelPath.toUtf8().data());
// the underlying document pointer doesn't change, so we can't check that; use the path name's instead
bool result = true;
if (newDocument == nullptr || newDocument->IsLevelLoadFailed() || (newDocument->GetLevelPathName() == previousPathName))
{
result = false;
}
return result;
} }
bool PyOpenLevelNoPrompt(const char* pLevelName) bool PyOpenLevelNoPrompt(const char* pLevelName)
@ -407,6 +398,11 @@ inline namespace Commands
{ {
return AZ::Debug::Trace::WaitForDebugger(timeoutSeconds); return AZ::Debug::Trace::WaitForDebugger(timeoutSeconds);
} }
AZStd::string PyGetFileAlias(AZStd::string alias)
{
return AZ::IO::FileIOBase::GetInstance()->GetAlias(alias.c_str());
}
} }
namespace AzToolsFramework namespace AzToolsFramework
@ -457,6 +453,8 @@ namespace AzToolsFramework
addLegacyGeneral(behaviorContext->Method("attach_debugger", PyAttachDebugger, nullptr, "Prompts for attaching the debugger")); addLegacyGeneral(behaviorContext->Method("attach_debugger", PyAttachDebugger, nullptr, "Prompts for attaching the debugger"));
addLegacyGeneral(behaviorContext->Method("wait_for_debugger", PyWaitForDebugger, behaviorContext->MakeDefaultValues(-1.f), "Pauses this thread execution until the debugger has been attached")); addLegacyGeneral(behaviorContext->Method("wait_for_debugger", PyWaitForDebugger, behaviorContext->MakeDefaultValues(-1.f), "Pauses this thread execution until the debugger has been attached"));
addLegacyGeneral(behaviorContext->Method("get_file_alias", PyGetFileAlias, nullptr, "Retrieves path for IO alias"));
// this will put these methods into the 'azlmbr.legacy.checkout_dialog' module // this will put these methods into the 'azlmbr.legacy.checkout_dialog' module
auto addCheckoutDialog = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder) auto addCheckoutDialog = [](AZ::BehaviorContext::GlobalMethodBuilder methodBuilder)
{ {

@ -75,7 +75,10 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook) ->Field("CaptureCursorLook", &CameraMovementSettings::m_captureCursorLook)
->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted) ->Field("OrbitYawRotationInverted", &CameraMovementSettings::m_orbitYawRotationInverted)
->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX) ->Field("PanInvertedX", &CameraMovementSettings::m_panInvertedX)
->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY); ->Field("PanInvertedY", &CameraMovementSettings::m_panInvertedY)
->Field("DefaultPositionX", &CameraMovementSettings::m_defaultCameraPositionX)
->Field("DefaultPositionY", &CameraMovementSettings::m_defaultCameraPositionY)
->Field("DefaultPositionZ", &CameraMovementSettings::m_defaultCameraPositionZ);
serialize.Class<CameraInputSettings>() serialize.Class<CameraInputSettings>()
->Version(2) ->Version(2)
@ -154,7 +157,16 @@ void CEditorPreferencesPage_ViewportCamera::Reflect(AZ::SerializeContext& serial
"Invert direction of pan in local Y axis") "Invert direction of pan in local Y axis")
->DataElement( ->DataElement(
AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor", AZ::Edit::UIHandlers::CheckBox, &CameraMovementSettings::m_captureCursorLook, "Camera Capture Look Cursor",
"Should the cursor be captured (hidden) while performing free look"); "Should the cursor be captured (hidden) while performing free look")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionX, "Default X Camera Position",
"Default X Camera Position when a level is opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionY, "Default Y Camera Position",
"Default Y Camera Position when a level is opened")
->DataElement(
AZ::Edit::UIHandlers::SpinBox, &CameraMovementSettings::m_defaultCameraPositionZ, "Default Z Camera Position",
"Default Z Camera Position when a level is opened");
editContext->Class<CameraInputSettings>("Camera Input Settings", "") editContext->Class<CameraInputSettings>("Camera Input Settings", "")
->DataElement( ->DataElement(
@ -271,6 +283,12 @@ void CEditorPreferencesPage_ViewportCamera::OnApply()
SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted); SandboxEditor::SetCameraOrbitYawRotationInverted(m_cameraMovementSettings.m_orbitYawRotationInverted);
SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX); SandboxEditor::SetCameraPanInvertedX(m_cameraMovementSettings.m_panInvertedX);
SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY); SandboxEditor::SetCameraPanInvertedY(m_cameraMovementSettings.m_panInvertedY);
SandboxEditor::SetDefaultCameraEditorPosition(
AZ::Vector3(
m_cameraMovementSettings.m_defaultCameraPositionX,
m_cameraMovementSettings.m_defaultCameraPositionY,
m_cameraMovementSettings.m_defaultCameraPositionZ
));
SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId); SandboxEditor::SetCameraTranslateForwardChannelId(m_cameraInputSettings.m_translateForwardChannelId);
SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId); SandboxEditor::SetCameraTranslateBackwardChannelId(m_cameraInputSettings.m_translateBackwardChannelId);
@ -308,6 +326,11 @@ void CEditorPreferencesPage_ViewportCamera::InitializeSettings()
m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX(); m_cameraMovementSettings.m_panInvertedX = SandboxEditor::CameraPanInvertedX();
m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY(); m_cameraMovementSettings.m_panInvertedY = SandboxEditor::CameraPanInvertedY();
AZ::Vector3 defaultCameraPosition = SandboxEditor::DefaultEditorCameraPosition();
m_cameraMovementSettings.m_defaultCameraPositionX = defaultCameraPosition.GetX();
m_cameraMovementSettings.m_defaultCameraPositionY = defaultCameraPosition.GetY();
m_cameraMovementSettings.m_defaultCameraPositionZ = defaultCameraPosition.GetZ();
m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName(); m_cameraInputSettings.m_translateForwardChannelId = SandboxEditor::CameraTranslateForwardChannelId().GetName();
m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName(); m_cameraInputSettings.m_translateBackwardChannelId = SandboxEditor::CameraTranslateBackwardChannelId().GetName();
m_cameraInputSettings.m_translateLeftChannelId = SandboxEditor::CameraTranslateLeftChannelId().GetName(); m_cameraInputSettings.m_translateLeftChannelId = SandboxEditor::CameraTranslateLeftChannelId().GetName();

@ -57,6 +57,9 @@ private:
bool m_orbitYawRotationInverted; bool m_orbitYawRotationInverted;
bool m_panInvertedX; bool m_panInvertedX;
bool m_panInvertedY; bool m_panInvertedY;
float m_defaultCameraPositionX;
float m_defaultCameraPositionY;
float m_defaultCameraPositionZ;
AZ::Crc32 RotateSmoothingVisibility() const AZ::Crc32 RotateSmoothingVisibility() const
{ {

@ -52,6 +52,9 @@ namespace SandboxEditor
constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId"; constexpr AZStd::string_view CameraOrbitDollyIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitDollyId";
constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId"; constexpr AZStd::string_view CameraOrbitPanIdSetting = "/Amazon/Preferences/Editor/Camera/OrbitPanId";
constexpr AZStd::string_view CameraFocusIdSetting = "/Amazon/Preferences/Editor/Camera/FocusId"; constexpr AZStd::string_view CameraFocusIdSetting = "/Amazon/Preferences/Editor/Camera/FocusId";
constexpr AZStd::string_view CameraDefaultStartingPositionX = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/x";
constexpr AZStd::string_view CameraDefaultStartingPositionY = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/y";
constexpr AZStd::string_view CameraDefaultStartingPositionZ = "/Amazon/Preferences/Editor/Camera/DefaultStartingPosition/z";
template<typename T> template<typename T>
void SetRegistry(const AZStd::string_view setting, T&& value) void SetRegistry(const AZStd::string_view setting, T&& value)
@ -111,6 +114,21 @@ namespace SandboxEditor
return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>(); return AZStd::make_unique<EditorViewportSettingsCallbacksImpl>();
} }
AZ::Vector3 DefaultEditorCameraPosition()
{
float xPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionX, 0.0));
float yPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionY, -10.0));
float zPosition = aznumeric_cast<float>(GetRegistry(CameraDefaultStartingPositionZ, 4.0));
return AZ::Vector3(xPosition, yPosition, zPosition);
}
void SetDefaultCameraEditorPosition(const AZ::Vector3 defaultCameraPosition)
{
SetRegistry(CameraDefaultStartingPositionX, defaultCameraPosition.GetX());
SetRegistry(CameraDefaultStartingPositionY, defaultCameraPosition.GetY());
SetRegistry(CameraDefaultStartingPositionZ, defaultCameraPosition.GetZ());
}
AZ::u64 MaxItemsShownInAssetBrowserSearch() AZ::u64 MaxItemsShownInAssetBrowserSearch()
{ {
return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50)); return GetRegistry(AssetBrowserMaxItemsShownInSearchSetting, aznumeric_cast<AZ::u64>(50));

@ -12,6 +12,7 @@
#include <AzCore/Settings/SettingsRegistry.h> #include <AzCore/Settings/SettingsRegistry.h>
#include <AzCore/std/smart_ptr/unique_ptr.h> #include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Math/Vector3.h>
#include <AzFramework/Input/Channels/InputChannelId.h> #include <AzFramework/Input/Channels/InputChannelId.h>
namespace SandboxEditor namespace SandboxEditor
@ -32,6 +33,9 @@ namespace SandboxEditor
//! event will fire when a value in the settings registry (editorpreferences.setreg) is modified. //! event will fire when a value in the settings registry (editorpreferences.setreg) is modified.
SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks(); SANDBOX_API AZStd::unique_ptr<EditorViewportSettingsCallbacks> CreateEditorViewportSettingsCallbacks();
SANDBOX_API AZ::Vector3 DefaultEditorCameraPosition();
SANDBOX_API void SetDefaultCameraEditorPosition(AZ::Vector3 defaultCameraPosition);
SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch(); SANDBOX_API AZ::u64 MaxItemsShownInAssetBrowserSearch();
SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown); SANDBOX_API void SetMaxItemsShownInAssetBrowserSearch(AZ::u64 numberOfItemsShown);

@ -112,7 +112,6 @@ void StartFixedCursorMode(QObject *viewport);
#define RENDER_MESH_TEST_DISTANCE (0.2f) #define RENDER_MESH_TEST_DISTANCE (0.2f)
#define CURSOR_FONT_HEIGHT 8.0f #define CURSOR_FONT_HEIGHT 8.0f
namespace AZ::ViewportHelpers namespace AZ::ViewportHelpers
{ {
static const char TextCantCreateCameraNoLevel[] = "Cannot create camera when no level is loaded."; static const char TextCantCreateCameraNoLevel[] = "Cannot create camera when no level is loaded.";
@ -623,16 +622,10 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
PopDisableRendering(); PopDisableRendering();
{ {
AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
float sx = terrainAabb.GetXExtent();
float sy = terrainAabb.GetYExtent();
Matrix34 viewTM; Matrix34 viewTM;
viewTM.SetIdentity(); viewTM.SetIdentity();
// Initial camera will be at middle of the map at the height of 2
// meters above the terrain (default terrain height is 32) viewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f));
SetViewTM(viewTM); SetViewTM(viewTM);
UpdateScene(); UpdateScene();
@ -647,16 +640,10 @@ void EditorViewportWidget::OnEditorNotifyEvent(EEditorNotifyEvent event)
PopDisableRendering(); PopDisableRendering();
{ {
AZ::Aabb terrainAabb = AZ::Aabb::CreateFromPoint(AZ::Vector3::CreateZero());
AzFramework::Terrain::TerrainDataRequestBus::BroadcastResult(terrainAabb, &AzFramework::Terrain::TerrainDataRequests::GetTerrainAabb);
float sx = terrainAabb.GetXExtent();
float sy = terrainAabb.GetYExtent();
Matrix34 viewTM; Matrix34 viewTM;
viewTM.SetIdentity(); viewTM.SetIdentity();
// Initial camera will be at middle of the map at the height of 2
// meters above the terrain (default terrain height is 32) viewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
viewTM.SetTranslation(Vec3(sx * 0.5f, sy * 0.5f, 34.0f));
SetViewTM(viewTM); SetViewTM(viewTM);
} }
break; break;
@ -1345,10 +1332,6 @@ void EditorViewportWidget::keyPressEvent(QKeyEvent* event)
void EditorViewportWidget::SetViewTM(const Matrix34& tm) void EditorViewportWidget::SetViewTM(const Matrix34& tm)
{ {
if (m_viewSourceType == ViewSourceType::None)
{
m_defaultViewTM = tm;
}
SetViewTM(tm, false); SetViewTM(tm, false);
} }
@ -1445,6 +1428,10 @@ void EditorViewportWidget::SetViewTM(const Matrix34& camMatrix, bool bMoveOnly)
"Please report this as a bug." "Please report this as a bug."
); );
} }
else if (shouldUpdateObject == ShouldUpdateObject::No)
{
GetCurrentAtomView()->SetCameraTransform(LYTransformToAZMatrix3x4(camMatrix));
}
if (m_pressedKeyState == KeyPressedState::PressedThisFrame) if (m_pressedKeyState == KeyPressedState::PressedThisFrame)
{ {
@ -2028,6 +2015,9 @@ void EditorViewportWidget::SetDefaultCamera()
m_viewSourceType = ViewSourceType::None; m_viewSourceType = ViewSourceType::None;
GetViewManager()->SetCameraObjectId(GUID_NULL); GetViewManager()->SetCameraObjectId(GUID_NULL);
SetName(m_defaultViewName); SetName(m_defaultViewName);
// Set the default Editor Camera position.
m_defaultViewTM.SetTranslation(Vec3(m_editorViewportSettings.DefaultEditorCameraPosition()));
SetViewTM(m_defaultViewTM); SetViewTM(m_defaultViewTM);
// Synchronize the configured editor viewport FOV to the default camera // Synchronize the configured editor viewport FOV to the default camera
@ -2530,6 +2520,11 @@ bool EditorViewportSettings::StickySelectEnabled() const
return SandboxEditor::StickySelectEnabled(); return SandboxEditor::StickySelectEnabled();
} }
AZ::Vector3 EditorViewportSettings::DefaultEditorCameraPosition() const
{
return SandboxEditor::DefaultEditorCameraPosition();
}
AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once); AZ_CVAR_EXTERNED(bool, ed_previewGameInFullscreen_once);
bool EditorViewportWidget::ShouldPreviewFullscreen() const bool EditorViewportWidget::ShouldPreviewFullscreen() const
@ -2641,5 +2636,4 @@ void EditorViewportWidget::StopFullscreenPreview()
// Show the main window // Show the main window
MainWindow::instance()->show(); MainWindow::instance()->show();
} }
#include <moc_EditorViewportWidget.cpp> #include <moc_EditorViewportWidget.cpp>

@ -78,6 +78,7 @@ struct EditorViewportSettings : public AzToolsFramework::ViewportInteraction::Vi
float ManipulatorLineBoundWidth() const override; float ManipulatorLineBoundWidth() const override;
float ManipulatorCircleBoundWidth() const override; float ManipulatorCircleBoundWidth() const override;
bool StickySelectEnabled() const override; bool StickySelectEnabled() const override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
}; };
// EditorViewportWidget window // EditorViewportWidget window

@ -31,11 +31,11 @@
#include <AzToolsFramework/Entity/EditorEntityContextBus.h> #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
#define MUSIC_LEVEL_LIBRARY_FILE "Music.xml" #define MUSIC_LEVEL_LIBRARY_FILE "music.xml"
#define MATERIAL_LEVEL_LIBRARY_FILE "Materials.xml" #define MATERIAL_LEVEL_LIBRARY_FILE "materials.xml"
#define RESOURCE_LIST_FILE "ResourceList.txt" #define RESOURCE_LIST_FILE "resourcelist.txt"
#define USED_RESOURCE_LIST_FILE "UsedResourceList.txt" #define USED_RESOURCE_LIST_FILE "usedresourcelist.txt"
#define SHADER_LIST_FILE "ShadersList.txt" #define SHADER_LIST_FILE "shaderslist.txt"
#define GetAValue(rgb) ((BYTE)((rgb) >> 24)) #define GetAValue(rgb) ((BYTE)((rgb) >> 24))
@ -185,9 +185,9 @@ bool CGameExporter::Export(unsigned int flags, [[maybe_unused]] EEndian eExportE
ExportOcclusionMesh(sLevelPath.toUtf8().data()); ExportOcclusionMesh(sLevelPath.toUtf8().data());
//! Export Level data. //! Export Level data.
CLogFile::WriteLine("Exporting LevelData.xml"); CLogFile::WriteLine("Exporting leveldata.xml");
ExportLevelData(sLevelPath); ExportLevelData(sLevelPath);
CLogFile::WriteLine("Exporting LevelData.xml done."); CLogFile::WriteLine("Exporting leveldata.xml done.");
ExportLevelInfo(sLevelPath); ExportLevelInfo(sLevelPath);
@ -266,26 +266,26 @@ void CGameExporter::ExportOcclusionMesh(const char* pszGamePath)
void CGameExporter::ExportLevelData(const QString& path, bool /*bExportMission*/) void CGameExporter::ExportLevelData(const QString& path, bool /*bExportMission*/)
{ {
IEditor* pEditor = GetIEditor(); IEditor* pEditor = GetIEditor();
pEditor->SetStatusText(QObject::tr("Exporting LevelData.xml...")); pEditor->SetStatusText(QObject::tr("Exporting leveldata.xml..."));
char versionString[256]; char versionString[256];
pEditor->GetFileVersion().ToString(versionString); pEditor->GetFileVersion().ToString(versionString);
XmlNodeRef root = XmlHelpers::CreateXmlNode("LevelData"); XmlNodeRef root = XmlHelpers::CreateXmlNode("leveldata");
root->setAttr("SandboxVersion", versionString); root->setAttr("SandboxVersion", versionString);
XmlNodeRef rootAction = XmlHelpers::CreateXmlNode("LevelDataAction"); XmlNodeRef rootAction = XmlHelpers::CreateXmlNode("leveldataaction");
rootAction->setAttr("SandboxVersion", versionString); rootAction->setAttr("SandboxVersion", versionString);
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Save Level Data XML // Save Level Data XML
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
QString levelDataFile = path + "LevelData.xml"; QString levelDataFile = path + "leveldata.xml";
XmlString xmlData = root->getXML(); XmlString xmlData = root->getXML();
CCryMemFile file; CCryMemFile file;
file.Write(xmlData.c_str(), static_cast<unsigned int>(xmlData.length())); file.Write(xmlData.c_str(), static_cast<unsigned int>(xmlData.length()));
m_levelPak.m_pakFile.UpdateFile(levelDataFile.toUtf8().data(), file); m_levelPak.m_pakFile.UpdateFile(levelDataFile.toUtf8().data(), file);
QString levelDataActionFile = path + "LevelDataAction.xml"; QString levelDataActionFile = path + "leveldataaction.xml";
XmlString xmlDataAction = rootAction->getXML(); XmlString xmlDataAction = rootAction->getXML();
CCryMemFile fileAction; CCryMemFile fileAction;
fileAction.Write(xmlDataAction.c_str(), static_cast<unsigned int>(xmlDataAction.length())); fileAction.Write(xmlDataAction.c_str(), static_cast<unsigned int>(xmlDataAction.length()));
@ -298,7 +298,7 @@ void CGameExporter::ExportLevelData(const QString& path, bool /*bExportMission*/
if (savedEntities) if (savedEntities)
{ {
QString entitiesFile; QString entitiesFile;
entitiesFile = QStringLiteral("%1%2.entities_xml").arg(path, "Mission0"); entitiesFile = QStringLiteral("%1%2.entities_xml").arg(path, "mission0");
m_levelPak.m_pakFile.UpdateFile(entitiesFile.toUtf8().data(), entitySaveBuffer.begin(), static_cast<int>(entitySaveBuffer.size())); m_levelPak.m_pakFile.UpdateFile(entitiesFile.toUtf8().data(), entitySaveBuffer.begin(), static_cast<int>(entitySaveBuffer.size()));
} }
} }
@ -326,7 +326,7 @@ void CGameExporter::ExportLevelInfo(const QString& path)
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// Save LevelInfo file. // Save LevelInfo file.
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
QString filename = path + "LevelInfo.xml"; QString filename = path + "levelinfo.xml";
XmlString xmlData = root->getXML(); XmlString xmlData = root->getXML();
CCryMemFile file; CCryMemFile file;

@ -38,6 +38,8 @@ namespace UnitTest
void BeginCursorCapture() override; void BeginCursorCapture() override;
void EndCursorCapture() override; void EndCursorCapture() override;
bool IsMouseOver() const override; bool IsMouseOver() const override;
void SetOverrideCursor(AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride) override;
void ClearOverrideCursor() override;
private: private:
AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr; AzToolsFramework::QtEventToAzInputMapper* m_inputChannelMapper = nullptr;
@ -58,6 +60,17 @@ namespace UnitTest
return true; return true;
} }
void ViewportMouseCursorRequestImpl::SetOverrideCursor(
[[maybe_unused]] AzToolsFramework::ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
// noop
}
void ViewportMouseCursorRequestImpl::ClearOverrideCursor()
{
// noop
}
class ModularViewportCameraControllerFixture : public AllocatorsTestFixture class ModularViewportCameraControllerFixture : public AllocatorsTestFixture
{ {
public: public:

@ -76,6 +76,7 @@ namespace AZ
else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown) else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_ProviderIsDown)
{ {
message = "Failed to put entries/dependencies into source control as the provider is not available.\n"; message = "Failed to put entries/dependencies into source control as the provider is not available.\n";
reportAsWarning = true;
} }
else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid) else if (info.m_status == AzToolsFramework::SourceControlStatus::SCS_CertificateInvalid)
{ {

@ -192,82 +192,101 @@ namespace AZ
}; };
//! SettingsRegistry notifier handler which updates relevant registry settings based //! SettingsRegistry notifier handler which is responsible for loading
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key. //! the project.json file at the new project path
struct UpdateProjectSettingsEventHandler //! if an update to '<BootstrapSettingsRootKey>/project_path' key occurs.
struct ProjectPathChangedEventHandler
{ {
UpdateProjectSettingsEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine) ProjectPathChangedEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry } : m_registry{ registry }
, m_commandLine{ commandLine }
{ {
} }
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type) void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{ {
// Update the project settings when the project path is set
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString; using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// #1 Update the project settings when the project path is set
const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path"; const auto projectPathKey = FixedValueString(AZ::SettingsRegistryMergeUtils::BootstrapSettingsRootKey) + "/project_path";
AZ::IO::FixedMaxPath newProjectPath; AZ::IO::FixedMaxPath newProjectPath;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path) if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectPathKey, path)
&& m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath) && m_registry.Get(newProjectPath.Native(), projectPathKey) && newProjectPath != m_oldProjectPath)
{ {
UpdateProjectSettingsFromProjectPath(AZ::IO::PathView(newProjectPath)); // Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
projectMetadataFile /= "project.json";
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
} }
}
private:
AZ::IO::FixedMaxPath m_oldProjectPath;
AZ::SettingsRegistryInterface& m_registry;
};
// #2 Update the project specialization when the project name is set //! SettingsRegistry notifier handler which adds the project name as a specialization tag
//! to the registry
//! if an update to '<ProjectSettingsRootKey>/project_name' key occurs.
struct ProjectNameChangedEventHandler
{
ProjectNameChangedEventHandler(AZ::SettingsRegistryInterface& registry)
: m_registry{ registry }
{
}
void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{
// Update the project specialization when the project name is set
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name"; const auto projectNameKey = FixedValueString(AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey) + "/project_name";
FixedValueString newProjectName; FixedValueString newProjectName;
if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path) if (SettingsRegistryMergeUtils::IsPathAncestorDescendantOrEqual(projectNameKey, path)
&& m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName) && m_registry.Get(newProjectName, projectNameKey) && newProjectName != m_oldProjectName)
{ {
UpdateProjectSpecializationFromProjectName(newProjectName); // Add the project_name as a specialization for loading the build system dependency .setreg files
} auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
// #3 Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
if (path == AZ::SettingsRegistryMergeUtils::CommandLineValueChangedKey) m_oldProjectName.c_str());
{ m_registry.Remove(oldProjectNameSpecialization);
UpdateCommandLine(); m_oldProjectName = newProjectName;
m_registry.Set(newProjectNameSpecialization, true);
} }
} }
//! Add the project name as a specialization underneath the /Amazon/AzCore/Settings/Specializations path private:
//! and remove the current project name specialization if one exists. AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
void UpdateProjectSpecializationFromProjectName(AZStd::string_view newProjectName) AZ::SettingsRegistryInterface& m_registry;
{ };
using FixedValueString = AZ::SettingsRegistryInterface::FixedValueString;
// Add the project_name as a specialization for loading the build system dependency .setreg files
auto newProjectNameSpecialization = FixedValueString::format("%s/%.*s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
aznumeric_cast<int>(newProjectName.size()), newProjectName.data());
auto oldProjectNameSpecialization = FixedValueString::format("%s/%s", AZ::SettingsRegistryMergeUtils::SpecializationsRootKey,
m_oldProjectName.c_str());
m_registry.Remove(oldProjectNameSpecialization);
m_oldProjectName = newProjectName;
m_registry.Set(newProjectNameSpecialization, true);
}
void UpdateProjectSettingsFromProjectPath(AZ::IO::PathView newProjectPath) //! SettingsRegistry notifier handler which updates relevant registry settings based
//! on an update to '/Amazon/AzCore/Bootstrap/project_path' key.
struct UpdateCommandLineEventHandler
{
UpdateCommandLineEventHandler(AZ::SettingsRegistryInterface& registry, AZ::CommandLine& commandLine)
: m_registry{ registry }
, m_commandLine{ commandLine }
{ {
// Update old Project path before attempting to merge in new Settings Registry values in order to prevent recursive calls
m_oldProjectPath = newProjectPath;
// Merge the project.json file into settings registry under ProjectSettingsRootKey path.
AZ::IO::FixedMaxPath projectMetadataFile{ AZ::SettingsRegistryMergeUtils::FindEngineRoot(m_registry) / newProjectPath };
projectMetadataFile /= "project.json";
m_registry.MergeSettingsFile(projectMetadataFile.Native(),
AZ::SettingsRegistryInterface::Format::JsonMergePatch, AZ::SettingsRegistryMergeUtils::ProjectSettingsRootKey);
// Update all the runtime file paths based on the new "project_path" value.
AZ::SettingsRegistryMergeUtils::MergeSettingsToRegistry_AddRuntimeFilePaths(m_registry);
} }
void UpdateCommandLine() void operator()(AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{ {
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine); // Update the ComponentApplication CommandLine instance when the command line settings are merged into the Settings Registry
if (path == AZ::SettingsRegistryMergeUtils::CommandLineValueChangedKey)
{
AZ::SettingsRegistryMergeUtils::GetCommandLineFromRegistry(m_registry, m_commandLine);
}
} }
private: private:
AZ::IO::FixedMaxPath m_oldProjectPath;
AZ::SettingsRegistryInterface::FixedValueString m_oldProjectName;
AZ::SettingsRegistryInterface& m_registry; AZ::SettingsRegistryInterface& m_registry;
AZ::CommandLine& m_commandLine; AZ::CommandLine& m_commandLine;
}; };
@ -462,7 +481,12 @@ namespace AZ
// 1. The 'project_path' key changes // 1. The 'project_path' key changes
// 2. The project specialization when the 'project-name' key changes // 2. The project specialization when the 'project-name' key changes
// 3. The ComponentApplication command line when the command line is stored to the registry // 3. The ComponentApplication command line when the command line is stored to the registry
m_projectChangedHandler = m_settingsRegistry->RegisterNotifier(UpdateProjectSettingsEventHandler{ *m_settingsRegistry, m_commandLine }); m_projectPathChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectPathChangedEventHandler{
*m_settingsRegistry });
m_projectNameChangedHandler = m_settingsRegistry->RegisterNotifier(ProjectNameChangedEventHandler{
*m_settingsRegistry });
m_commandLineUpdatedHandler = m_settingsRegistry->RegisterNotifier(UpdateCommandLineEventHandler{
*m_settingsRegistry, m_commandLine });
// Merge Command Line arguments // Merge Command Line arguments
constexpr bool executeRegDumpCommands = false; constexpr bool executeRegDumpCommands = false;
@ -515,11 +539,12 @@ namespace AZ
Destroy(); Destroy();
} }
// The m_projectChangedHandler stores an AZStd::function internally // The SettingsRegistry Notify handlers stores an AZStd::function internally
// which allocates using the AZ SystemAllocator // which may allocates using the AZ SystemAllocator(if the functor > 16 bytes)
// m_projectChangedHandler is being default value initialized // The handlers are being default value initialized to clear out the AZStd::function
// to clear out the AZStd::function m_commandLineUpdatedHandler = {};
m_projectChangedHandler = {}; m_projectNameChangedHandler = {};
m_projectPathChangedHandler = {};
// Delete the AZ::IConsole if it was created by this application instance // Delete the AZ::IConsole if it was created by this application instance
if (m_ownsConsole) if (m_ownsConsole)

@ -390,7 +390,9 @@ namespace AZ
AZ::IO::FixedMaxPath m_engineRoot; AZ::IO::FixedMaxPath m_engineRoot;
AZ::IO::FixedMaxPath m_appRoot; AZ::IO::FixedMaxPath m_appRoot;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectChangedHandler; AZ::SettingsRegistryInterface::NotifyEventHandler m_projectPathChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_projectNameChangedHandler;
AZ::SettingsRegistryInterface::NotifyEventHandler m_commandLineUpdatedHandler;
// ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console // ConsoleFunctorHandle is responsible for unregistering the Settings Registry Console
// from the m_console member when it goes out of scope // from the m_console member when it goes out of scope

@ -639,9 +639,10 @@ namespace AZ
{ {
// Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey // Make sure the there is a JSON object at the ConsoleRuntimeCommandKey or ConsoleAutoexecKey
// So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects) // So that JSON Patch is able to add values underneath that object (JSON Patch doesn't create intermediate objects)
settingsRegistry.MergeSettings(R"({ "Amazon": { "AzCore": { "Runtime": { "ConsoleCommands": {} } } })" settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
R"(,"O3DE": { "Autoexec": { "ConsoleCommands": {} } } })", IConsole::ConsoleRuntimeCommandKey);
SettingsRegistryInterface::Format::JsonMergePatch); settingsRegistry.MergeSettings(R"({})", SettingsRegistryInterface::Format::JsonMergePatch,
IConsole::ConsoleAutoexecCommandKey);
m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this }); m_consoleCommandKeyHandler = settingsRegistry.RegisterNotifier(ConsoleCommandKeyNotificationHandler{ settingsRegistry, *this });
JsonApplyPatchSettings applyPatchSettings; JsonApplyPatchSettings applyPatchSettings;

@ -577,7 +577,9 @@ namespace AZ
} }
azstrcat(lines[i], AZ_ARRAY_SIZE(lines[i]), "\n"); azstrcat(lines[i], AZ_ARRAY_SIZE(lines[i]), "\n");
AZ_Printf(window, "%s", lines[i]); // feed back into the trace system so that listeners can get it. // Use Output instead of AZ_Printf to be consistent with the exception output code and avoid
// this accidentally being suppressed as a normal message
Output(window, lines[i]);
} }
} }
} }

@ -0,0 +1,254 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/Json/JsonImporter.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
namespace AZ
{
JsonSerializationResult::ResultCode JsonImportResolver::ResolveNestedImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element)
{
using namespace JsonSerializationResult;
for (auto& path : importPathStack)
{
if (importPath == path)
{
return settings.m_reporting(
AZStd::string::format("'%s' was already imported in this chain. This indicates a cyclic dependency.", importPath.c_str()),
ResultCode(Tasks::Import, Outcomes::Catastrophic), element);
}
}
importPathStack.push_back(importPath);
AZ::StackedString importElement(AZ::StackedString::Format::JsonPointer);
JsonImportSettings nestedImportSettings;
nestedImportSettings.m_importer = settings.m_importer;
nestedImportSettings.m_reporting = settings.m_reporting;
nestedImportSettings.m_resolveFlags = ImportTracking::Dependencies;
ResultCode result = ResolveImports(jsonDoc, allocator, importPathStack, nestedImportSettings, importElement);
importPathStack.pop_back();
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode JsonImportResolver::ResolveImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, StackedString& element)
{
using namespace JsonSerializationResult;
if (jsonDoc.IsObject())
{
for (auto& field : jsonDoc.GetObject())
{
if(strncmp(field.name.GetString(), JsonSerialization::ImportDirectiveIdentifier, field.name.GetStringLength()) == 0)
{
const rapidjson::Value& importDirective = field.value;
AZ::IO::FixedMaxPath importAbsPath = importPathStack.back();
importAbsPath.RemoveFilename();
AZStd::string importName;
if (importDirective.IsObject())
{
auto filenameField = importDirective.FindMember("filename");
if (filenameField != importDirective.MemberEnd())
{
importName = AZStd::string(filenameField->value.GetString(), filenameField->value.GetStringLength());
}
}
else
{
importName = AZStd::string(importDirective.GetString(), importDirective.GetStringLength());
}
importAbsPath.Append(importName);
rapidjson::Value patch;
ResultCode resolveResult = settings.m_importer->ResolveImport(&jsonDoc, patch, importDirective, importAbsPath, allocator);
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
{
return resolveResult;
}
if ((settings.m_resolveFlags & ImportTracking::Imports) == ImportTracking::Imports)
{
rapidjson::Pointer path(element.Get().data(), element.Get().size());
settings.m_importer->AddImportDirective(path, importName);
}
if ((settings.m_resolveFlags & ImportTracking::Dependencies) == ImportTracking::Dependencies)
{
settings.m_importer->AddImportedFile(importAbsPath.String());
}
ResultCode result = ResolveNestedImports(jsonDoc, allocator, importPathStack, settings, importAbsPath, element);
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
settings.m_importer->ApplyPatch(jsonDoc, patch, allocator);
}
else if (field.value.IsObject() || field.value.IsArray())
{
ScopedStackedString entryName(element, AZStd::string_view(field.name.GetString(), field.name.GetStringLength()));
ResultCode result = ResolveImports(field.value, allocator, importPathStack, settings, element);
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
}
}
}
else if(jsonDoc.IsArray())
{
int index = 0;
for (rapidjson::Value::ValueIterator elem = jsonDoc.Begin(); elem != jsonDoc.End(); ++elem, ++index)
{
if (!elem->IsObject() && !elem->IsArray())
{
continue;
}
ScopedStackedString entryName(element, index);
ResultCode result = ResolveImports(*elem, allocator, importPathStack, settings, element);
if (result.GetOutcome() == Outcomes::Catastrophic)
{
return result;
}
}
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode JsonImportResolver::RestoreImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
{
using namespace JsonSerializationResult;
if (jsonDoc.IsObject() || jsonDoc.IsArray())
{
const BaseJsonImporter::ImportDirectivesList& importDirectives = settings.m_importer->GetImportDirectives();
for (auto& import : importDirectives)
{
rapidjson::Pointer importPtr = import.first;
rapidjson::Value* currentValue = importPtr.Get(jsonDoc);
rapidjson::Value importedValue(rapidjson::kObjectType);
importedValue.AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(import.second.c_str()), allocator);
ResultCode resolveResult = JsonSerialization::ResolveImports(importedValue, allocator, settings);
if (resolveResult.GetOutcome() == Outcomes::Catastrophic)
{
return resolveResult;
}
rapidjson::Value patch;
settings.m_importer->CreatePatch(patch, importedValue, *currentValue, allocator);
settings.m_importer->RestoreImport(currentValue, patch, allocator, import.second);
}
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective,
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator)
{
using namespace JsonSerializationResult;
auto importedObject = JsonSerializationUtils::ReadJsonFile(importedFilePath.Native());
if (importedObject.IsSuccess())
{
rapidjson::Value& importedDoc = importedObject.GetValue();
if (importDirective.IsObject())
{
auto patchField = importDirective.FindMember("patch");
if (patchField != importDirective.MemberEnd())
{
patch.CopyFrom(patchField->value, allocator);
}
}
importPtr->CopyFrom(importedDoc, allocator);
}
else
{
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::RestoreImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator, const AZStd::string& importFilename)
{
using namespace JsonSerializationResult;
importPtr->SetObject();
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
{
rapidjson::Value importDirective(rapidjson::kObjectType);
importDirective.AddMember(rapidjson::StringRef("filename"), rapidjson::StringRef(importFilename.c_str()), allocator);
importDirective.AddMember(rapidjson::StringRef("patch"), patch, allocator);
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), importDirective, allocator);
}
else
{
importPtr->AddMember(rapidjson::StringRef(JsonSerialization::ImportDirectiveIdentifier), rapidjson::StringRef(importFilename.c_str()), allocator);
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::ApplyPatch(rapidjson::Value& target,
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator)
{
using namespace JsonSerializationResult;
if ((patch.IsObject() && patch.MemberCount() > 0) || (patch.IsArray() && !patch.Empty()))
{
return AZ::JsonSerialization::ApplyPatch(target, allocator, patch, JsonMergeApproach::JsonMergePatch);
}
return ResultCode(Tasks::Import, Outcomes::Success);
}
JsonSerializationResult::ResultCode BaseJsonImporter::CreatePatch(rapidjson::Value& patch,
const rapidjson::Value& source, const rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator)
{
return JsonSerialization::CreatePatch(patch, allocator, source, target, JsonMergeApproach::JsonMergePatch);
}
void BaseJsonImporter::AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile)
{
m_importDirectives.emplace_back(jsonPtr, AZStd::move(importFile));
}
void BaseJsonImporter::AddImportedFile(AZStd::string importedFile)
{
m_importedFiles.insert(AZStd::move(importedFile));
}
const BaseJsonImporter::ImportDirectivesList& BaseJsonImporter::GetImportDirectives()
{
return m_importDirectives;
}
const BaseJsonImporter::ImportedFilesList& BaseJsonImporter::GetImportedFiles()
{
return m_importedFiles;
}
} // namespace AZ

@ -0,0 +1,108 @@
/*
* 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/IO/Path/Path.h>
#include <AzCore/JSON/document.h>
#include <AzCore/JSON/pointer.h>
#include <AzCore/RTTI/RTTI.h>
#include <AzCore/std/containers/unordered_set.h>
#include <AzCore/std/containers/vector.h>
#include <AzCore/Serialization/Json/JsonSerializationResult.h>
#include <AzCore/Serialization/Json/StackedString.h>
namespace AZ
{
struct JsonImportSettings;
class BaseJsonImporter
{
public:
AZ_RTTI(BaseJsonImporter, "{7B225807-7B43-430F-8B11-C794DCF5ACA5}");
using ImportDirectivesList = AZStd::vector<AZStd::pair<rapidjson::Pointer, AZStd::string>>;
using ImportedFilesList = AZStd::unordered_set<AZStd::string>;
virtual JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective,
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator);
virtual JsonSerializationResult::ResultCode RestoreImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator,
const AZStd::string& importFilename);
virtual JsonSerializationResult::ResultCode ApplyPatch(rapidjson::Value& target,
const rapidjson::Value& patch, rapidjson::Document::AllocatorType& allocator);
virtual JsonSerializationResult::ResultCode CreatePatch(rapidjson::Value& patch,
const rapidjson::Value& source, const rapidjson::Value& target,
rapidjson::Document::AllocatorType& allocator);
void AddImportDirective(const rapidjson::Pointer& jsonPtr, AZStd::string importFile);
const ImportDirectivesList& GetImportDirectives();
void AddImportedFile(AZStd::string importedFile);
const ImportedFilesList& GetImportedFiles();
virtual ~BaseJsonImporter() = default;
protected:
ImportDirectivesList m_importDirectives;
ImportedFilesList m_importedFiles;
};
enum class ImportTracking : AZ::u8
{
None = 0,
Dependencies = (1<<0),
Imports = (1<<1),
All = (Dependencies | Imports)
};
AZ_DEFINE_ENUM_BITWISE_OPERATORS(ImportTracking);
class JsonImportResolver final
{
public:
using ImportPathStack = AZStd::vector<AZ::IO::FixedMaxPath>;
JsonImportResolver() = delete;
JsonImportResolver& operator=(const JsonImportResolver& rhs) = delete;
JsonImportResolver& operator=(JsonImportResolver&& rhs) = delete;
JsonImportResolver(const JsonImportResolver& rhs) = delete;
JsonImportResolver(JsonImportResolver&& rhs) = delete;
~JsonImportResolver() = delete;
static JsonSerializationResult::ResultCode ResolveImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, StackedString& element);
static JsonSerializationResult::ResultCode RestoreImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
private:
static JsonSerializationResult::ResultCode ResolveNestedImports(rapidjson::Value& jsonDoc,
rapidjson::Document::AllocatorType& allocator, ImportPathStack& importPathStack,
JsonImportSettings& settings, const AZ::IO::FixedMaxPath& importPath, StackedString& element);
};
struct JsonImportSettings final
{
JsonSerializationResult::JsonIssueCallback m_reporting;
BaseJsonImporter* m_importer = nullptr;
ImportTracking m_resolveFlags = ImportTracking::All;
AZ::IO::FixedMaxPath m_loadedJsonPath;
};
} // namespace AZ

@ -706,7 +706,7 @@ namespace AZ
rapidjson::Value(rapidjson::kNullType), field.value, element, settings); rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
} }
if (result.GetOutcome() == Outcomes::Success) if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
{ {
rapidjson::Value name; rapidjson::Value name;
name.CopyFrom(field.name, allocator, true); name.CopyFrom(field.name, allocator, true);
@ -717,6 +717,10 @@ namespace AZ
{ {
return result; return result;
} }
else
{
resultCode.Combine(result);
}
} }
// Do an extra pass to find all the fields that are removed. // Do an extra pass to find all the fields that are removed.
@ -751,7 +755,7 @@ namespace AZ
rapidjson::Value value; rapidjson::Value value;
ResultCode result = CreateMergePatchInternal(value, allocator, ResultCode result = CreateMergePatchInternal(value, allocator,
rapidjson::Value(rapidjson::kNullType), field.value, element, settings); rapidjson::Value(rapidjson::kNullType), field.value, element, settings);
if (result.GetOutcome() == Outcomes::Success) if (result.GetOutcome() == Outcomes::Success || result.GetOutcome() == Outcomes::PartialDefaults)
{ {
rapidjson::Value name; rapidjson::Value name;
name.CopyFrom(field.name, allocator, true); name.CopyFrom(field.name, allocator, true);
@ -762,11 +766,20 @@ namespace AZ
{ {
return result; return result;
} }
else
{
resultCode.Combine(result);
}
}
if (target.MemberCount() == 0)
{
resultCode.Combine(settings.m_reporting("Added empty object to JSON Merge Patch.",
ResultCode(Tasks::CreatePatch, Outcomes::Success), element));
} }
} }
patch = AZStd::move(resultValue); patch = AZStd::move(resultValue);
resultCode.Combine(ResultCode(Tasks::CreatePatch, Outcomes::Success));
return resultCode; return resultCode;
} }
else else

@ -10,6 +10,7 @@
#include <AzCore/Component/ComponentApplicationBus.h> #include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Serialization/Json/BaseJsonSerializer.h> #include <AzCore/Serialization/Json/BaseJsonSerializer.h>
#include <AzCore/Serialization/Json/JsonDeserializer.h> #include <AzCore/Serialization/Json/JsonDeserializer.h>
#include <AzCore/Serialization/Json/JsonImporter.h>
#include <AzCore/Serialization/Json/JsonMerger.h> #include <AzCore/Serialization/Json/JsonMerger.h>
#include <AzCore/Serialization/Json/JsonSerialization.h> #include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/JsonSerializer.h> #include <AzCore/Serialization/Json/JsonSerializer.h>
@ -19,11 +20,6 @@
namespace AZ namespace AZ
{ {
const char* JsonSerialization::TypeIdFieldIdentifier = "$type";
const char* JsonSerialization::DefaultStringIdentifier = "{}";
const char* JsonSerialization::KeyFieldIdentifier = "Key";
const char* JsonSerialization::ValueFieldIdentifier = "Value";
namespace JsonSerializationInternal namespace JsonSerializationInternal
{ {
template<typename T> template<typename T>
@ -394,6 +390,60 @@ namespace AZ
} }
} }
JsonSerializationResult::ResultCode JsonSerialization::ResolveImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
{
using namespace JsonSerializationResult;
if (settings.m_importer == nullptr)
{
AZ_Assert(false, "Importer object needs to be provided");
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
}
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
{
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
};
if (!settings.m_reporting)
{
settings.m_reporting = issueReportingCallback;
}
JsonImportResolver::ImportPathStack importPathStack;
importPathStack.push_back(settings.m_loadedJsonPath);
StackedString element(StackedString::Format::JsonPointer);
return JsonImportResolver::ResolveImports(jsonDoc, allocator, importPathStack, settings, element);
}
JsonSerializationResult::ResultCode JsonSerialization::RestoreImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings)
{
using namespace JsonSerializationResult;
if (settings.m_importer == nullptr)
{
AZ_Assert(false, "Importer object needs to be provided");
return ResultCode(Tasks::Import, Outcomes::Catastrophic);
}
AZStd::string scratchBuffer;
auto issueReportingCallback = [&scratchBuffer](AZStd::string_view message, ResultCode result, AZStd::string_view target) -> ResultCode
{
return JsonSerialization::DefaultIssueReporter(scratchBuffer, message, result, target);
};
if (!settings.m_reporting)
{
settings.m_reporting = issueReportingCallback;
}
settings.m_resolveFlags = ImportTracking::None;
return JsonImportResolver::RestoreImports(jsonDoc, allocator, settings);
}
JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer, JsonSerializationResult::ResultCode JsonSerialization::DefaultIssueReporter(AZStd::string& scratchBuffer,
AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path) AZStd::string_view message, JsonSerializationResult::ResultCode result, AZStd::string_view path)
{ {

@ -18,6 +18,8 @@
namespace AZ namespace AZ
{ {
class BaseJsonSerializer; class BaseJsonSerializer;
struct JsonImportSettings;
enum class JsonMergeApproach enum class JsonMergeApproach
{ {
@ -51,10 +53,11 @@ namespace AZ
class JsonSerialization final class JsonSerialization final
{ {
public: public:
static const char* TypeIdFieldIdentifier; static constexpr const char* TypeIdFieldIdentifier = "$type";
static const char* DefaultStringIdentifier; static constexpr const char* DefaultStringIdentifier = "{}";
static const char* KeyFieldIdentifier; static constexpr const char* KeyFieldIdentifier = "Key";
static const char* ValueFieldIdentifier; static constexpr const char* ValueFieldIdentifier = "Value";
static constexpr const char* ImportDirectiveIdentifier = "$import";
//! Merges two json values together by applying "patch" to "target" using the selected merge algorithm. //! Merges two json values together by applying "patch" to "target" using the selected merge algorithm.
//! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will //! This version of ApplyPatch is destructive to "target". If the patch can't be correctly applied it will
@ -284,6 +287,22 @@ namespace AZ
//! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error". //! @return An enum containing less, equal or greater. In case of an error, the value for the enum will "error".
static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs); static JsonSerializerCompareResult Compare(const rapidjson::Value& lhs, const rapidjson::Value& rhs);
//! Resolves all import directives, including nested imports, in the given document. An importer object needs to be passed
//! in through the settings.
//! @param jsonDoc The json document in which to resolve imports.
//! @param allocator The allocator associated with the json document.
//! @param settings Additional settings that control the way the imports are resolved.
static JsonSerializationResult::ResultCode ResolveImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
//! Restores all import directives that were present in the json document. The same importer object that was
//! passed into ResolveImports through the settings needs to be passed here through settings as well.
//! @param jsonDoc The json document in which to restore imports.
//! @param allocator The allocator associated with the json document.
//! @param settings Additional settings that control the way the imports are restored.
static JsonSerializationResult::ResultCode RestoreImports(
rapidjson::Value& jsonDoc, rapidjson::Document::AllocatorType& allocator, JsonImportSettings& settings);
private: private:
JsonSerialization() = delete; JsonSerialization() = delete;
~JsonSerialization() = delete; ~JsonSerialization() = delete;

@ -69,6 +69,9 @@ namespace AZ
case Tasks::CreatePatch: case Tasks::CreatePatch:
target.append("a create patch operation "); target.append("a create patch operation ");
break; break;
case Tasks::Import:
target.append("an import operation");
break;
default: default:
target.append("an unknown operation "); target.append("an unknown operation ");
break; break;

@ -32,7 +32,8 @@ namespace AZ
ReadField, //!< Task to read a field from JSON to a value. ReadField, //!< Task to read a field from JSON to a value.
WriteValue, //!< Task to write a value to a JSON field. WriteValue, //!< Task to write a value to a JSON field.
Merge, //!< Task to merge two JSON values/documents together. Merge, //!< Task to merge two JSON values/documents together.
CreatePatch //!< Task to create a patch to transform one value/document to another. CreatePatch, //!< Task to create a patch to transform one value/document to another.
Import //!< Task to import a JSON document.
}; };
//! Describes how the task was processed. //! Describes how the task was processed.

@ -192,26 +192,34 @@ namespace AZ
//! @param path An offset at which traversal should start. //! @param path An offset at which traversal should start.
//! @return Whether or not entries could be visited. //! @return Whether or not entries could be visited.
virtual bool Visit(const VisitorCallback& callback, AZStd::string_view path) const = 0; virtual bool Visit(const VisitorCallback& callback, AZStd::string_view path) const = 0;
//! Register a callback that will be called whenever an entry gets a new/updated value. //! Register a callback that will be called whenever an entry gets a new/updated value.
//!
//! @callback The function to call when an entry gets a new/updated value. //! @callback The function to call when an entry gets a new/updated value.
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) = 0; //! @return NotifyEventHandler instance which must persist to receive event signal
//! Register a callback that will be called whenever an entry gets a new/updated value. [[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback callback) = 0;
//! @callback The function to call when an entry gets a new/updated value. //! Register a notify event handler with the NotifyEvent.
[[nodiscard]] virtual NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) = 0; //! The handler will be called whenever an entry gets a new/updated value.
//! @param handler The handler to register with the NotifyEvent.
virtual void RegisterNotifier(NotifyEventHandler& handler) = 0;
//! Register a function that will be called before a file is merged. //! Register a function that will be called before a file is merged.
//! @callback The function to call before a file is merged. //! @param callback The function to call before a file is merged.
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) = 0; //! @return PreMergeEventHandler instance which must persist to receive event signal
//! Register a function that will be called before a file is merged. [[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) = 0;
//! @callback The function to call before a file is merged. //! Register a pre-merge handler with the PreMergeEvent.
[[nodiscard]] virtual PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) = 0; //! The handler will be called before a file is merged.
//! @param handler The hanlder to register with the PreMergeEvent.
virtual void RegisterPreMergeEvent(PreMergeEventHandler& handler) = 0;
//! Register a function that will be called after a file is merged. //! Register a function that will be called after a file is merged.
//! @callback The function to call after a file is merged. //! @param callback The function to call after a file is merged.
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) = 0; //! @return PostMergeEventHandler instance which must persist to receive event signal
//! Register a function that will be called after a file is merged. [[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) = 0;
//! @callback The function to call after a file is merged. //! Register a post-merge hahndler with the PostMergeEvent.
[[nodiscard]] virtual PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) = 0; //! The handler will be called after a file is merged.
//! @param handler The handler to register with the PostmergeEVent.
virtual void RegisterPostMergeEvent(PostMergeEventHandler& hanlder) = 0;
//! Gets the boolean value at the provided path. //! Gets the boolean value at the provided path.
//! @param result The target to write the result to. //! @param result The target to write the result to.
@ -326,23 +334,25 @@ namespace AZ
//! - all digits and dot -> floating point number //! - all digits and dot -> floating point number
//! - Everything else is considered a string. //! - Everything else is considered a string.
//! @param argument The command line argument. //! @param argument The command line argument.
//! @param structure which contains functors which determine what characters are delimiters //! @param anchorKey The key where the merged command line argument will be anchored under
//! @param commandLineSettings structure which contains functors which determine what characters are delimiters
//! @return True if the command line argument could be parsed, otherwise false. //! @return True if the command line argument could be parsed, otherwise false.
virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey = "", virtual bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey = "",
const CommandLineArgumentSettings& commandLineSettings = {}) = 0; const CommandLineArgumentSettings& commandLineSettings = {}) = 0;
//! Merges the json data provided into the settings registry. //! Merges the json data provided into the settings registry.
//! @param data The json data stored in a string. //! @param data The json data stored in a string.
//! @param format The format of the provided data. //! @param format The format of the provided data.
//! @param anchorKey The key where the merged json content will be anchored under.
//! @return True if the data was successfully merged, otherwise false. //! @return True if the data was successfully merged, otherwise false.
virtual bool MergeSettings(AZStd::string_view data, Format format) = 0; virtual bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") = 0;
//! Loads a settings file and merges it into the registry. //! Loads a settings file and merges it into the registry.
//! @param path The path to the registry file. //! @param path The path to the registry file.
//! @param format The format of the text data in the file at the provided path. //! @param format The format of the text data in the file at the provided path.
//! @param rootKey The key where the root of the settings file will be stored under. //! @param anchorKey The key where the content of the settings file will be anchored.
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to //! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
//! reduce the number of intermediate memory allocations. //! reduce the number of intermediate memory allocations.
//! @return True if the registry file was successfully merged, otherwise false. //! @return True if the registry file was successfully merged, otherwise false.
virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey = "", virtual bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
AZStd::vector<char>* scratchBuffer = nullptr) = 0; AZStd::vector<char>* scratchBuffer = nullptr) = 0;
//! Loads all settings files in a folder and merges them into the registry. //! Loads all settings files in a folder and merges them into the registry.
//! With the specializations "a" and "b" and platform "c" the files would be loaded in the order: //! With the specializations "a" and "b" and platform "c" the files would be loaded in the order:
@ -357,11 +367,12 @@ namespace AZ
//! @param platform An optional name of a platform. Platform overloads are located at <path>/Platform/<platform>/ //! @param platform An optional name of a platform. Platform overloads are located at <path>/Platform/<platform>/
//! Files in a platform are applied in the same order as for the main folder but always after the same file //! Files in a platform are applied in the same order as for the main folder but always after the same file
//! in the main folder. //! in the main folder.
//! @param anchorKey The registry path location where the settings will be anchored
//! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to //! @param scratchBuffer An optional buffer that's used to load the file into. Use this when loading multiple patches to
//! reduce the number of intermediate memory allocations. //! reduce the number of intermediate memory allocations.
//! @return True if the registry folder was successfully merged, otherwise false. //! @return True if the registry folder was successfully merged, otherwise false.
virtual bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations, virtual bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
AZStd::string_view platform = {}, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0; AZStd::string_view platform = {}, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) = 0;
//! Stores the settings structure which is used when merging settings to the Settings Registry //! Stores the settings structure which is used when merging settings to the Settings Registry
//! using JSON Merge Patch or JSON Merge Patch. //! using JSON Merge Patch or JSON Merge Patch.

@ -13,7 +13,7 @@
#include <AzCore/IO/FileReader.h> #include <AzCore/IO/FileReader.h>
#include <AzCore/IO/Path/Path.h> #include <AzCore/IO/Path/Path.h>
#include <AzCore/JSON/error/en.h> #include <AzCore/JSON/error/en.h>
#include <AzCore/NativeUI//NativeUIRequests.h> #include <AzCore/NativeUI/NativeUIRequests.h>
#include <AzCore/Serialization/Json/JsonSerialization.h> #include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Serialization/Json/StackedString.h> #include <AzCore/Serialization/Json/StackedString.h>
#include <AzCore/Settings/SettingsRegistryImpl.h> #include <AzCore/Settings/SettingsRegistryImpl.h>
@ -21,6 +21,34 @@
#include <AzCore/std/sort.h> #include <AzCore/std/sort.h>
#include <AzCore/std/parallel/scoped_lock.h> #include <AzCore/std/parallel/scoped_lock.h>
namespace AZ::SettingsRegistryImplInternal
{
AZ::SettingsRegistryInterface::Type RapidjsonToSettingsRegistryType(const rapidjson::Value& value)
{
using Type = AZ::SettingsRegistryInterface::Type;
switch (value.GetType())
{
case rapidjson::Type::kNullType:
return Type::Null;
case rapidjson::Type::kFalseType:
return Type::Boolean;
case rapidjson::Type::kTrueType:
return Type::Boolean;
case rapidjson::Type::kObjectType:
return Type::Object;
case rapidjson::Type::kArrayType:
return Type::Array;
case rapidjson::Type::kStringType:
return Type::String;
case rapidjson::Type::kNumberType:
return value.IsDouble() ? Type::FloatingPoint :
Type::Integer;
}
return Type::NoType;
}
}
namespace AZ namespace AZ
{ {
template<typename T> template<typename T>
@ -28,7 +56,7 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
// rapidjson::Pointer assets that the supplied string // rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
@ -70,7 +98,7 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
// rapidjson::Pointer assets that the supplied string // rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
@ -161,7 +189,7 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
// rapidjson::Pointer assets that the supplied string // rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
@ -212,9 +240,9 @@ namespace AZ
return Visit(visitor, path); return Visit(visitor, path);
} }
auto SettingsRegistryImpl::RegisterNotifier(const NotifyCallback& callback) -> NotifyEventHandler auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback callback) -> NotifyEventHandler
{ {
NotifyEventHandler notifyHandler{ callback }; NotifyEventHandler notifyHandler{ AZStd::move(callback) };
{ {
AZStd::scoped_lock lock(m_notifierMutex); AZStd::scoped_lock lock(m_notifierMutex);
notifyHandler.Connect(m_notifiers); notifyHandler.Connect(m_notifiers);
@ -222,14 +250,10 @@ namespace AZ
return notifyHandler; return notifyHandler;
} }
auto SettingsRegistryImpl::RegisterNotifier(NotifyCallback&& callback) -> NotifyEventHandler auto SettingsRegistryImpl::RegisterNotifier(NotifyEventHandler& notifyHandler) -> void
{ {
NotifyEventHandler notifyHandler{ AZStd::move(callback) }; AZStd::scoped_lock lock(m_notifierMutex);
{ notifyHandler.Connect(m_notifiers);
AZStd::scoped_lock lock(m_notifierMutex);
notifyHandler.Connect(m_notifiers);
}
return notifyHandler;
} }
void SettingsRegistryImpl::ClearNotifiers() void SettingsRegistryImpl::ClearNotifiers()
@ -238,9 +262,9 @@ namespace AZ
m_notifiers.DisconnectAllHandlers(); m_notifiers.DisconnectAllHandlers();
} }
auto SettingsRegistryImpl::RegisterPreMergeEvent(const PreMergeEventCallback& callback) -> PreMergeEventHandler auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback callback) -> PreMergeEventHandler
{ {
PreMergeEventHandler preMergeHandler{ callback }; PreMergeEventHandler preMergeHandler{ AZStd::move(callback) };
{ {
AZStd::scoped_lock lock(m_settingMutex); AZStd::scoped_lock lock(m_settingMutex);
preMergeHandler.Connect(m_preMergeEvent); preMergeHandler.Connect(m_preMergeEvent);
@ -248,19 +272,15 @@ namespace AZ
return preMergeHandler; return preMergeHandler;
} }
auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventCallback&& callback) -> PreMergeEventHandler auto SettingsRegistryImpl::RegisterPreMergeEvent(PreMergeEventHandler& preMergeHandler) -> void
{ {
PreMergeEventHandler preMergeHandler{ AZStd::move(callback) }; AZStd::scoped_lock lock(m_settingMutex);
{ preMergeHandler.Connect(m_preMergeEvent);
AZStd::scoped_lock lock(m_settingMutex);
preMergeHandler.Connect(m_preMergeEvent);
}
return preMergeHandler;
} }
auto SettingsRegistryImpl::RegisterPostMergeEvent(const PostMergeEventCallback& callback) -> PostMergeEventHandler auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback callback) -> PostMergeEventHandler
{ {
PostMergeEventHandler postMergeHandler{ callback }; PostMergeEventHandler postMergeHandler{ AZStd::move(callback) };
{ {
AZStd::scoped_lock lock(m_settingMutex); AZStd::scoped_lock lock(m_settingMutex);
postMergeHandler.Connect(m_postMergeEvent); postMergeHandler.Connect(m_postMergeEvent);
@ -268,14 +288,10 @@ namespace AZ
return postMergeHandler; return postMergeHandler;
} }
auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventCallback&& callback) -> PostMergeEventHandler auto SettingsRegistryImpl::RegisterPostMergeEvent(PostMergeEventHandler& postMergeHandler) -> void
{ {
PostMergeEventHandler postMergeHandler{ AZStd::move(callback) }; AZStd::scoped_lock lock(m_settingMutex);
{ postMergeHandler.Connect(m_postMergeEvent);
AZStd::scoped_lock lock(m_settingMutex);
postMergeHandler.Connect(m_postMergeEvent);
}
return postMergeHandler;
} }
void SettingsRegistryImpl::ClearMergeEvents() void SettingsRegistryImpl::ClearMergeEvents()
@ -297,7 +313,36 @@ namespace AZ
localNotifierEvent = AZStd::move(m_notifiers); localNotifierEvent = AZStd::move(m_notifiers);
} }
localNotifierEvent.Signal(jsonPath, type); // Signal the NotifyEvent for each queued argument
decltype(m_signalNotifierQueue) localNotifierQueue;
{
AZStd::scoped_lock signalLock(m_signalMutex);
m_signalNotifierQueue.push_back({ FixedValueString{jsonPath}, type });
// If the signal count was 0, then a dispatch is in progress
if (m_signalCount++ == 0)
{
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
}
}
while (!localNotifierQueue.empty())
{
for (SignalNotifierArgs notifierArgs : localNotifierQueue)
{
localNotifierEvent.Signal(notifierArgs.m_jsonPath, notifierArgs.m_type);
}
// Clear the local notifier queue and check if more notifiers have been added
localNotifierQueue = {};
{
AZStd::scoped_lock signalLock(m_signalMutex);
AZStd::swap(localNotifierQueue, m_signalNotifierQueue);
}
}
{
AZStd::scoped_lock signalLock(m_signalMutex);
--m_signalCount;
}
{ {
// Swap the local handlers with the current m_notifiers which // Swap the local handlers with the current m_notifiers which
@ -314,39 +359,19 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
//rapidjson::Pointer assets that the supplied string //rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
} }
rapidjson::Pointer pointer(path.data(), path.length()); rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid()) if (pointer.IsValid())
{ {
AZStd::scoped_lock lock(m_settingMutex); AZStd::scoped_lock lock(m_settingMutex);
const rapidjson::Value* value = pointer.Get(m_settings); if (const rapidjson::Value* value = pointer.Get(m_settings); value != nullptr)
if (value)
{ {
switch (value->GetType()) return SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(*value);
{
case rapidjson::Type::kNullType:
return Type::Null;
case rapidjson::Type::kFalseType:
return Type::Boolean;
case rapidjson::Type::kTrueType:
return Type::Boolean;
case rapidjson::Type::kObjectType:
return Type::Object;
case rapidjson::Type::kArrayType:
return Type::Array;
case rapidjson::Type::kStringType:
return Type::String;
case rapidjson::Type::kNumberType:
return
value->IsDouble() ? Type::FloatingPoint :
Type::Integer;
}
} }
} }
return Type::NoType; return Type::NoType;
@ -392,7 +417,7 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
// rapidjson::Pointer assets that the supplied string // rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
@ -471,13 +496,12 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
//rapidjson::Pointer assets that the supplied string // rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
} }
rapidjson::Pointer pointer(path.data(), path.length()); rapidjson::Pointer pointer(path.data(), path.length());
if (pointer.IsValid()) if (pointer.IsValid())
{ {
@ -486,10 +510,14 @@ namespace AZ
value, nullptr, valueTypeID, m_serializationSettings); value, nullptr, valueTypeID, m_serializationSettings);
if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Halted) if (jsonResult.GetProcessing() != JsonSerializationResult::Processing::Halted)
{ {
AZStd::scoped_lock lock(m_settingMutex); auto anchorType = Type::NoType;
rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator()); {
setting = AZStd::move(store); AZStd::scoped_lock lock(m_settingMutex);
SignalNotifier(path, Type::Object); rapidjson::Value& setting = pointer.Create(m_settings, m_settings.GetAllocator());
setting = AZStd::move(store);
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(setting);
}
SignalNotifier(path, anchorType);
return true; return true;
} }
} }
@ -500,7 +528,7 @@ namespace AZ
{ {
if (path.empty()) if (path.empty())
{ {
// rapidjson::Pointer assets that the supplied string // rapidjson::Pointer asserts that the supplied string
// is not nullptr even if the supplied size is 0 // is not nullptr even if the supplied size is 0
// Setting to empty string to prevent assert // Setting to empty string to prevent assert
path = ""; path = "";
@ -605,7 +633,7 @@ namespace AZ
return Set(key, value); return Set(key, value);
} }
bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format) bool SettingsRegistryImpl::MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey)
{ {
rapidjson::Document jsonPatch; rapidjson::Document jsonPatch;
constexpr int flags = rapidjson::kParseStopWhenDoneFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag; constexpr int flags = rapidjson::kParseStopWhenDoneFlag | rapidjson::kParseCommentsFlag | rapidjson::kParseTrailingCommasFlag;
@ -631,17 +659,43 @@ namespace AZ
return false; return false;
} }
AZStd::scoped_lock lock(m_settingMutex); rapidjson::Pointer anchorPath;
if (!anchorKey.empty())
{
anchorPath = rapidjson::Pointer(anchorKey.data(), anchorKey.size());
if (!anchorPath.IsValid())
{
rapidjson::Pointer pointer(AZ_SETTINGS_REGISTRY_HISTORY_KEY "/-");
AZ_Error("Settings Registry", false, R"(Anchor path "%.*s" is invalid.)", AZ_STRING_ARG(anchorKey));
AZStd::scoped_lock lock(m_settingMutex);
pointer.Create(m_settings, m_settings.GetAllocator()).SetObject()
.AddMember(rapidjson::StringRef("Error"), rapidjson::StringRef("Invalid anchor key."), m_settings.GetAllocator())
.AddMember(rapidjson::StringRef("Path"),
rapidjson::Value(anchorKey.data(), aznumeric_caster(anchorKey.size()), m_settings.GetAllocator()),
m_settings.GetAllocator());
return false;
}
}
JsonSerializationResult::ResultCode mergeResult = auto anchorType = AZ::SettingsRegistryInterface::Type::NoType;
JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach);
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{ {
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry."); AZStd::scoped_lock lock(m_settingMutex);
return false; rapidjson::Value& anchorRoot = anchorPath.IsValid() ? anchorPath.Create(m_settings, m_settings.GetAllocator())
: m_settings;
JsonSerializationResult::ResultCode mergeResult =
JsonSerialization::ApplyPatch(anchorRoot, m_settings.GetAllocator(), jsonPatch, mergeApproach);
if (mergeResult.GetProcessing() != JsonSerializationResult::Processing::Completed)
{
AZ_Error("Settings Registry", false, "Failed to fully merge data into registry.");
return false;
}
// The settings have been successfully merged, query the type at the anchor key
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(anchorRoot);
} }
SignalNotifier("", Type::Object); SignalNotifier(anchorKey, anchorType);
return true; return true;
} }
@ -1225,10 +1279,12 @@ namespace AZ
ScopedMergeEvent scopedMergeEvent(m_preMergeEvent, m_postMergeEvent, path, rootKey); ScopedMergeEvent scopedMergeEvent(m_preMergeEvent, m_postMergeEvent, path, rootKey);
JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge); JsonSerializationResult::ResultCode mergeResult(JsonSerializationResult::Tasks::Merge);
auto anchorType = Type::NoType;
if (rootKey.empty()) if (rootKey.empty())
{ {
AZStd::scoped_lock lock(m_settingMutex); AZStd::scoped_lock lock(m_settingMutex);
mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings); mergeResult = JsonSerialization::ApplyPatch(m_settings, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(m_settings);
} }
else else
{ {
@ -1238,6 +1294,7 @@ namespace AZ
AZStd::scoped_lock lock(m_settingMutex); AZStd::scoped_lock lock(m_settingMutex);
Value& rootValue = root.Create(m_settings, m_settings.GetAllocator()); Value& rootValue = root.Create(m_settings, m_settings.GetAllocator());
mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings); mergeResult = JsonSerialization::ApplyPatch(rootValue, m_settings.GetAllocator(), jsonPatch, mergeApproach, m_applyPatchSettings);
anchorType = SettingsRegistryImplInternal::RapidjsonToSettingsRegistryType(rootValue);
} }
else else
{ {
@ -1265,7 +1322,7 @@ namespace AZ
pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator()); pointer.Create(m_settings, m_settings.GetAllocator()).SetString(path, m_settings.GetAllocator());
} }
SignalNotifier("", Type::Object); SignalNotifier(rootKey, anchorType);
return true; return true;
} }

@ -48,14 +48,14 @@ namespace AZ
Type GetType(AZStd::string_view path) const override; Type GetType(AZStd::string_view path) const override;
bool Visit(Visitor& visitor, AZStd::string_view path) const override; bool Visit(Visitor& visitor, AZStd::string_view path) const override;
bool Visit(const VisitorCallback& callback, AZStd::string_view path) const override; bool Visit(const VisitorCallback& callback, AZStd::string_view path) const override;
[[nodiscard]] NotifyEventHandler RegisterNotifier(const NotifyCallback& callback) override; [[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback callback) override;
[[nodiscard]] NotifyEventHandler RegisterNotifier(NotifyCallback&& callback) override; void RegisterNotifier(NotifyEventHandler& hanlder) override;
void ClearNotifiers(); void ClearNotifiers();
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(const PreMergeEventCallback& callback) override; [[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback callback) override;
[[nodiscard]] PreMergeEventHandler RegisterPreMergeEvent(PreMergeEventCallback&& callback) override; void RegisterPreMergeEvent(PreMergeEventHandler& handler) override;
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(const PostMergeEventCallback& callback) override; [[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback callback) override;
[[nodiscard]] PostMergeEventHandler RegisterPostMergeEvent(PostMergeEventCallback&& callback) override; void RegisterPostMergeEvent(PostMergeEventHandler& handler) override;
void ClearMergeEvents(); void ClearMergeEvents();
bool Get(bool& result, AZStd::string_view path) const override; bool Get(bool& result, AZStd::string_view path) const override;
@ -76,13 +76,13 @@ namespace AZ
bool Remove(AZStd::string_view path) override; bool Remove(AZStd::string_view path) override;
bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view rootKey, bool MergeCommandLineArgument(AZStd::string_view argument, AZStd::string_view anchorKey,
const CommandLineArgumentSettings& commandLineSettings) override; const CommandLineArgumentSettings& commandLineSettings) override;
bool MergeSettings(AZStd::string_view data, Format format) override; bool MergeSettings(AZStd::string_view data, Format format, AZStd::string_view anchorKey = "") override;
bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view rootKey, bool MergeSettingsFile(AZStd::string_view path, Format format, AZStd::string_view anchorKey = "",
AZStd::vector<char>* scratchBuffer = nullptr) override; AZStd::vector<char>* scratchBuffer = nullptr) override;
bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations, bool MergeSettingsFolder(AZStd::string_view path, const Specializations& specializations,
AZStd::string_view platform, AZStd::string_view rootKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override; AZStd::string_view platform, AZStd::string_view anchorKey = "", AZStd::vector<char>* scratchBuffer = nullptr) override;
void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override; void SetApplyPatchSettings(const AZ::JsonApplyPatchSettings& applyPatchSettings) override;
void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override; void GetApplyPatchSettings(AZ::JsonApplyPatchSettings& applyPatchSettings) override;
@ -121,6 +121,19 @@ namespace AZ
PreMergeEvent m_preMergeEvent; PreMergeEvent m_preMergeEvent;
PostMergeEvent m_postMergeEvent; PostMergeEvent m_postMergeEvent;
//! NOTE: During SignalNotifier, the registered notify event handlers are moved to a local NotifyEvent
//! Therefore setting a value within the registry during signaling will queue future SignalNotifer calls
//! These calls will then be invoked after the current signaling has completex
//! This is done to avoid deadlock if another thread attempts to access register a notifier or signal one
mutable AZStd::mutex m_signalMutex;
struct SignalNotifierArgs
{
FixedValueString m_jsonPath;
Type m_type;
};
AZStd::deque<SignalNotifierArgs> m_signalNotifierQueue;
AZStd::atomic_int m_signalCount{};
rapidjson::Document m_settings; rapidjson::Document m_settings;
JsonSerializerSettings m_serializationSettings; JsonSerializerSettings m_serializationSettings;
JsonDeserializerSettings m_deserializationSettings; JsonDeserializerSettings m_deserializationSettings;

@ -23,12 +23,12 @@ namespace AZ
MOCK_CONST_METHOD1(GetType, Type(AZStd::string_view)); MOCK_CONST_METHOD1(GetType, Type(AZStd::string_view));
MOCK_CONST_METHOD2(Visit, bool(Visitor&, AZStd::string_view)); MOCK_CONST_METHOD2(Visit, bool(Visitor&, AZStd::string_view));
MOCK_CONST_METHOD2(Visit, bool(const VisitorCallback&, AZStd::string_view)); MOCK_CONST_METHOD2(Visit, bool(const VisitorCallback&, AZStd::string_view));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(const NotifyCallback&)); MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback));
MOCK_METHOD1(RegisterNotifier, NotifyEventHandler(NotifyCallback&&)); MOCK_METHOD1(RegisterNotifier, void(NotifyEventHandler&));
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(const PreMergeEventCallback&)); MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback));
MOCK_METHOD1(RegisterPreMergeEvent, PreMergeEventHandler(PreMergeEventCallback&&)); MOCK_METHOD1(RegisterPreMergeEvent, void(PreMergeEventHandler&));
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(const PostMergeEventCallback&)); MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback));
MOCK_METHOD1(RegisterPostMergeEvent, PostMergeEventHandler(PostMergeEventCallback&&)); MOCK_METHOD1(RegisterPostMergeEvent, void(PostMergeEventHandler&));
MOCK_CONST_METHOD2(Get, bool(bool&, AZStd::string_view)); MOCK_CONST_METHOD2(Get, bool(bool&, AZStd::string_view));
MOCK_CONST_METHOD2(Get, bool(s64&, AZStd::string_view)); MOCK_CONST_METHOD2(Get, bool(s64&, AZStd::string_view));
@ -49,7 +49,7 @@ namespace AZ
MOCK_METHOD1(Remove, bool(AZStd::string_view)); MOCK_METHOD1(Remove, bool(AZStd::string_view));
MOCK_METHOD3(MergeCommandLineArgument, bool(AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&)); MOCK_METHOD3(MergeCommandLineArgument, bool(AZStd::string_view, AZStd::string_view, const CommandLineArgumentSettings&));
MOCK_METHOD2(MergeSettings, bool(AZStd::string_view, Format)); MOCK_METHOD3(MergeSettings, bool(AZStd::string_view, Format, AZStd::string_view));
MOCK_METHOD4(MergeSettingsFile, bool(AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*)); MOCK_METHOD4(MergeSettingsFile, bool(AZStd::string_view, Format, AZStd::string_view, AZStd::vector<char>*));
MOCK_METHOD5( MOCK_METHOD5(
MergeSettingsFolder, MergeSettingsFolder,

@ -522,6 +522,8 @@ set(FILES
Serialization/Json/IntSerializer.cpp Serialization/Json/IntSerializer.cpp
Serialization/Json/JsonDeserializer.h Serialization/Json/JsonDeserializer.h
Serialization/Json/JsonDeserializer.cpp Serialization/Json/JsonDeserializer.cpp
Serialization/Json/JsonImporter.cpp
Serialization/Json/JsonImporter.h
Serialization/Json/JsonMerger.h Serialization/Json/JsonMerger.h
Serialization/Json/JsonMerger.cpp Serialization/Json/JsonMerger.cpp
Serialization/Json/JsonSerialization.h Serialization/Json/JsonSerialization.h

@ -0,0 +1,413 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Serialization/Json/JsonImporter.h>
#include <AzCore/Serialization/Json/JsonUtils.h>
#include <Tests/Serialization/Json/JsonSerializationTests.h>
namespace JsonSerializationTests
{
class JsonImportingTests;
class JsonImporterCustom
: public AZ::BaseJsonImporter
{
public:
AZ_RTTI(JsonImporterCustom, "{003F5896-71E0-4A50-A14F-08C319B06AD0}");
AZ::JsonSerializationResult::ResultCode ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective,
const AZ::IO::FixedMaxPath& importedFilePath, rapidjson::Document::AllocatorType& allocator) override;
JsonImporterCustom(JsonImportingTests* tests)
{
testClass = tests;
}
private:
JsonImportingTests* testClass;
};
class JsonImportingTests
: public BaseJsonSerializerFixture
{
public:
void SetUp() override
{
BaseJsonSerializerFixture::SetUp();
}
void TearDown() override
{
BaseJsonSerializerFixture::TearDown();
}
void GetTestDocument(const AZStd::string& docName, rapidjson::Document& out)
{
const char *objectJson = R"({
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "value_3"
})";
const char *arrayJson = R"([
{ "element_1" : "value_1" },
{ "element_2" : "value_2" },
{ "element_3" : "value_3" }
])";
const char *nestedImportJson = R"({
"desc" : "Nested Import",
"obj" : {"$import" : "object.json"}
})";
const char *nestedImportCycle1Json = R"({
"desc" : "Nested Import Cycle 1",
"obj" : {"$import" : "nested_import_c2.json"}
})";
const char *nestedImportCycle2Json = R"({
"desc" : "Nested Import Cycle 2",
"obj" : {"$import" : "nested_import_c1.json"}
})";
if (docName.compare("object.json") == 0)
{
out.Parse(objectJson);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("array.json") == 0)
{
out.Parse(arrayJson);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("nested_import.json") == 0)
{
out.Parse(nestedImportJson);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("nested_import_c1.json") == 0)
{
out.Parse(nestedImportCycle1Json);
ASSERT_FALSE(out.HasParseError());
}
else if (docName.compare("nested_import_c2.json") == 0)
{
out.Parse(nestedImportCycle2Json);
ASSERT_FALSE(out.HasParseError());
}
}
protected:
void TestImportLoadStore(const char* input, const char* expectedImportedValue)
{
m_jsonDocument->Parse(input);
ASSERT_FALSE(m_jsonDocument->HasParseError());
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
rapidjson::Document expectedOutcome;
expectedOutcome.Parse(expectedImportedValue);
ASSERT_FALSE(expectedOutcome.HasParseError());
TestResolveImports(importerObj);
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutcome.GetObject());
rapidjson::Document originalInput;
originalInput.Parse(input);
ASSERT_FALSE(originalInput.HasParseError());
TestRestoreImports(importerObj);
Expect_DocStrEq(m_jsonDocument->GetObject(), originalInput.GetObject());
m_jsonDocument->SetObject();
delete importerObj;
}
void TestImportCycle(const char* input)
{
m_jsonDocument->Parse(input);
ASSERT_FALSE(m_jsonDocument->HasParseError());
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
AZ::JsonSerializationResult::ResultCode result = TestResolveImports(importerObj);
EXPECT_EQ(result.GetOutcome(), AZ::JsonSerializationResult::Outcomes::Catastrophic);
m_jsonDocument->SetObject();
delete importerObj;
}
void TestInsertNewImport(const char* input, const char* expectedRestoredValue)
{
m_jsonDocument->Parse(input);
ASSERT_FALSE(m_jsonDocument->HasParseError());
JsonImporterCustom* importerObj = new JsonImporterCustom(this);
TestResolveImports(importerObj);
importerObj->AddImportDirective(rapidjson::Pointer("/object_2"), "object.json");
rapidjson::Document expectedOutput;
expectedOutput.Parse(expectedRestoredValue);
ASSERT_FALSE(expectedOutput.HasParseError());
TestRestoreImports(importerObj);
Expect_DocStrEq(m_jsonDocument->GetObject(), expectedOutput.GetObject());
m_jsonDocument->SetObject();
delete importerObj;
}
AZ::JsonSerializationResult::ResultCode TestResolveImports(JsonImporterCustom* importerObj)
{
AZ::JsonImportSettings settings;
settings.m_importer = importerObj;
return AZ::JsonSerialization::ResolveImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
}
AZ::JsonSerializationResult::ResultCode TestRestoreImports(JsonImporterCustom* importerObj)
{
AZ::JsonImportSettings settings;
settings.m_importer = importerObj;
return AZ::JsonSerialization::RestoreImports(m_jsonDocument->GetObject(), m_jsonDocument->GetAllocator(), settings);
}
};
AZ::JsonSerializationResult::ResultCode JsonImporterCustom::ResolveImport(rapidjson::Value* importPtr,
rapidjson::Value& patch, const rapidjson::Value& importDirective, const AZ::IO::FixedMaxPath& importedFilePath,
rapidjson::Document::AllocatorType& allocator)
{
AZ::JsonSerializationResult::ResultCode resultCode(AZ::JsonSerializationResult::Tasks::Import);
rapidjson::Document importedDoc;
testClass->GetTestDocument(importedFilePath.String(), importedDoc);
if (importDirective.IsObject())
{
auto patchField = importDirective.FindMember("patch");
if (patchField != importDirective.MemberEnd())
{
patch.CopyFrom(patchField->value, allocator);
}
}
importPtr->CopyFrom(importedDoc, allocator);
return resultCode;
}
// Test Cases
TEST_F(JsonImportingTests, ImportSimpleObjectTest)
{
const char* inputFile = R"(
{
"name" : "simple_object_import",
"object": {"$import" : "object.json"}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_object_import",
"object": {
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "value_3"
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, ImportSimpleObjectPatchTest)
{
const char* inputFile = R"(
{
"name" : "simple_object_import",
"object": {
"$import" : {
"filename" : "object.json",
"patch" : { "field_2" : "patched_value" }
}
}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_object_import",
"object": {
"field_1" : "value_1",
"field_2" : "patched_value",
"field_3" : "value_3"
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, ImportSimpleArrayTest)
{
const char* inputFile = R"(
{
"name" : "simple_array_import",
"object": {"$import" : "array.json"}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_array_import",
"object": [
{ "element_1" : "value_1" },
{ "element_2" : "value_2" },
{ "element_3" : "value_3" }
]
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, ImportSimpleArrayPatchTest)
{
const char* inputFile = R"(
{
"name" : "simple_array_import",
"object": {
"$import" : {
"filename" : "array.json",
"patch" : [ { "element_1" : "patched_value" } ]
}
}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_array_import",
"object": [
{ "element_1" : "patched_value" }
]
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, NestedImportTest)
{
const char* inputFile = R"(
{
"name" : "nested_import",
"object": {"$import" : "nested_import.json"}
}
)";
const char* expectedOutput = R"(
{
"name" : "nested_import",
"object": {
"desc" : "Nested Import",
"obj" : {
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "value_3"
}
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, NestedImportPatchTest)
{
const char* inputFile = R"(
{
"name" : "nested_import",
"object": {
"$import" : {
"filename" : "nested_import.json",
"patch" : { "obj" : { "field_3" : "patched_value" } }
}
}
}
)";
const char* expectedOutput = R"(
{
"name" : "nested_import",
"object": {
"desc" : "Nested Import",
"obj" : {
"field_1" : "value_1",
"field_2" : "value_2",
"field_3" : "patched_value"
}
}
}
)";
TestImportLoadStore(inputFile, expectedOutput);
}
TEST_F(JsonImportingTests, NestedImportCycleTest)
{
const char* inputFile = R"(
{
"name" : "nested_import_cycle",
"object": {"$import" : "nested_import_c1.json"}
}
)";
TestImportCycle(inputFile);
}
TEST_F(JsonImportingTests, InsertNewImportTest)
{
const char* inputFile = R"(
{
"name" : "simple_object_import",
"object_1": {"$import" : "object.json"},
"object_2": {
"field_1" : "other_value",
"field_2" : "value_2",
"field_3" : "value_3"
}
}
)";
const char* expectedOutput = R"(
{
"name" : "simple_object_import",
"object_1": {"$import" : "object.json"},
"object_2": {
"$import" : {
"filename" : "object.json",
"patch" : { "field_1" : "other_value" }
}
}
}
)";
TestInsertNewImport(inputFile, expectedOutput);
}
}

@ -1299,6 +1299,35 @@ namespace SettingsRegistryTests
EXPECT_FALSE(m_registry->MergeCommandLineArgument(" ", {}, {})); EXPECT_FALSE(m_registry->MergeCommandLineArgument(" ", {}, {}));
} }
//
// MergeSettings
//
TEST_F(SettingsRegistryTest, MergeSettings_MergeJsonWithAnchorKey_StoresSettingsUnderneathKey)
{
constexpr AZStd::string_view anchorKey = "/Anchor/Root/0";
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
EXPECT_TRUE(m_registry->MergeSettings(R"({ "Test": "1" })", mergeFormat, anchorKey));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, m_registry->GetType("/Anchor/Root"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Object, m_registry->GetType("/Anchor/Root/0"));
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::String, m_registry->GetType("/Anchor/Root/0/Test"));
}
TEST_F(SettingsRegistryTest, MergeSettings_NotifierSignals_AtAnchorKeyAndStoresMergeType)
{
AZStd::string_view anchorKey = "/Anchor/Root";
bool callbackInvoked{};
auto callback = [anchorKey, &callbackInvoked](AZStd::string_view path, AZ::SettingsRegistryInterface::Type type)
{
EXPECT_EQ(anchorKey, path);
EXPECT_EQ(AZ::SettingsRegistryInterface::Type::Array, type);
callbackInvoked = true;
};
auto testNotifier1 = m_registry->RegisterNotifier(callback);
constexpr auto mergeFormat = AZ::SettingsRegistryInterface::Format::JsonMergePatch;
EXPECT_TRUE(m_registry->MergeSettings(R"([ "Test" ])", mergeFormat, anchorKey));
EXPECT_TRUE(callbackInvoked);
}
// //
// MergeSettingsFile // MergeSettingsFile
// //
@ -1331,7 +1360,7 @@ namespace SettingsRegistryTests
auto callback = [this](AZStd::string_view path, AZ::SettingsRegistryInterface::Type) auto callback = [this](AZStd::string_view path, AZ::SettingsRegistryInterface::Type)
{ {
EXPECT_TRUE(path.empty()); EXPECT_EQ("/Path", path);
AZ::s64 value = -1; AZ::s64 value = -1;
bool result = m_registry->Get(value, "/Path/Test"); bool result = m_registry->Get(value, "/Path/Test");
EXPECT_TRUE(result); EXPECT_TRUE(result);

@ -121,6 +121,7 @@ set(FILES
Serialization/Json/TestCases_Classes.cpp Serialization/Json/TestCases_Classes.cpp
Serialization/Json/TestCases_Compare.cpp Serialization/Json/TestCases_Compare.cpp
Serialization/Json/TestCases_Enum.cpp Serialization/Json/TestCases_Enum.cpp
Serialization/Json/TestCases_Importing.cpp
Serialization/Json/TestCases_Patching.cpp Serialization/Json/TestCases_Patching.cpp
Serialization/Json/TestCases_Pointers.h Serialization/Json/TestCases_Pointers.h
Serialization/Json/TestCases_Pointers.cpp Serialization/Json/TestCases_Pointers.cpp

@ -1924,13 +1924,11 @@ namespace AZ::IO
ArchiveLocationPriority Archive::GetPakPriority() const ArchiveLocationPriority Archive::GetPakPriority() const
{ {
int pakPriority = aznumeric_cast<int>(ArchiveVars{}.nPriority); int pakPriority = aznumeric_cast<int>(ArchiveVars{}.nPriority);
#if defined(AZ_ENABLE_TRACING)
if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr) if (auto console = AZ::Interface<AZ::IConsole>::Get(); console != nullptr)
{ {
AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority); [[maybe_unused]] AZ::GetValueResult getCvarResult = console->GetCvarValue("sys_PakPriority", pakPriority);
AZ_Error("Archive", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sys_PakPriority console variable failed with error %s", AZ::GetEnumString(getCvarResult)); AZ_Error("Archive", getCvarResult == AZ::GetValueResult::Success, "Lookup of 'sys_PakPriority console variable failed with error %s", AZ::GetEnumString(getCvarResult));
} }
#endif
return static_cast<ArchiveLocationPriority>(pakPriority); return static_cast<ArchiveLocationPriority>(pakPriority);
} }

@ -43,7 +43,7 @@ namespace AzFramework
class IMatchmakingAsyncRequests class IMatchmakingAsyncRequests
{ {
public: public:
AZ_RTTI(ISessionAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}"); AZ_RTTI(IMatchmakingAsyncRequests, "{53513480-2D02-493C-B44E-96AA27F42429}");
IMatchmakingAsyncRequests() = default; IMatchmakingAsyncRequests() = default;
virtual ~IMatchmakingAsyncRequests() = default; virtual ~IMatchmakingAsyncRequests() = default;
@ -60,4 +60,31 @@ namespace AzFramework
// @param stopMatchmakingRequest The request of StopMatchmaking operation // @param stopMatchmakingRequest The request of StopMatchmaking operation
virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0; virtual void StopMatchmakingAsync(const StopMatchmakingRequest& stopMatchmakingRequest) = 0;
}; };
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
} // namespace AzFramework } // namespace AzFramework

@ -13,36 +13,10 @@
namespace AzFramework namespace AzFramework
{ {
//! MatchmakingAsyncRequestNotifications
//! The notifications correspond to matchmaking async requests
class MatchmakingAsyncRequestNotifications
: public AZ::EBusTraits
{
public:
// Safeguard handler for multi-threaded use case
using MutexType = AZStd::recursive_mutex;
//////////////////////////////////////////////////////////////////////////
// EBusTraits overrides
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
//////////////////////////////////////////////////////////////////////////
// OnAcceptMatchAsyncComplete is fired once AcceptMatchAsync completes
virtual void OnAcceptMatchAsyncComplete() = 0;
// OnStartMatchmakingAsyncComplete is fired once StartMatchmakingAsync completes
// @param matchmakingTicketId The unique identifier for the matchmaking ticket
virtual void OnStartMatchmakingAsyncComplete(const AZStd::string& matchmakingTicketId) = 0;
// OnStopMatchmakingAsyncComplete is fired once StopMatchmakingAsync completes
virtual void OnStopMatchmakingAsyncComplete() = 0;
};
using MatchmakingAsyncRequestNotificationBus = AZ::EBus<MatchmakingAsyncRequestNotifications>;
//! MatchmakingNotifications //! MatchmakingNotifications
//! The matchmaking notifications to listen for performing required operations //! The matchmaking notifications to listen for performing required operations
class MatchAcceptanceNotifications //! based on matchmaking ticket event
class MatchmakingNotifications
: public AZ::EBusTraits : public AZ::EBusTraits
{ {
public: public:
@ -55,8 +29,18 @@ namespace AzFramework
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single; static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::Single;
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// OnMatchAcceptance is fired when DescribeMatchmaking ticket status is REQUIRES_ACCEPTANCE // OnMatchAcceptance is fired when match is found and pending on acceptance
// Use this notification to accept found match
virtual void OnMatchAcceptance() = 0; virtual void OnMatchAcceptance() = 0;
// OnMatchComplete is fired when match is complete
virtual void OnMatchComplete() = 0;
// OnMatchError is fired when match is processed with error
virtual void OnMatchError() = 0;
// OnMatchFailure is fired when match is failed to complete
virtual void OnMatchFailure() = 0;
}; };
using MatchAcceptanceNotificationBus = AZ::EBus<MatchAcceptanceNotifications>; using MatchmakingNotificationBus = AZ::EBus<MatchmakingNotifications>;
} // namespace AzFramework } // namespace AzFramework

@ -22,7 +22,7 @@ namespace AzFramework
AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType)); AZStd::scoped_ptr<ProcessWatcher> pWatcher(LaunchProcess(processLaunchInfo, communicationType));
if (!pWatcher) if (!pWatcher)
{ {
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process '%s %s'\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
return false; return false;
} }
else else
@ -31,7 +31,7 @@ namespace AzFramework
ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator(); ProcessCommunicator* pCommunicator = pWatcher->GetCommunicator();
if (!pCommunicator || !pCommunicator->IsValid()) if (!pCommunicator || !pCommunicator->IsValid())
{ {
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str()); AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: No communicator for watcher's process (%s %s)!\n", processLaunchInfo.m_processExecutableString.c_str(), processLaunchInfo.m_commandlineParameters.c_str());
return false; return false;
} }
else else

@ -30,22 +30,42 @@ namespace AzFramework
////////////////////////////////////////////////////////////////////////// //////////////////////////////////////////////////////////////////////////
// OnSessionHealthCheck is fired in health check process // OnSessionHealthCheck is fired in health check process
// @return The result of all OnSessionHealthCheck // Use this notification to perform any custom health check
// @return True if OnSessionHealthCheck succeeds, false otherwise
virtual bool OnSessionHealthCheck() = 0; virtual bool OnSessionHealthCheck() = 0;
// OnCreateSessionBegin is fired at the beginning of session creation // OnCreateSessionBegin is fired at the beginning of session creation process
// Use this notification to perform any necessary configuration or initialization before
// creating session
// @param sessionConfig The properties to describe a session // @param sessionConfig The properties to describe a session
// @return The result of all OnCreateSessionBegin notifications // @return True if OnCreateSessionBegin succeeds, false otherwise
virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0; virtual bool OnCreateSessionBegin(const SessionConfig& sessionConfig) = 0;
// OnDestroySessionBegin is fired at the beginning of session termination // OnCreateSessionEnd is fired at the end of session creation process
// @return The result of all OnDestroySessionBegin notifications // Use this notification to perform any follow-up operation after session is created and active
virtual void OnCreateSessionEnd() = 0;
// OnDestroySessionBegin is fired at the beginning of session termination process
// Use this notification to perform any cleanup operation before destroying session,
// like gracefully disconnect players, cleanup data, etc.
// @return True if OnDestroySessionBegin succeeds, false otherwise
virtual bool OnDestroySessionBegin() = 0; virtual bool OnDestroySessionBegin() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update // OnDestroySessionEnd is fired at the end of session termination process
// Use this notification to perform any follow-up operation after session is destroyed,
// like shutdown application process, etc.
virtual void OnDestroySessionEnd() = 0;
// OnUpdateSessionBegin is fired at the beginning of session update process
// Use this notification to perform any configuration or initialization to handle
// the session settings changing
// @param sessionConfig The properties to describe a session // @param sessionConfig The properties to describe a session
// @param updateReason The reason for session update // @param updateReason The reason for session update
virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0; virtual void OnUpdateSessionBegin(const SessionConfig& sessionConfig, const AZStd::string& updateReason) = 0;
// OnUpdateSessionBegin is fired at the end of session update process
// Use this notification to perform any follow-up operations after session is updated
virtual void OnUpdateSessionEnd() = 0;
}; };
using SessionNotificationBus = AZ::EBus<SessionNotifications>; using SessionNotificationBus = AZ::EBus<SessionNotifications>;
} // namespace AzFramework } // namespace AzFramework

@ -308,7 +308,7 @@ namespace AzFramework
event.data.data32[2] = 0; event.data.data32[2] = 0;
event.data.data32[3] = 1; event.data.data32[3] = 1;
event.data.data32[4] = 0; event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event( [[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event); (const char*)&event);
AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN"); AZ_Assert(ValidateXcbResult(xcbCheckResult), "Failed to set _NET_WM_STATE_FULLSCREEN");
@ -333,7 +333,7 @@ namespace AzFramework
event.data.data32[2] = 0; event.data.data32[2] = 0;
event.data.data32[3] = 0; event.data.data32[3] = 0;
event.data.data32[4] = 0; event.data.data32[4] = 0;
xcb_void_cookie_t xcbCheckResult = xcb_send_event( [[maybe_unused]] xcb_void_cookie_t xcbCheckResult = xcb_send_event(
m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT, m_xcbConnection, 1, m_xcbRootScreen->root, XCB_EVENT_MASK_STRUCTURE_NOTIFY | XCB_EVENT_MASK_SUBSTRUCTURE_REDIRECT,
(const char*)&event); (const char*)&event);
AZ_Assert( AZ_Assert(

@ -91,31 +91,42 @@ namespace AzFramework
return processId == 0; return processId == 0;
} }
/*! Executes a command in the child process after the fork operation has been executed. /*! Executes a command in the child process after the fork operation
* This function will never return. If the execvp command fails this will call _exit with * has been executed. This function will never return. If the execvpe
* the errno value as the return value since continuing execution after a execvp command * command fails this will call _exit since continuing execution after
* is invalid (it will be running the parent's code and in its address space and will * a execvpe command is invalid (it will be running the parent's code
* cause many issues). * and in its address space and will cause many issues).
*
* This function runs after a `fork()` call. `fork()` creates a copy of
* the current process, including the current state of the process's
* memory, at the time the call is made. However, it only creates a
* copy of the one thread that called `fork()`. This means that if any
* mutexes are locked by other threads at the time of `fork()`, those
* mutexes will remain locked in the child process, with no way to
* unlock them. So this function needs to ensure that it does as little
* work as possible.
* *
* \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer. * \param commandAndArgs - Array of strings that has the command to execute in index 0 with any args for the command following. Last element must be a null pointer.
* \param envionrmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer. * \param environmentVariables - Array of strings that contains environment variables that command should use. Last element must be a null pointer.
* \param processLaunchInfo - struct containing information about luanching the command * \param processLaunchInfo - struct containing information about luanching the command
* \param startupInfo - struct containing information needed to startup the command * \param startupInfo - struct containing information needed to startup the command
* \param errorPipe - a pipe file descriptor used to communicate a failed execvpe call's error code to the parent process
*/ */
void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo) [[noreturn]] static void ExecuteCommandAsChild(char** commandAndArgs, char** environmentVariables, const ProcessLauncher::ProcessLaunchInfo& processLaunchInfo, StartupInfo& startupInfo, const AZStd::array<int, 2>& errorPipe)
{ {
close(errorPipe[0]);
if (!processLaunchInfo.m_workingDirectory.empty()) if (!processLaunchInfo.m_workingDirectory.empty())
{ {
int res = chdir(processLaunchInfo.m_workingDirectory.c_str()); int res = chdir(processLaunchInfo.m_workingDirectory.c_str());
if (res != 0) if (res != 0)
{ {
std::cerr << strerror(errno) << std::endl; write(errorPipe[1], &errno, sizeof(int));
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to change the launched process' directory to '%s'.", processLaunchInfo.m_workingDirectory.c_str());
// We *have* to _exit as we are the child process and simply // We *have* to _exit as we are the child process and simply
// returning at this point would mean we would start running // returning at this point would mean we would start running
// the code from our parent process and that will just wreck // the code from our parent process and that will just wreck
// havoc. // havoc.
_exit(errno); _exit(0);
} }
} }
@ -135,15 +146,17 @@ namespace AzFramework
startupInfo.SetupHandlesForChildProcess(); startupInfo.SetupHandlesForChildProcess();
execve(commandAndArgs[0], commandAndArgs, environmentVariables); execvpe(commandAndArgs[0], commandAndArgs, environmentVariables);
const int errval = errno;
// If we get here then execve failed to run the requested program and // If we get here then execvpe failed to run the requested program and
// we have an error. In this case we need to exit the child process // we have an error. In this case we need to exit the child process
// to stop it from continuing to run as a clone of the parent // to stop it from continuing to run as a clone of the parent.
AZ_TracePrintf("Process Watcher", "ProcessWatcher::LaunchProcessAndRetrieveOutput: Unable to launch process %s : errno = %s ", commandAndArgs[0], strerror(errno)); // Communicate the error code back to the parent via a pipe for the
std::cerr << strerror(errno) << std::endl; // parent to read.
write(errorPipe[1], &errval, sizeof(errval));
_exit(errno); _exit(0);
} }
} }
@ -212,9 +225,8 @@ namespace AzFramework
AZStd::string outputString; AZStd::string outputString;
bool inQuotes = false; bool inQuotes = false;
for (size_t pos = 0; pos < processLaunchInfo.m_commandlineParameters.size(); ++pos) for (const char currentChar : processLaunchInfo.m_commandlineParameters)
{ {
char currentChar = processLaunchInfo.m_commandlineParameters[pos];
if (currentChar == '"') if (currentChar == '"')
{ {
inQuotes = !inQuotes; inQuotes = !inQuotes;
@ -231,7 +243,7 @@ namespace AzFramework
outputString.push_back(currentChar); outputString.push_back(currentChar);
} }
} }
if (!outputString.empty()) if (!outputString.empty())
{ {
commandTokens.push_back(outputString); commandTokens.push_back(outputString);
@ -249,10 +261,10 @@ namespace AzFramework
return false; return false;
} }
// Because of the way execve is defined we need to copy the strings from // Because of the way execvpe is defined we need to copy the strings from
// AZ::string (using c_str() returns a const char*) into a non-const char* // AZ::string (using c_str() returns a const char*) into a non-const char*
// Need to add one more as exec requires the array's last element to be a null pointer // Need to add one more as execvpe requires the array's last element to be a null pointer
char** commandAndArgs = new char*[commandTokens.size() + 1]; char** commandAndArgs = new char*[commandTokens.size() + 1];
for (int i = 0; i < commandTokens.size(); ++i) for (int i = 0; i < commandTokens.size(); ++i)
{ {
@ -275,7 +287,7 @@ namespace AzFramework
azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str()); azstrcat(environmentVariable.get(), envVarString.size() + 1, envVarString.c_str());
environmentVariablesVector.emplace_back(environmentVariable.get()); environmentVariablesVector.emplace_back(environmentVariable.get());
} }
// Adding one more as exec expects the array to have a nullptr as the last element // Adding one more as execvpe expects the array to have a nullptr as the last element
environmentVariablesVector.emplace_back(nullptr); environmentVariablesVector.emplace_back(nullptr);
environmentVariables = environmentVariablesVector.data(); environmentVariables = environmentVariablesVector.data();
} }
@ -288,15 +300,50 @@ namespace AzFramework
AZ_Assert(environmentVariables, "Environment variables for current process not available\n"); AZ_Assert(environmentVariables, "Environment variables for current process not available\n");
} }
// Set up a pipe to communicate the error code from the subprocess's execvpe call
AZStd::array<int, 2> childErrorPipeFds{};
pipe(childErrorPipeFds.data());
// This configures the write end of the pipe to close on calls to `exec`
fcntl(childErrorPipeFds[1], F_SETFD, fcntl(childErrorPipeFds[1], F_GETFD) | FD_CLOEXEC);
pid_t child_pid = fork(); pid_t child_pid = fork();
if (IsIdChildProcess(child_pid)) if (IsIdChildProcess(child_pid))
{ {
ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo); ExecuteCommandAsChild(commandAndArgs, environmentVariables, processLaunchInfo, processData.m_startupInfo, childErrorPipeFds);
} }
processData.m_childProcessId = child_pid;
// Close these handles as they are only to be used by the child process // Close these handles as they are only to be used by the child process
processData.m_startupInfo.CloseAllHandles(); processData.m_startupInfo.CloseAllHandles();
close(childErrorPipeFds[1]);
{
int errorCodeFromChild = 0;
int count = 0;
// Read from the error pipe.
// * If the child's call to execvpe succeeded, then the pipe will
// be closed due to setting FD_CLOEXEC on the write end of the
// pipe. `read()` will return 0.
// * If the child's call to execvpe failed, the child will have
// written the error code to the pipe. `read()` will return >0, and
// the data to be read is the error code from execvpe.
while ((count = read(childErrorPipeFds[0], &errorCodeFromChild, sizeof(errorCodeFromChild))) == -1)
{
if (errno != EAGAIN && errno != EINTR)
{
break;
}
}
if (count)
{
AZ_TracePrintf("Process Watcher", "ProcessLauncher::LaunchProcess: Unable to launch process %s : errno = %s\n", commandAndArgs[0], strerror(errorCodeFromChild));
processData.m_childProcessIsDone = true;
child_pid = -1;
}
}
close(childErrorPipeFds[0]);
processData.m_childProcessId = child_pid;
for (int i = 0; i < commandTokens.size(); i++) for (int i = 0; i < commandTokens.size(); i++)
{ {

@ -46,6 +46,8 @@ namespace AzManipulatorTestFramework
virtual void UpdateVisibility() = 0; virtual void UpdateVisibility() = 0;
//! Set if sticky select is enabled or not. //! Set if sticky select is enabled or not.
virtual void SetStickySelect(bool enabled) = 0; virtual void SetStickySelect(bool enabled) = 0;
//! Get default Editor Camera Position.
virtual AZ::Vector3 DefaultEditorCameraPosition() const = 0;
}; };
//! This interface is used to simulate the manipulator manager while the manipulators are under test. //! This interface is used to simulate the manipulator manager while the manipulators are under test.

@ -36,6 +36,7 @@ namespace AzManipulatorTestFramework
int GetViewportId() const override; int GetViewportId() const override;
void UpdateVisibility() override; void UpdateVisibility() override;
void SetStickySelect(bool enabled) override; void SetStickySelect(bool enabled) override;
AZ::Vector3 DefaultEditorCameraPosition() const override;
// ViewportInteractionRequestBus overrides ... // ViewportInteractionRequestBus overrides ...
AzFramework::CameraState GetCameraState() override; AzFramework::CameraState GetCameraState() override;

@ -120,6 +120,11 @@ namespace AzManipulatorTestFramework
m_stickySelect = enabled; m_stickySelect = enabled;
} }
AZ::Vector3 ViewportInteraction::DefaultEditorCameraPosition() const
{
return {};
}
void ViewportInteraction::SetGridSize(float size) void ViewportInteraction::SetGridSize(float size)
{ {
m_gridSize = size; m_gridSize = size;

@ -65,7 +65,7 @@ namespace AzNetworking
: m_delta(delta) : m_delta(delta)
, m_dataSerializer(m_delta.GetBufferPtr(), m_delta.GetBufferCapacity()) , m_dataSerializer(m_delta.GetBufferPtr(), m_delta.GetBufferCapacity())
{ {
m_namePrefix.reserve(128); ;
} }
DeltaSerializerCreate::~DeltaSerializerCreate() DeltaSerializerCreate::~DeltaSerializerCreate()
@ -73,7 +73,7 @@ namespace AzNetworking
// Delete any left over records that might be hanging around // Delete any left over records that might be hanging around
for (auto iter : m_records) for (auto iter : m_records)
{ {
delete iter.second; delete iter;
} }
m_records.clear(); m_records.clear();
} }
@ -160,28 +160,13 @@ namespace AzNetworking
return SerializeHelper(buffer, bufferCapacity, isString, outSize, name); return SerializeHelper(buffer, bufferCapacity, isString, outSize, name);
} }
AZStd::string DeltaSerializerCreate::GetNextObjectName(const char* name) bool DeltaSerializerCreate::BeginObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{ {
AZStd::string objectName = name;
objectName += ".";
objectName += AZStd::to_string(m_objectCounter);
++m_objectCounter;
return objectName;
}
bool DeltaSerializerCreate::BeginObject(const char* name, [[maybe_unused]] const char* typeName)
{
m_nameLengthStack.push_back(m_namePrefix.length());
m_namePrefix += GetNextObjectName(name);
m_namePrefix += ".";
return true; return true;
} }
bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName) bool DeltaSerializerCreate::EndObject([[maybe_unused]] const char* name, [[maybe_unused]] const char* typeName)
{ {
const size_t prevLen = m_nameLengthStack.back();
m_nameLengthStack.pop_back();
m_namePrefix.resize(prevLen);
return true; return true;
} }
@ -205,25 +190,15 @@ namespace AzNetworking
{ {
typedef AbstractValue::ValueT<T> ValueType; typedef AbstractValue::ValueT<T> ValueType;
const size_t prevLen = m_namePrefix.length(); AbstractValue::BaseValue* baseValue = m_records.size() > m_objectCounter ? m_records[m_objectCounter] : nullptr;
m_namePrefix += GetNextObjectName(name); ++m_objectCounter;
const AZ::HashValue32 nameHash = AZ::TypeHash32(m_namePrefix.c_str());
m_namePrefix.resize(prevLen);
AbstractValue::BaseValue*& baseValue = m_records[nameHash];
// If we are in the gather records phase, just save off the value records // If we are in the gather records phase, just save off the value records
if (m_gatheringRecords) if (m_gatheringRecords)
{ {
if (baseValue != nullptr) AZ_Assert(baseValue == nullptr, "Expected to create a new record but found a pre-existing one at index %d", m_objectCounter - 1);
{
AZ_Assert(false, "Duplicate name encountered in delta serializer. This will cause data to be serialized incorrectly.");
return false;
}
baseValue = new ValueType(value); baseValue = new ValueType(value);
m_records.push_back(baseValue);
} }
else // If we are not gathering records, then we are comparing them else // If we are not gathering records, then we are comparing them
{ {

@ -90,8 +90,6 @@ namespace AzNetworking
DeltaSerializerCreate(const DeltaSerializerCreate&) = delete; DeltaSerializerCreate(const DeltaSerializerCreate&) = delete;
DeltaSerializerCreate& operator=(const DeltaSerializerCreate&) = delete; DeltaSerializerCreate& operator=(const DeltaSerializerCreate&) = delete;
AZStd::string GetNextObjectName(const char* name);
template <typename T> template <typename T>
bool SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name); bool SerializeHelper(T& value, uint32_t bufferCapacity, bool isString, uint32_t& outSize, const char* name);
@ -105,9 +103,7 @@ namespace AzNetworking
bool m_gatheringRecords = false; bool m_gatheringRecords = false;
uint32_t m_objectCounter = 0; uint32_t m_objectCounter = 0;
AZStd::string m_namePrefix; AZStd::vector<AbstractValue::BaseValue*> m_records;
AZStd::vector<size_t> m_nameLengthStack;
AZStd::unordered_map<AZ::HashValue32, AbstractValue::BaseValue*> m_records;
NetworkInputSerializer m_dataSerializer; NetworkInputSerializer m_dataSerializer;
}; };

@ -11,4 +11,213 @@
namespace UnitTest namespace UnitTest
{ {
struct DeltaDataElement
{
AzNetworking::PacketId m_packetId = AzNetworking::InvalidPacketId;
uint32_t m_id = 0;
AZ::TimeMs m_timeMs = AZ::TimeMs{ 0 };
float m_blendFactor = 0.f;
AZStd::vector<int> m_growVector, m_shrinkVector;
bool Serialize(AzNetworking::ISerializer& serializer)
{
if (!serializer.Serialize(m_packetId, "PacketId")
|| !serializer.Serialize(m_id, "Id")
|| !serializer.Serialize(m_timeMs, "TimeMs")
|| !serializer.Serialize(m_blendFactor, "BlendFactor")
|| !serializer.Serialize(m_growVector, "GrowVector")
|| !serializer.Serialize(m_shrinkVector, "ShrinkVector"))
{
return false;
}
return true;
}
};
struct DeltaDataContainer
{
AZStd::string m_containerName;
AZStd::array<DeltaDataElement, 32> m_container;
// This logic is modeled after NetworkInputArray serialization in the Multiplayer Gem
bool Serialize(AzNetworking::ISerializer& serializer)
{
// Always serialize the full first element
if(!m_container[0].Serialize(serializer))
{
return false;
}
for (uint32_t i = 1; i < m_container.size(); ++i)
{
if (serializer.GetSerializerMode() == AzNetworking::SerializerMode::WriteToObject)
{
AzNetworking::SerializerDelta deltaSerializer;
// Read out the delta
if (!deltaSerializer.Serialize(serializer))
{
return false;
}
// Start with previous value
m_container[i] = m_container[i - 1];
// Then apply delta
AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
if (!applySerializer.ApplyDelta(m_container[i]))
{
return false;
}
}
else
{
AzNetworking::SerializerDelta deltaSerializer;
// Create the delta
AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
if (!createSerializer.CreateDelta(m_container[i - 1], m_container[i]))
{
return false;
}
// Then write out the delta
if (!deltaSerializer.Serialize(serializer))
{
return false;
}
}
}
return true;
}
// This logic is modeled after NetworkInputArray serialization in the Multiplayer Gem
bool SerializeNoDelta(AzNetworking::ISerializer& serializer)
{
for (uint32_t i = 0; i < m_container.size(); ++i)
{
if(!m_container[i].Serialize(serializer))
{
return false;
}
}
return true;
}
};
class DeltaSerializerTests
: public UnitTest::AllocatorsTestFixture
{
public:
void SetUp() override
{
UnitTest::AllocatorsTestFixture::SetUp();
}
void TearDown() override
{
UnitTest::AllocatorsTestFixture::TearDown();
}
};
static constexpr float BLEND_FACTOR_SCALE = 1.1f;
static constexpr uint32_t TIME_SCALE = 10;
DeltaDataContainer TestDeltaContainer()
{
DeltaDataContainer testContainer;
AZStd::vector<int> growVector, shrinkVector;
shrinkVector.resize(testContainer.m_container.array_size);
testContainer.m_containerName = "TestContainer";
for (int i = 0; i < testContainer.m_container.array_size; ++i)
{
testContainer.m_container[i].m_packetId = AzNetworking::PacketId(i);
testContainer.m_container[i].m_id = i;
testContainer.m_container[i].m_timeMs = AZ::TimeMs(i * TIME_SCALE);
testContainer.m_container[i].m_blendFactor = BLEND_FACTOR_SCALE * i;
growVector.push_back(i);
testContainer.m_container[i].m_growVector = growVector;
shrinkVector.resize(testContainer.m_container.array_size - i);
testContainer.m_container[i].m_shrinkVector = shrinkVector;
}
return testContainer;
}
TEST_F(DeltaSerializerTests, DeltaArray)
{
DeltaDataContainer inContainer = TestDeltaContainer();
AZStd::array<uint8_t, 2048> buffer;
AzNetworking::NetworkInputSerializer inSerializer(buffer.data(), static_cast<uint32_t>(buffer.size()));
// Always serialize the full first element
EXPECT_TRUE(inContainer.Serialize(inSerializer));
DeltaDataContainer outContainer;
AzNetworking::NetworkOutputSerializer outSerializer(buffer.data(), static_cast<uint32_t>(buffer.size()));
EXPECT_TRUE(outContainer.Serialize(outSerializer));
for (uint32_t i = 0; i > outContainer.m_container.size(); ++i)
{
EXPECT_EQ(inContainer.m_container[i].m_blendFactor, outContainer.m_container[i].m_blendFactor);
EXPECT_EQ(inContainer.m_container[i].m_id, outContainer.m_container[i].m_id);
EXPECT_EQ(inContainer.m_container[i].m_packetId, outContainer.m_container[i].m_packetId);
EXPECT_EQ(inContainer.m_container[i].m_timeMs, outContainer.m_container[i].m_timeMs);
EXPECT_EQ(inContainer.m_container[i].m_growVector[i], outContainer.m_container[i].m_growVector[i]);
EXPECT_EQ(inContainer.m_container[i].m_growVector.size(), outContainer.m_container[i].m_growVector.size());
EXPECT_EQ(inContainer.m_container[i].m_shrinkVector.size(), outContainer.m_container[i].m_shrinkVector.size());
}
}
TEST_F(DeltaSerializerTests, DeltaSerializerCreateUnused)
{
// Every function here should return a constant value regardless of inputs
AzNetworking::SerializerDelta deltaSerializer;
AzNetworking::DeltaSerializerCreate createSerializer(deltaSerializer);
EXPECT_EQ(createSerializer.GetCapacity(), 0);
EXPECT_EQ(createSerializer.GetSize(), 0);
EXPECT_EQ(createSerializer.GetBuffer(), nullptr);
EXPECT_EQ(createSerializer.GetSerializerMode(), AzNetworking::SerializerMode::ReadFromObject);
createSerializer.ClearTrackedChangesFlag(); //NO-OP
EXPECT_FALSE(createSerializer.GetTrackedChangesFlag());
EXPECT_TRUE(createSerializer.BeginObject("CreateSerializer", "Begin"));
EXPECT_TRUE(createSerializer.EndObject("CreateSerializer", "End"));
}
TEST_F(DeltaSerializerTests, DeltaArraySize)
{
DeltaDataContainer deltaContainer = TestDeltaContainer();
DeltaDataContainer noDeltaContainer = TestDeltaContainer();
AZStd::array<uint8_t, 2048> deltaBuffer;
AzNetworking::NetworkInputSerializer deltaSerializer(deltaBuffer.data(), static_cast<uint32_t>(deltaBuffer.size()));
AZStd::array<uint8_t, 2048> noDeltaBuffer;
AzNetworking::NetworkInputSerializer noDeltaSerializer(noDeltaBuffer.data(), static_cast<uint32_t>(noDeltaBuffer.size()));
EXPECT_TRUE(deltaContainer.Serialize(deltaSerializer));
EXPECT_FALSE(noDeltaContainer.SerializeNoDelta(noDeltaSerializer)); // Should run out of space
EXPECT_EQ(noDeltaSerializer.GetCapacity(), noDeltaSerializer.GetSize()); // Verify that the serializer filled up
EXPECT_FALSE(noDeltaSerializer.IsValid()); // and that it is no longer valid due to lack of space
}
TEST_F(DeltaSerializerTests, DeltaSerializerApplyUnused)
{
// Every function here should return a constant value regardless of inputs
AzNetworking::SerializerDelta deltaSerializer;
AzNetworking::DeltaSerializerApply applySerializer(deltaSerializer);
EXPECT_EQ(applySerializer.GetCapacity(), 0);
EXPECT_EQ(applySerializer.GetSize(), 0);
EXPECT_EQ(applySerializer.GetBuffer(), nullptr);
EXPECT_EQ(applySerializer.GetSerializerMode(), AzNetworking::SerializerMode::WriteToObject);
applySerializer.ClearTrackedChangesFlag(); //NO-OP
EXPECT_FALSE(applySerializer.GetTrackedChangesFlag());
EXPECT_TRUE(applySerializer.BeginObject("CreateSerializer", "Begin"));
EXPECT_TRUE(applySerializer.EndObject("CreateSerializer", "End"));
}
} }

@ -5,24 +5,19 @@
* SPDX-License-Identifier: Apache-2.0 OR MIT * SPDX-License-Identifier: Apache-2.0 OR MIT
* *
*/ */
#include <QCursor>
#include <AzCore/Component/Entity.h> #include <AzCore/Component/Entity.h>
#include <AzQtComponents/Components/ToastNotification.h>
#include <AzQtComponents/Components/ui_ToastNotification.h>
#include <GraphCanvas/Widgets/ToastNotification/ToastNotification.h> #include <QCursor>
#include <StaticLib/GraphCanvas/Widgets/ToastNotification/ui_ToastNotification.h> #include <QIcon>
#include <QToolButton>
#include <GraphCanvas/Components/ToastBus.h> #include <QPropertyAnimation>
namespace GraphCanvas namespace AzQtComponents
{ {
//////////////////////
// ToastNotification
//////////////////////
ToastNotification::ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration) ToastNotification::ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration)
: QDialog(parent, Qt::FramelessWindowHint) : QDialog(parent, Qt::FramelessWindowHint)
, m_toastId(AZ::Entity::MakeId())
, m_closeOnClick(true) , m_closeOnClick(true)
, m_ui(new Ui::ToastNotification()) , m_ui(new Ui::ToastNotification())
, m_fadeAnimation(nullptr) , m_fadeAnimation(nullptr)
@ -36,35 +31,35 @@ namespace GraphCanvas
QIcon toastIcon; QIcon toastIcon;
switch (toastConfiguration.GetToastType()) switch (toastConfiguration.m_toastType)
{ {
case ToastType::Error: case ToastType::Error:
toastIcon = QIcon(":/GraphCanvasEditorResources/toast_error_icon.png"); toastIcon = QIcon(":/stylesheet/img/logging/error.svg");
break; break;
case ToastType::Warning: case ToastType::Warning:
toastIcon = QIcon(":/GraphCanvasEditorResources/toast_warning_icon.png"); toastIcon = QIcon(":/stylesheet/img/logging/warning-yellow.svg");
break; break;
case ToastType::Information: case ToastType::Information:
toastIcon = QIcon(":/GraphCanvasEditorResources/toast_information_icon.png"); toastIcon = QIcon(":/stylesheet/img/logging/information.svg");
break; break;
case ToastType::Custom: case ToastType::Custom:
toastIcon = QIcon(toastConfiguration.GetCustomToastImage().c_str()); toastIcon = QIcon(toastConfiguration.m_customIconImage);
default: default:
break; break;
} }
m_ui->iconLabel->setPixmap(toastIcon.pixmap(64, 64)); m_ui->iconLabel->setPixmap(toastIcon.pixmap(64, 64));
m_ui->titleLabel->setText(toastConfiguration.GetTitleLabel().c_str()); m_ui->titleLabel->setText(toastConfiguration.m_title);
m_ui->mainLabel->setText(toastConfiguration.GetDescriptionLabel().c_str()); m_ui->mainLabel->setText(toastConfiguration.m_description);
m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.GetDuration().count())); m_lifeSpan.setInterval(aznumeric_cast<int>(toastConfiguration.m_duration.count()));
m_closeOnClick = toastConfiguration.GetCloseOnClick(); m_closeOnClick = toastConfiguration.m_closeOnClick;
m_ui->closeButton->setVisible(m_closeOnClick); m_ui->closeButton->setVisible(m_closeOnClick);
QObject::connect(m_ui->closeButton, &QToolButton::clicked, this, &ToastNotification::accept); QObject::connect(m_ui->closeButton, &QToolButton::clicked, this, &ToastNotification::accept);
m_fadeDuration = toastConfiguration.GetFadeDuration(); m_fadeDuration = toastConfiguration.m_fadeDuration;
QObject::connect(&m_lifeSpan, &QTimer::timeout, this, &ToastNotification::FadeOut); QObject::connect(&m_lifeSpan, &QTimer::timeout, this, &ToastNotification::FadeOut);
} }
@ -73,11 +68,6 @@ namespace GraphCanvas
{ {
} }
ToastId ToastNotification::GetToastId() const
{
return m_toastId;
}
void ToastNotification::ShowToastAtCursor() void ToastNotification::ShowToastAtCursor()
{ {
QPoint globalCursorPos = QCursor::pos(); QPoint globalCursorPos = QCursor::pos();
@ -131,8 +121,6 @@ namespace GraphCanvas
{ {
StartTimer(); StartTimer();
} }
emit ToastNotificationShown();
} }
void ToastNotification::hideEvent(QHideEvent* hideEvent) void ToastNotification::hideEvent(QHideEvent* hideEvent)
@ -147,7 +135,6 @@ namespace GraphCanvas
delete m_fadeAnimation; delete m_fadeAnimation;
} }
ToastNotificationBus::Event(GetToastId(), &ToastNotifications::OnToastDismissed);
emit ToastNotificationHidden(); emit ToastNotificationHidden();
} }
@ -155,7 +142,7 @@ namespace GraphCanvas
{ {
if (m_closeOnClick) if (m_closeOnClick)
{ {
ToastNotificationBus::Event(GetToastId(), &ToastNotifications::OnToastInteraction); emit ToastNotificationInteraction();
accept(); accept();
} }
} }
@ -205,5 +192,5 @@ namespace GraphCanvas
accept(); accept();
} }
} }
#include <StaticLib/GraphCanvas/Widgets/ToastNotification/moc_ToastNotification.cpp> #include "Components/moc_ToastNotification.cpp"
} }

@ -8,17 +8,13 @@
#pragma once #pragma once
#if !defined(Q_MOC_RUN) #if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzQtComponents/Components/ToastNotificationConfiguration.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <QEvent> #include <QEvent>
#include <QDialog> #include <QDialog>
#include <QMouseEvent> #include <QMouseEvent>
#include <QPropertyAnimation>
#include <QTimer> #include <QTimer>
#include <AzCore/Component/TickBus.h>
#include <AzCore/std/chrono/chrono.h>
#include <AzCore/std/smart_ptr/unique_ptr.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <GraphCanvas/Editor/EditorTypes.h>
#endif #endif
namespace Ui namespace Ui
@ -26,20 +22,20 @@ namespace Ui
class ToastNotification; class ToastNotification;
} }
namespace GraphCanvas QT_FORWARD_DECLARE_CLASS(QPropertyAnimation)
namespace AzQtComponents
{ {
class ToastNotification class AZ_QT_COMPONENTS_API ToastNotification
: public QDialog : public QDialog
{ {
Q_OBJECT Q_OBJECT
public: public:
AZ_CLASS_ALLOCATOR(ToastNotification, AZ::SystemAllocator, 0); AZ_CLASS_ALLOCATOR(ToastNotification, AZ::SystemAllocator, 0);
ToastNotification(QWidget* parent, const ToastConfiguration& configuration); ToastNotification(QWidget* parent, const ToastConfiguration& toastConfiguration);
virtual ~ToastNotification(); virtual ~ToastNotification();
ToastId GetToastId() const;
// Shows the toast notification relative to the current cursor. // Shows the toast notification relative to the current cursor.
void ShowToastAtCursor(); void ShowToastAtCursor();
@ -53,30 +49,26 @@ namespace GraphCanvas
// QDialog // QDialog
void showEvent(QShowEvent* showEvent) override; void showEvent(QShowEvent* showEvent) override;
void hideEvent(QHideEvent* hideEvent) override; void hideEvent(QHideEvent* hideEvent) override;
void mousePressEvent(QMouseEvent* mouseEvent) override; void mousePressEvent(QMouseEvent* mouseEvent) override;
bool eventFilter(QObject* object, QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override;
////
public slots: public slots:
void StartTimer(); void StartTimer();
void FadeOut(); void FadeOut();
signals: signals:
void ToastNotificationShown();
void ToastNotificationHidden(); void ToastNotificationHidden();
void ToastNotificationInteraction();
private: private:
QPropertyAnimation* m_fadeAnimation; QPropertyAnimation* m_fadeAnimation;
AZStd::chrono::milliseconds m_fadeDuration;
ToastId m_toastId;
bool m_closeOnClick; bool m_closeOnClick;
QTimer m_lifeSpan; QTimer m_lifeSpan;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_fadeDuration;
AZStd::unique_ptr<Ui::ToastNotification> m_ui; AZStd::unique_ptr<Ui::ToastNotification> m_ui;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
}; };
} } // namespace AzQtComponents

@ -101,7 +101,7 @@
<string/> <string/>
</property> </property>
<property name="pixmap"> <property name="pixmap">
<pixmap resource="../Resources/GraphCanvasEditorResources.qrc">:/GraphCanvasEditorResources/toast_information_icon.png</pixmap> <pixmap resource="resources.qrc">:/stylesheet/img/logging/information.svg</pixmap>
</property> </property>
<property name="scaledContents"> <property name="scaledContents">
<bool>true</bool> <bool>true</bool>
@ -203,8 +203,8 @@
<string>...</string> <string>...</string>
</property> </property>
<property name="icon"> <property name="icon">
<iconset resource="../Resources/GraphCanvasEditorResources.qrc"> <iconset resource="resources.qrc">
<normaloff>:/GraphCanvasEditorResources/lineedit_clear.png</normaloff>:/GraphCanvasEditorResources/lineedit_clear.png</iconset> <normaloff>:/stylesheet/img/close_x.svg</normaloff>:/stylesheet/img/close_x.svg</iconset>
</property> </property>
</widget> </widget>
</item> </item>
@ -230,8 +230,7 @@
</layout> </layout>
</widget> </widget>
<resources> <resources>
<include location="../Resources/GraphCanvasEditorResources.qrc"/> <include location="resources.qrc"/>
<include location="../Resources/GraphCanvasEditorResources.qrc"/>
</resources> </resources>
<connections/> <connections/>
</ui> </ui>

@ -0,0 +1,18 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzQtComponents/Components/ToastNotificationConfiguration.h>
namespace AzQtComponents
{
ToastConfiguration::ToastConfiguration(ToastType toastType, const QString& title, const QString& description)
: m_toastType(toastType)
, m_title(title)
, m_description(description)
{
}
} // namespace AzQtComponents

@ -0,0 +1,46 @@
/*
* 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
#if !defined(Q_MOC_RUN)
#include <AzQtComponents/AzQtComponentsAPI.h>
#include <AzCore/Memory/SystemAllocator.h>
#include <AzCore/std/chrono/chrono.h>
#include <QString>
#endif
namespace AzQtComponents
{
enum class ToastType
{
Information,
Warning,
Error,
Custom
};
class AZ_QT_COMPONENTS_API ToastConfiguration
{
public:
AZ_CLASS_ALLOCATOR(ToastConfiguration, AZ::SystemAllocator, 0);
ToastConfiguration(ToastType toastType, const QString& title, const QString& description);
bool m_closeOnClick = true;
ToastType m_toastType = ToastType::Information;
QString m_title;
QString m_description;
QString m_customIconImage;
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::chrono::milliseconds m_duration = AZStd::chrono::milliseconds(5000);
AZStd::chrono::milliseconds m_fadeDuration = AZStd::chrono::milliseconds(250);
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
} // namespace AzQtComponents

@ -49,6 +49,11 @@ set(FILES
Components/Titlebar.h Components/Titlebar.h
Components/TitleBarOverdrawHandler.cpp Components/TitleBarOverdrawHandler.cpp
Components/TitleBarOverdrawHandler.h Components/TitleBarOverdrawHandler.h
Components/ToastNotification.cpp
Components/ToastNotification.h
Components/ToastNotificationConfiguration.h
Components/ToastNotificationConfiguration.cpp
Components/ToastNotification.ui
Components/ToolButtonComboBox.cpp Components/ToolButtonComboBox.cpp
Components/ToolButtonComboBox.h Components/ToolButtonComboBox.h
Components/ToolButtonLineEdit.cpp Components/ToolButtonLineEdit.cpp

@ -7,6 +7,7 @@
*/ */
#include <dlfcn.h> #include <dlfcn.h>
#include <iostream> #include <iostream>
#include <AzCore/IO/Path/Path.h>
#include <AzTest/Platform.h> #include <AzTest/Platform.h>
#include <sys/types.h> #include <sys/types.h>
@ -20,11 +21,16 @@ public:
explicit ModuleHandle(const std::string& lib) explicit ModuleHandle(const std::string& lib)
: m_libHandle(nullptr) : m_libHandle(nullptr)
{ {
std::string libext = lib; AZ::IO::FixedMaxPath libext = AZStd::string_view{ lib.c_str(), lib.size() };
if (!AZ::Test::EndsWith(libext, ".dylib")) if (!libext.Stem().Native().starts_with(AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX))
{ {
libext += ".dylib"; libext = AZ_TRAIT_OS_DYNAMIC_LIBRARY_PREFIX + libext.Native();
} }
if (libext.Extension() != AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION)
{
libext.Native() += AZ_TRAIT_OS_DYNAMIC_LIBRARY_EXTENSION;
}
m_libHandle = dlopen(libext.c_str(), RTLD_NOW); m_libHandle = dlopen(libext.c_str(), RTLD_NOW);
const char* error = dlerror(); const char* error = dlerror();
if (error) if (error)

@ -381,22 +381,13 @@ namespace AzToolsFramework
return; return;
} }
bool isPrefabSystemEnabled = false; // Even though these child entities will immediately be destroyed, their entity info may be recycled
AzFramework::ApplicationRequests::Bus::BroadcastResult( // Ensure they don't have any lingering inaccurate parent data
isPrefabSystemEnabled, &AzFramework::ApplicationRequests::IsPrefabSystemEnabled); auto children = entityInfo.GetChildren();
for (auto childId : children)
// For slices, orphan any children that remain attached to the entity {
// For prefabs, this is an unneeded operation because the prefab system handles the orphans ReparentChild(childId, AZ::EntityId(), entityId);
// and the extra reparenting operation can be problematic for consumers subscribed to entity m_entityOrphanTable[entityId].insert(childId);
// events, such as the entity outliner.
if (!isPrefabSystemEnabled)
{
auto children = entityInfo.GetChildren();
for (auto childId : children)
{
ReparentChild(childId, AZ::EntityId(), entityId);
m_entityOrphanTable[entityId].insert(childId);
}
} }
m_savedOrderInfo[entityId] = AZStd::make_pair(entityInfo.GetParent(), entityInfo.GetIndexForSorting()); m_savedOrderInfo[entityId] = AZStd::make_pair(entityInfo.GetParent(), entityInfo.GetIndexForSorting());
@ -1200,26 +1191,41 @@ namespace AzToolsFramework
auto childItr = m_childIndexCache.find(childId); auto childItr = m_childIndexCache.find(childId);
if (childItr == m_childIndexCache.end()) if (childItr == m_childIndexCache.end())
{ {
//cache indices for faster lookup // m_children is guaranteed to be ordered by EntityId, do a sorted insertion
m_childIndexCache[childId] = static_cast<AZ::u64>(m_children.size()); auto insertedChildIndex = AZStd::upper_bound(m_children.begin(), m_children.end(), childId);
m_children.push_back(childId); insertedChildIndex = m_children.insert(insertedChildIndex, childId);
// Cache all affected child indices for fast lookup
for (auto it = insertedChildIndex; it != m_children.end(); ++it)
{
const AZ::u64 newChildIndex = static_cast<AZ::u64>(it - m_children.begin());
m_childIndexCache[*it] = newChildIndex;
}
} }
} }
void EditorEntityModel::EditorEntityModelEntry::RemoveChild(AZ::EntityId childId) void EditorEntityModel::EditorEntityModelEntry::RemoveChild(AZ::EntityId childId)
{ {
auto childItr = m_childIndexCache.find(childId); // Retrieve our child index from the cache
if (childItr != m_childIndexCache.end()) auto cachedIndexItr = m_childIndexCache.find(childId);
if (cachedIndexItr == m_childIndexCache.end())
{ {
// Take the last entry and move it into the removed spot instead of deleting the entry and having to move all AZ_Assert(false, "Attempted to remove an unknown child");
// following entries one step down. return;
AZ::EntityId backEntity = m_children.back(); }
m_children[childItr->second] = backEntity;
// Update cached index for the moved id to the new index. // Build an iterator for m_children based on our cached index
m_childIndexCache[backEntity] = childItr->second; auto childItr = m_children.begin() + cachedIndexItr->second;
// Now remove the deleted id from the children and cache.
m_childIndexCache.erase(childId); // Remove our child from the cache
m_children.erase(m_children.end() - 1); m_childIndexCache.erase(cachedIndexItr);
// Remove our child, fix up the cache entries for any subsequent children
auto elementsToFixItr = m_children.erase(childItr);
for (auto it = elementsToFixItr; it != m_children.end(); ++it)
{
const AZ::u64 newChildIndex = static_cast<AZ::u64>(it - m_children.begin());
m_childIndexCache[*it] = newChildIndex;
} }
} }
@ -1256,8 +1262,17 @@ namespace AzToolsFramework
AZ::u64 EditorEntityModel::EditorEntityModelEntry::GetChildIndex(AZ::EntityId childId) const AZ::u64 EditorEntityModel::EditorEntityModelEntry::GetChildIndex(AZ::EntityId childId) const
{ {
// Return the cached index, if available.
auto childItr = m_childIndexCache.find(childId); auto childItr = m_childIndexCache.find(childId);
return childItr != m_childIndexCache.end() ? childItr->second : static_cast<AZ::u64>(m_children.size()); if (childItr != m_childIndexCache.end())
{
return childItr->second;
}
// On initialization, GetChildIndex may be queried for a childId that is not yet in the child list.
// Return the position it would be inserted at in EditorEntityModelEntry::AddChild
auto targetChildPositionItr = AZStd::upper_bound(m_children.begin(), m_children.end(), childId);
return static_cast<AZ::u64>(targetChildPositionItr - m_children.begin());
} }
AZStd::string EditorEntityModel::EditorEntityModelEntry::GetName() const AZStd::string EditorEntityModel::EditorEntityModelEntry::GetName() const

@ -53,7 +53,7 @@ namespace AzToolsFramework
AZ_Assert(m_loaderInterface != nullptr, AZ_Assert(m_loaderInterface != nullptr,
"Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work"); "Couldn't get prefab loader interface, it's a requirement for PrefabEntityOwnership system to work");
m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "NewLevel.prefab")); m_rootInstance = AZStd::unique_ptr<Prefab::Instance>(m_prefabSystemComponent->CreatePrefab({}, {}, "newLevel.prefab"));
m_sliceOwnershipService.BusConnect(m_entityContextId); m_sliceOwnershipService.BusConnect(m_entityContextId);
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage; m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
m_editorSliceOwnershipService.BusConnect(); m_editorSliceOwnershipService.BusConnect();

@ -244,6 +244,17 @@ namespace AzToolsFramework
const auto eventType = event->type(); const auto eventType = event->type();
if (eventType == QEvent::Type::MouseMove)
{
// clear override cursor when moving outside of the viewport
const auto* mouseEvent = static_cast<const QMouseEvent*>(event);
if (m_overrideCursor && !m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(mouseEvent->globalPos())))
{
qApp->restoreOverrideCursor();
m_overrideCursor = false;
}
}
// Only accept mouse & key release events that originate from an object that is not our target widget, // Only accept mouse & key release events that originate from an object that is not our target widget,
// as we don't want to erroneously intercept user input meant for another component. // as we don't want to erroneously intercept user input meant for another component.
if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease) if (object != m_sourceWidget && eventType != QEvent::Type::KeyRelease && eventType != QEvent::Type::MouseButtonRelease)
@ -262,7 +273,7 @@ namespace AzToolsFramework
if (eventType == QEvent::FocusIn) if (eventType == QEvent::FocusIn)
{ {
const auto globalCursorPosition = QCursor::pos(); const auto globalCursorPosition = QCursor::pos();
if (m_sourceWidget->geometry().contains(globalCursorPosition)) if (m_sourceWidget->geometry().contains(m_sourceWidget->mapFromGlobal(globalCursorPosition)))
{ {
HandleMouseMoveEvent(globalCursorPosition); HandleMouseMoveEvent(globalCursorPosition);
} }
@ -452,4 +463,32 @@ namespace AzToolsFramework
} }
} }
} }
static Qt::CursorShape QtCursorFromAzCursor(const ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
switch (cursorStyleOverride)
{
case ViewportInteraction::CursorStyleOverride::Forbidden:
return Qt::ForbiddenCursor;
default:
return Qt::ArrowCursor;
}
}
void QtEventToAzInputMapper::SetOverrideCursor(ViewportInteraction::CursorStyleOverride cursorStyleOverride)
{
ClearOverrideCursor();
qApp->setOverrideCursor(QtCursorFromAzCursor(cursorStyleOverride));
m_overrideCursor = true;
}
void QtEventToAzInputMapper::ClearOverrideCursor()
{
if (m_overrideCursor)
{
qApp->restoreOverrideCursor();
m_overrideCursor = false;
}
}
} // namespace AzToolsFramework } // namespace AzToolsFramework

@ -15,10 +15,11 @@
#include <AzFramework/Input/Channels/InputChannelDeltaWithSharedPosition2D.h> #include <AzFramework/Input/Channels/InputChannelDeltaWithSharedPosition2D.h>
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedModifierKeyStates.h> #include <AzFramework/Input/Channels/InputChannelDigitalWithSharedModifierKeyStates.h>
#include <AzFramework/Input/Channels/InputChannelDigitalWithSharedPosition2D.h> #include <AzFramework/Input/Channels/InputChannelDigitalWithSharedPosition2D.h>
#include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h> #include <AzFramework/Input/Devices/Keyboard/InputDeviceKeyboard.h>
#include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h> #include <AzFramework/Input/Devices/Mouse/InputDeviceMouse.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
#include <QEvent> #include <QEvent>
#include <QObject> #include <QObject>
#include <QPoint> #include <QPoint>
@ -55,6 +56,9 @@ namespace AzToolsFramework
//! like a dolly or rotation, where mouse movement is important but cursor location is not. //! like a dolly or rotation, where mouse movement is important but cursor location is not.
void SetCursorCaptureEnabled(bool enabled); void SetCursorCaptureEnabled(bool enabled);
void SetOverrideCursor(ViewportInteraction::CursorStyleOverride cursorStyleOverride);
void ClearOverrideCursor();
// QObject overrides... // QObject overrides...
bool eventFilter(QObject* object, QEvent* event) override; bool eventFilter(QObject* object, QEvent* event) override;
@ -164,6 +168,8 @@ namespace AzToolsFramework
bool m_enabled = true; bool m_enabled = true;
// Flags whether or not the cursor is being constrained to the source widget (for invisible mouse movement). // Flags whether or not the cursor is being constrained to the source widget (for invisible mouse movement).
bool m_capturingCursor = false; bool m_capturingCursor = false;
// Flags whether the cursor has been overridden.
bool m_overrideCursor = false;
// Our viewport-specific AZ devices. We control their internal input channel states. // Our viewport-specific AZ devices. We control their internal input channel states.
AZStd::unique_ptr<EditorQtMouseDevice> m_mouseDevice; AZStd::unique_ptr<EditorQtMouseDevice> m_mouseDevice;

@ -28,7 +28,9 @@ namespace AzToolsFramework::Prefab
"Instance Entity Mapper Interface could not be found. " "Instance Entity Mapper Interface could not be found. "
"Check that it is being correctly initialized."); "Check that it is being correctly initialized.");
EditorEntityInfoNotificationBus::Handler::BusConnect();
EditorEntityContextNotificationBus::Handler::BusConnect(); EditorEntityContextNotificationBus::Handler::BusConnect();
PrefabPublicNotificationBus::Handler::BusConnect();
AZ::Interface<PrefabFocusInterface>::Register(this); AZ::Interface<PrefabFocusInterface>::Register(this);
AZ::Interface<PrefabFocusPublicInterface>::Register(this); AZ::Interface<PrefabFocusPublicInterface>::Register(this);
} }
@ -37,10 +39,12 @@ namespace AzToolsFramework::Prefab
{ {
AZ::Interface<PrefabFocusPublicInterface>::Unregister(this); AZ::Interface<PrefabFocusPublicInterface>::Unregister(this);
AZ::Interface<PrefabFocusInterface>::Unregister(this); AZ::Interface<PrefabFocusInterface>::Unregister(this);
PrefabPublicNotificationBus::Handler::BusDisconnect();
EditorEntityContextNotificationBus::Handler::BusDisconnect(); EditorEntityContextNotificationBus::Handler::BusDisconnect();
EditorEntityInfoNotificationBus::Handler::BusDisconnect();
} }
void PrefabFocusHandler::Initialize() void PrefabFocusHandler::InitializeEditorInterfaces()
{ {
m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get(); m_containerEntityInterface = AZ::Interface<ContainerEntityInterface>::Get();
AZ_Assert( AZ_Assert(
@ -55,13 +59,6 @@ namespace AzToolsFramework::Prefab
"Prefab - PrefabFocusHandler - " "Prefab - PrefabFocusHandler - "
"Focus Mode Interface could not be found. " "Focus Mode Interface could not be found. "
"Check that it is being correctly initialized."); "Check that it is being correctly initialized.");
m_instanceEntityMapperInterface = AZ::Interface<InstanceEntityMapperInterface>::Get();
AZ_Assert(
m_instanceEntityMapperInterface,
"Prefab - PrefabFocusHandler - "
"Instance Entity Mapper Interface could not be found. "
"Check that it is being correctly initialized.");
} }
PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId) PrefabFocusOperationResult PrefabFocusHandler::FocusOnOwningPrefab(AZ::EntityId entityId)
@ -90,12 +87,12 @@ namespace AzToolsFramework::Prefab
PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index) PrefabFocusOperationResult PrefabFocusHandler::FocusOnPathIndex([[maybe_unused]] AzFramework::EntityContextId entityContextId, int index)
{ {
if (index < 0 || index >= m_instanceFocusVector.size()) if (index < 0 || index >= m_instanceFocusHierarchy.size())
{ {
return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex.")); return AZ::Failure(AZStd::string("Prefab Focus Handler: Invalid index on FocusOnPathIndex."));
} }
InstanceOptionalReference focusedInstance = m_instanceFocusVector[index]; InstanceOptionalReference focusedInstance = m_instanceFocusHierarchy[index];
FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId()); FocusOnOwningPrefab(focusedInstance->get().GetContainerEntityId());
@ -134,41 +131,37 @@ namespace AzToolsFramework::Prefab
return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on.")); return AZ::Failure(AZStd::string("Prefab Focus Handler: invalid instance to focus on."));
} }
if (!m_isInitialized) // Close all container entities in the old path.
{ CloseInstanceContainers(m_instanceFocusHierarchy);
Initialize();
}
if (!m_focusedInstance.has_value() || &m_focusedInstance->get() != &focusedInstance->get())
{
// Close all container entities in the old path
CloseInstanceContainers(m_instanceFocusVector);
m_focusedInstance = focusedInstance; m_focusedInstance = focusedInstance;
m_focusedTemplateId = focusedInstance->get().GetTemplateId(); m_focusedTemplateId = focusedInstance->get().GetTemplateId();
AZ::EntityId containerEntityId; AZ::EntityId containerEntityId;
if (focusedInstance->get().GetParentInstance() != AZStd::nullopt) if (focusedInstance->get().GetParentInstance() != AZStd::nullopt)
{ {
containerEntityId = focusedInstance->get().GetContainerEntityId(); containerEntityId = focusedInstance->get().GetContainerEntityId();
} }
else else
{ {
containerEntityId = AZ::EntityId(); containerEntityId = AZ::EntityId();
} }
// Focus on the descendants of the container entity // Focus on the descendants of the container entity in the Editor, if the interface is initialized.
if (m_focusModeInterface)
{
m_focusModeInterface->SetFocusRoot(containerEntityId); m_focusModeInterface->SetFocusRoot(containerEntityId);
}
// Refresh path variables // Refresh path variables.
RefreshInstanceFocusList(); RefreshInstanceFocusList();
RefreshInstanceFocusPath();
// Open all container entities in the new path // Open all container entities in the new path.
OpenInstanceContainers(m_instanceFocusVector); OpenInstanceContainers(m_instanceFocusHierarchy);
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged); PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
return AZ::Success(); return AZ::Success();
} }
@ -220,49 +213,80 @@ namespace AzToolsFramework::Prefab
const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const const int PrefabFocusHandler::GetPrefabFocusPathLength([[maybe_unused]] AzFramework::EntityContextId entityContextId) const
{ {
return aznumeric_cast<int>(m_instanceFocusVector.size()); return aznumeric_cast<int>(m_instanceFocusHierarchy.size());
} }
void PrefabFocusHandler::OnEntityStreamLoadSuccess() void PrefabFocusHandler::OnContextReset()
{ {
if (!m_isInitialized)
{
Initialize();
}
// Clear the old focus vector // Clear the old focus vector
m_instanceFocusVector.clear(); m_instanceFocusHierarchy.clear();
// Focus on the root prefab (AZ::EntityId() will default to it) // Focus on the root prefab (AZ::EntityId() will default to it)
FocusOnPrefabInstanceOwningEntityId(AZ::EntityId()); FocusOnPrefabInstanceOwningEntityId(AZ::EntityId());
} }
void PrefabFocusHandler::OnEntityInfoUpdatedName(AZ::EntityId entityId, [[maybe_unused]]const AZStd::string& name)
{
// Determine if the entityId is the container for any of the instances in the vector
auto result = AZStd::find_if(
m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end(),
[entityId](const InstanceOptionalReference& instance)
{
return (instance->get().GetContainerEntityId() == entityId);
}
);
if (result != m_instanceFocusHierarchy.end())
{
// Refresh the path and notify changes.
RefreshInstanceFocusPath();
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
}
void PrefabFocusHandler::OnPrefabInstancePropagationEnd()
{
// Refresh the path and notify changes in case propagation updated any container names.
RefreshInstanceFocusPath();
PrefabFocusNotificationBus::Broadcast(&PrefabFocusNotifications::OnPrefabFocusChanged);
}
void PrefabFocusHandler::RefreshInstanceFocusList() void PrefabFocusHandler::RefreshInstanceFocusList()
{ {
m_instanceFocusVector.clear(); m_instanceFocusHierarchy.clear();
m_instanceFocusPath.clear();
AZStd::list<InstanceOptionalReference> instanceFocusList; AZStd::list<InstanceOptionalReference> instanceFocusList;
// Use a support list to easily push front while traversing the prefab hierarchy
InstanceOptionalReference currentInstance = m_focusedInstance; InstanceOptionalReference currentInstance = m_focusedInstance;
while (currentInstance.has_value()) while (currentInstance.has_value())
{ {
instanceFocusList.push_front(currentInstance); m_instanceFocusHierarchy.emplace_back(currentInstance);
currentInstance = currentInstance->get().GetParentInstance(); currentInstance = currentInstance->get().GetParentInstance();
} }
// Populate internals using the support list // Invert the vector, since we need the top instance to be at index 0
for (auto& instance : instanceFocusList) AZStd::reverse(m_instanceFocusHierarchy.begin(), m_instanceFocusHierarchy.end());
}
void PrefabFocusHandler::RefreshInstanceFocusPath()
{
m_instanceFocusPath.clear();
for (const InstanceOptionalReference& instance : m_instanceFocusHierarchy)
{ {
m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName()); m_instanceFocusPath.Append(instance->get().GetContainerEntity()->get().GetName());
m_instanceFocusVector.emplace_back(instance);
} }
} }
void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const void PrefabFocusHandler::OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{ {
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances) for (const InstanceOptionalReference& instance : instances)
{ {
if (instance.has_value()) if (instance.has_value())
@ -274,6 +298,12 @@ namespace AzToolsFramework::Prefab
void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const void PrefabFocusHandler::CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const
{ {
// If this is called outside the Editor, this interface won't be initialized.
if (!m_containerEntityInterface)
{
return;
}
for (const InstanceOptionalReference& instance : instances) for (const InstanceOptionalReference& instance : instances)
{ {
if (instance.has_value()) if (instance.has_value())

@ -11,9 +11,11 @@
#include <AzCore/Memory/SystemAllocator.h> #include <AzCore/Memory/SystemAllocator.h>
#include <AzToolsFramework/Entity/EditorEntityContextBus.h> #include <AzToolsFramework/Entity/EditorEntityContextBus.h>
#include <AzToolsFramework/Entity/EditorEntityInfoBus.h>
#include <AzToolsFramework/FocusMode/FocusModeInterface.h> #include <AzToolsFramework/FocusMode/FocusModeInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusInterface.h> #include <AzToolsFramework/Prefab/PrefabFocusInterface.h>
#include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h> #include <AzToolsFramework/Prefab/PrefabFocusPublicInterface.h>
#include <AzToolsFramework/Prefab/PrefabPublicNotificationBus.h>
#include <AzToolsFramework/Prefab/Template/Template.h> #include <AzToolsFramework/Prefab/Template/Template.h>
namespace AzToolsFramework namespace AzToolsFramework
@ -30,7 +32,9 @@ namespace AzToolsFramework::Prefab
class PrefabFocusHandler final class PrefabFocusHandler final
: private PrefabFocusInterface : private PrefabFocusInterface
, private PrefabFocusPublicInterface , private PrefabFocusPublicInterface
, private PrefabPublicNotificationBus::Handler
, private EditorEntityContextNotificationBus::Handler , private EditorEntityContextNotificationBus::Handler
, private EditorEntityInfoNotificationBus::Handler
{ {
public: public:
AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0); AZ_CLASS_ALLOCATOR(PrefabFocusHandler, AZ::SystemAllocator, 0);
@ -38,9 +42,8 @@ namespace AzToolsFramework::Prefab
PrefabFocusHandler(); PrefabFocusHandler();
~PrefabFocusHandler(); ~PrefabFocusHandler();
void Initialize();
// PrefabFocusInterface overrides ... // PrefabFocusInterface overrides ...
void InitializeEditorInterfaces() override;
PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override; PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) override;
TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override; TemplateId GetFocusedPrefabTemplateId(AzFramework::EntityContextId entityContextId) const override;
InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override; InstanceOptionalReference GetFocusedPrefabInstance(AzFramework::EntityContextId entityContextId) const override;
@ -54,25 +57,34 @@ namespace AzToolsFramework::Prefab
const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override; const int GetPrefabFocusPathLength(AzFramework::EntityContextId entityContextId) const override;
// EditorEntityContextNotificationBus overrides ... // EditorEntityContextNotificationBus overrides ...
void OnEntityStreamLoadSuccess() override; void OnContextReset() override;
// EditorEntityInfoNotificationBus overrides ...
void OnEntityInfoUpdatedName(AZ::EntityId entityId, const AZStd::string& name) override;
// PrefabPublicNotifications overrides ...
void OnPrefabInstancePropagationEnd();
private: private:
PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance); PrefabFocusOperationResult FocusOnPrefabInstance(InstanceOptionalReference focusedInstance);
void RefreshInstanceFocusList(); void RefreshInstanceFocusList();
void RefreshInstanceFocusPath();
void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const; void OpenInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const; void CloseInstanceContainers(const AZStd::vector<InstanceOptionalReference>& instances) const;
//! The instance the editor is currently focusing on.
InstanceOptionalReference m_focusedInstance; InstanceOptionalReference m_focusedInstance;
//! The templateId of the focused instance.
TemplateId m_focusedTemplateId; TemplateId m_focusedTemplateId;
AZStd::vector<InstanceOptionalReference> m_instanceFocusVector; //! The list of instances going from the root (index 0) to the focused instance.
AZStd::vector<InstanceOptionalReference> m_instanceFocusHierarchy;
//! A path containing the names of the containers in the instance focus hierarchy, separated with a /.
AZ::IO::Path m_instanceFocusPath; AZ::IO::Path m_instanceFocusPath;
ContainerEntityInterface* m_containerEntityInterface = nullptr; ContainerEntityInterface* m_containerEntityInterface = nullptr;
FocusModeInterface* m_focusModeInterface = nullptr; FocusModeInterface* m_focusModeInterface = nullptr;
InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr; InstanceEntityMapperInterface* m_instanceEntityMapperInterface = nullptr;
bool m_isInitialized = false;
}; };
} // namespace AzToolsFramework::Prefab } // namespace AzToolsFramework::Prefab

@ -26,6 +26,11 @@ namespace AzToolsFramework::Prefab
public: public:
AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}"); AZ_RTTI(PrefabFocusInterface, "{F3CFA37B-5FD8-436A-9C30-60EB54E350E1}");
//! Initializes the editor interfaces for Prefab Focus mode.
//! If this is not called on initialization, the Prefab Focus Mode functions will still work
//! but won't trigger the Editor APIs to visualize focus mode on the UI.
virtual void InitializeEditorInterfaces() = 0;
//! Set the focused prefab instance to the owning instance of the entityId provided. //! Set the focused prefab instance to the owning instance of the entityId provided.
//! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on. //! @param entityId The entityId of the entity whose owning instance we want the prefab system to focus on.
virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0; virtual PrefabFocusOperationResult FocusOnPrefabInstanceOwningEntityId(AZ::EntityId entityId) = 0;

@ -66,7 +66,16 @@ namespace AzToolsFramework
bool Template::IsValid() const bool Template::IsValid() const
{ {
return !m_prefabDom.IsNull() && !m_filePath.empty(); if (m_prefabDom.IsNull() || m_filePath.empty())
{
return false;
}
else if (!m_prefabDom.IsObject())
{
return false;
}
auto source = m_prefabDom.FindMember(PrefabDomUtils::SourceName);
return (source != m_prefabDom.MemberEnd());
} }
bool Template::IsLoadedWithErrors() const bool Template::IsLoadedWithErrors() const
@ -175,6 +184,26 @@ namespace AzToolsFramework
return findInstancesResult->get(); return findInstancesResult->get();
} }
bool Template::IsProcedural() const
{
if (m_isProcedural.has_value())
{
return m_isProcedural.value();
}
else if (!IsValid())
{
return false;
}
auto source = m_prefabDom.FindMember(PrefabDomUtils::SourceName);
if (!source->value.IsString())
{
return false;
}
AZ::IO::PathView path(source->value.GetString());
m_isProcedural = AZStd::make_optional(path.Extension().Match(".procprefab"));
return m_isProcedural.value();
}
const AZ::IO::Path& Template::GetFilePath() const const AZ::IO::Path& Template::GetFilePath() const
{ {
return m_filePath; return m_filePath;

@ -65,6 +65,9 @@ namespace AzToolsFramework
const AZ::IO::Path& GetFilePath() const; const AZ::IO::Path& GetFilePath() const;
void SetFilePath(const AZ::IO::PathView& path); void SetFilePath(const AZ::IO::PathView& path);
// To tell if this Template was created from an product asset
bool IsProcedural() const;
private: private:
// Container for keeping links representing the Template's nested instances. // Container for keeping links representing the Template's nested instances.
Links m_links; Links m_links;
@ -80,6 +83,9 @@ namespace AzToolsFramework
// Flag to tell if this Template has changes that have yet to be saved to file. // Flag to tell if this Template has changes that have yet to be saved to file.
bool m_isDirty = false; bool m_isDirty = false;
// Flag to tell if this Template was generated outside the Editor
mutable AZStd::optional<bool> m_isProcedural;
}; };
} // namespace Prefab } // namespace Prefab
} // namespace AzToolsFramework } // namespace AzToolsFramework

@ -20,6 +20,8 @@
#include <AzFramework/Process/ProcessWatcher.h> #include <AzFramework/Process/ProcessWatcher.h>
#include <AzToolsFramework/SourceControl/PerforceConnection.h> #include <AzToolsFramework/SourceControl/PerforceConnection.h>
#include <QProcess>
namespace AzToolsFramework namespace AzToolsFramework
{ {
namespace namespace
@ -75,9 +77,17 @@ namespace AzToolsFramework
m_resolveKey = true; m_resolveKey = true;
m_testTrust = false; m_testTrust = false;
// set up signals before we start thread. // set up signals before we start thread.
m_shutdownThreadSignal = false; m_shutdownThreadSignal = false;
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
// Check to see if the 'p4' command is available at the command line
int p4VersionExitCode = QProcess::execute("p4", QStringList{ "-V" });
m_p4ApplicationDetected = (p4VersionExitCode == 0);
if (m_p4ApplicationDetected)
{
m_WorkerThread = AZStd::thread(AZStd::bind(&PerforceComponent::ThreadWorker, this));
}
SourceControlConnectionRequestBus::Handler::BusConnect(); SourceControlConnectionRequestBus::Handler::BusConnect();
SourceControlCommandBus::Handler::BusConnect(); SourceControlCommandBus::Handler::BusConnect();
@ -88,10 +98,13 @@ namespace AzToolsFramework
SourceControlCommandBus::Handler::BusDisconnect(); SourceControlCommandBus::Handler::BusDisconnect();
SourceControlConnectionRequestBus::Handler::BusDisconnect(); SourceControlConnectionRequestBus::Handler::BusDisconnect();
m_shutdownThreadSignal = true; // tell the thread to die. if (m_p4ApplicationDetected)
m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal {
m_WorkerThread.join(); // wait for the thread to finish. m_shutdownThreadSignal = true; // tell the thread to die.
m_WorkerThread = AZStd::thread(); m_WorkerSemaphore.release(1); // wake up the thread so that it sees the signal
m_WorkerThread.join(); // wait for the thread to finish.
m_WorkerThread = AZStd::thread();
}
SetConnection(nullptr); SetConnection(nullptr);
} }

@ -260,5 +260,7 @@ namespace AzToolsFramework
AZStd::atomic_bool m_validConnection; AZStd::atomic_bool m_validConnection;
SourceControlState m_connectionState; SourceControlState m_connectionState;
bool m_p4ApplicationDetected { false };
}; };
} // namespace AzToolsFramework } // namespace AzToolsFramework

@ -0,0 +1,91 @@
/*
* 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
#if !defined(Q_MOC_RUN)
#include <AzCore/EBus/EBus.h>
#include <AzCore/Component/EntityId.h>
#include <AzQtComponents/Components/ToastNotificationConfiguration.h>
#include <QPoint>
#endif
namespace AzToolsFramework
{
typedef AZ::EntityId ToastId;
/**
* An EBus for receiving notifications when a user interacts with or dismisses
* a toast notification.
*/
class ToastNotifications
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = ToastId;
virtual void OnToastInteraction() {}
virtual void OnToastDismissed() {}
};
using ToastNotificationBus = AZ::EBus<ToastNotifications>;
typedef AZ::u32 ToastRequestBusId;
/**
* An EBus used to hide or show toast notifications. Generally, these request are handled by a
* ToastNotificationsView that has been created with a specific ToastRequestBusId
* e.g. AZ_CRC("ExampleToastNotificationView")
*/
class ToastRequests
: public AZ::EBusTraits
{
public:
static const AZ::EBusHandlerPolicy HandlerPolicy = AZ::EBusHandlerPolicy::Multiple;
static const AZ::EBusAddressPolicy AddressPolicy = AZ::EBusAddressPolicy::ById;
using BusIdType = ToastRequestBusId; // bus is addressed by CRC of the view name
/**
* Hide a toast notification widget.
*
* @param toastId The toast notification's ToastId
*/
virtual void HideToastNotification(const ToastId& toastId) = 0;
/**
* Show a toast notification with the specified toast configuration. When handled by a ToastNotificationsView,
* notifications are queued and presented to the user in sequence.
*
* @param toastConfiguration The toast configuration
* @return a ToastId
*/
virtual ToastId ShowToastNotification(const AzQtComponents::ToastConfiguration& toastConfiguration) = 0;
/**
* Show a toast notification with the specified toast configuration at the current moust cursor location.
*
* @param toastConfiguration The toast configuration
* @return a ToastId
*/
virtual ToastId ShowToastAtCursor(const AzQtComponents::ToastConfiguration& toastConfiguration) = 0;
/**
* Show a toast notification with the specified toast configuration at the specified location.
*
* @param screenPosition The screen position
* @param anchorPoint The anchorPoint for the toast notification widget
* @param toastConfiguration The toast configuration
* @return a ToastId
*/
virtual ToastId ShowToastAtPoint(const QPoint& screenPosition, const QPointF& anchorPoint, const AzQtComponents::ToastConfiguration&) = 0;
};
using ToastRequestBus = AZ::EBus<ToastRequests>;
}

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

Loading…
Cancel
Save