Merge branch 'main' into ly-as-sdk/LYN-2948

main
pappeste 5 years ago
commit eece07efd3

@ -46,7 +46,7 @@ SortIncludes: true
SpaceAfterLogicalNot: false
SpaceAfterTemplateKeyword: false
SpaceBeforeAssignmentOperators: true
SpaceBeforeCpp11BracedList: true
SpaceBeforeCpp11BracedList: false
SpaceBeforeCtorInitializerColon: true
SpaceBeforeInheritanceColon: true
SpaceBeforeParens: ControlStatements

@ -0,0 +1,210 @@
"""
All or portions of this file Copyright (c) Amazon.com, Inc. or its affiliates or
its licensors.
For complete copyright and license terms please see the LICENSE at the root of this
distribution (the "License"). All use of this software is governed by the License,
or, if provided, by the license below or the license accompanying this file. Do not
remove or modify any license notices. This file is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
"""
# fmt: off
class Tests():
new_event_created = ("New Script Event created", "New Script Event not created")
child_event_created = ("Child Event created", "Child Event not created")
params_added = ("New parameters added", "New parameters are not added")
file_saved = ("Script event file saved", "Script event file did not save")
node_found = ("Node found in Script Canvas", "Node not found in Script Canvas")
# fmt: on
def ScriptEvents_AllParamDatatypes_CreationSuccess():
"""
Summary:
Parameters of all types can be created.
Expected Behavior:
The Method handles the large number of Parameters gracefully.
Parameters of all data types can be successfully created.
Updated ScriptEvent toast appears in Script Canvas.
Test Steps:
1) Open Asset Editor
2) Initially create new Script Event file with one method
3) Add new method and set name to it
4) Add new parameters of each type
5) Verify if parameters are added
6) Expand the parameter rows
7) Set different names and datatypes for each parameter
8) Save file and verify node in SC Node Palette
9) Close Asset Editor
Note:
- This test file must be called from the Open 3D Engine Editor command terminal
- Any passed and failed tests are written to the Editor.log file.
Parsing the file or running a log_monitor are required to observe the test results.
:return: None
"""
import os
from utils import TestHelper as helper
import pyside_utils
# Open 3D Engine imports
import azlmbr.legacy.general as general
import azlmbr.editor as editor
import azlmbr.bus as bus
# Pyside imports
from PySide2 import QtWidgets, QtTest, QtCore
GENERAL_WAIT = 1.0 # seconds
FILE_PATH = os.path.join("AutomatedTesting", "TestAssets", "test_file.scriptevents")
N_VAR_TYPES = 10 # Top 10 variable types
TEST_METHOD_NAME = "test_method_name"
editor_window = pyside_utils.get_editor_main_window()
asset_editor = asset_editor_widget = container = menu_bar = None
sc = node_palette = tree = search_frame = search_box = None
def initialize_asset_editor_qt_objects():
nonlocal asset_editor, asset_editor_widget, container, menu_bar
asset_editor = editor_window.findChild(QtWidgets.QDockWidget, "Asset Editor")
asset_editor_widget = asset_editor.findChild(QtWidgets.QWidget, "AssetEditorWindowClass")
container = asset_editor_widget.findChild(QtWidgets.QWidget, "ContainerForRows")
menu_bar = asset_editor_widget.findChild(QtWidgets.QMenuBar)
def initialize_sc_qt_objects():
nonlocal sc, node_palette, tree, search_frame, search_box
sc = editor_window.findChild(QtWidgets.QDockWidget, "Script Canvas")
if sc.findChild(QtWidgets.QDockWidget, "NodePalette") is None:
action = pyside_utils.find_child_by_pattern(sc, {"text": "Node Palette", "type": QtWidgets.QAction})
action.trigger()
node_palette = sc.findChild(QtWidgets.QDockWidget, "NodePalette")
tree = node_palette.findChild(QtWidgets.QTreeView, "treeView")
search_frame = node_palette.findChild(QtWidgets.QFrame, "searchFrame")
search_box = search_frame.findChild(QtWidgets.QLineEdit, "searchFilter")
def save_file():
editor.AssetEditorWidgetRequestsBus(bus.Broadcast, "SaveAssetAs", FILE_PATH)
action = pyside_utils.find_child_by_pattern(menu_bar, {"type": QtWidgets.QAction, "iconText": "Save"})
action.trigger()
# wait till file is saved, to validate that check the text of QLabel at the bottom of the AssetEditor,
# if there are no unsaved changes we will not have any * in the text
label = asset_editor.findChild(QtWidgets.QLabel, "textEdit")
return helper.wait_for_condition(lambda: "*" not in label.text(), 3.0)
def expand_container_rows(object_name):
children = container.findChildren(QtWidgets.QFrame, object_name)
for child in children:
check_box = child.findChild(QtWidgets.QCheckBox)
if check_box and not check_box.isChecked():
QtTest.QTest.mouseClick(check_box, QtCore.Qt.LeftButton, QtCore.Qt.NoModifier)
def node_palette_search(node_name):
search_box.setText(node_name)
helper.wait_for_condition(lambda: search_box.text() == node_name, 1.0)
# Try clicking ENTER in search box multiple times
for _ in range(20):
QtTest.QTest.keyClick(search_box, QtCore.Qt.Key_Enter, QtCore.Qt.NoModifier)
if pyside_utils.find_child_by_pattern(tree, {"text": node_name}) is not None:
break
def verify_added_params():
for index in range(N_VAR_TYPES):
if container.findChild(QtWidgets.QFrame, f"[{index}]") is None:
return False
return True
# 1) Open Asset Editor
general.idle_enable(True)
# Initially close the Asset Editor and then reopen to ensure we don't have any existing assets open
general.close_pane("Asset Editor")
general.open_pane("Asset Editor")
helper.wait_for_condition(lambda: general.is_pane_visible("Asset Editor"), 5.0)
# 2) Initially create new Script Event file with one method
initialize_asset_editor_qt_objects()
action = pyside_utils.find_child_by_pattern(menu_bar, {"type": QtWidgets.QAction, "text": "Script Events"})
action.trigger()
result = helper.wait_for_condition(
lambda: container.findChild(QtWidgets.QFrame, "Events") is not None
and container.findChild(QtWidgets.QFrame, "Events").findChild(QtWidgets.QToolButton, "") is not None,
3 * GENERAL_WAIT,
)
Report.result(Tests.new_event_created, result)
# 3) Add new method and set name to it
add_event = container.findChild(QtWidgets.QFrame, "Events").findChild(QtWidgets.QToolButton, "")
add_event.click()
result = helper.wait_for_condition(
lambda: asset_editor_widget.findChild(QtWidgets.QFrame, "EventName") is not None, GENERAL_WAIT
)
Report.result(Tests.child_event_created, result)
expand_container_rows("EventName")
expand_container_rows("Name")
initialize_asset_editor_qt_objects()
children = container.findChildren(QtWidgets.QFrame, "Name")
for child in children:
line_edit = child.findChild(QtWidgets.QLineEdit)
if line_edit is not None and line_edit.text() == "MethodName":
line_edit.setText(TEST_METHOD_NAME)
# 4) Add new parameters of each type
helper.wait_for_condition(lambda: container.findChild(QtWidgets.QFrame, "Parameters") is not None, 2.0)
parameters = container.findChild(QtWidgets.QFrame, "Parameters")
add_param = parameters.findChild(QtWidgets.QToolButton, "")
for _ in range(N_VAR_TYPES):
add_param.click()
# 5) Verify if parameters are added
result = helper.wait_for_condition(verify_added_params, 3.0)
Report.result(Tests.params_added, result)
# 6) Expand the parameter rows (to render QFrame 'Type' for each param)
for index in range(N_VAR_TYPES):
expand_container_rows(f"[{index}]")
# 7) Set different names and datatypes for each parameter
expand_container_rows("Name")
children = container.findChildren(QtWidgets.QFrame, "Name")
index = 0
for child in children:
line_edit = child.findChild(QtWidgets.QLineEdit)
if line_edit is not None and line_edit.text() == "ParameterName":
line_edit.setText(f"param_{index}")
index += 1
children = container.findChildren(QtWidgets.QFrame, "Type")
index = 0
for child in children:
combo_box = child.findChild(QtWidgets.QComboBox)
if combo_box is not None and index < N_VAR_TYPES:
combo_box.setCurrentIndex(index)
index += 1
# 8) Save file and verify node in SC Node Palette
Report.result(Tests.file_saved, save_file())
general.open_pane("Script Canvas")
helper.wait_for_condition(lambda: general.is_pane_visible("Script Canvas"), 5.0)
initialize_sc_qt_objects()
node_palette_search(TEST_METHOD_NAME)
get_node_index = lambda: pyside_utils.find_child_by_pattern(tree, {"text": TEST_METHOD_NAME}) is not None
result = helper.wait_for_condition(get_node_index, 2.0)
Report.result(Tests.node_found, result)
# 9) Close Asset Editor
general.close_pane("Asset Editor")
general.close_pane("Script Canvas")
if __name__ == "__main__":
import ImportPathHelper as imports
imports.init()
from utils import Report
Report.start_test(ScriptEvents_AllParamDatatypes_CreationSuccess)

@ -113,10 +113,6 @@ class TestAutomation(TestAutomationBase):
from . import Debugger_HappyPath_TargetMultipleGraphs as test_module
self._run_test(request, workspace, editor, test_module)
def test_Debugging_TargetMultipleGraphs(self, request, workspace, editor, launcher_platform, project):
from . import Debugging_TargetMultipleGraphs as test_module
self._run_test(request, workspace, editor, test_module)
@pytest.mark.parametrize("level", ["tmp_level"])
def test_Debugger_HappyPath_TargetMultipleEntities(self, request, workspace, editor, launcher_platform, project, level):
def teardown():
@ -318,3 +314,29 @@ class TestScriptCanvasTests(object):
timeout=60,
)
def test_ScriptEvents_AllParamDatatypes_CreationSuccess(self, request, workspace, editor, launcher_platform):
def teardown():
file_system.delete(
[os.path.join(workspace.paths.project(), "TestAssets", "test_file.scriptevents")], True, True
)
request.addfinalizer(teardown)
file_system.delete(
[os.path.join(workspace.paths.project(), "TestAssets", "test_file.scriptevents")], True, True
)
expected_lines = [
"Success: New Script Event created",
"Success: Child Event created",
"Success: New parameters added",
"Success: Script event file saved",
"Success: Node found in Script Canvas",
]
hydra.launch_and_validate_results(
request,
TEST_DIRECTORY,
editor,
"ScriptEvents_AllParamDatatypes_CreationSuccess.py",
expected_lines,
auto_test_mode=False,
timeout=60,
)

@ -12,6 +12,7 @@
#pragma once
#include <AzCore/EBus/EBus.h>
#include <AzFramework/Entity/EntityContextBus.h>
#include <AzFramework/Render/GeometryIntersectionStructures.h>
namespace AzFramework
@ -35,12 +36,12 @@ namespace AzFramework
AzFramework::EntityContextId m_contextId;
};
//! Interface for intersection requests, implement this interface for making your component
//! render geometry intersectable.
//! Interface for intersection requests.
//! Implement this interface to make your component 'intersectable'.
class IntersectionRequests
: public AZ::EBusTraits
{
//! Policy for notifying the Intersector bus of entities connected/disconnected to this ebus
//! Policy for notifying the Intersector bus of entities connected/disconnected to this EBus
//! so it updates the internal data of the entities
template<class Bus>
struct IntersectionRequestsConnectionPolicy

@ -144,7 +144,7 @@ namespace AzFramework
z = AZStd::atan2(-orientation.GetElement(1, 2), orientation.GetElement(1, 1));
}
return {x, y, z};
return { x, y, z };
}
void UpdateCameraFromTransform(Camera& camera, const AZ::Transform& transform)
@ -179,7 +179,7 @@ namespace AzFramework
{
const auto nextCamera = m_cameras.StepCamera(targetCamera, m_motionDelta, m_scrollDelta, deltaTime);
m_motionDelta = ScreenVector{0, 0};
m_motionDelta = ScreenVector{ 0, 0 };
m_scrollDelta = 0.0f;
return nextCamera;
@ -213,7 +213,10 @@ namespace AzFramework
auto& cameraInput = m_idleCameraInputs[i];
const bool canBegin = cameraInput->Beginning() &&
AZStd::all_of(m_activeCameraInputs.cbegin(), m_activeCameraInputs.cend(),
[](const auto& input) { return !input->Exclusive(); }) &&
[](const auto& input)
{
return !input->Exclusive();
}) &&
(!cameraInput->Exclusive() || (cameraInput->Exclusive() && m_activeCameraInputs.empty()));
if (canBegin)
@ -231,7 +234,8 @@ namespace AzFramework
const Camera nextCamera = AZStd::accumulate(
AZStd::begin(m_activeCameraInputs), AZStd::end(m_activeCameraInputs), targetCamera,
[cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera) {
[cursorDelta, scrollDelta, deltaTime](Camera acc, auto& camera)
{
acc = camera->StepCamera(acc, cursorDelta, scrollDelta, deltaTime);
return acc;
});
@ -284,7 +288,8 @@ namespace AzFramework
bool RotateCameraInput::HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, [[maybe_unused]] float scrollDelta)
{
const ClickDetector::ClickEvent clickEvent = [&event, this] {
const ClickDetector::ClickEvent clickEvent = [&event, this]
{
if (const auto& input = AZStd::get_if<DiscreteInputEvent>(&event))
{
if (input->m_channelId == m_rotateChannelId)
@ -330,7 +335,10 @@ namespace AzFramework
nextCamera.m_pitch -= float(cursorDelta.m_y) * ed_cameraSystemRotateSpeed;
nextCamera.m_yaw -= float(cursorDelta.m_x) * ed_cameraSystemRotateSpeed;
const auto clampRotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
const auto clampRotation = [](const float angle)
{
return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
};
nextCamera.m_yaw = clampRotation(nextCamera.m_yaw);
// clamp pitch to be +-90 degrees
@ -377,9 +385,10 @@ namespace AzFramework
const auto deltaPanX = float(cursorDelta.m_x) * panAxes.m_horizontalAxis * ed_cameraSystemPanSpeed;
const auto deltaPanY = float(cursorDelta.m_y) * panAxes.m_verticalAxis * ed_cameraSystemPanSpeed;
const auto inv = [](const bool invert) {
constexpr float Dir[] = {1.0f, -1.0f};
return Dir[static_cast<int>(invert)];
const auto inv = [](const bool invert)
{
constexpr float Dir[] = { 1.0f, -1.0f };
return Dir[aznumeric_cast<int>(invert)];
};
nextCamera.m_lookAt += deltaPanX * inv(ed_cameraSystemPanInvertX);
@ -475,7 +484,8 @@ namespace AzFramework
const auto axisY = translationBasis.GetBasisY();
const auto axisZ = translationBasis.GetBasisZ();
const float speed = [boost = m_boost]() {
const float speed = [boost = m_boost]()
{
return ed_cameraSystemTranslateSpeed * (boost ? ed_cameraSystemBoostMultiplier : 1.0f);
}();
@ -555,10 +565,12 @@ namespace AzFramework
if (Beginning())
{
const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn] {
const auto hasLookAt = [&nextCamera, &targetCamera, &lookAtFn = m_lookAtFn]
{
if (lookAtFn)
{
if (const auto lookAt = lookAtFn())
// pass through the camera's position and look vector for use in the lookAt function
if (const auto lookAt = lookAtFn(targetCamera.Translation(), targetCamera.Rotation().GetBasisY()))
{
auto transform = AZ::Transform::CreateLookAt(targetCamera.m_lookAt, *lookAt);
nextCamera.m_lookDist = -lookAt->GetDistance(targetCamera.m_lookAt);
@ -692,14 +704,20 @@ namespace AzFramework
Camera SmoothCamera(const Camera& currentCamera, const Camera& targetCamera, const float deltaTime)
{
const auto clamp_rotation = [](const float angle) { return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi); };
const auto clamp_rotation = [](const float angle)
{
return AZStd::fmod(angle + AZ::Constants::TwoPi, AZ::Constants::TwoPi);
};
// keep yaw in 0 - 360 range
float targetYaw = clamp_rotation(targetCamera.m_yaw);
const float currentYaw = clamp_rotation(currentCamera.m_yaw);
// return the sign of the float input (-1, 0, 1)
const auto sign = [](const float value) { return aznumeric_cast<float>((0.0f < value) - (value < 0.0f)); };
const auto sign = [](const float value)
{
return aznumeric_cast<float>((0.0f < value) - (value < 0.0f));
};
// ensure smooth transition when moving across 0 - 360 boundary
const float yawDelta = targetYaw - currentYaw;
@ -727,26 +745,28 @@ namespace AzFramework
const auto& inputChannelId = inputChannel.GetInputChannelId();
const auto& inputDeviceId = inputChannel.GetInputDevice().GetInputDeviceId();
const bool wasMouseButton =
AZStd::any_of(InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(), [inputChannelId](const auto& button) {
const bool wasMouseButton = AZStd::any_of(
InputDeviceMouse::Button::All.begin(), InputDeviceMouse::Button::All.end(),
[inputChannelId](const auto& button)
{
return button == inputChannelId;
});
if (inputChannelId == InputDeviceMouse::Movement::X)
{
return HorizontalMotionEvent{(int)inputChannel.GetValue()};
return HorizontalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
}
else if (inputChannelId == InputDeviceMouse::Movement::Y)
{
return VerticalMotionEvent{(int)inputChannel.GetValue()};
return VerticalMotionEvent{ aznumeric_cast<int>(inputChannel.GetValue()) };
}
else if (inputChannelId == InputDeviceMouse::Movement::Z)
{
return ScrollEvent{inputChannel.GetValue()};
return ScrollEvent{ inputChannel.GetValue() };
}
else if (wasMouseButton || InputDeviceKeyboard::IsKeyboardDevice(inputDeviceId))
{
return DiscreteInputEvent{inputChannelId, inputChannel.GetState()};
return DiscreteInputEvent{ inputChannelId, inputChannel.GetState() };
}
return AZStd::monostate{};

@ -34,9 +34,9 @@ namespace AzFramework
AZ::Vector3 m_lookAt = AZ::Vector3::CreateZero(); //!< Position of camera when m_lookDist is zero,
//!< or position of m_lookAt when m_lookDist is greater
//!< than zero.
float m_yaw{0.0};
float m_pitch{0.0};
float m_lookDist{0.0}; //!< Zero gives first person free look, otherwise orbit about m_lookAt
float m_yaw{ 0.0 };
float m_pitch{ 0.0 };
float m_lookDist{ 0.0 }; //!< Zero gives first person free look, otherwise orbit about m_lookAt
//! View camera transform (v in MVP).
AZ::Transform View() const;
@ -195,7 +195,11 @@ namespace AzFramework
inline bool Cameras::Exclusive() const
{
return AZStd::any_of(
m_activeCameraInputs.begin(), m_activeCameraInputs.end(), [](const auto& cameraInput) { return cameraInput->Exclusive(); });
m_activeCameraInputs.begin(), m_activeCameraInputs.end(),
[](const auto& cameraInput)
{
return cameraInput->Exclusive();
});
}
//! Responsible for updating a series of cameras given various inputs.
@ -237,7 +241,7 @@ namespace AzFramework
inline PanAxes LookPan(const Camera& camera)
{
const AZ::Matrix3x3 orientation = camera.Rotation();
return {orientation.GetBasisX(), orientation.GetBasisZ()};
return { orientation.GetBasisX(), orientation.GetBasisZ() };
}
inline PanAxes OrbitPan(const Camera& camera)
@ -245,12 +249,13 @@ namespace AzFramework
const AZ::Matrix3x3 orientation = camera.Rotation();
const auto basisX = orientation.GetBasisX();
const auto basisY = [&orientation] {
const auto basisY = [&orientation]
{
const auto forward = orientation.GetBasisY();
return AZ::Vector3(forward.GetX(), forward.GetY(), 0.0f).GetNormalized();
}();
return {basisX, basisY};
return { basisX, basisY };
}
class PanCameraInput : public CameraInput
@ -285,7 +290,8 @@ namespace AzFramework
const AZ::Matrix3x3 orientation = camera.Rotation();
const auto basisX = orientation.GetBasisX();
const auto basisY = [&orientation] {
const auto basisY = [&orientation]
{
const auto forward = orientation.GetBasisY();
return AZ::Vector3(forward.GetX(), forward.GetY(), 0.0f).GetNormalized();
}();
@ -398,7 +404,7 @@ namespace AzFramework
class OrbitCameraInput : public CameraInput
{
public:
using LookAtFn = AZStd::function<AZStd::optional<AZ::Vector3>()>;
using LookAtFn = AZStd::function<AZStd::optional<AZ::Vector3>(const AZ::Vector3& position, const AZ::Vector3& direction)>;
// CameraInput overrides ...
bool HandleEvents(const InputEvent& event, const ScreenVector& cursorDelta, float scrollDelta) override;

@ -57,7 +57,6 @@ namespace AzToolsFramework
"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_sliceOwnershipService.BusConnect(m_entityContextId);
m_sliceOwnershipService.m_shouldAssertForLegacySlicesUsage = m_shouldAssertForLegacySlicesUsage;
m_editorSliceOwnershipService.BusConnect();
@ -90,6 +89,8 @@ namespace AzToolsFramework
}
void PrefabEditorEntityOwnershipService::Reset()
{
if (m_rootInstance)
{
Prefab::TemplateId templateId = m_rootInstance->GetTemplateId();
if (templateId != Prefab::InvalidTemplateId)
@ -99,6 +100,7 @@ namespace AzToolsFramework
}
m_rootInstance->Reset();
m_rootInstance->SetContainerEntityName("Level");
}
AzFramework::EntityOwnershipServiceNotificationBus::Event(
m_entityContextId, &AzFramework::EntityOwnershipServiceNotificationBus::Events::OnEntityOwnershipServiceReset);
@ -202,7 +204,7 @@ namespace AzToolsFramework
}
m_rootInstance->SetTemplateId(templateId);
m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GetRelativePathToProject(filename));
m_rootInstance->SetTemplateSourcePath(m_loaderInterface->GenerateRelativePath(filename));
m_rootInstance->SetContainerEntityName("Level");
m_prefabSystemComponent->PropagateTemplateChanges(templateId);
@ -220,7 +222,7 @@ namespace AzToolsFramework
bool PrefabEditorEntityOwnershipService::SaveToStream(AZ::IO::GenericStream& stream, AZStd::string_view filename)
{
AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename);
AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(filename);
AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath);
m_rootInstance->SetTemplateSourcePath(relativePath);
@ -267,7 +269,7 @@ namespace AzToolsFramework
void PrefabEditorEntityOwnershipService::CreateNewLevelPrefab(AZStd::string_view filename, const AZStd::string& templateFilename)
{
AZ::IO::Path relativePath = m_loaderInterface->GetRelativePathToProject(filename);
AZ::IO::Path relativePath = m_loaderInterface->GenerateRelativePath(filename);
AzToolsFramework::Prefab::TemplateId templateId = m_prefabSystemComponent->GetTemplateIdFromFilePath(relativePath);
m_rootInstance->SetTemplateSourcePath(relativePath);
@ -378,9 +380,14 @@ namespace AzToolsFramework
Prefab::InstanceOptionalReference PrefabEditorEntityOwnershipService::GetRootPrefabInstance()
{
AZ_Assert(m_rootInstance, "A valid root prefab instance couldn't be found in PrefabEditorEntityOwnershipService.");
if (m_rootInstance)
{
return *m_rootInstance;
}
return AZStd::nullopt;
}
const AZStd::vector<AZ::Data::Asset<AZ::Data::AssetData>>& PrefabEditorEntityOwnershipService::GetPlayInEditorAssetData()
{
return m_playInEditorData.m_assets;

@ -124,7 +124,7 @@ namespace AzToolsFramework
"PrefabLoaderInterface could not be found. It is required to load Prefab Instances");
// Make sure we have a relative path
instance->m_templateSourcePath = loaderInterface->GetRelativePathToProject(instance->m_templateSourcePath);
instance->m_templateSourcePath = loaderInterface->GenerateRelativePath(instance->m_templateSourcePath);
TemplateId templateId = prefabSystemComponentInterface->GetTemplateIdFromFilePath(instance->GetTemplateSourcePath());

@ -18,7 +18,9 @@
#include <AzCore/Settings/SettingsRegistryMergeUtils.h>
#include <AzCore/StringFunc/StringFunc.h>
#include <AzFramework/Asset/AssetSystemBus.h>
#include <AzFramework/FileFunc/FileFunc.h>
#include <AzToolsFramework/API/EditorAssetSystemAPI.h>
#include <AzToolsFramework/API/ToolsApplicationAPI.h>
#include <AzToolsFramework/Prefab/PrefabDomUtils.h>
#include <AzToolsFramework/Prefab/PrefabSystemComponentInterface.h>
@ -112,7 +114,7 @@ namespace AzToolsFramework
return InvalidTemplateId;
}
AZ::IO::Path relativePath = GetRelativePathToProject(originPath);
AZ::IO::Path relativePath = GenerateRelativePath(originPath);
// Cyclical dependency detected if the prefab file is already part of the progressed
// file path set.
@ -385,21 +387,100 @@ namespace AzToolsFramework
AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path).MakePreferred();
if (pathWithOSSeparator.IsAbsolute())
{
// If an absolute path was passed in, just return it as-is.
return path;
}
return AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
// A relative path was passed in, so try to turn it back into an absolute path.
AZ::IO::Path fullPath;
bool pathFound = false;
AZ::Data::AssetInfo assetInfo;
AZStd::string rootFolder;
AZStd::string inputPath(path.Native());
// Given an input path that's expected to exist, try to look it up.
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GetSourceInfoBySourcePath,
inputPath.c_str(), assetInfo, rootFolder);
if (pathFound)
{
// The asset system provided us with a valid root folder and relative path, so return it.
fullPath = AZ::IO::Path(rootFolder) / assetInfo.m_relativePath;
}
else
{
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
// a unit test, so just execute the fallback logic without an error.
[[maybe_unused]] bool assetProcessorReady = false;
AzFramework::AssetSystemRequestBus::BroadcastResult(
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
AZ_Error(
"Prefab", !assetProcessorReady, "Full source path for '%.*s' could not be determined. Using fallback logic.",
AZ_STRING_ARG(path.Native()));
// If a relative path was passed in, make it relative to the project root.
fullPath = AZ::IO::Path(m_projectPathWithOsSeparator).Append(pathWithOSSeparator);
}
AZ::IO::Path PrefabLoader::GetRelativePathToProject(AZ::IO::PathView path)
return fullPath;
}
AZ::IO::Path PrefabLoader::GenerateRelativePath(AZ::IO::PathView path)
{
bool pathFound = false;
AZStd::string relativePath;
AZStd::string rootFolder;
AZ::IO::Path finalPath;
// The asset system allows for paths to be relative to multiple root folders, using a priority system.
// This request will make the input path relative to the most appropriate, highest-priority root folder.
AzToolsFramework::AssetSystemRequestBus::BroadcastResult(
pathFound, &AzToolsFramework::AssetSystemRequestBus::Events::GenerateRelativeSourcePath, path.Native(),
relativePath, rootFolder);
if (pathFound && !relativePath.empty())
{
// A relative path was generated successfully, so return it.
finalPath = relativePath;
}
else
{
// If for some reason the Asset system couldn't provide a relative path, provide some fallback logic.
// Check to see if the AssetProcessor is ready. If it *is* and we didn't get a path, print an error then follow
// the fallback logic. If it's *not* ready, we're probably either extremely early in a tool startup flow or inside
// a unit test, so just execute the fallback logic without an error.
[[maybe_unused]] bool assetProcessorReady = false;
AzFramework::AssetSystemRequestBus::BroadcastResult(
assetProcessorReady, &AzFramework::AssetSystemRequestBus::Events::AssetProcessorIsReady);
AZ_Error("Prefab", !assetProcessorReady,
"Relative source path for '%.*s' could not be determined. Using project path as relative root.",
AZ_STRING_ARG(path.Native()));
AZ::IO::Path pathWithOSSeparator = AZ::IO::Path(path.Native()).MakePreferred();
if (!pathWithOSSeparator.IsAbsolute())
if (pathWithOSSeparator.IsAbsolute())
{
return path;
// If an absolute path was passed in, make it relative to the project path.
finalPath = AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator);
}
else
{
// If a relative path was passed in, just return it.
finalPath = path;
}
}
return AZ::IO::Path(path.Native(), '/').MakePreferred().LexicallyRelative(m_projectPathWithSlashSeparator);
return finalPath;
}
AZ::IO::Path PrefabLoaderInterface::GeneratePath()

@ -91,9 +91,11 @@ namespace AzToolsFramework
//! The path will always have the correct separator for the current OS
AZ::IO::Path GetFullPath(AZ::IO::PathView path) override;
//! Converts path into a relative path to the project, this will be the paths in .prefab file.
//! The path will always have '/' separator.
AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) override;
//! Converts path into a path that's relative to the highest-priority containing folder of all the folders registered
//! with the engine.
//! This path will be the path that appears in the .prefab file.
//! The path will always use the '/' separator.
AZ::IO::Path GenerateRelativePath(AZ::IO::PathView path) override;
//! Returns if the path is a valid path for a prefab
static bool IsValidPrefabPath(AZ::IO::PathView path);

@ -74,9 +74,11 @@ namespace AzToolsFramework
//! The path will always have the correct separator for the current OS
virtual AZ::IO::Path GetFullPath(AZ::IO::PathView path) = 0;
//! Converts path into a relative path to the current project, this will be the paths in .prefab file.
//! The path will always have '/' separator.
virtual AZ::IO::Path GetRelativePathToProject(AZ::IO::PathView path) = 0;
//! Converts path into a path that's relative to the highest-priority containing folder of all the folders registered
//! with the engine.
//! This path will be the path that appears in the .prefab file.
//! The path will always use the '/' separator.
virtual AZ::IO::Path GenerateRelativePath(AZ::IO::PathView path) = 0;
protected:

@ -318,7 +318,7 @@ namespace AzToolsFramework
}
//Detect whether this instantiation would produce a cyclical dependency
auto relativePath = m_prefabLoaderInterface->GetRelativePathToProject(filePath);
auto relativePath = m_prefabLoaderInterface->GenerateRelativePath(filePath);
Prefab::TemplateId templateId = m_prefabSystemComponentInterface->GetTemplateIdFromFilePath(relativePath);
if (templateId == InvalidTemplateId)

@ -95,7 +95,7 @@ namespace AzToolsFramework
const AZStd::vector<AZ::Entity*>& entities, AZStd::vector<AZStd::unique_ptr<Instance>>&& instancesToConsume,
AZ::IO::PathView filePath, AZStd::unique_ptr<AZ::Entity> containerEntity, bool shouldCreateLinks)
{
AZ::IO::Path relativeFilePath = m_prefabLoader.GetRelativePathToProject(filePath);
AZ::IO::Path relativeFilePath = m_prefabLoader.GenerateRelativePath(filePath);
if (GetTemplateIdFromFilePath(relativeFilePath) != InvalidTemplateId)
{
AZ_Error("Prefab", false,

@ -333,7 +333,8 @@ namespace AzToolsFramework
}
}
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(selectedEntities, s_prefabLoaderInterface->GetRelativePathToProject(prefabFilePath.data()));
auto createPrefabOutcome = s_prefabPublicInterface->CreatePrefab(
selectedEntities, s_prefabLoaderInterface->GenerateRelativePath(prefabFilePath.data()));
if (!createPrefabOutcome.IsSuccess())
{

@ -1221,7 +1221,8 @@ void EditorViewportWidget::SetViewportId(int id)
AzFramework::ReloadCameraKeyBindings();
auto controller = AZStd::make_shared<AtomToolsFramework::ModularViewportCameraController>();
controller->SetCameraListBuilderCallback([](AzFramework::Cameras& cameras)
controller->SetCameraListBuilderCallback(
[](AzFramework::Cameras& cameras)
{
auto firstPersonRotateCamera = AZStd::make_shared<AzFramework::RotateCameraInput>(AzFramework::CameraFreeLookButton);
auto firstPersonPanCamera =
@ -1230,17 +1231,39 @@ void EditorViewportWidget::SetViewportId(int id)
auto firstPersonWheelCamera = AZStd::make_shared<AzFramework::ScrollTranslationCameraInput>();
auto orbitCamera = AZStd::make_shared<AzFramework::OrbitCameraInput>();
orbitCamera->SetLookAtFn([]() -> AZStd::optional<AZ::Vector3> {
orbitCamera->SetLookAtFn(
[](const AZ::Vector3& position, const AZ::Vector3& direction) -> AZStd::optional<AZ::Vector3>
{
AZStd::optional<AZ::Transform> manipulatorTransform;
AzToolsFramework::EditorTransformComponentSelectionRequestBus::EventResult(
manipulatorTransform, AzToolsFramework::GetEntityContextId(),
&AzToolsFramework::EditorTransformComponentSelectionRequestBus::Events::GetManipulatorTransform);
// initially attempt to use manipulator transform if one exists (there is a selection)
if (manipulatorTransform)
{
return manipulatorTransform->GetTranslation();
}
const float RayDistance = 1000.0f;
AzFramework::RenderGeometry::RayRequest ray;
ray.m_startWorldPosition = position;
ray.m_endWorldPosition = position + direction * RayDistance;
ray.m_onlyVisible = true;
AzFramework::RenderGeometry::RayResult renderGeometryIntersectionResult;
AzFramework::RenderGeometry::IntersectorBus::EventResult(
renderGeometryIntersectionResult, AzToolsFramework::GetEntityContextId(),
&AzFramework::RenderGeometry::IntersectorInterface::RayIntersect, ray);
// attempt a ray intersection with any visible mesh and return the intersection position if successful
if (renderGeometryIntersectionResult)
{
return renderGeometryIntersectionResult.m_worldPosition;
}
// if there is no selection or no intersection, fallback to default camera orbit behavior (ground plane
// intersection)
return {};
});

@ -244,7 +244,7 @@ namespace AZ
}
else
{
return SavePrefab(templateId);
return SavePrefab(outputPath, templateId);
}
}
@ -318,7 +318,7 @@ namespace AZ
nestedPrefabPath.ReplaceExtension("prefab");
auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
nestedPrefabPath = prefabLoaderInterface->GetRelativePathToProject(nestedPrefabPath);
nestedPrefabPath = prefabLoaderInterface->GenerateRelativePath(nestedPrefabPath);
AzToolsFramework::Prefab::TemplateId nestedTemplateId =
prefabSystemComponentInterface->GetTemplateIdFromFilePath(nestedPrefabPath);
@ -439,19 +439,33 @@ namespace AZ
AZ::Debug::Trace::Instance().Output("", "\n");
}
bool SliceConverter::SavePrefab(AzToolsFramework::Prefab::TemplateId templateId)
bool SliceConverter::SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId)
{
auto prefabLoaderInterface = AZ::Interface<AzToolsFramework::Prefab::PrefabLoaderInterface>::Get();
if (!prefabLoaderInterface->SaveTemplate(templateId))
AZStd::string out;
if (prefabLoaderInterface->SaveTemplateToString(templateId, out))
{
AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n");
IO::SystemFile outputFile;
if (!outputFile.Open(
AZStd::string(outputPath.Native()).c_str(),
IO::SystemFile::OpenMode::SF_OPEN_CREATE |
IO::SystemFile::OpenMode::SF_OPEN_CREATE_PATH |
IO::SystemFile::OpenMode::SF_OPEN_WRITE_ONLY))
{
AZ_Error("Convert-Slice", false, " Unable to create output file '%.*s'.", AZ_STRING_ARG(outputPath.Native()));
return false;
}
outputFile.Write(out.data(), out.size());
outputFile.Close();
return true;
}
AZ_Printf("Convert-Slice", " Could not save prefab - internal error (Json write operation failure).\n");
return false;
}
bool SliceConverter::ConnectToAssetProcessor()
{
AzFramework::AssetSystem::ConnectionSettings connectionSettings;

@ -56,7 +56,7 @@ namespace AZ
AZ::SliceComponent::SliceInstance& instance, AZ::Data::Asset<AZ::SliceAsset>& sliceAsset,
AzToolsFramework::Prefab::TemplateReference nestedTemplate, AzToolsFramework::Prefab::Instance* topLevelInstance);
static void PrintPrefab(AzToolsFramework::Prefab::TemplateId templateId);
static bool SavePrefab(AzToolsFramework::Prefab::TemplateId templateId);
static bool SavePrefab(AZ::IO::PathView outputPath, AzToolsFramework::Prefab::TemplateId templateId);
};
} // namespace SerializeContextTools
} // namespace AZ

@ -35,14 +35,13 @@ namespace AZ
, private MeshComponentNotificationBus::Handler
{
public:
using BaseClass = EditorRenderComponentAdapter<MeshComponentController, MeshComponent, MeshComponentConfig>;
AZ_EDITOR_COMPONENT(AZ::Render::EditorMeshComponent, EditorMeshComponentTypeId, BaseClass);
static void Reflect(AZ::ReflectContext* context);
EditorMeshComponent() = default;
EditorMeshComponent(const MeshComponentConfig& config);
explicit EditorMeshComponent(const MeshComponentConfig& config);
// AZ::Component overrides ...
void Activate() override;

@ -25,12 +25,11 @@ namespace AZ
: public AzFramework::Components::ComponentAdapter<MeshComponentController, MeshComponentConfig>
{
public:
using BaseClass = AzFramework::Components::ComponentAdapter<MeshComponentController, MeshComponentConfig>;
AZ_COMPONENT(AZ::Render::MeshComponent, MeshComponentTypeId, BaseClass);
MeshComponent() = default;
MeshComponent(const MeshComponentConfig& config);
explicit MeshComponent(const MeshComponentConfig& config);
static void Reflect(AZ::ReflectContext* context);
};

@ -177,28 +177,33 @@ namespace AZ
FixUpModelAsset(m_configuration.m_modelAsset);
}
void MeshComponentController::Activate(AZ::EntityId entityId)
void MeshComponentController::Activate(const AZ::EntityComponentIdPair& entityComponentIdPair)
{
FixUpModelAsset(m_configuration.m_modelAsset);
m_entityId = entityId;
const AZ::EntityId entityId = entityComponentIdPair.GetEntityId();
m_entityComponentIdPair = entityComponentIdPair;
m_transformInterface = TransformBus::FindFirstHandler(m_entityId);
m_transformInterface = TransformBus::FindFirstHandler(entityId);
AZ_Warning("MeshComponentController", m_transformInterface, "Unable to attach to a TransformBus handler. This mesh will always be rendered at the origin.");
m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(m_entityId);
m_meshFeatureProcessor = RPI::Scene::GetFeatureProcessorForEntity<MeshFeatureProcessorInterface>(entityId);
AZ_Error("MeshComponentController", m_meshFeatureProcessor, "Unable to find a MeshFeatureProcessorInterface on the entityId.");
m_cachedNonUniformScale = AZ::Vector3::CreateOne();
AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, m_entityId, &AZ::NonUniformScaleRequests::GetScale);
AZ::NonUniformScaleRequestBus::Event(m_entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent,
AZ::NonUniformScaleRequestBus::EventResult(m_cachedNonUniformScale, entityId, &AZ::NonUniformScaleRequests::GetScale);
AZ::NonUniformScaleRequestBus::Event(entityId, &AZ::NonUniformScaleRequests::RegisterScaleChangedEvent,
m_nonUniformScaleChangedHandler);
MeshComponentRequestBus::Handler::BusConnect(m_entityId);
TransformNotificationBus::Handler::BusConnect(m_entityId);
MaterialReceiverRequestBus::Handler::BusConnect(m_entityId);
MaterialComponentNotificationBus::Handler::BusConnect(m_entityId);
AzFramework::BoundsRequestBus::Handler::BusConnect(m_entityId);
MeshComponentRequestBus::Handler::BusConnect(entityId);
TransformNotificationBus::Handler::BusConnect(entityId);
MaterialReceiverRequestBus::Handler::BusConnect(entityId);
MaterialComponentNotificationBus::Handler::BusConnect(entityId);
AzFramework::BoundsRequestBus::Handler::BusConnect(entityId);
AzFramework::EntityContextId contextId;
AzFramework::EntityIdContextQueryBus::EventResult(
contextId, entityId, &AzFramework::EntityIdContextQueries::GetOwningContextId);
AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusConnect({entityId, contextId});
//Buses must be connected before RegisterModel in case requests are made as a result of HandleModelChange
RegisterModel();
@ -209,6 +214,7 @@ namespace AZ
// Buses must be disconnected after unregistering the model, otherwise they can't deliver the events during the process.
UnregisterModel();
AzFramework::RenderGeometry::IntersectionRequestBus::Handler::BusDisconnect();
AzFramework::BoundsRequestBus::Handler::BusDisconnect();
MeshComponentRequestBus::Handler::BusDisconnect();
TransformNotificationBus::Handler::BusDisconnect();
@ -219,7 +225,7 @@ namespace AZ
m_meshFeatureProcessor = nullptr;
m_transformInterface = nullptr;
m_entityId = AZ::EntityId(AZ::EntityId::InvalidEntityId);
m_entityComponentIdPair = AZ::EntityComponentIdPair(AZ::EntityId(), AZ::InvalidComponentId);
m_configuration.m_modelAsset.Release();
}
@ -293,10 +299,11 @@ namespace AZ
Data::Asset<RPI::ModelAsset> modelAsset = m_meshFeatureProcessor->GetModelAsset(m_meshHandle);
if (model && modelAsset)
{
const AZ::EntityId entityId = m_entityComponentIdPair.GetEntityId();
m_configuration.m_modelAsset = modelAsset;
MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model);
MaterialReceiverNotificationBus::Event(m_entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged);
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(m_entityId);
MeshComponentNotificationBus::Event(entityId, &MeshComponentNotificationBus::Events::OnModelReady, m_configuration.m_modelAsset, model);
MaterialReceiverNotificationBus::Event(entityId, &MaterialReceiverNotificationBus::Events::OnMaterialAssignmentsChanged);
AZ::Interface<AzFramework::IEntityBoundsUnion>::Get()->RefreshEntityLocalBoundsUnion(entityId);
}
}
@ -304,8 +311,10 @@ namespace AZ
{
if (m_meshFeatureProcessor && m_configuration.m_modelAsset.GetId().IsValid())
{
const AZ::EntityId entityId = m_entityComponentIdPair.GetEntityId();
MaterialAssignmentMap materials;
MaterialComponentRequestBus::EventResult(materials, m_entityId, &MaterialComponentRequests::GetMaterialOverrides);
MaterialComponentRequestBus::EventResult(materials, entityId, &MaterialComponentRequests::GetMaterialOverrides);
m_meshFeatureProcessor->ReleaseMesh(m_meshHandle);
m_meshHandle = m_meshFeatureProcessor->AcquireMesh(m_configuration.m_modelAsset, materials,
@ -330,7 +339,8 @@ namespace AZ
{
if (m_meshFeatureProcessor && m_meshHandle.IsValid())
{
MeshComponentNotificationBus::Event(m_entityId, &MeshComponentNotificationBus::Events::OnModelPreDestroy);
MeshComponentNotificationBus::Event(
m_entityComponentIdPair.GetEntityId(), &MeshComponentNotificationBus::Events::OnModelPreDestroy);
m_meshFeatureProcessor->ReleaseMesh(m_meshHandle);
}
}
@ -462,5 +472,34 @@ namespace AZ
return Aabb::CreateNull();
}
}
AzFramework::RenderGeometry::RayResult MeshComponentController::RenderGeometryIntersect(
const AzFramework::RenderGeometry::RayRequest& ray)
{
AzFramework::RenderGeometry::RayResult result;
if (const Data::Instance<RPI::Model> model = GetModel())
{
float t;
AZ::Vector3 normal;
if (model->RayIntersection(
m_transformInterface->GetWorldTM(), m_cachedNonUniformScale, ray.m_startWorldPosition,
ray.m_endWorldPosition - ray.m_startWorldPosition, t, normal))
{
// note: this is a temporary workaround to handle cases where model->RayIntersection
// returns negative distances, follow-up ATOM-15673
const auto absT = AZStd::abs(t);
// fill in ray result structure after successful intersection
const auto intersectionLine = (ray.m_endWorldPosition - ray.m_startWorldPosition);
result.m_uv = AZ::Vector2::CreateZero();
result.m_worldPosition = ray.m_startWorldPosition + intersectionLine * absT;
result.m_worldNormal = normal;
result.m_distance = intersectionLine.GetLength() * absT;
result.m_entityAndComponent = m_entityComponentIdPair;
}
}
return result;
}
} // namespace Render
} // namespace AZ

@ -13,11 +13,13 @@
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/ComponentBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Component/NonUniformScaleBus.h>
#include <AtomCore/Instance/InstanceDatabase.h>
#include <AzFramework/Render/GeometryIntersectionBus.h>
#include <AzFramework/Visibility/BoundsBus.h>
#include <Atom/RPI.Public/Model/Model.h>
@ -32,9 +34,7 @@ namespace AZ
{
namespace Render
{
/**
* A configuration structure for the MeshComponentController
*/
//! A configuration structure for the MeshComponentController
class MeshComponentConfig final
: public AZ::ComponentConfig
{
@ -57,6 +57,7 @@ namespace AZ
class MeshComponentController final
: private MeshComponentRequestBus::Handler
, public AzFramework::BoundsRequestBus::Handler
, public AzFramework::RenderGeometry::IntersectionRequestBus::Handler
, private TransformNotificationBus::Handler
, private MaterialReceiverRequestBus::Handler
, private MaterialComponentNotificationBus::Handler
@ -77,7 +78,7 @@ namespace AZ
MeshComponentController() = default;
MeshComponentController(const MeshComponentConfig& config);
void Activate(AZ::EntityId entityId);
void Activate(const AZ::EntityComponentIdPair& entityComponentIdPair);
void Deactivate();
void SetConfiguration(const MeshComponentConfig& config);
const MeshComponentConfig& GetConfiguration() const;
@ -103,10 +104,13 @@ namespace AZ
void SetVisibility(bool visible) override;
bool GetVisibility() const override;
// BoundsRequestBus and MeshComponentRequestBus ...
// BoundsRequestBus and MeshComponentRequestBus overrides ...
AZ::Aabb GetWorldBounds() override;
AZ::Aabb GetLocalBounds() override;
// IntersectionRequestBus overrides ...
AzFramework::RenderGeometry::RayResult RenderGeometryIntersect(const AzFramework::RenderGeometry::RayRequest& ray) override;
// TransformNotificationBus::Handler overrides ...
void OnTransformChanged(const AZ::Transform& local, const AZ::Transform& world) override;
@ -134,7 +138,7 @@ namespace AZ
Render::MeshFeatureProcessorInterface* m_meshFeatureProcessor = nullptr;
Render::MeshFeatureProcessorInterface::MeshHandle m_meshHandle;
TransformInterface* m_transformInterface = nullptr;
AZ::EntityId m_entityId;
AZ::EntityComponentIdPair m_entityComponentIdPair;
bool m_isVisible = true;
MeshComponentConfig m_configuration;
AZ::Vector3 m_cachedNonUniformScale = AZ::Vector3::CreateOne();

@ -794,6 +794,9 @@ namespace PhysX
);
}
// Convex and primitive methods can only have 1 material
const bool limitToOneMaterial = pxMeshGroup.GetExportAsConvex() || pxMeshGroup.GetExportAsPrimitive();
for (AZ::u32 faceIndex = 0; faceIndex < faceCount; ++faceIndex)
{
AZStd::string materialName = DefaultMaterialName;
@ -810,6 +813,14 @@ namespace PhysX
}
materialName = localFbxMaterialsList[materialId];
// Keep using the first material when it has to be limited to one.
if (limitToOneMaterial &&
assetMaterialData.m_fbxMaterialNames.size() == 1 &&
assetMaterialData.m_fbxMaterialNames[0] != materialName)
{
materialName = assetMaterialData.m_fbxMaterialNames[0];
}
}
const AZ::SceneAPI::DataTypes::IMeshData::Face& face = nodeMesh->GetFaceInfo(faceIndex);

Loading…
Cancel
Save