Merge branch 'development' of https://github.com/o3de/o3de into daimini/FocusMode/setup

This commit is contained in:
Danilo Aimini
2021-09-22 10:03:42 -07:00
112 changed files with 4161 additions and 1015 deletions
+31
View File
@@ -254,4 +254,35 @@ if(PAL_TRAIT_BUILD_TESTS_SUPPORTED)
ly_add_googletest(
NAME Legacy::EditorLib.Tests
)
ly_add_target(
NAME EditorLib.Camera.Tests ${PAL_TRAIT_TEST_TARGET_TYPE}
NAMESPACE Legacy
FILES_CMAKE
Lib/Tests/Camera/editor_lib_camera_test_files.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
BUILD_DEPENDENCIES
PRIVATE
AZ::AzCore
AZ::AzTest
AZ::AzToolsFramework
AZ::AzTestShared
Legacy::EditorLib
Gem::Camera.Editor
Gem::AtomToolsFramework.Static
RUNTIME_DEPENDENCIES
Legacy::EditorLib
)
ly_add_source_properties(
SOURCES Lib/Tests/Camera/test_EditorCamera.cpp
PROPERTY COMPILE_DEFINITIONS
VALUES CAMERA_EDITOR_MODULE="$<TARGET_FILE_BASE_NAME:Camera.Editor>"
)
ly_add_googletest(
NAME Legacy::EditorLib.Camera.Tests
)
endif()
@@ -9,6 +9,7 @@
#include <EditorModularViewportCameraComposer.h>
#include <AtomToolsFramework/Viewport/ModularViewportCameraControllerRequestBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/std/smart_ptr/make_shared.h>
#include <AzFramework/Render/IntersectorInterface.h>
#include <AzToolsFramework/Viewport/ViewportMessages.h>
@@ -34,10 +35,12 @@ namespace SandboxEditor
: m_viewportId(viewportId)
{
EditorModularViewportCameraComposerNotificationBus::Handler::BusConnect(viewportId);
Camera::EditorCameraNotificationBus::Handler::BusConnect();
}
EditorModularViewportCameraComposer::~EditorModularViewportCameraComposer()
{
Camera::EditorCameraNotificationBus::Handler::BusDisconnect();
EditorModularViewportCameraComposerNotificationBus::Handler::BusDisconnect();
}
@@ -283,4 +286,22 @@ namespace SandboxEditor
m_orbitCamera->SetOrbitInputChannelId(SandboxEditor::CameraOrbitChannelId());
m_orbitDollyMoveCamera->SetDollyInputChannelId(SandboxEditor::CameraOrbitDollyChannelId());
}
void EditorModularViewportCameraComposer::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId)
{
if (viewEntityId.IsValid())
{
AZ::Transform worldFromLocal = AZ::Transform::CreateIdentity();
AZ::TransformBus::EventResult(worldFromLocal, viewEntityId, &AZ::TransformBus::Events::GetWorldTM);
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame,
worldFromLocal);
}
else
{
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
m_viewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
}
}
} // namespace SandboxEditor
@@ -10,13 +10,16 @@
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
#include <AzFramework/Viewport/CameraInput.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <EditorModularViewportCameraComposerBus.h>
#include <SandboxAPI.h>
namespace SandboxEditor
{
//! Type responsible for building the editor's modular viewport camera controller.
class EditorModularViewportCameraComposer : private EditorModularViewportCameraComposerNotificationBus::Handler
class EditorModularViewportCameraComposer
: private EditorModularViewportCameraComposerNotificationBus::Handler
, private Camera::EditorCameraNotificationBus::Handler
{
public:
SANDBOX_API explicit EditorModularViewportCameraComposer(AzFramework::ViewportId viewportId);
@@ -32,6 +35,9 @@ namespace SandboxEditor
// EditorModularViewportCameraComposerNotificationBus overrides ...
void OnEditorModularViewportCameraComposerSettingsChanged() override;
// EditorCameraNotificationBus overrides ...
void OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) override;
AZStd::shared_ptr<AzFramework::RotateCameraInput> m_firstPersonRotateCamera;
AZStd::shared_ptr<AzFramework::PanCameraInput> m_firstPersonPanCamera;
AZStd::shared_ptr<AzFramework::TranslateCameraInput> m_firstPersonTranslateCamera;
@@ -0,0 +1,225 @@
/*
* 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 <AZTestShared/Math/MathTestHelpers.h>
#include <AtomToolsFramework/Viewport/ModularViewportCameraController.h>
#include <AzCore/UnitTest/TestTypes.h>
#include <AzFramework/Viewport/ViewportControllerList.h>
#include <AzTest/GemTestEnvironment.h>
#include <AzToolsFramework/API/EditorCameraBus.h>
#include <AzToolsFramework/ToolsComponents/TransformComponent.h>
#include <EditorModularViewportCameraComposer.h>
namespace UnitTest
{
class EditorCameraTestEnvironment : public AZ::Test::GemTestEnvironment
{
// AZ::Test::GemTestEnvironment overrides ...
void AddGemsAndComponents() override;
};
void EditorCameraTestEnvironment::AddGemsAndComponents()
{
AddDynamicModulePaths({ CAMERA_EDITOR_MODULE });
AddComponentDescriptors({ AzToolsFramework::Components::TransformComponent::CreateDescriptor() });
}
class EditorCameraFixture : public ::testing::Test
{
public:
AtomToolsFramework::ModularCameraViewportContext* m_cameraViewportContextView = nullptr;
AZStd::unique_ptr<SandboxEditor::EditorModularViewportCameraComposer> m_editorModularViewportCameraComposer;
AZStd::unique_ptr<AZ::DynamicModuleHandle> m_editorLibHandle;
AzFramework::ViewportControllerListPtr m_controllerList;
AZStd::unique_ptr<AZ::Entity> m_entity;
static const AzFramework::ViewportId TestViewportId;
void SetUp() override
{
m_editorLibHandle = AZ::DynamicModuleHandle::Create("EditorLib");
[[maybe_unused]] const bool loaded = m_editorLibHandle->Load(true);
AZ_Assert(loaded, "EditorLib could not be loaded");
m_controllerList = AZStd::make_shared<AzFramework::ViewportControllerList>();
m_controllerList->RegisterViewportContext(TestViewportId);
m_entity = AZStd::make_unique<AZ::Entity>();
m_entity->Init();
m_entity->CreateComponent<AzToolsFramework::Components::TransformComponent>();
m_entity->Activate();
m_editorModularViewportCameraComposer = AZStd::make_unique<SandboxEditor::EditorModularViewportCameraComposer>(TestViewportId);
auto controller = m_editorModularViewportCameraComposer->CreateModularViewportCameraController();
// set some overrides for the test
controller->SetCameraViewportContextBuilderCallback(
[this](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext) mutable
{
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::PlaceholderModularCameraViewportContextImpl>();
m_cameraViewportContextView = cameraViewportContext.get();
});
m_controllerList->Add(controller);
}
void TearDown() override
{
m_editorModularViewportCameraComposer.reset();
m_cameraViewportContextView = nullptr;
m_entity.reset();
m_editorLibHandle = {};
}
};
const AzFramework::ViewportId EditorCameraFixture::TestViewportId = AzFramework::ViewportId(1337);
TEST_F(EditorCameraFixture, ModularViewportCameraControllerReferenceFrameUpdatedWhenViewportEntityisChanged)
{
// Given
const auto entityTransform = AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(10.0f, 5.0f, -2.0f));
AZ::TransformBus::Event(m_entity->GetId(), &AZ::TransformBus::Events::SetWorldTM, entityTransform);
// When
// imitate viewport entity changing
Camera::EditorCameraNotificationBus::Broadcast(
&Camera::EditorCameraNotificationBus::Events::OnViewportViewEntityChanged, m_entity->GetId());
// ensure the viewport updates after the viewport view entity change
const float deltaTime = 1.0f / 60.0f;
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(deltaTime), AZ::ScriptTimePoint() });
// retrieve updated camera transform
const AZ::Transform cameraTransform = m_cameraViewportContextView->GetCameraTransform();
// Then
// camera transform matches that of the entity
EXPECT_THAT(cameraTransform, IsClose(entityTransform));
}
TEST_F(EditorCameraFixture, ReferenceFrameRemainsIdentityAfterExternalCameraTransformChangeWhenNotSet)
{
// Given
m_cameraViewportContextView->SetCameraTransform(AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f)));
// When
AZ::Transform referenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
referenceFrame, TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
// Then
// reference frame is still the identity
EXPECT_THAT(referenceFrame, IsClose(AZ::Transform::CreateIdentity()));
}
TEST_F(EditorCameraFixture, ExternalCameraTransformChangeWhenReferenceFrameIsSetUpdatesReferenceFrame)
{
// Given
const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame);
const AZ::Transform nextTransform = AZ::Transform::CreateTranslation(AZ::Vector3(10.0f, 20.0f, 30.0f));
m_cameraViewportContextView->SetCameraTransform(nextTransform);
// When
AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
currentReferenceFrame, TestViewportId,
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
// Then
EXPECT_THAT(currentReferenceFrame, IsClose(nextTransform));
}
TEST_F(EditorCameraFixture, ReferenceFrameReturnedToIdentityAfterClear)
{
// Given
const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame);
// When
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::ClearReferenceFrame);
AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
currentReferenceFrame, TestViewportId,
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
// Then
EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity()));
}
TEST_F(EditorCameraFixture, InterpolateToTransform)
{
// When
AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform,
transformToInterpolateTo, 0.0f);
// simulate interpolation
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
const auto finalTransform = m_cameraViewportContextView->GetCameraTransform();
// Then
EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo));
}
TEST_F(EditorCameraFixture, InterpolateToTransformWithReferenceSpaceSet)
{
// Given
const AZ::Transform referenceFrame = AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationX(AZ::DegToRad(90.0f)), AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::SetReferenceFrame, referenceFrame);
AZ::Transform transformToInterpolateTo = AZ::Transform::CreateFromQuaternionAndTranslation(
AZ::Quaternion::CreateRotationZ(AZ::DegToRad(90.0f)), AZ::Vector3(20.0f, 40.0f, 60.0f));
// When
AtomToolsFramework::ModularViewportCameraControllerRequestBus::Event(
TestViewportId, &AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::InterpolateToTransform,
transformToInterpolateTo, 0.0f);
// simulate interpolation
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
m_controllerList->UpdateViewport({ TestViewportId, AzFramework::FloatSeconds(0.5f), AZ::ScriptTimePoint() });
AZ::Transform currentReferenceFrame = AZ::Transform::CreateTranslation(AZ::Vector3(1.0f, 2.0f, 3.0f));
AtomToolsFramework::ModularViewportCameraControllerRequestBus::EventResult(
currentReferenceFrame, TestViewportId,
&AtomToolsFramework::ModularViewportCameraControllerRequestBus::Events::GetReferenceFrame);
const auto finalTransform = m_cameraViewportContextView->GetCameraTransform();
// Then
EXPECT_THAT(finalTransform, IsClose(transformToInterpolateTo));
EXPECT_THAT(currentReferenceFrame, IsClose(AZ::Transform::CreateIdentity()));
}
} // namespace UnitTest
// required to support running integration tests with the Camera Gem
AZTEST_EXPORT int AZ_UNIT_TEST_HOOK_NAME(int argc, char** argv)
{
::testing::InitGoogleMock(&argc, argv);
AZ::Test::printUnusedParametersWarning(argc, argv);
AZ::Test::addTestEnvironments({ new UnitTest::EditorCameraTestEnvironment() });
int result = RUN_ALL_TESTS();
return result;
}
IMPLEMENT_TEST_EXECUTABLE_MAIN();
@@ -58,28 +58,6 @@ namespace UnitTest
return true;
}
class TestModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext
{
public:
AZ::Transform GetCameraTransform() const override
{
return m_cameraTransform;
}
void SetCameraTransform(const AZ::Transform& transform) override
{
m_cameraTransform = transform;
}
void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler&) override
{
// noop
}
private:
AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity();
};
class ModularViewportCameraControllerFixture : public AllocatorsTestFixture
{
public:
@@ -146,7 +124,7 @@ namespace UnitTest
controller->SetCameraViewportContextBuilderCallback(
[this](AZStd::unique_ptr<AtomToolsFramework::ModularCameraViewportContext>& cameraViewportContext)
{
cameraViewportContext = AZStd::make_unique<TestModularCameraViewportContextImpl>();
cameraViewportContext = AZStd::make_unique<AtomToolsFramework::PlaceholderModularCameraViewportContextImpl>();
m_cameraViewportContextView = cameraViewportContext.get();
});
@@ -3642,7 +3642,7 @@ namespace AzToolsFramework
}
}
void EditorTransformComponentSelection::OnViewportViewEntityChanged(const AZ::EntityId& newViewId)
void EditorTransformComponentSelection::OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId)
{
AZ_PROFILE_FUNCTION(AzToolsFramework);
@@ -3650,12 +3650,12 @@ namespace AzToolsFramework
// match the editor camera translation/orientation), record the entity id if we have
// a manipulator tracking it (entity id exists in m_entityIdManipulator lookups)
// and remove it when recreating manipulators (see InitializeManipulators)
if (newViewId.IsValid())
if (viewEntityId.IsValid())
{
const auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(newViewId);
const auto entityIdLookupIt = m_entityIdManipulators.m_lookups.find(viewEntityId);
if (entityIdLookupIt != m_entityIdManipulators.m_lookups.end())
{
m_editorCameraComponentEntityId = newViewId;
m_editorCameraComponentEntityId = viewEntityId;
RegenerateManipulators();
}
}
@@ -270,7 +270,7 @@ namespace AzToolsFramework
void OnTransformChanged(const AZ::Transform& localTM, const AZ::Transform& worldTM) override;
// Camera::EditorCameraNotificationBus overrides ...
void OnViewportViewEntityChanged(const AZ::EntityId& newViewId) override;
void OnViewportViewEntityChanged(const AZ::EntityId& viewEntityId) override;
// EditorContextVisibilityNotificationBus overrides ...
void OnEntityVisibilityChanged(bool visibility) override;
@@ -176,7 +176,10 @@ namespace AZ
m_isAssetCatalogLoaded = true;
RPI::RPISystemInterface::Get()->InitializeSystemAssets();
if (!RPI::RPISystemInterface::Get()->IsInitialized())
{
RPI::RPISystemInterface::Get()->InitializeSystemAssets();
}
if (!RPI::RPISystemInterface::Get()->IsInitialized())
{
@@ -40,11 +40,10 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
//! Reflection Probe (smallest probe volume that overlaps the object position)
struct ReflectionProbeData
{
float3 m_aabbPos;
float3 m_outerAabbMin;
float3 m_outerAabbMax;
float3 m_innerAabbMin;
float3 m_innerAabbMax;
row_major float3x4 m_modelToWorld;
row_major float3x4 m_modelToWorldInverse; // does not include extents
float3 m_outerObbHalfLengths;
float3 m_innerObbHalfLengths;
float m_padding;
bool m_useReflectionProbe;
bool m_useParallaxCorrection;
@@ -52,4 +51,32 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
ReflectionProbeData m_reflectionProbeData;
TextureCube m_reflectionProbeCubeMap;
float4x4 GetReflectionProbeWorldMatrix()
{
float4x4 modelToWorld = float4x4(
float4(1, 0, 0, 0),
float4(0, 1, 0, 0),
float4(0, 0, 1, 0),
float4(0, 0, 0, 1));
modelToWorld[0] = m_reflectionProbeData.m_modelToWorld[0];
modelToWorld[1] = m_reflectionProbeData.m_modelToWorld[1];
modelToWorld[2] = m_reflectionProbeData.m_modelToWorld[2];
return modelToWorld;
}
float4x4 GetReflectionProbeWorldMatrixInverse()
{
float4x4 modelToWorldInverse = float4x4(
float4(1, 0, 0, 0),
float4(0, 1, 0, 0),
float4(0, 0, 1, 0),
float4(0, 0, 0, 1));
modelToWorldInverse[0] = m_reflectionProbeData.m_modelToWorldInverse[0];
modelToWorldInverse[1] = m_reflectionProbeData.m_modelToWorldInverse[1];
modelToWorldInverse[2] = m_reflectionProbeData.m_modelToWorldInverse[2];
return modelToWorldInverse;
}
}
@@ -50,10 +50,10 @@ float GetRoughnessMip(float roughness)
return roughness * maxRoughnessMip;
}
// compute parallax corrected reflection vector
// compute parallax corrected reflection vector, AABB version
// we do this by finding the intersection with the volume and adjusting the reflection vector for the surface position
// https://seblagarde.wordpress.com/2012/09/29/image-based-lighting-approaches-and-parallax-corrected-cubemap/
float3 ApplyParallaxCorrection(float3 aabbMin, float3 aabbMax, float3 aabbPos, float3 positionWS, float3 reflectDir)
float3 ApplyParallaxCorrectionAABB(float3 aabbMin, float3 aabbMax, float3 aabbPos, float3 positionWS, float3 reflectDir)
{
float3 rcpReflectDir = 1.0f / reflectDir;
float3 intersectA = (aabbMax - positionWS) * rcpReflectDir;
@@ -63,3 +63,10 @@ float3 ApplyParallaxCorrection(float3 aabbMin, float3 aabbMax, float3 aabbPos, f
float3 intersectPos = reflectDir * distance + positionWS;
return (intersectPos - aabbPos);
}
// compute parallax corrected reflection vector, OBB version
float3 ApplyParallaxCorrectionOBB(float4x4 obbTransformInverse, float3 obbHalfExtents, float3 positionWS, float3 reflectDir)
{
float4 p = mul(obbTransformInverse, float4(positionWS, 1.0f));
return ApplyParallaxCorrectionAABB(-obbHalfExtents, obbHalfExtents, float3(0.0f, 0.0f, 0.0f), p, reflectDir);
}
@@ -48,10 +48,9 @@ float3 GetIblSpecular(
{
if (ObjectSrg::m_reflectionProbeData.m_useParallaxCorrection)
{
reflectDir = ApplyParallaxCorrection(
ObjectSrg::m_reflectionProbeData.m_outerAabbMin,
ObjectSrg::m_reflectionProbeData.m_outerAabbMax,
ObjectSrg::m_reflectionProbeData.m_aabbPos,
reflectDir = ApplyParallaxCorrectionOBB(
ObjectSrg::GetReflectionProbeWorldMatrixInverse(),
ObjectSrg::m_reflectionProbeData.m_outerObbHalfLengths,
position,
reflectDir);
}
@@ -60,11 +59,10 @@ float3 GetIblSpecular(
probeSpecular *= (specularF0 * brdf.x + brdf.y);
// compute blend amount based on world position in the reflection probe volume
float blendAmount = ComputeLerpBetweenInnerOuterAABBs(
ObjectSrg::m_reflectionProbeData.m_innerAabbMin,
ObjectSrg::m_reflectionProbeData.m_innerAabbMax,
ObjectSrg::m_reflectionProbeData.m_outerAabbMax,
ObjectSrg::m_reflectionProbeData.m_aabbPos,
float blendAmount = ComputeLerpBetweenInnerOuterOBBs(
ObjectSrg::GetReflectionProbeWorldMatrixInverse(),
ObjectSrg::m_reflectionProbeData.m_innerObbHalfLengths,
ObjectSrg::m_reflectionProbeData.m_outerObbHalfLengths,
position);
outSpecular = lerp(outSpecular, probeSpecular, blendAmount);
@@ -63,7 +63,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// make sure the pixel belongs to this probe volume
// this is necessary since it could have the correct stencil value but actually reside
// in another volume that's in between the camera and the volume we're rendering
if (!AabbContainsPoint(ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, positionWS))
if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_outerObbHalfLengths, positionWS))
{
discard;
}
@@ -71,11 +71,15 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// determine blend based on position with respect to the inner and outer AABBs
// if it's inside the inner AABB it blends at 100%, otherwise it's the percentage of the distance between the inner/outer AABB
float blendWeight = 1.0f;
if (!AabbContainsPoint(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, positionWS))
if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, positionWS))
{
// not inside the inner AABB, so it's in between the inner and outer AABBs
// compute blend amount based on the distance to the outer AABB
blendWeight = ComputeLerpBetweenInnerOuterAABBs(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS);
blendWeight = ComputeLerpBetweenInnerOuterOBBs(
ObjectSrg::GetWorldMatrixInverse(),
ObjectSrg::m_innerObbHalfLengths,
ObjectSrg::m_outerObbHalfLengths,
positionWS);
}
// write the blend weight (additive) at this position for the probe volume
@@ -12,12 +12,12 @@
#include <Atom/Features/PBR/Microfacet/Fresnel.azsli>
// compute final probe specular using the probe cubemap and the roughness, normals, and specularF0 for the surface
bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float3 aabbMin, float3 aabbMax, uint sampleIndex, out float3 specular)
bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float4x4 obbTransformInverse, float3 outerObbHalfLengths, uint sampleIndex, out float3 specular)
{
// make sure the pixel belongs to this probe volume
// this is necessary since it could have the correct stencil value but actually reside
// in another volume that's in between the camera and the volume we're rendering
if (!AabbContainsPoint(aabbMin, aabbMax, positionWS))
if (!ObbContainsPoint(obbTransformInverse, outerObbHalfLengths, positionWS))
{
return false;
}
@@ -47,7 +47,11 @@ bool ComputeProbeSpecular(float2 screenCoords, float3 positionWS, float3 aabbMin
float3 localReflectDir = reflectDir;
if (ObjectSrg::m_useParallaxCorrection)
{
localReflectDir = ApplyParallaxCorrection(ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS, reflectDir);
localReflectDir = ApplyParallaxCorrectionOBB(
ObjectSrg::GetWorldMatrixInverse(),
ObjectSrg::m_outerObbHalfLengths,
positionWS,
reflectDir);
}
// sample reflection cubemap with the appropriate roughness mip
@@ -75,7 +75,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// compute specular using the probe cubemap and the roughness, normals, and specularF0 for the surface
float3 specular = float3(0.0f, 0.0f, 0.0f);
if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, sampleIndex, specular))
if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, sampleIndex, specular))
{
discard;
}
@@ -13,12 +13,9 @@
ShaderResourceGroup ObjectSrg : SRG_PerObject
{
row_major float3x4 m_modelToWorld;
float3 m_aabbPos;
float3 m_outerAabbMin;
float3 m_outerAabbMax;
float3 m_innerAabbMin;
float3 m_innerAabbMax;
row_major float3x4 m_modelToWorldInverse; // does not include extents
float3 m_outerObbHalfLengths;
float3 m_innerObbHalfLengths;
bool m_useParallaxCorrection;
TextureCube m_reflectionCubeMap;
@@ -35,4 +32,18 @@ ShaderResourceGroup ObjectSrg : SRG_PerObject
modelToWorld[2] = ObjectSrg::m_modelToWorld[2];
return modelToWorld;
}
float4x4 GetWorldMatrixInverse()
{
float4x4 modelToWorldInverse = float4x4(
float4(1, 0, 0, 0),
float4(0, 1, 0, 0),
float4(0, 0, 1, 0),
float4(0, 0, 0, 1));
modelToWorldInverse[0] = ObjectSrg::m_modelToWorldInverse[0];
modelToWorldInverse[1] = ObjectSrg::m_modelToWorldInverse[1];
modelToWorldInverse[2] = ObjectSrg::m_modelToWorldInverse[2];
return modelToWorldInverse;
}
}
@@ -77,7 +77,7 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// compute specular using the probe cubemap and the roughness, normals, and specularF0 for the surface
float3 specular = float3(0.0f, 0.0f, 0.0f);
if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::m_outerAabbMin, ObjectSrg::m_outerAabbMax, sampleIndex, specular))
if (!ComputeProbeSpecular(IN.m_position.xy, positionWS, ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_outerObbHalfLengths, sampleIndex, specular))
{
discard;
}
@@ -85,13 +85,17 @@ PSOutput MainPS(VSOutput IN, in uint sampleIndex : SV_SampleIndex)
// determine blend based on position with respect to the inner and outer AABBs
// if it's inside the inner AABB it blends at 100%, otherwise it's the percentage of the distance between the inner/outer AABB
float blendWeight = 1.0f;
if (!AabbContainsPoint(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, positionWS))
if (!ObbContainsPoint(ObjectSrg::GetWorldMatrixInverse(), ObjectSrg::m_innerObbHalfLengths, positionWS))
{
// not inside the inner AABB, so it's in between the inner and outer AABBs
// compute blend amount based on the distance to the outer AABB
blendWeight = ComputeLerpBetweenInnerOuterAABBs(ObjectSrg::m_innerAabbMin, ObjectSrg::m_innerAabbMax, ObjectSrg::m_outerAabbMax, ObjectSrg::m_aabbPos, positionWS);
blendWeight = ComputeLerpBetweenInnerOuterOBBs(
ObjectSrg::GetWorldMatrixInverse(),
ObjectSrg::m_innerObbHalfLengths,
ObjectSrg::m_outerObbHalfLengths,
positionWS);
}
// retrieve the blend weight of all probes at this location
float blendWeightAllProbes = PassSrg::m_blendWeight.Load(IN.m_position.xy, sampleIndex).r;
@@ -1111,20 +1111,17 @@ namespace AZ
if (reflectionProbeFeatureProcessor && (m_descriptor.m_useForwardPassIblSpecular || m_hasForwardPassIblSpecularMaterial))
{
// retrieve probe constant indices
AZ::RHI::ShaderInputConstantIndex posConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_aabbPos"));
AZ_Error("MeshDataInstance", posConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex modelToWorldConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorld"));
AZ_Error("MeshDataInstance", modelToWorldConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex outerAabbMinConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerAabbMin"));
AZ_Error("MeshDataInstance", outerAabbMinConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex modelToWorldInverseConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_modelToWorldInverse"));
AZ_Error("MeshDataInstance", modelToWorldInverseConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex outerAabbMaxConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerAabbMax"));
AZ_Error("MeshDataInstance", outerAabbMaxConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex outerObbHalfLengthsConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_outerObbHalfLengths"));
AZ_Error("MeshDataInstance", outerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex innerAabbMinConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerAabbMin"));
AZ_Error("MeshDataInstance", innerAabbMinConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex innerAabbMaxConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerAabbMax"));
AZ_Error("MeshDataInstance", innerAabbMaxConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex innerObbHalfLengthsConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_innerObbHalfLengths"));
AZ_Error("MeshDataInstance", innerObbHalfLengthsConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
AZ::RHI::ShaderInputConstantIndex useReflectionProbeConstantIndex = m_shaderResourceGroup->FindShaderInputConstantIndex(Name("m_reflectionProbeData.m_useReflectionProbe"));
AZ_Error("MeshDataInstance", useReflectionProbeConstantIndex.IsValid(), "Failed to find ReflectionProbe constant index");
@@ -1146,11 +1143,10 @@ namespace AZ
if (!reflectionProbes.empty() && reflectionProbes[0])
{
m_shaderResourceGroup->SetConstant(posConstantIndex, reflectionProbes[0]->GetPosition());
m_shaderResourceGroup->SetConstant(outerAabbMinConstantIndex, reflectionProbes[0]->GetOuterAabbWs().GetMin());
m_shaderResourceGroup->SetConstant(outerAabbMaxConstantIndex, reflectionProbes[0]->GetOuterAabbWs().GetMax());
m_shaderResourceGroup->SetConstant(innerAabbMinConstantIndex, reflectionProbes[0]->GetInnerAabbWs().GetMin());
m_shaderResourceGroup->SetConstant(innerAabbMaxConstantIndex, reflectionProbes[0]->GetInnerAabbWs().GetMax());
m_shaderResourceGroup->SetConstant(modelToWorldConstantIndex, reflectionProbes[0]->GetTransform());
m_shaderResourceGroup->SetConstant(modelToWorldInverseConstantIndex, Matrix3x4::CreateFromTransform(reflectionProbes[0]->GetTransform()).GetInverseFull());
m_shaderResourceGroup->SetConstant(outerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetOuterObbWs().GetHalfLengths());
m_shaderResourceGroup->SetConstant(innerObbHalfLengthsConstantIndex, reflectionProbes[0]->GetInnerObbWs().GetHalfLengths());
m_shaderResourceGroup->SetConstant(useReflectionProbeConstantIndex, true);
m_shaderResourceGroup->SetConstant(useParallaxCorrectionConstantIndex, reflectionProbes[0]->GetUseParallaxCorrection());
@@ -138,43 +138,39 @@ namespace AZ
if (m_updateSrg)
{
// stencil Srg
// Note: the stencil pass uses a slightly reduced inner AABB to avoid seams
// Note: the stencil pass uses a slightly reduced inner OBB to avoid seams
Vector3 innerExtentsReduced = m_innerExtents - Vector3(0.1f, 0.1f, 0.1f);
Matrix3x4 modelToWorldStencil = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(innerExtentsReduced);
Matrix3x4 modelToWorldStencil = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(innerExtentsReduced);
m_stencilSrg->SetConstant(m_reflectionRenderData->m_modelToWorldStencilConstantIndex, modelToWorldStencil);
m_stencilSrg->Compile();
Matrix3x4 modelToWorldInverse = Matrix3x4::CreateFromTransform(m_transform).GetInverseFull();
// blend weight Srg
Matrix3x4 modelToWorldOuter = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_outerExtents);
Matrix3x4 modelToWorldOuter = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_outerExtents);
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldOuter);
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse);
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths());
m_blendWeightSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection);
m_blendWeightSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage);
m_blendWeightSrg->Compile();
// render outer Srg
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldOuter);
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse);
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths());
m_renderOuterSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection);
m_renderOuterSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage);
m_renderOuterSrg->Compile();
// render inner Srg
Matrix3x4 modelToWorldInner = Matrix3x4::CreateFromMatrix3x3AndTranslation(Matrix3x3::CreateIdentity(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_innerExtents);
Matrix3x4 modelToWorldInner = Matrix3x4::CreateFromQuaternionAndTranslation(m_transform.GetRotation(), m_transform.GetTranslation()) * Matrix3x4::CreateScale(m_innerExtents);
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_modelToWorldRenderConstantIndex, modelToWorldInner);
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_aabbPosRenderConstantIndex, m_outerAabbWs.GetCenter());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerAabbMinRenderConstantIndex, m_outerAabbWs.GetMin());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerAabbMaxRenderConstantIndex, m_outerAabbWs.GetMax());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerAabbMinRenderConstantIndex, m_innerAabbWs.GetMin());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerAabbMaxRenderConstantIndex, m_innerAabbWs.GetMax());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_modelToWorldInverseRenderConstantIndex, modelToWorldInverse);
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_outerObbHalfLengthsRenderConstantIndex, m_outerObbWs.GetHalfLengths());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_innerObbHalfLengthsRenderConstantIndex, m_innerObbWs.GetHalfLengths());
m_renderInnerSrg->SetConstant(m_reflectionRenderData->m_useParallaxCorrectionRenderConstantIndex, m_useParallaxCorrection);
m_renderInnerSrg->SetImage(m_reflectionRenderData->m_reflectionCubeMapRenderImageIndex, m_cubeMapImage);
m_renderInnerSrg->Compile();
@@ -244,22 +240,22 @@ namespace AZ
m_outerExtents *= m_transform.GetUniformScale();
m_innerExtents *= m_transform.GetUniformScale();
m_outerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_outerExtents / 2.0f);
m_innerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_innerExtents / 2.0f);
m_outerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_outerExtents / 2.0f);
m_innerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_innerExtents / 2.0f);
m_updateSrg = true;
}
void ReflectionProbe::SetOuterExtents(const AZ::Vector3& outerExtents)
{
m_outerExtents = outerExtents * m_transform.GetUniformScale();
m_outerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_outerExtents / 2.0f);
m_outerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_outerExtents / 2.0f);
m_updateSrg = true;
}
void ReflectionProbe::SetInnerExtents(const AZ::Vector3& innerExtents)
{
m_innerExtents = innerExtents * m_transform.GetUniformScale();
m_innerAabbWs = Aabb::CreateCenterHalfExtents(m_transform.GetTranslation(), m_innerExtents / 2.0f);
m_innerObbWs = Obb::CreateFromPositionRotationAndHalfLengths(m_transform.GetTranslation(), m_transform.GetRotation(), m_innerExtents / 2.0f);
m_updateSrg = true;
}
@@ -410,13 +406,14 @@ namespace AZ
lod.m_screenCoverageMax = 1.0f;
// update cullable bounds
Aabb outerAabb = Aabb::CreateFromObb(m_outerObbWs);
Vector3 center;
float radius;
m_outerAabbWs.GetAsSphere(center, radius);
outerAabb.GetAsSphere(center, radius);
m_cullable.m_cullData.m_boundingSphere = Sphere(center, radius);
m_cullable.m_cullData.m_boundingObb = m_outerAabbWs.GetTransformedObb(AZ::Transform::CreateIdentity());
m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = m_outerAabbWs;
m_cullable.m_cullData.m_boundingObb = m_outerObbWs;
m_cullable.m_cullData.m_visibilityEntry.m_boundingVolume = outerAabb;
m_cullable.m_cullData.m_visibilityEntry.m_userData = &m_cullable;
m_cullable.m_cullData.m_visibilityEntry.m_typeFlags = AzFramework::VisibilityEntry::TYPE_RPI_Cullable;
@@ -55,15 +55,13 @@ namespace AZ
RHI::DrawListTag m_renderOuterDrawListTag;
RHI::DrawListTag m_renderInnerDrawListTag;
RHI::ShaderInputConstantIndex m_modelToWorldStencilConstantIndex;
RHI::ShaderInputConstantIndex m_modelToWorldRenderConstantIndex;
RHI::ShaderInputConstantIndex m_aabbPosRenderConstantIndex;
RHI::ShaderInputConstantIndex m_outerAabbMinRenderConstantIndex;
RHI::ShaderInputConstantIndex m_outerAabbMaxRenderConstantIndex;
RHI::ShaderInputConstantIndex m_innerAabbMinRenderConstantIndex;
RHI::ShaderInputConstantIndex m_innerAabbMaxRenderConstantIndex;
RHI::ShaderInputConstantIndex m_useParallaxCorrectionRenderConstantIndex;
RHI::ShaderInputImageIndex m_reflectionCubeMapRenderImageIndex;
RHI::ShaderInputNameIndex m_modelToWorldStencilConstantIndex = "m_modelToWorld";
RHI::ShaderInputNameIndex m_modelToWorldRenderConstantIndex = "m_modelToWorld";
RHI::ShaderInputNameIndex m_modelToWorldInverseRenderConstantIndex = "m_modelToWorldInverse";
RHI::ShaderInputNameIndex m_outerObbHalfLengthsRenderConstantIndex = "m_outerObbHalfLengths";
RHI::ShaderInputNameIndex m_innerObbHalfLengthsRenderConstantIndex = "m_innerObbHalfLengths";
RHI::ShaderInputNameIndex m_useParallaxCorrectionRenderConstantIndex = "m_useParallaxCorrection";
RHI::ShaderInputNameIndex m_reflectionCubeMapRenderImageIndex = "m_reflectionCubeMap";
};
// ReflectionProbe manages all aspects of a single probe, including rendering, visualization, and cubemap generation
@@ -78,6 +76,7 @@ namespace AZ
void Simulate(uint32_t probeIndex);
const Vector3& GetPosition() const { return m_transform.GetTranslation(); }
const AZ::Transform& GetTransform() const { return m_transform; }
void SetTransform(const AZ::Transform& transform);
const AZ::Vector3& GetOuterExtents() const { return m_outerExtents; }
@@ -86,8 +85,8 @@ namespace AZ
const AZ::Vector3& GetInnerExtents() const { return m_innerExtents; }
void SetInnerExtents(const AZ::Vector3& innerExtents);
const Aabb& GetOuterAabbWs() const { return m_outerAabbWs; }
const Aabb& GetInnerAabbWs() const { return m_innerAabbWs; }
const Obb& GetOuterObbWs() const { return m_outerObbWs; }
const Obb& GetInnerObbWs() const { return m_innerObbWs; }
const Data::Instance<RPI::Image>& GetCubeMapImage() const { return m_cubeMapImage; }
void SetCubeMapImage(const Data::Instance<RPI::Image>& cubeMapImage, const AZStd::string& relativePath);
@@ -133,9 +132,9 @@ namespace AZ
AZ::Vector3 m_outerExtents = AZ::Vector3(0.0f, 0.0f, 0.0f);
AZ::Vector3 m_innerExtents = AZ::Vector3(0.0f, 0.0f, 0.0f);
// probe volume AABBs (world space), built from position and extents
Aabb m_outerAabbWs;
Aabb m_innerAabbWs;
// probe volume OBBs (world space), built from position and extents
Obb m_outerObbWs;
Obb m_innerObbWs;
// cubemap
Data::Instance<RPI::Image> m_cubeMapImage;
@@ -77,61 +77,6 @@ namespace AZ
m_reflectionRenderData.m_renderInnerSrgLayout,
m_reflectionRenderData.m_renderInnerDrawListTag);
// create ShaderResourceGroups here so we can get the layout and cache the indices
// Note: the SRGs are not needed beyond this method since each probe creates its own SRGs, we are just interested in the indices
// cache probe stencil shader indices
Data::Instance<RPI::ShaderResourceGroup> stencilSrg = RPI::ShaderResourceGroup::Create(
m_reflectionRenderData.m_stencilShader->GetAsset(),
m_reflectionRenderData.m_stencilShader->GetSupervariantIndex(),
m_reflectionRenderData.m_stencilSrgLayout->GetName());
AZ_Error("ReflectionProbeFeatureProcessor", stencilSrg.get(), "Failed to create stencil back face shader resource group");
const RHI::ShaderResourceGroupLayout* stencilSrgLayout = stencilSrg->GetLayout();
Name modelToWorldConstantName = Name("m_modelToWorld");
m_reflectionRenderData.m_modelToWorldStencilConstantIndex = stencilSrgLayout->FindShaderInputConstantIndex(modelToWorldConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldStencilConstantIndex.IsValid(), "Failed to find stencil shader input constant [%s]", modelToWorldConstantName.GetCStr());
// cache probe render shader indices
// Note: the outer and inner render shaders use the same Srg
Data::Instance<RPI::ShaderResourceGroup> renderReflectionSrg = RPI::ShaderResourceGroup::Create(
m_reflectionRenderData.m_renderOuterShader->GetAsset(),
m_reflectionRenderData.m_renderOuterShader->GetSupervariantIndex(),
m_reflectionRenderData.m_renderOuterSrgLayout->GetName());
AZ_Error("ReflectionProbeFeatureProcessor", renderReflectionSrg.get(), "Failed to create render reflection shader resource group");
const RHI::ShaderResourceGroupLayout* renderReflectionSrgLayout = renderReflectionSrg->GetLayout();
m_reflectionRenderData.m_modelToWorldRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(modelToWorldConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_modelToWorldRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", modelToWorldConstantName.GetCStr());
Name aabbPosConstantName = Name("m_aabbPos");
m_reflectionRenderData.m_aabbPosRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(aabbPosConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_aabbPosRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", aabbPosConstantName.GetCStr());
Name outerAabbMinConstantName = Name("m_outerAabbMin");
m_reflectionRenderData.m_outerAabbMinRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerAabbMinConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerAabbMinRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerAabbMinConstantName.GetCStr());
Name outerAabbMaxConstantName = Name("m_outerAabbMax");
m_reflectionRenderData.m_outerAabbMaxRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(outerAabbMaxConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_outerAabbMaxRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", outerAabbMaxConstantName.GetCStr());
Name innerAabbMinConstantName = Name("m_innerAabbMin");
m_reflectionRenderData.m_innerAabbMinRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerAabbMinConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerAabbMinRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerAabbMinConstantName.GetCStr());
Name innerAabbMaxConstantName = Name("m_innerAabbMax");
m_reflectionRenderData.m_innerAabbMaxRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(innerAabbMaxConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_innerAabbMaxRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", innerAabbMaxConstantName.GetCStr());
Name useParallaxCorrectionConstantName = Name("m_useParallaxCorrection");
m_reflectionRenderData.m_useParallaxCorrectionRenderConstantIndex = renderReflectionSrgLayout->FindShaderInputConstantIndex(useParallaxCorrectionConstantName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_useParallaxCorrectionRenderConstantIndex.IsValid(), "Failed to find render shader input constant [%s]", useParallaxCorrectionConstantName.GetCStr());
Name reflectionCubeMapImageName = Name("m_reflectionCubeMap");
m_reflectionRenderData.m_reflectionCubeMapRenderImageIndex = renderReflectionSrgLayout->FindShaderInputImageIndex(reflectionCubeMapImageName);
AZ_Error("ReflectionProbeFeatureProcessor", m_reflectionRenderData.m_reflectionCubeMapRenderImageIndex.IsValid(), "Failed to find render shader input image [%s]", reflectionCubeMapImageName.GetCStr());
EnableSceneNotification();
}
@@ -197,10 +142,11 @@ namespace AZ
// sort the probes by descending inner volume size, so the smallest volumes are rendered last
auto sortFn = [](AZStd::shared_ptr<ReflectionProbe> const& probe1, AZStd::shared_ptr<ReflectionProbe> const& probe2) -> bool
{
const Aabb& aabb1 = probe1->GetInnerAabbWs();
const Aabb& aabb2 = probe2->GetInnerAabbWs();
float size1 = aabb1.GetXExtent() * aabb1.GetZExtent() * aabb1.GetYExtent();
float size2 = aabb2.GetXExtent() * aabb2.GetZExtent() * aabb2.GetYExtent();
const Obb& obb1 = probe1->GetInnerObbWs();
const Obb& obb2 = probe2->GetInnerObbWs();
float size1 = obb1.GetHalfLengthX() * obb1.GetHalfLengthZ() * obb1.GetHalfLengthY();
float size2 = obb2.GetHalfLengthX() * obb2.GetHalfLengthZ() * obb2.GetHalfLengthY();
return (size1 > size2);
};
@@ -345,7 +291,7 @@ namespace AZ
// simple AABB check to find the reflection probes that contain the position
for (auto& reflectionProbe : m_reflectionProbes)
{
if (reflectionProbe->GetOuterAabbWs().Contains(position)
if (reflectionProbe->GetOuterObbWs().Contains(position)
&& reflectionProbe->GetCubeMapImage()
&& reflectionProbe->GetCubeMapImage()->IsInitialized())
{
@@ -116,6 +116,22 @@ float ComputeLerpBetweenInnerOuterAABBs(float3 innerAabbMin, float3 innerAabbMax
return totalDistance > 0.0f ? saturate(shortestDistance / totalDistance) : 1.0f;
}
// returns true if the Obb contains the specified point
bool ObbContainsPoint(float4x4 obbTransformInverse, float3 obbHalfExtents, float3 testPoint)
{
// get the position in Obb local space, force to positive quadrant with abs()
float4 p = abs(mul(obbTransformInverse, float4(testPoint, 1.0f)));
return AabbContainsPoint(-obbHalfExtents, obbHalfExtents, p);
}
// computes [0..1] percentage of a point that's in between the inner and outer OBBs
float ComputeLerpBetweenInnerOuterOBBs(float3x4 obbTransformInverse, float3 innerObbHalfExtents, float3 outerObbHalfExtents, float3 position)
{
// get the position in Obb local space, force to positive quadrant with abs()
float3 p = abs(mul(obbTransformInverse, float4(position, 1.0f)));
return ComputeLerpBetweenInnerOuterAABBs(-innerObbHalfExtents, innerObbHalfExtents, outerObbHalfExtents, float3(0.0f, 0.0f, 0.0f), p);
}
// ---------- Normal Encoding -----------
// Encode/Decode functions for Signed Octahedron normals
@@ -30,6 +30,7 @@
#include <AzCore/IO/IOUtils.h>
#include <AzCore/IO/Path/Path.h>
#include <AzCore/Serialization/Json/JsonSerialization.h>
#include <AzCore/Settings/SettingsRegistry.h>
namespace AZ
{
@@ -46,7 +47,7 @@ namespace AZ
{
AssetBuilderSDK::AssetBuilderDesc materialBuilderDescriptor;
materialBuilderDescriptor.m_name = JobKey;
materialBuilderDescriptor.m_version = 107; // ATOM-14918
materialBuilderDescriptor.m_version = 108; // Set materialtype dependency to OrderOnce
materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.material", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
materialBuilderDescriptor.m_patterns.push_back(AssetBuilderSDK::AssetBuilderPattern("*.materialtype", AssetBuilderSDK::AssetBuilderPattern::PatternType::Wildcard));
materialBuilderDescriptor.m_busId = azrtti_typeid<MaterialBuilder>();
@@ -66,21 +67,19 @@ namespace AZ
//! Adds all relevant dependencies for a referenced source file, considering that the path might be relative to the original file location or a full asset path.
//! This will usually include multiple source dependencies and a single job dependency, but will include only source dependencies if the file is not found.
//! Note the AssetBuilderSDK::JobDependency::m_platformIdentifier will not be set by this function. The calling code must set this value before passing back
//! to the AssetBuilderSDK::CreateJobsResponse.
void AddPossibleDependencies(
AZStd::string_view currentFilePath, AZStd::string_view referencedParentPath,
AZStd::vector<AssetBuilderSDK::SourceFileDependency>& sourceFileDependencies,
const char* jobKey, AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies)
//! to the AssetBuilderSDK::CreateJobsResponse. If isOrderedOnceForMaterialTypes is true and the dependency is a materialtype file, the job dependency type
//! will be set to JobDependencyType::OrderOnce.
void AddPossibleDependencies(AZStd::string_view currentFilePath,
AZStd::string_view referencedParentPath,
const char* jobKey,
AZStd::vector<AssetBuilderSDK::JobDependency>& jobDependencies,
bool isOrderedOnceForMaterialTypes = false)
{
bool dependencyFileFound = false;
AZStd::vector<AZStd::string> possibleDependencies = RPI::AssetUtils::GetPossibleDepenencyPaths(currentFilePath, referencedParentPath);
for (auto& file : possibleDependencies)
{
AssetBuilderSDK::SourceFileDependency sourceFileDependency;
sourceFileDependency.m_sourceFileDependencyPath = file;
sourceFileDependencies.push_back(sourceFileDependency);
// The first path found is the highest priority, and will have a job dependency, as this is the one
// the builder will actually use
if (!dependencyFileFound)
@@ -93,8 +92,11 @@ namespace AZ
{
AssetBuilderSDK::JobDependency jobDependency;
jobDependency.m_jobKey = jobKey;
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
jobDependency.m_sourceFile.m_sourceFileDependencyPath = file;
const bool isMaterialTypeFile = AzFramework::StringFunc::Path::IsExtension(file.c_str(), MaterialTypeSourceData::Extension);
jobDependency.m_type = (isMaterialTypeFile && isOrderedOnceForMaterialTypes) ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order;
jobDependencies.push_back(jobDependency);
}
}
@@ -173,8 +175,9 @@ namespace AZ
for (auto& shader : materialTypeSourceData.GetValue().m_shaderCollection)
{
AddPossibleDependencies(request.m_sourceFile, shader.m_shaderFilePath,
response.m_sourceFileDependencyList, "Shader Asset",
AddPossibleDependencies(request.m_sourceFile,
shader.m_shaderFilePath,
"Shader Asset",
outputJobDescriptor.m_jobDependencyList);
}
@@ -184,9 +187,10 @@ namespace AZ
for (const MaterialFunctorSourceData::AssetDependency& dependency : dependencies)
{
AddPossibleDependencies(request.m_sourceFile, dependency.m_sourceFilePath,
response.m_sourceFileDependencyList,
dependency.m_jobKey.c_str(), outputJobDescriptor.m_jobDependencyList);
AddPossibleDependencies(request.m_sourceFile,
dependency.m_sourceFilePath,
dependency.m_jobKey.c_str(),
outputJobDescriptor.m_jobDependencyList);
}
}
}
@@ -219,11 +223,24 @@ namespace AZ
parentMaterialPath = materialTypePath;
}
// If includeMaterialPropertyNames is false, then a job dependency is needed so the material builder can validate MaterialAsset properties
// against the MaterialTypeAsset at asset build time.
// If includeMaterialPropertyNames is true, the material properties will be validated at runtime when the material is loaded, so the job dependency
// is needed only for first-time processing to set up the initial MaterialAsset. This speeds up AP processing time when a materialtype file
// is edited (e.g. 10s when editing StandardPBR.materialtype on AtomTest project from 45s).
bool includeMaterialPropertyNames = true;
if (auto settingsRegistry = AZ::SettingsRegistry::Get(); settingsRegistry != nullptr)
{
settingsRegistry->Get(includeMaterialPropertyNames, "/O3DE/Atom/RPI/MaterialBuilder/IncludeMaterialPropertyNames");
}
// Register dependency on the parent material source file so we can load it and use it's data to build this variant material.
// Note, we don't need a direct dependency on the material type because the parent material will depend on it.
AddPossibleDependencies(request.m_sourceFile, parentMaterialPath,
response.m_sourceFileDependencyList,
JobKey, outputJobDescriptor.m_jobDependencyList);
AddPossibleDependencies(request.m_sourceFile,
parentMaterialPath,
JobKey,
outputJobDescriptor.m_jobDependencyList,
includeMaterialPropertyNames);
}
}
@@ -44,7 +44,7 @@ namespace AZ
if (auto* serialize = azrtti_cast<SerializeContext*>(context))
{
serialize->Class<MaterialAssetDependenciesComponent, Component>()
->Version(4)
->Version(5) // Set materialtype dependency to OrderOnce
->Attribute(Edit::Attributes::SystemComponentTags, AZStd::vector<Crc32>({ AssetBuilderSDK::ComponentTags::AssetBuilder }));
}
}
@@ -78,10 +78,7 @@ namespace AZ
AZStd::string materialTypePath;
RPI::MaterialConverterBus::BroadcastResult(materialTypePath, &RPI::MaterialConverterBus::Events::GetMaterialTypePath);
bool includeMaterialPropertyNames = true;
RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames);
// TODO: Use includeMaterialPropertyNames to break materialtype dependency on fbx files. Materialasset's dependency on materialtypeasset will need to be decoupled first
if (conversionEnabled && !materialTypePath.empty() /*&& !includeMaterialPropertyNames*/)
if (conversionEnabled && !materialTypePath.empty())
{
AssetBuilderSDK::SourceFileDependency materialTypeSource;
materialTypeSource.m_sourceFileDependencyPath = materialTypePath;
@@ -90,7 +87,15 @@ namespace AZ
jobDependency.m_jobKey = "Atom Material Builder";
jobDependency.m_sourceFile = materialTypeSource;
jobDependency.m_platformIdentifier = platformIdentifier;
jobDependency.m_type = AssetBuilderSDK::JobDependencyType::Order;
// If includeMaterialPropertyNames is false, then a job dependency is needed so the material builder can validate
// MaterialAsset properties against the MaterialTypeAsset at asset build time. If includeMaterialPropertyNames is true, the
// material properties will be validated at runtime when the material is loaded, so the job dependency is needed only for
// first-time processing to set up the initial MaterialAsset. This speeds up AP processing time when a materialtype file is
// edited (e.g. 10s when editing StandardPBR.materialtype on AtomTest project from 45s).
bool includeMaterialPropertyNames = true;
RPI::MaterialConverterBus::BroadcastResult(includeMaterialPropertyNames, &RPI::MaterialConverterBus::Events::ShouldIncludeMaterialPropertyNames);
jobDependency.m_type = includeMaterialPropertyNames ? AssetBuilderSDK::JobDependencyType::OrderOnce : AssetBuilderSDK::JobDependencyType::Order;
jobDependencyList.push_back(jobDependency);
}
@@ -116,11 +116,18 @@ namespace AtomToolsFramework
// ModularViewportCameraControllerRequestBus overrides ...
void InterpolateToTransform(const AZ::Transform& worldFromLocal, float lookAtDistance) override;
AZStd::optional<AZ::Vector3> LookAtAfterInterpolation() const override;
AZ::Transform GetReferenceFrame() const override;
void SetReferenceFrame(const AZ::Transform& worldFromLocal) override;
void ClearReferenceFrame() override;
private:
// AzFramework::ViewportDebugDisplayEventBus overrides ...
void DisplayViewport(const AzFramework::ViewportInfo& viewportInfo, AzFramework::DebugDisplayRequests& debugDisplay) override;
//! Update the reference frame after a change has been made to the camera
//! view without updating the internal camera via user input.
void RefreshReferenceFrame();
//! The current mode the camera controller is in.
enum class CameraMode
{
@@ -139,6 +146,8 @@ namespace AtomToolsFramework
AzFramework::Camera m_camera; //!< The current camera state (pitch/yaw/position/look-distance).
AzFramework::Camera m_targetCamera; //!< The target (next) camera state that m_camera is catching up to.
AzFramework::Camera m_previousCamera; //!< The state of the camera from the previous frame.
AZStd::optional<AzFramework::Camera> m_storedCamera; //!< A potentially stored camera for when a custom reference frame is set.
AzFramework::CameraSystem m_cameraSystem; //!< The camera system responsible for managing all CameraInputs.
AzFramework::CameraProps m_cameraProps; //!< Camera properties to control rotate and translate smoothness.
CameraControllerPriorityFn m_priorityFn; //!< Controls at what priority the camera controller should respond to events.
@@ -147,6 +156,7 @@ namespace AtomToolsFramework
CameraMode m_cameraMode = CameraMode::Control; //!< The current mode the camera is operating in.
AZStd::optional<AZ::Vector3> m_lookAtAfterInterpolation; //!< The look at point after an interpolation has finished.
//!< Will be cleared when the view changes (camera looks away).
AZ::Transform m_referenceFrameOverride = AZ::Transform::CreateIdentity(); //!<
//! Flag to prevent circular updates of the camera transform (while the viewport transform is being updated internally).
bool m_updatingTransformInternally = false;
//! Listen for camera view changes outside of the camera controller.
@@ -154,4 +164,17 @@ namespace AtomToolsFramework
//! The current instance of the modular camera viewport context.
AZStd::unique_ptr<ModularCameraViewportContext> m_modularCameraViewportContext;
};
//! Placeholder implementation for ModularCameraViewportContext (useful for verifying the interface).
class PlaceholderModularCameraViewportContextImpl : public AtomToolsFramework::ModularCameraViewportContext
{
public:
AZ::Transform GetCameraTransform() const override;
void SetCameraTransform(const AZ::Transform& transform) override;
void ConnectViewMatrixChangedHandler(AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler) override;
private:
AZ::Transform m_cameraTransform = AZ::Transform::CreateIdentity();
AZ::RPI::ViewportContext::MatrixChangedEvent m_viewMatrixChangedEvent;
};
} // namespace AtomToolsFramework
@@ -35,6 +35,16 @@ namespace AtomToolsFramework
//! Look at point after an interpolation has finished and no translation has occurred.
virtual AZStd::optional<AZ::Vector3> LookAtAfterInterpolation() const = 0;
//! Return the current reference frame.
//! @note If a reference frame has not been set or a frame has been cleared, this is just the identity.
virtual AZ::Transform GetReferenceFrame() const = 0;
//! Set a new reference frame other than the identity for the camera controller.
virtual void SetReferenceFrame(const AZ::Transform& worldFromLocal) = 0;
//! Clear the current reference frame to restore the identity.
virtual void ClearReferenceFrame() = 0;
protected:
~ModularViewportCameraControllerRequests() = default;
};
@@ -87,6 +87,7 @@ namespace AtomToolsFramework
AtomToolsApplication ::~AtomToolsApplication()
{
m_styleManager.reset();
AtomToolsMainWindowNotificationBus::Handler::BusDisconnect();
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
@@ -174,12 +175,14 @@ namespace AtomToolsFramework
AZ::Data::AssetCatalogRequestBus::Broadcast(&AZ::Data::AssetCatalogRequestBus::Events::LoadCatalog, "@assets@/assetcatalog.xml");
AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets();
if (!AZ::RPI::RPISystemInterface::Get()->IsInitialized())
{
AZ::RPI::RPISystemInterface::Get()->InitializeSystemAssets();
}
LoadSettings();
AtomToolsMainWindowNotificationBus::Handler::BusConnect();
AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::CreateMainWindow);
auto editorPythonEventsInterface = AZ::Interface<AzToolsFramework::EditorPythonEventsInterface>::Get();
@@ -206,6 +209,7 @@ namespace AtomToolsFramework
{
// before modules are unloaded, destroy UI to free up any assets it cached
AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow);
m_styleManager.reset();
AzToolsFramework::EditorPythonConsoleNotificationBus::Handler::BusDisconnect();
AzToolsFramework::AssetDatabase::AssetDatabaseRequestsBus::Handler::BusDisconnect();
@@ -461,6 +465,7 @@ namespace AtomToolsFramework
void AtomToolsApplication::Stop()
{
AtomToolsMainWindowFactoryRequestBus::Broadcast(&AtomToolsMainWindowFactoryRequestBus::Handler::DestroyMainWindow);
m_styleManager.reset();
UnloadSettings();
Base::Stop();
@@ -468,7 +473,7 @@ namespace AtomToolsFramework
void AtomToolsApplication::QueryApplicationType(AZ::ApplicationTypeQuery& appType) const
{
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Game;
appType.m_maskValue = AZ::ApplicationTypeQuery::Masks::Tool;
}
void AtomToolsApplication::OnTraceMessage([[maybe_unused]] AZStd::string_view message)
@@ -30,6 +30,18 @@ namespace AtomToolsFramework
"");
AZ_CVAR(float, ed_cameraSystemOrbitPointSize, 0.1f, nullptr, AZ::ConsoleFunctorFlags::Null, "");
AZ::Transform TransformFromMatrix4x4(const AZ::Matrix4x4& matrix)
{
const auto rotation = AZ::Matrix3x3::CreateFromMatrix4x4(matrix);
const auto translation = matrix.GetTranslation();
return AZ::Transform::CreateFromMatrix3x3AndTranslation(rotation, translation);
}
AZ::Matrix4x4 Matrix4x4FromTransform(const AZ::Transform& transform)
{
return AZ::Matrix4x4::CreateFromQuaternionAndTranslation(transform.GetRotation(), transform.GetTranslation());
}
// debug
void DrawPreviewAxis(AzFramework::DebugDisplayRequests& display, const AZ::Transform& transform, const float axisLength)
{
@@ -167,11 +179,19 @@ namespace AtomToolsFramework
controller->SetupCameraControllerPriority(m_priorityFn);
controller->SetupCameraControllerViewportContext(m_modularCameraViewportContext);
auto handleCameraChange = [this](const AZ::Matrix4x4&)
auto handleCameraChange = [this]([[maybe_unused]] const AZ::Matrix4x4& cameraView)
{
// ignore these updates if the camera is being updated internally
if (!m_updatingTransformInternally)
{
if (m_storedCamera.has_value())
{
// if an external change occurs ensure we update the stored reference frame if one is set
RefreshReferenceFrame();
return;
}
m_previousCamera = m_targetCamera;
UpdateCameraFromTransform(m_targetCamera, m_modularCameraViewportContext->GetCameraTransform());
m_camera = m_targetCamera;
}
@@ -231,7 +251,7 @@ namespace AtomToolsFramework
}
}
m_modularCameraViewportContext->SetCameraTransform(m_camera.Transform());
m_modularCameraViewportContext->SetCameraTransform(m_referenceFrameOverride * m_camera.Transform());
}
else if (m_cameraMode == CameraMode::Animation)
{
@@ -240,6 +260,8 @@ namespace AtomToolsFramework
return t * t * t * (t * (t * 6.0f - 15.0f) + 10.0f);
};
m_cameraAnimation.m_time = AZ::GetClamp(m_cameraAnimation.m_time + event.m_deltaTime.count(), 0.0f, 1.0f);
const auto& [transformStart, transformEnd, animationTime] = m_cameraAnimation;
const float transitionTime = smootherStepFn(animationTime);
@@ -253,14 +275,13 @@ namespace AtomToolsFramework
m_camera.m_lookAt = current.GetTranslation();
m_targetCamera = m_camera;
m_modularCameraViewportContext->SetCameraTransform(current);
if (animationTime >= 1.0f)
{
m_cameraMode = CameraMode::Control;
RefreshReferenceFrame();
}
m_cameraAnimation.m_time = AZ::GetClamp(animationTime + event.m_deltaTime.count(), 0.0f, 1.0f);
m_modularCameraViewportContext->SetCameraTransform(current);
}
m_updatingTransformInternally = false;
@@ -280,7 +301,7 @@ namespace AtomToolsFramework
void ModularViewportCameraControllerInstance::InterpolateToTransform(const AZ::Transform& worldFromLocal, const float lookAtDistance)
{
m_cameraMode = CameraMode::Animation;
m_cameraAnimation = CameraAnimation{ m_camera.Transform(), worldFromLocal, 0.0f };
m_cameraAnimation = CameraAnimation{ m_referenceFrameOverride * m_camera.Transform(), worldFromLocal, 0.0f };
m_lookAtAfterInterpolation = worldFromLocal.GetTranslation() + worldFromLocal.GetBasisY() * lookAtDistance;
}
@@ -288,4 +309,59 @@ namespace AtomToolsFramework
{
return m_lookAtAfterInterpolation;
}
AZ::Transform ModularViewportCameraControllerInstance::GetReferenceFrame() const
{
return m_referenceFrameOverride;
}
void ModularViewportCameraControllerInstance::SetReferenceFrame(const AZ::Transform& worldFromLocal)
{
if (!m_storedCamera.has_value())
{
m_storedCamera = m_previousCamera;
}
m_referenceFrameOverride = worldFromLocal;
m_targetCamera.m_pitch = 0.0f;
m_targetCamera.m_yaw = 0.0f;
m_targetCamera.m_lookAt = AZ::Vector3::CreateZero();
m_targetCamera.m_lookDist = 0.0f;
m_camera = m_targetCamera;
}
void ModularViewportCameraControllerInstance::ClearReferenceFrame()
{
m_referenceFrameOverride = AZ::Transform::CreateIdentity();
if (m_storedCamera.has_value())
{
m_targetCamera = m_storedCamera.value();
m_camera = m_targetCamera;
}
m_storedCamera.reset();
}
void ModularViewportCameraControllerInstance::RefreshReferenceFrame()
{
m_referenceFrameOverride = m_modularCameraViewportContext->GetCameraTransform() * m_camera.Transform().GetInverse();
}
AZ::Transform PlaceholderModularCameraViewportContextImpl::GetCameraTransform() const
{
return m_cameraTransform;
}
void PlaceholderModularCameraViewportContextImpl::SetCameraTransform(const AZ::Transform& transform)
{
m_cameraTransform = transform;
m_viewMatrixChangedEvent.Signal(AzFramework::CameraViewFromCameraTransform(Matrix4x4FromTransform(transform)));
}
void PlaceholderModularCameraViewportContextImpl::ConnectViewMatrixChangedHandler(
AZ::RPI::ViewportContext::MatrixChangedEvent::Handler& handler)
{
handler.Connect(m_viewMatrixChangedEvent);
}
} // namespace AtomToolsFramework
@@ -93,12 +93,14 @@ ly_add_target(
AUTOMOC
FILES_CMAKE
materialeditor_files.cmake
Source/Platform/${PAL_PLATFORM_NAME}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
${pal_source_dir}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
Source
Source/Platform/${PAL_PLATFORM_NAME}
${pal_source_dir}
PUBLIC
Include
BUILD_DEPENDENCIES
@@ -108,8 +110,14 @@ ly_add_target(
Gem::MaterialEditor.Window
Gem::MaterialEditor.Viewport
Gem::MaterialEditor.Document
RUNTIME_DEPENDENCIES
Gem::AtomToolsFramework.Editor
Gem::EditorPythonBindings.Editor
Gem::ImageProcessingAtom.Editor
)
ly_set_gem_variant_to_load(TARGETS MaterialEditor VARIANTS Tools)
# Add a 'builders' alias to allow the MaterialEditor root gem path to be added to the generated
# cmake_dependencies.<project>.assetprocessor.setreg to allow the asset scan folder for it to be added
ly_create_alias(NAME MaterialEditor.Builders NAMESPACE Gem)
@@ -118,26 +126,6 @@ ly_create_alias(NAME MaterialEditor.Builders NAMESPACE Gem)
# Editor opens up the MaterialEditor
ly_add_dependencies(Editor Gem::MaterialEditor)
ly_add_target_files(
TARGETS
MaterialEditor
FILES
${CMAKE_CURRENT_LIST_DIR}/../MaterialEditor.xml
OUTPUT_SUBDIRECTORY
Gems/Atom/Tools/MaterialEditor
)
ly_add_target_dependencies(
TARGETS
MaterialEditor
DEPENDENCIES_FILES
tool_dependencies.cmake
Source/Platform/${PAL_PLATFORM_NAME}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
# The Material Editor needs the LyShine "Tools" gem variant for the custom LyShine pass
DEPENDENT_TARGETS
Gem::LyShine.Tools
)
# Inject the project path into the MaterialEditor VS debugger command arguments if the build system being invoked
# in a project centric view
if(NOT PROJECT_NAME STREQUAL "O3DE")
@@ -1,38 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/string.h>
#include <QWidget>
#include <AzFramework/Windowing/WindowBus.h>
namespace Platform
{
void LoadPluginDependencies()
{
AZ_Warning("Material Editor", false, "LoadPluginDependencies() function is not implemented");
}
void ProcessInput(void* message)
{
AZ_Warning("Material Editor", false, "ProcessInput() function is not implemented");
}
AzFramework::NativeWindowHandle GetWindowHandle(WId winId)
{
AZ_Warning("Material Editor", false, "GetWindowHandle() function is not implemented");
AZ_UNUSED(winId);
return nullptr;
}
AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window)
{
AZ_Warning("Material Editor", false, "GetClientAreaSize() function is not implemented");
AZ_UNUSED(window);
return AzFramework::WindowSize{1,1};
}
}
@@ -9,5 +9,4 @@
set(FILES
MaterialEditor_Traits_Platform.h
MaterialEditor_Traits_Linux.h
MaterialEditor_Linux.cpp
)
@@ -6,5 +6,5 @@
#
#
set(GEM_DEPENDENCIES
set(LY_RUNTIME_DEPENDENCIES
)
@@ -1,38 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/string.h>
#include <QWidget>
#include <AzFramework/Windowing/WindowBus.h>
namespace Platform
{
void LoadPluginDependencies()
{
AZ_Warning("Material Editor", false, "LoadPluginDependencies() function is not implemented");
}
void ProcessInput(void* message)
{
AZ_Warning("Material Editor", false, "ProcessInput() function is not implemented");
}
AzFramework::NativeWindowHandle GetWindowHandle(WId winId)
{
AZ_Warning("Material Editor", false, "GetWindowHandle() function is not implemented");
AZ_UNUSED(winId);
return nullptr;
}
AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window)
{
AZ_Warning("Material Editor", false, "GetClientAreaSize() function is not implemented");
AZ_UNUSED(window);
return AzFramework::WindowSize{1,1};
}
}
@@ -9,5 +9,4 @@
set(FILES
MaterialEditor_Traits_Platform.h
MaterialEditor_Traits_Mac.h
MaterialEditor_Mac.cpp
)
@@ -6,5 +6,5 @@
#
#
set(GEM_DEPENDENCIES
set(LY_RUNTIME_DEPENDENCIES
)
@@ -1,58 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/string.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <QDir>
#include <QWidget>
#include <MaterialEditor_Traits_Platform.h>
namespace Platform
{
void ProcessInput(void* message)
{
MSG* msg = (MSG*)message;
// Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system,
// but only while in game mode so we don't accumulate raw input events before we start actually
// ticking the input devices, otherwise the queued events will get sent when entering game mode.
if (msg->message == WM_INPUT)
{
UINT rawInputSize;
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
LPBYTE rawInputBytes = new BYTE[rawInputSize];
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
AzFramework::RawInputNotificationBusWindows::Broadcast(
&AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput);
}
}
AzFramework::NativeWindowHandle GetWindowHandle(WId winId)
{
return reinterpret_cast<HWND>(winId);
}
AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window)
{
RECT r;
if (GetWindowRect(reinterpret_cast<HWND>(window), &r))
{
return AzFramework::WindowSize{aznumeric_cast<uint32_t>(r.right - r.left), aznumeric_cast<uint32_t>(r.bottom - r.top)};
}
else
{
AZ_Assert(false, "Failed to get dimensions for window");
return AzFramework::WindowSize{};
}
}
}
@@ -9,6 +9,5 @@
set(FILES
MaterialEditor_Traits_Platform.h
MaterialEditor_Traits_Windows.h
MaterialEditor_Windows.cpp
MaterialEditor.rc
)
@@ -6,6 +6,6 @@
#
#
set(GEM_DEPENDENCIES
set(LY_RUNTIME_DEPENDENCIES
Gem::QtForPython.Editor
)
@@ -231,12 +231,10 @@ namespace MaterialEditor
MaterialViewportNotificationBus::Handler::BusConnect();
AZ::TickBus::Handler::BusConnect();
AZ::TransformNotificationBus::MultiHandler::BusConnect(m_cameraEntity->GetId());
AzFramework::WindowSystemRequestBus::Handler::BusConnect();
}
MaterialViewportRenderer::~MaterialViewportRenderer()
{
AzFramework::WindowSystemRequestBus::Handler::BusDisconnect();
AZ::TransformNotificationBus::MultiHandler::BusDisconnect();
AZ::TickBus::Handler::BusDisconnect();
AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler::BusDisconnect();
@@ -287,11 +285,6 @@ namespace MaterialEditor
return m_viewportController;
}
AzFramework::NativeWindowHandle MaterialViewportRenderer::GetDefaultWindowHandle()
{
return (m_windowContext) ? m_windowContext->GetWindowHandle() : nullptr;
}
void MaterialViewportRenderer::OnDocumentOpened(const AZ::Uuid& documentId)
{
AZ::Data::Instance<AZ::RPI::Material> materialInstance;
@@ -16,7 +16,6 @@
#include <AtomToolsFramework/Document/AtomToolsDocumentNotificationBus.h>
#include <AzCore/Component/TickBus.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Windowing/WindowBus.h>
#include <Viewport/InputController/MaterialEditorViewportInputController.h>
namespace AZ
@@ -46,7 +45,6 @@ namespace MaterialEditor
, public AtomToolsFramework::AtomToolsDocumentNotificationBus::Handler
, public MaterialViewportNotificationBus::Handler
, public AZ::TransformNotificationBus::MultiHandler
, public AzFramework::WindowSystemRequestBus::Handler
{
public:
AZ_CLASS_ALLOCATOR(MaterialViewportRenderer, AZ::SystemAllocator, 0);
@@ -81,9 +79,6 @@ namespace MaterialEditor
// AZ::TransformNotificationBus::MultiHandler overrides...
void OnTransformChanged(const AZ::Transform&, const AZ::Transform&) override;
// AzFramework::WindowSystemRequestBus::Handler overrides ...
AzFramework::NativeWindowHandle GetDefaultWindowHandle() override;
using DirectionalLightHandle = AZ::Render::DirectionalLightFeatureProcessorInterface::LightHandle;
AZ::Data::Instance<AZ::RPI::SwapChainPass> m_swapChainPass;
@@ -13,24 +13,16 @@
#include <Atom/RPI.Public/ViewportContextBus.h>
#include <Atom/RPI.Public/WindowContext.h>
#include <Source/Viewport/MaterialViewportRenderer.h>
#include <Source/Viewport/MaterialViewportWidget.h>
#include <Viewport/MaterialViewportRenderer.h>
#include <Viewport/MaterialViewportWidget.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QAbstractEventDispatcher>
#include <QWindow>
#include "Source/Viewport/ui_MaterialViewportWidget.h"
#include "Viewport/ui_MaterialViewportWidget.h"
AZ_POP_DISABLE_WARNING
#include <AzCore/PlatformIncl.h>
#include <AzFramework/Viewport/ViewportControllerList.h>
namespace Platform
{
void ProcessInput(void* message);
}
namespace MaterialEditor
{
@@ -40,11 +32,6 @@ namespace MaterialEditor
{
m_ui->setupUi(this);
if (auto dispatcher = QAbstractEventDispatcher::instance())
{
dispatcher->installNativeEventFilter(this);
}
// The viewport context created by AtomToolsFramework::RenderViewportWidget has no name.
// Systems like frame capturing and post FX expect there to be a context with DefaultViewportContextName
auto viewportContextManager = AZ::Interface<AZ::RPI::ViewportContextRequestsInterface>::Get();
@@ -54,13 +41,4 @@ namespace MaterialEditor
m_renderer = AZStd::make_unique<MaterialViewportRenderer>(GetViewportContext()->GetWindowContext());
GetControllerList()->Add(m_renderer->GetController());
}
// This is a temporary fix to get input working in Qt window, otherwise it wont receive input events
// This will later be handled on the QApplication subclass level
bool MaterialViewportWidget::nativeEventFilter(const QByteArray& /*eventType*/, void* message, long* /*result*/)
{
Platform::ProcessInput(message);
return false;
}
} // namespace MaterialEditor
@@ -8,12 +8,10 @@
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Windowing/WindowBus.h>
AZ_PUSH_DISABLE_WARNING(4251 4800, "-Wunknown-warning-option") // disable warnings spawned by QT
#include <QWidget>
#include <QAbstractNativeEventFilter>
AZ_POP_DISABLE_WARNING
#endif
@@ -38,14 +36,11 @@ namespace MaterialEditor
class MaterialViewportWidget
: public AtomToolsFramework::RenderViewportWidget
, public QAbstractNativeEventFilter
{
public:
MaterialViewportWidget(QWidget* parent = nullptr);
QScopedPointer<Ui::MaterialViewportWidget> m_ui;
AZStd::unique_ptr<MaterialViewportRenderer> m_renderer;
bool nativeEventFilter(const QByteArray& eventType, void* message, long* result) override;
};
} // namespace MaterialEditor
@@ -10,5 +10,4 @@ set(FILES
Source/main.cpp
Source/MaterialEditorApplication.cpp
Source/MaterialEditorApplication.h
tool_dependencies.cmake
)
@@ -1,22 +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
#
#
set(GEM_DEPENDENCIES
Gem::Atom_RHI_Null.Private
Gem::Atom_RHI_DX12.Private
Gem::Atom_RHI_Vulkan.Private
Gem::Atom_RHI.Private
Gem::Atom_Component_DebugCamera
Gem::Atom_RPI.Editor
Gem::Atom_RPI.Builders
Gem::Atom_Feature_Common.Editor
Gem::AtomToolsFramework.Editor
Gem::AtomLyIntegration_CommonFeatures.Editor
Gem::EditorPythonBindings.Editor
Gem::ImageProcessingAtom.Editor
)
@@ -1,69 +0,0 @@
<ObjectStream version="3">
<Class name="ComponentApplication::Descriptor" version="2" type="{70277A3E-2AF5-4309-9BBF-6161AFBDE792}">
<Class name="bool" field="useExistingAllocator" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="grabAllMemory" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="allocationRecords" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="allocationRecordsSaveNames" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="allocationRecordsAttemptDecodeImmediately" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="int" field="recordingMode" value="2" type="{72039442-EB38-4D42-A1AD-CB68F7E0EEF6}"/>
<Class name="AZ::u64" field="stackRecordLevels" value="5" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
<Class name="bool" field="autoIntegrityCheck" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="markUnallocatedMemory" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="doNotUsePools" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="enableScriptReflection" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="unsigned int" field="pageSize" value="65536" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="unsigned int" field="poolPageSize" value="4096" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="unsigned int" field="blockAlignment" value="65536" type="{43DA906B-7DEF-4CA8-9790-854106D3F983}"/>
<Class name="AZ::u64" field="blockSize" value="0" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
<Class name="AZ::u64" field="reservedOS" value="0" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
<Class name="AZ::u64" field="reservedDebug" value="0" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
<Class name="bool" field="enableDrilling" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="AZStd::vector" field="modules" type="{8E779F80-AEAA-565B-ABB1-DE10B18CF995}">
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RHI_DX12.Private.e011969cf32442fdaac2443a960ab5ff.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RHI_Vulkan.Private.150d40d376124d98a388dfe890551c03.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RHI.Private.fb7f322c8bdb42228d9e155c954f98bd.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RHI_DX12.Private.e011969cf32442fdaac2443a960ab5ff.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RHI.Private.fb7f322c8bdb42228d9e155c954f98bd.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RPI.Private.a218db9eb2114477b46600fea4441a6c.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_Component_DebugCamera.013d1b42ad314c929b292c143bcbf045.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_RPI.Builders.a218db9eb2114477b46600fea4441a6c.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.Atom_Feature_Common.Editor.b58e5eed0901428ca78544b04dbd61bd.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.AtomLyIntegration_CommonFeatures.Editor.4e981f3b17394f5d84d674fff0f54f4f.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.EditorPythonBindings.Editor.b658359393884c4381c2fe2952b1472a.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
<Class name="DynamicModuleDescriptor" field="element" type="{D2932FA3-9942-4FD2-A703-2E750F57C003}">
<Class name="AZStd::string" field="dynamicLibraryPath" value="Gem.ImageProcessingAtom.Editor.9d10b00be96045caa64c705e5772cb64.v0.1.0" type="{189CC2ED-FDDE-5680-91D4-9F630A79187F}"/>
</Class>
</Class>
</Class>
<Class name="AZ::Entity" version="2" type="{75651658-8663-478D-9090-2432DFCAFA44}">
<Class name="EntityId" field="Id" version="1" type="{6383F1D3-BB27-4E6B-A49A-6409B2059EAA}">
<Class name="AZ::u64" field="id" value="0" type="{D6597933-47CD-4FC8-B911-63F3E2B0993A}"/>
</Class>
<Class name="AZStd::string" field="Name" value="SystemEntity" type="{03AAAB3F-5C47-5A66-9EBC-D5FA4DB353C9}"/>
<Class name="AZStd::vector" field="Components" type="{0D23B755-6E8F-5C6C-B7C9-A352A55DC1DF}"/>
<Class name="bool" field="IsDependencyReady" value="false" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
<Class name="bool" field="IsRuntimeActive" value="true" type="{A0CA880C-AFE4-43CB-926C-59AC48496112}"/>
</Class>
</ObjectStream>
@@ -43,6 +43,7 @@ ly_add_target(
NAMESPACE Gem
AUTOMOC
AUTOUIC
AUTORCC
FILES_CMAKE
shadermanagementconsolewindow_files.cmake
INCLUDE_DIRECTORIES
@@ -64,6 +65,8 @@ ly_add_target(
FILES_CMAKE
shadermanagementconsole_files.cmake
${pal_source_dir}/platform_${PAL_PLATFORM_NAME_LOWERCASE}_files.cmake
PLATFORM_INCLUDE_FILES
${pal_source_dir}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
INCLUDE_DIRECTORIES
PRIVATE
.
@@ -78,27 +81,16 @@ ly_add_target(
Gem::ShaderManagementConsole.Window
Gem::ShaderManagementConsole.Document
RUNTIME_DEPENDENCIES
Gem::Atom_RHI_DX12.Private
Gem::Atom_RHI_Vulkan.Private
Gem::Atom_RHI.Private
Gem::Atom_RPI.Private
Gem::Atom_RPI.Builders
Gem::Atom_Feature_Common.Editor
Gem::AtomToolsFramework.Editor
Gem::EditorPythonBindings.Editor
)
ly_set_gem_variant_to_load(TARGETS ShaderManagementConsole VARIANTS Tools)
# Add build dependency to Editor for the ShaderManagementConsole application since
# Editor opens up the ShaderManagementConsole
ly_add_dependencies(Editor Gem::ShaderManagementConsole)
ly_add_target_dependencies(
TARGETS
ShaderManagementConsole
DEPENDENCIES_FILES
tool_dependencies.cmake
Source/Platform/${PAL_PLATFORM_NAME}/tool_dependencies_${PAL_PLATFORM_NAME_LOWERCASE}.cmake
)
# Inject the project path into the ShaderManagementConsole VS debugger command arguments if the build system being invoked
# in a project centric view
if(NOT PROJECT_NAME STREQUAL "O3DE")
@@ -108,9 +100,14 @@ endif()
# Adds the ShaderManagementConsole target as a C preprocessor define so that it can be used as a Settings Registry
# specialization in order to look up the generated .setreg which contains the dependencies
# specified for the target.
set_source_files_properties(
Source/ShaderManagementConsoleApplication.cpp
PROPERTIES
COMPILE_DEFINITIONS
LY_CMAKE_TARGET="ShaderManagementConsole"
)
if(TARGET ShaderManagementConsole)
set_source_files_properties(
Source/ShaderManagementConsoleApplication.cpp
PROPERTIES
COMPILE_DEFINITIONS
LY_CMAKE_TARGET="ShaderManagementConsole"
)
else()
message(FATAL_ERROR "Cannot set LY_CMAKE_TARGET define to ShaderManagementConsole as the target doesn't exist anymore."
" Perhaps it has been renamed")
endif()
@@ -6,4 +6,4 @@
#
#
set(PAL_TRAIT_ATOM_SHADER_MANAGEMENT_CONSOLE_APPLICATION_SUPPORTED FALSE)
set(PAL_TRAIT_ATOM_SHADER_MANAGEMENT_CONSOLE_APPLICATION_SUPPORTED TRUE)
@@ -0,0 +1,11 @@
/*
* 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
#define AZ_TRAIT_SHADER_MANAGEMENT_CONSOLE_EXT ""
@@ -0,0 +1,10 @@
/*
* 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 <ShaderManagementConsole_Traits_Linux.h>
@@ -7,4 +7,6 @@
#
set(FILES
ShaderManagementConsole_Traits_Platform.h
ShaderManagementConsole_Traits_Linux.h
)
@@ -6,5 +6,5 @@
#
#
set(GEM_DEPENDENCIES
set(LY_RUNTIME_DEPENDENCIES
)
@@ -1,33 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/string.h>
#include <QWidget>
#include <AzFramework/Windowing/WindowBus.h>
namespace Platform
{
void ProcessInput(void* message)
{
AZ_Warning("Shader Management Console", false, "ProcessInput() function is not implemented");
}
AzFramework::NativeWindowHandle GetWindowHandle(WId winId)
{
AZ_Warning("Shader Management Console", false, "GetWindowHandle() function is not implemented");
AZ_UNUSED(winId);
return nullptr;
}
AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window)
{
AZ_Warning("Shader Management Console", false, "GetClientAreaSize() function is not implemented");
AZ_UNUSED(window);
return AzFramework::WindowSize{1,1};
}
}
@@ -9,5 +9,4 @@
set(FILES
ShaderManagementConsole_Traits_Platform.h
ShaderManagementConsole_Traits_Mac.h
ShaderManagementConsole_Mac.cpp
)
@@ -6,5 +6,5 @@
#
#
set(GEM_DEPENDENCIES
set(LY_RUNTIME_DEPENDENCIES
)
@@ -1,56 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/std/string/string.h>
#include <QWidget>
#include <ShaderManagementConsole_Traits_Platform.h>
namespace Platform
{
void ProcessInput(void* message)
{
MSG* msg = (MSG*)message;
// Ensure that the Windows WM_INPUT messages get passed through to the AzFramework input system,
// but only while in game mode so we don't accumulate raw input events before we start actually
// ticking the input devices, otherwise the queued events will get sent when entering game mode.
if (msg->message == WM_INPUT)
{
UINT rawInputSize;
const UINT rawInputHeaderSize = sizeof(RAWINPUTHEADER);
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, NULL, &rawInputSize, rawInputHeaderSize);
LPBYTE rawInputBytes = new BYTE[rawInputSize];
GetRawInputData((HRAWINPUT)msg->lParam, RID_INPUT, rawInputBytes, &rawInputSize, rawInputHeaderSize);
RAWINPUT* rawInput = (RAWINPUT*)rawInputBytes;
AzFramework::RawInputNotificationBusWindows::Broadcast(
&AzFramework::RawInputNotificationBusWindows::Events::OnRawInputEvent, *rawInput);
}
}
AzFramework::NativeWindowHandle GetWindowHandle(WId winId)
{
return reinterpret_cast<HWND>(winId);
}
AzFramework::WindowSize GetClientAreaSize(AzFramework::NativeWindowHandle window)
{
RECT r;
if (GetWindowRect(reinterpret_cast<HWND>(window), &r))
{
return AzFramework::WindowSize{aznumeric_cast<uint32_t>(r.right - r.left), aznumeric_cast<uint32_t>(r.bottom - r.top)};
}
else
{
AZ_Assert(false, "Failed to get dimensions for window");
return AzFramework::WindowSize{};
}
}
}
@@ -9,6 +9,5 @@
set(FILES
ShaderManagementConsole_Traits_Platform.h
ShaderManagementConsole_Traits_Windows.h
ShaderManagementConsole_Windows.cpp
ShaderManagementConsole.rc
)
@@ -6,6 +6,6 @@
#
#
set(GEM_DEPENDENCIES
set(LY_RUNTIME_DEPENDENCIES
Gem::QtForPython.Editor
)
@@ -1,10 +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
#
#
set(GEM_DEPENDENCIES
)
@@ -1,22 +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
#
#
set(GEM_DEPENDENCIES
Gem::Atom_RHI_Null.Private
Gem::Atom_RHI_DX12.Private
Gem::Atom_RHI_Vulkan.Private
Gem::Atom_RHI.Private
Gem::Atom_Component_DebugCamera
Gem::Atom_RPI.Editor
Gem::Atom_RPI.Builders
Gem::Atom_Feature_Common.Editor
Gem::AtomToolsFramework.Editor
Gem::AtomLyIntegration_CommonFeatures.Editor
Gem::EditorPythonBindings.Editor
Gem::ImageProcessingAtom.Editor
)
@@ -82,6 +82,8 @@ namespace AZ::Render
void AtomViewportDisplayIconsSystemComponent::Activate()
{
m_drawContextRegistered = false;
AzToolsFramework::EditorViewportIconDisplay::Register(this);
Bootstrap::NotificationBus::Handler::BusConnect();
@@ -97,9 +99,10 @@ namespace AZ::Render
{
return;
}
if (perViewportDynamicDrawInterface)
if (perViewportDynamicDrawInterface && m_drawContextRegistered)
{
perViewportDynamicDrawInterface->UnregisterDynamicDrawContext(m_drawContextName);
m_drawContextRegistered = false;
}
AzToolsFramework::EditorViewportIconDisplay::Unregister(this);
@@ -367,6 +370,8 @@ namespace AZ::Render
drawContext->EndInit();
});
m_drawContextRegistered = true;
Data::AssetBus::Handler::BusDisconnect();
}
} // namespace AZ::Render
@@ -77,6 +77,8 @@ namespace AZ
};
AZStd::unordered_map<IconId, IconData> m_iconData;
IconId m_currentId = 0;
bool m_drawContextRegistered = false;
};
} // namespace Render
} // namespace AZ
@@ -212,6 +212,9 @@ namespace AZ
AZ::Vector3 position = AZ::Vector3::CreateZero();
AZ::TransformBus::EventResult(position, GetEntityId(), &AZ::TransformBus::Events::GetWorldTranslation);
AZ::Quaternion rotationQuaternion = AZ::Quaternion::CreateIdentity();
AZ::TransformBus::EventResult(rotationQuaternion, GetEntityId(), &AZ::TransformBus::Events::GetWorldRotationQuaternion);
AZ::Matrix3x3 rotationMatrix = AZ::Matrix3x3::CreateFromQuaternion(rotationQuaternion);
float scale = 1.0f;
AZ::TransformBus::EventResult(scale, GetEntityId(), &AZ::TransformBus::Events::GetLocalUniformScale);
@@ -224,9 +227,7 @@ namespace AZ
AZ::Vector3 innerExtents(configuration.m_innerWidth, configuration.m_innerLength, configuration.m_innerHeight);
innerExtents *= scale;
AZ::Vector3 innerMin(position.GetX() - innerExtents.GetX() / 2, position.GetY() - innerExtents.GetY() / 2, position.GetZ() - innerExtents.GetZ() / 2);
AZ::Vector3 innerMax(position.GetX() + innerExtents.GetX() / 2, position.GetY() + innerExtents.GetY() / 2, position.GetZ() + innerExtents.GetZ() / 2);
debugDisplay.DrawWireBox(innerMin, innerMax);
debugDisplay.DrawWireOBB(position, rotationMatrix.GetBasisX(), rotationMatrix.GetBasisY(), rotationMatrix.GetBasisZ(), innerExtents / 2.0f);
}
AZ::Aabb EditorReflectionProbeComponent::GetEditorSelectionBoundsViewport([[maybe_unused]] const AzFramework::ViewportInfo& viewportInfo)
+1
View File
@@ -57,6 +57,7 @@ if (PAL_TRAIT_BUILD_HOST_TOOLS)
PRIVATE
AZ::AzToolsFramework
Gem::Camera.Static
Gem::AtomToolsFramework.Static
RUNTIME_DEPENDENCIES
Legacy::EditorCommon
)
@@ -372,6 +372,8 @@ namespace EMotionFX
// updates the skinning matrices of all nodes
void ActorInstance::UpdateSkinningMatrices()
{
AZ_PROFILE_SCOPE(Animation, "ActorInstance::UpdateSkinningMatrices");
AZ::Matrix3x4* skinningMatrices = m_transformData->GetSkinningMatrices();
const Pose* pose = m_transformData->GetCurrentPose();
@@ -596,6 +598,8 @@ namespace EMotionFX
// update the bounding volume
void ActorInstance::UpdateBounds(size_t geomLODLevel, EBoundsType boundsType, uint32 itemFrequency)
{
AZ_PROFILE_SCOPE(Animation, "ActorInstance::UpdateBounds");
// depending on the bounding volume update type
switch (boundsType)
{
@@ -218,6 +218,8 @@ namespace EMotionFX
// output the results into the internal pose object
void AnimGraphInstance::Output(Pose* outputPose)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::Output");
// reset max used
const uint32 threadIndex = m_actorInstance->GetThreadIndex();
AnimGraphPosePool& posePool = GetEMotionFX().GetThreadData(threadIndex)->GetPosePool();
@@ -854,6 +856,8 @@ namespace EMotionFX
// synchronize all nodes, based on sync tracks etc
void AnimGraphInstance::Update(float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::Update");
// pass 0: (Optional, networking only) When this instance is shared between network, restore the instance using an animgraph snapshot.
if (m_snapshot)
{
@@ -940,6 +944,8 @@ namespace EMotionFX
// reset all node pose ref counts
void AnimGraphInstance::ResetPoseRefCountsForAllNodes()
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::ResetPoseRefCountsForAllNodes");
const size_t numNodes = m_animGraph->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
@@ -951,6 +957,8 @@ namespace EMotionFX
// reset all node pose ref counts
void AnimGraphInstance::ResetRefDataRefCountsForAllNodes()
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::ResetRefDataRefCountsForAllNodes");
const size_t numNodes = m_animGraph->GetNumNodes();
for (size_t i = 0; i < numNodes; ++i)
{
@@ -962,6 +970,8 @@ namespace EMotionFX
// reset all node flags
void AnimGraphInstance::ResetFlagsForAllObjects()
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphInstance::ResetFlagsForAllObjects");
MCore::MemSet(m_objectFlags.data(), 0, sizeof(uint32) * m_objectFlags.size());
for (AnimGraphInstance* childInstance : m_childAnimGraphInstances)
@@ -394,6 +394,8 @@ namespace EMotionFX
// the main process method of the final node
void AnimGraphMotionNode::Output(AnimGraphInstance* animGraphInstance)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphMotionNode::Output");
// if this motion is disabled, output the bind pose
if (m_disabled)
{
@@ -537,6 +539,8 @@ namespace EMotionFX
void AnimGraphMotionNode::UniqueData::Update()
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphMotionNode::Update");
AnimGraphMotionNode* motionNode = azdynamic_cast<AnimGraphMotionNode*>(m_object);
AZ_Assert(motionNode, "Unique data linked to incorrect node type.");
@@ -92,6 +92,8 @@ namespace EMotionFX
void AnimGraphStateMachine::Output(AnimGraphInstance* animGraphInstance)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::Update");
ActorInstance* actorInstance = animGraphInstance->GetActorInstance();
AnimGraphPose* outputPose = nullptr;
@@ -476,6 +478,8 @@ namespace EMotionFX
void AnimGraphStateMachine::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::Update");
UniqueData* uniqueData = static_cast<UniqueData*>(FindOrCreateUniqueNodeData(animGraphInstance));
// Defer switch to entry state.
@@ -622,6 +626,8 @@ namespace EMotionFX
void AnimGraphStateMachine::PostUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::PostUpdate");
RequestRefDatas(animGraphInstance);
UniqueData* uniqueData = static_cast<UniqueData*>(FindOrCreateUniqueNodeData(animGraphInstance));
AnimGraphRefCountedData* data = uniqueData->GetRefCountedData();
@@ -1344,6 +1350,8 @@ namespace EMotionFX
void AnimGraphStateMachine::TopDownUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::TopDownUpdate");
UniqueData* uniqueData = static_cast<UniqueData*>(FindOrCreateUniqueNodeData(animGraphInstance));
if (!IsTransitioning(uniqueData))
@@ -166,6 +166,8 @@ namespace EMotionFX
void BlendSpace1DNode::Output(AnimGraphInstance* animGraphInstance)
{
AZ_PROFILE_SCOPE(Animation, "BlendSpace1DNode::Output");
if (!AnimGraphInstanceExists(animGraphInstance))
{
return;
@@ -276,6 +278,8 @@ namespace EMotionFX
void BlendSpace1DNode::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "BlendSpace1DNode::Update");
if (!m_disabled)
{
EMotionFX::BlendTreeConnection* paramConnection = GetInputPort(INPUTPORT_VALUE).m_connection;
@@ -282,6 +282,8 @@ namespace EMotionFX
void BlendSpace2DNode::Output(AnimGraphInstance* animGraphInstance)
{
AZ_PROFILE_SCOPE(Animation, "BlendSpace2DNode::Output");
if (!AnimGraphInstanceExists(animGraphInstance))
{
return;
@@ -402,6 +404,8 @@ namespace EMotionFX
void BlendSpace2DNode::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "BlendSpace2DNode::Update");
if (!AnimGraphInstanceExists(animGraphInstance))
{
return;
@@ -116,10 +116,11 @@ namespace EMotionFX
return nullptr;
}
// process the blend tree and calculate its output
void BlendTree::Output(AnimGraphInstance* animGraphInstance)
{
AZ_PROFILE_SCOPE(Animation, "BlendTree::Output");
AZ_Assert(m_finalNode, "There should always be a final node. Something seems to be wrong with the blend tree creation.");
// get the output pose
@@ -164,6 +165,8 @@ namespace EMotionFX
// post sync update
void BlendTree::PostUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "AnimGraphStateMachine::PostUpdate");
// if this node is disabled, exit
if (m_disabled)
{
@@ -212,6 +215,8 @@ namespace EMotionFX
// update all nodes
void BlendTree::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "BlendTree::Update");
// if this node is disabled, output the bind pose
if (m_disabled)
{
@@ -256,6 +261,8 @@ namespace EMotionFX
// top down update
void BlendTree::TopDownUpdate(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "BlendTree::TopDownUpdate");
// get the final node
AnimGraphNode* finalNode = GetRealFinalNode();
@@ -45,6 +45,8 @@ namespace EMotionFX
void BlendTreeBlend2Node::Update(AnimGraphInstance* animGraphInstance, float timePassedInSeconds)
{
AZ_PROFILE_SCOPE(Animation, "BlendTreeBlend2Node::Update");
if (m_disabled)
{
AnimGraphNodeData* uniqueData = FindOrCreateUniqueNodeData(animGraphInstance);
@@ -88,9 +90,10 @@ namespace EMotionFX
}
}
void BlendTreeBlend2Node::Output(AnimGraphInstance* animGraphInstance)
{
AZ_PROFILE_SCOPE(Animation, "BlendTreeBlend2Node::Output");
if (m_disabled)
{
RequestPoses(animGraphInstance);
@@ -147,6 +147,8 @@ namespace EMotionFX
// update motion queue and instances
void MotionSystem::Update(float timePassed, bool updateNodes)
{
AZ_PROFILE_SCOPE(Animation, "MotionSystem::Update");
MCORE_UNUSED(updateNodes);
// update the motion queue
@@ -69,7 +69,7 @@ namespace EMotionFX
void ClothJointWidget::InternalReinit()
{
if (m_selectedModelIndices.size() == 1)
if (GetSelectedModelIndices().size() == 1)
{
Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig();
if (nodeConfig)
@@ -94,17 +94,17 @@ namespace EMotionFX
void ClothJointWidget::OnAddCollider(const AZ::TypeId& colliderType)
{
ColliderHelpers::AddCollider(m_selectedModelIndices , PhysicsSetup::Cloth, colliderType);
ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::Cloth, colliderType);
}
void ClothJointWidget::OnCopyCollider(size_t colliderIndex)
{
ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Cloth);
ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Cloth);
}
void ClothJointWidget::OnPasteCollider(size_t colliderIndex, bool replace)
{
ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Cloth, replace);
ColliderHelpers::PasteColliderFromClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Cloth, replace);
}
void ClothJointWidget::OnRemoveCollider(size_t colliderIndex)
@@ -114,7 +114,7 @@ namespace EMotionFX
Physics::CharacterColliderNodeConfiguration* ClothJointWidget::GetNodeConfig() const
{
AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted");
AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single seleted");
Actor* actor = GetActor();
Node* joint = GetNode();
if (!actor || !joint)
@@ -65,7 +65,7 @@ namespace EMotionFX
void HitDetectionJointWidget::InternalReinit()
{
if (m_selectedModelIndices.size() == 1)
if (GetSelectedModelIndices().size() == 1)
{
Physics::CharacterColliderNodeConfiguration* hitDetectionNodeConfig = GetNodeConfig();
if (hitDetectionNodeConfig)
@@ -90,17 +90,17 @@ namespace EMotionFX
void HitDetectionJointWidget::OnAddCollider(const AZ::TypeId& colliderType)
{
ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::HitDetection, colliderType);
ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::HitDetection, colliderType);
}
void HitDetectionJointWidget::OnCopyCollider(size_t colliderIndex)
{
ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::HitDetection);
ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::HitDetection);
}
void HitDetectionJointWidget::OnPasteCollider(size_t colliderIndex, bool replace)
{
ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::HitDetection, replace);
ColliderHelpers::PasteColliderFromClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::HitDetection, replace);
}
void HitDetectionJointWidget::OnRemoveCollider(size_t colliderIndex)
@@ -110,7 +110,7 @@ namespace EMotionFX
Physics::CharacterColliderNodeConfiguration* HitDetectionJointWidget::GetNodeConfig()
{
AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted");
AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single seleted");
Actor* actor = GetActor();
Node* node = GetNode();
if (!actor || !node)
@@ -116,7 +116,8 @@ namespace EMotionFX
void RagdollNodeWidget::InternalReinit()
{
if (m_selectedModelIndices.size() == 1)
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
if (selectedModelIndices.size() == 1)
{
m_ragdollNodeEditor->ClearInstances(false);
@@ -142,7 +143,7 @@ namespace EMotionFX
m_collidersWidget->Reset();
}
m_jointLimitWidget->Update(m_selectedModelIndices[0]);
m_jointLimitWidget->Update(selectedModelIndices[0]);
m_ragdollNodeCard->setExpanded(true);
m_ragdollNodeCard->show();
m_jointLimitWidget->show();
@@ -169,31 +170,32 @@ namespace EMotionFX
void RagdollNodeWidget::OnAddRemoveRagdollNode()
{
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
if (GetRagdollNodeConfig())
{
// The node is present in the ragdoll, remove it.
RagdollNodeInspectorPlugin::RemoveFromRagdoll(m_selectedModelIndices);
RagdollNodeInspectorPlugin::RemoveFromRagdoll(selectedModelIndices);
}
else
{
// The node is not part of the ragdoll, add it.
RagdollNodeInspectorPlugin::AddToRagdoll(m_selectedModelIndices);
RagdollNodeInspectorPlugin::AddToRagdoll(selectedModelIndices);
}
}
void RagdollNodeWidget::OnAddCollider(const AZ::TypeId& colliderType)
{
ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::Ragdoll, colliderType);
ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::Ragdoll, colliderType);
}
void RagdollNodeWidget::OnCopyCollider(size_t colliderIndex)
{
ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Ragdoll);
ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Ragdoll);
}
void RagdollNodeWidget::OnPasteCollider(size_t colliderIndex, bool replace)
{
ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::Ragdoll, replace);
ColliderHelpers::PasteColliderFromClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::Ragdoll, replace);
}
void RagdollNodeWidget::OnRemoveCollider(size_t colliderIndex)
@@ -131,7 +131,8 @@ namespace EMotionFX
void SimulatedObjectColliderWidget::InternalReinit()
{
if (m_selectedModelIndices.size() == 1)
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
if (selectedModelIndices.size() == 1)
{
Physics::CharacterColliderNodeConfiguration* nodeConfig = GetNodeConfig();
if (nodeConfig)
@@ -172,12 +173,13 @@ namespace EMotionFX
}
AZStd::string labelText;
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
const AZStd::vector<SimulatedObject*>& simObjs = actor->GetSimulatedObjectSetup()->GetSimulatedObjects();
for (const SimulatedObject* obj : simObjs)
{
for (int i = 0; i < m_selectedModelIndices.size(); ++i)
for (int i = 0; i < selectedModelIndices.size(); ++i)
{
Node* node = m_selectedModelIndices[i].data(SkeletonModel::ROLE_POINTER).value<Node*>();
Node* node = selectedModelIndices[i].data(SkeletonModel::ROLE_POINTER).value<Node*>();
if (obj->FindSimulatedJointBySkeletonJointIndex(node->GetNodeIndex()))
{
if (!labelText.empty())
@@ -208,8 +210,9 @@ namespace EMotionFX
return;
}
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
// Only show the notification when it is single selection.
if (m_selectedModelIndices.size() != 1)
if (selectedModelIndices.size() != 1)
{
return;
}
@@ -250,17 +253,18 @@ namespace EMotionFX
void SimulatedObjectColliderWidget::OnAddCollider(const AZ::TypeId& colliderType)
{
ColliderHelpers::AddCollider(m_selectedModelIndices, PhysicsSetup::SimulatedObjectCollider, colliderType);
ColliderHelpers::AddCollider(GetSelectedModelIndices(), PhysicsSetup::SimulatedObjectCollider, colliderType);
}
void SimulatedObjectColliderWidget::OnCopyCollider(size_t colliderIndex)
{
ColliderHelpers::CopyColliderToClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider);
ColliderHelpers::CopyColliderToClipboard(GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider);
}
void SimulatedObjectColliderWidget::OnPasteCollider(size_t colliderIndex, bool replace)
{
ColliderHelpers::PasteColliderFromClipboard(m_selectedModelIndices.first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider, replace);
ColliderHelpers::PasteColliderFromClipboard(
GetSelectedModelIndices().first(), colliderIndex, PhysicsSetup::SimulatedObjectCollider, replace);
}
void SimulatedObjectColliderWidget::OnRemoveCollider(size_t colliderIndex)
@@ -270,7 +274,7 @@ namespace EMotionFX
Physics::CharacterColliderNodeConfiguration* SimulatedObjectColliderWidget::GetNodeConfig() const
{
AZ_Assert(m_selectedModelIndices.size() == 1, "Get Node config function only return the config when it is single seleted");
AZ_Assert(GetSelectedModelIndices().size() == 1, "Get Node config function only return the config when it is single seleted");
Actor* actor = GetActor();
Node* joint = GetNode();
if (!actor || !joint)
@@ -211,15 +211,15 @@ namespace EMotionFX
AZ::Outcome<const QModelIndexList&> SkeletonOutlinerPlugin::GetSelectedRowIndices()
{
return AZ::Success(m_selectedRows);
return AZ::Success(m_treeView->selectionModel()->selectedRows());
}
void SkeletonOutlinerPlugin::OnSelectionChanged([[maybe_unused]] const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
{
m_selectedRows = m_treeView->selectionModel()->selectedRows();
if (m_selectedRows.size() == 1)
QModelIndexList selectedRows = m_treeView->selectionModel()->selectedRows();
if (selectedRows.size() == 1)
{
const QModelIndex& modelIndex = m_selectedRows[0];
const QModelIndex& modelIndex = selectedRows[0];
Node* selectedNode = modelIndex.data(SkeletonModel::ROLE_POINTER).value<Node*>();
Actor* selectedActor = modelIndex.data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
SkeletonOutlinerNotificationBus::Broadcast(&SkeletonOutlinerNotifications::SingleNodeSelectionChanged, selectedActor, selectedNode);
@@ -72,14 +72,7 @@ namespace EMotionFX
setLayout(mainLayout);
AZ::Outcome<const QModelIndexList&> selectedRowIndicesOutcome;
QModelIndexList selectedModelIndices;
SkeletonOutlinerRequestBus::BroadcastResult(selectedRowIndicesOutcome, &SkeletonOutlinerRequests::GetSelectedRowIndices);
if (selectedRowIndicesOutcome.IsSuccess())
{
selectedModelIndices = selectedRowIndicesOutcome.GetValue();
}
Reinit(selectedModelIndices);
Reinit();
// Connect to the model.
SkeletonModel* skeletonModel = nullptr;
@@ -92,9 +85,9 @@ namespace EMotionFX
}
}
void SkeletonModelJointWidget::Reinit(const QModelIndexList& selectedModelIndices)
void SkeletonModelJointWidget::Reinit()
{
m_selectedModelIndices = selectedModelIndices;
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
if (!EMStudio::GetManager()->GetIgnoreVisibility() && !isVisible())
{
@@ -103,15 +96,15 @@ namespace EMotionFX
if (GetActor())
{
if (!m_selectedModelIndices.isEmpty())
if (!selectedModelIndices.isEmpty())
{
if (m_selectedModelIndices.size() == 1)
if (selectedModelIndices.size() == 1)
{
m_jointNameLabel->setText(GetNode()->GetName());
}
else
{
m_jointNameLabel->setText(QString("%1 joints selected").arg(m_selectedModelIndices.size()));
m_jointNameLabel->setText(QString("%1 joints selected").arg(selectedModelIndices.size()));
}
m_noSelectionWidget->hide();
@@ -136,7 +129,7 @@ namespace EMotionFX
void SkeletonModelJointWidget::showEvent(QShowEvent* event)
{
QWidget::showEvent(event);
Reinit(m_selectedModelIndices);
Reinit();
}
void SkeletonModelJointWidget::OnSelectionChanged([[maybe_unused]] const QItemSelection& selected, [[maybe_unused]] const QItemSelection& deselected)
@@ -146,36 +139,28 @@ namespace EMotionFX
if (skeletonModel)
{
const QModelIndexList selectedRows = skeletonModel->GetSelectionModel().selectedRows();
Reinit(selectedRows);
}
Reinit();
}
void SkeletonModelJointWidget::OnDataChanged([[maybe_unused]] const QModelIndex& topLeft, [[maybe_unused]] const QModelIndex& bottomRight, [[maybe_unused]] const QVector<int>& roles)
{
Reinit(m_selectedModelIndices);
Reinit();
}
void SkeletonModelJointWidget::OnModelReset()
{
Reinit(QModelIndexList());
Reinit();
}
Actor* SkeletonModelJointWidget::GetActor() const
{
Actor* actor = nullptr;
if (!m_selectedModelIndices.empty())
SkeletonModel* skeletonModel = nullptr;
SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
if (skeletonModel)
{
actor = m_selectedModelIndices[0].data(SkeletonModel::ROLE_ACTOR_POINTER).value<Actor*>();
}
if (!actor)
{
SkeletonModel* skeletonModel = nullptr;
SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
if (skeletonModel)
{
actor = skeletonModel->GetActor();
}
actor = skeletonModel->GetActor();
}
return actor;
}
@@ -183,10 +168,24 @@ namespace EMotionFX
Node* SkeletonModelJointWidget::GetNode() const
{
Node* node = nullptr;
if (!m_selectedModelIndices.empty())
const QModelIndexList& selectedModelIndices = GetSelectedModelIndices();
if (!selectedModelIndices.empty())
{
node = m_selectedModelIndices[0].data(SkeletonModel::ROLE_POINTER).value<Node*>();
node = selectedModelIndices[0].data(SkeletonModel::ROLE_POINTER).value<Node*>();
}
return node;
}
QModelIndexList SkeletonModelJointWidget::GetSelectedModelIndices() const
{
QModelIndexList selectedModelIndices;
SkeletonModel* skeletonModel = nullptr;
SkeletonOutlinerRequestBus::BroadcastResult(skeletonModel, &SkeletonOutlinerRequests::GetModel);
if (skeletonModel)
{
selectedModelIndices = skeletonModel->GetSelectionModel().selectedRows();
}
return selectedModelIndices;
}
} // namespace EMotionFX
@@ -33,13 +33,14 @@ namespace EMotionFX
virtual void CreateGUI();
void Reinit(const QModelIndexList& selectedModelIndices);
void Reinit();
void showEvent(QShowEvent* event) override;
protected:
Actor* GetActor() const;
Node* GetNode() const;
QModelIndexList GetSelectedModelIndices() const;
virtual QWidget* CreateContentWidget(QWidget* parent) = 0;
virtual QWidget* CreateNoSelectionWidget(QWidget* parent) = 0;
virtual void InternalReinit() = 0;
@@ -50,7 +51,6 @@ namespace EMotionFX
void OnModelReset();
protected:
QModelIndexList m_selectedModelIndices;
QLabel* m_jointNameLabel;
static int s_jointLabelSpacing;
static int s_jointNameSpacing;
@@ -49,7 +49,6 @@ namespace HttpRequestor
{
m_thread.join();
}
}
void Manager::AddRequest(Parameters && httpRequestParameters)
@@ -199,6 +199,8 @@ namespace Multiplayer
friend class NetworkEntityManager;
friend class EntityReplicationManager;
friend class HierarchyTests;
};
bool NetworkRoleHasController(NetEntityRole networkRole);
@@ -0,0 +1,48 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/ComponentBus.h>
namespace Multiplayer
{
using NetworkHierarchyChangedEvent = AZ::Event<const AZ::EntityId&>;
using NetworkHierarchyLeaveEvent = AZ::Event<>;
class NetworkHierarchyRequests
: public AZ::ComponentBus
{
public:
//! @returns true if the entity a hierarchical component attached should be considered for inclusion in a hierarchy
//! this should return false when an entity is deactivating
virtual bool IsHierarchyEnabled() const = 0;
//! @returns hierarchical entities, the first element is the top level root
virtual AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const = 0;
//! @returns the top level root of a hierarchy, or nullptr if this entity is not in a hierarchy
virtual AZ::Entity* GetHierarchicalRoot() const = 0;
//! @return true if this entity is a child entity within a hierarchy
virtual bool IsHierarchicalChild() const = 0;
//! @return true if this entity is the top level root of a hierarchy
virtual bool IsHierarchicalRoot() const = 0;
//! Binds the provided NetworkHierarchyChangedEvent handler to a Network Hierarchy component.
//! @param handler the handler to invoke when the entity's network hierarchy has been modified.
virtual void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) = 0;
//! Binds the provided NetworkHierarchyLeaveEvent handler to a Network Hierarchy component.
//! @param handler the handler to invoke when the entity left its network hierarchy.
virtual void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) = 0;
};
typedef AZ::EBus<NetworkHierarchyRequests> NetworkHierarchyRequestBus;
}
@@ -0,0 +1,86 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <Multiplayer/Components/NetworkHierarchyBus.h>
#include <Source/AutoGen/NetworkHierarchyChildComponent.AutoComponent.h>
namespace Multiplayer
{
class NetworkHierarchyRootComponent;
//! @class NetworkHierarchyChildComponent
//! @brief Component that declares network dependency on the parent of this entity
/*
* The parent of this entity should have @NetworkHierarchyChildComponent (or @NetworkHierarchyRootComponent).
* A network hierarchy is a collection of entities with one @NetworkHierarchyRootComponent at the top parent
* and one or more @NetworkHierarchyChildComponent on its child entities.
*/
class NetworkHierarchyChildComponent final
: public NetworkHierarchyChildComponentBase
, public NetworkHierarchyRequestBus::Handler
{
friend class NetworkHierarchyRootComponent;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyChildComponent, s_networkHierarchyChildComponentConcreteUuid, Multiplayer::NetworkHierarchyChildComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
NetworkHierarchyChildComponent();
//! NetworkHierarchyChildComponentBase overrides.
//! @{
void OnInit() override;
void OnActivate(EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(EntityIsMigrating entityIsMigrating) override;
//! @}
//! NetworkHierarchyRequestBus overrides.
//! @{
bool IsHierarchyEnabled() const override;
bool IsHierarchicalChild() const override;
bool IsHierarchicalRoot() const override { return false; }
AZ::Entity* GetHierarchicalRoot() const override;
AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const override;
void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override;
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @}
protected:
//! Used by @NetworkHierarchyRootComponent
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent);
//! Points to the top level root.
AZ::Entity* m_rootEntity = nullptr;
AZ::Event<NetEntityId>::Handler m_hierarchyRootNetIdChanged;
void OnHierarchyRootNetIdChanged(NetEntityId rootNetId);
NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent;
NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent;
//! Set to false when deactivating or otherwise not to be included in hierarchy considerations.
bool m_isHierarchyEnabled = true;
void NotifyChildrenHierarchyDisbanded();
};
}
@@ -0,0 +1,100 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include <AzCore/Component/Component.h>
#include <AzCore/Component/TransformBus.h>
#include <Multiplayer/Components/NetworkHierarchyBus.h>
#include <Source/AutoGen/NetworkHierarchyRootComponent.AutoComponent.h>
namespace Multiplayer
{
//! @class NetworkHierarchyRootComponent
//! @brief Component that declares the top level entity of a network hierarchy.
/*
* Call @GetHierarchicalEntities to get the list of hierarchical entities.
* A network hierarchy is meant to be a small group of entities. You can control the maximum supported size of
* a network hierarchy by modifying CVar @bg_hierarchyEntityMaxLimit.
*
* A root component marks either a top most root of a hierarchy, or an inner root of an attach hierarchy.
*/
class NetworkHierarchyRootComponent final
: public NetworkHierarchyRootComponentBase
, public NetworkHierarchyRequestBus::Handler
{
friend class NetworkHierarchyChildComponent;
public:
AZ_MULTIPLAYER_COMPONENT(Multiplayer::NetworkHierarchyRootComponent, s_networkHierarchyRootComponentConcreteUuid, Multiplayer::NetworkHierarchyRootComponentBase);
static void Reflect(AZ::ReflectContext* context);
static void GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required);
static void GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided);
static void GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible);
NetworkHierarchyRootComponent();
//! NetworkHierarchyRootComponentBase overrides.
//! @{
void OnInit() override;
void OnActivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
void OnDeactivate(Multiplayer::EntityIsMigrating entityIsMigrating) override;
//! @}
//! NetworkHierarchyRequestBus overrides.
//! @{
bool IsHierarchyEnabled() const override;
bool IsHierarchicalRoot() const override;
bool IsHierarchicalChild() const override;
AZStd::vector<AZ::Entity*> GetHierarchicalEntities() const override;
AZ::Entity* GetHierarchicalRoot() const override;
void BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler) override;
void BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler) override;
//! @}
protected:
void SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot);
private:
AZ::ChildChangedEvent::Handler m_childChangedHandler;
AZ::ParentChangedEvent::Handler m_parentChangedHandler;
void OnChildChanged(AZ::ChildChangeType type, AZ::EntityId child);
void OnParentChanged(AZ::EntityId oldParent, AZ::EntityId parent);
NetworkHierarchyChangedEvent m_networkHierarchyChangedEvent;
NetworkHierarchyLeaveEvent m_networkHierarchyLeaveEvent;
//! Points to the top level root, if this root is an inner root in this hierarchy.
AZ::Entity* m_rootEntity = nullptr;
AZStd::vector<AZ::Entity*> m_hierarchicalEntities;
//! Rebuilds hierarchy starting from this root component's entity.
void RebuildHierarchy();
//! @param underEntity Walk the child entities that belong to @underEntity and consider adding them to the hierarchy
//! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method,
//! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities.
//! @currentEntityCount will be modified to reflect the total entity count upon completion of this method.
//! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise
bool RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount);
//! @param entity Add the child entity and any of its relevant children to the hierarchy
//! @param currentEntityCount The total number of entities in the hierarchy prior to calling this method,
//! used to avoid adding too many entities to the hierarchy while walking recursively the relevant entities.
//! @currentEntityCount will be modified to reflect the total entity count upon completion of this method.
//! @returns false if an attempt was made to go beyond the maximum supported hierarchy size, true otherwise
bool RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount);
void SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity);
//! Set to false when deactivating or otherwise not to be included in hierarchy considerations.
bool m_isHierarchyEnabled = true;
};
}
@@ -31,9 +31,11 @@ namespace Multiplayer
private:
void OnPreRender(float deltaTime);
void OnCorrection();
void OnParentChanged(NetEntityId parentId);
EntityPreRenderEvent::Handler m_entityPreRenderEventHandler;
EntityCorrectionEvent::Handler m_entityCorrectionEventHandler;
AZ::Event<NetEntityId>::Handler m_parentChangedEventHandler;
Multiplayer::HostFrameId m_targetHostFrameId = HostFrameId(0);
};
@@ -49,7 +51,9 @@ namespace Multiplayer
private:
void OnTransformChangedEvent(const AZ::Transform& worldTm);
void OnParentIdChangedEvent(AZ::EntityId oldParent, AZ::EntityId newParent);
AZ::TransformChangedEvent::Handler m_transformChangedHandler;
AZ::ParentChangedEvent::Handler m_parentIdChangedHandler;
};
}
@@ -13,6 +13,7 @@
#include <AzCore/Component/Entity.h>
#include <AzCore/EBus/Event.h>
#include <AzCore/Asset/AssetCommon.h>
#include <AzFramework/Spawnable/SpawnableEntitiesInterface.h>
namespace Multiplayer
{
@@ -75,6 +76,15 @@ namespace Multiplayer
const AZ::Transform& transform
) = 0;
//! Requests a network spawnable to instantiate at a given transform
//! This is an async function. The instantiated entities are not available immediately but will be constructed by the spawnable system
//! The spawnable ticket has to be kept for the whole lifetime of the entities
//! @param netSpawnable the network spawnable to spawn
//! @param transform the transform where the spawnable should be spawned
//! @return the ticket for managing the spawned entities
[[nodiscard]] virtual AZStd::unique_ptr<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform) = 0;
//! Configures new networked entity
//! @param netEntity the entity to setup
//! @param prefabEntryId the name of the spawnable the entity originated from
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<Component
Name="NetworkHierarchyChildComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="false"
OverrideInclude="Multiplayer/Components/NetworkHierarchyChildComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<NetworkProperty Type="NetEntityId" Name="hierarchyRoot" Init="InvalidNetEntityId" ReplicateFrom="Authority" ReplicateTo="Client" IsRewindable="false" IsPredictable="false" IsPublic="true" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="true" />
</Component>
@@ -0,0 +1,14 @@
<?xml version="1.0"?>
<Component
Name="NetworkHierarchyRootComponent"
Namespace="Multiplayer"
OverrideComponent="true"
OverrideController="false"
OverrideInclude="Multiplayer/Components/NetworkHierarchyRootComponent.h"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<ComponentRelation Constraint="Required" HasController="true" Name="NetworkTransformComponent" Namespace="Multiplayer" Include="Multiplayer/Components/NetworkTransformComponent.h" />
<NetworkProperty Type="NetEntityId" Name="hierarchyRoot" Init="InvalidNetEntityId" ReplicateFrom="Authority" ReplicateTo="Client" IsRewindable="false" IsPredictable="false" IsPublic="true" Container="Object" ExposeToEditor="false" ExposeToScript="false" GenerateEventBindings="true" />
</Component>
@@ -0,0 +1,222 @@
/*
* 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/EditContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyBus.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
namespace Multiplayer
{
void NetworkHierarchyChildComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkHierarchyChildComponent, NetworkHierarchyChildComponentBase>()
->Version(1);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NetworkHierarchyChildComponent>(
"Network Hierarchy Child", "Declares a network dependency on the root of this hierarchy.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Multiplayer")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
;
}
}
NetworkHierarchyChildComponentBase::Reflect(context);
}
void NetworkHierarchyChildComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("NetworkTransformComponent"));
}
void NetworkHierarchyChildComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent"));
}
void NetworkHierarchyChildComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent"));
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent"));
}
NetworkHierarchyChildComponent::NetworkHierarchyChildComponent()
: m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); })
, m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); })
, m_hierarchyRootNetIdChanged([this](NetEntityId rootNetId) {OnHierarchyRootNetIdChanged(rootNetId); })
{
}
void NetworkHierarchyChildComponent::OnInit()
{
}
void NetworkHierarchyChildComponent::OnActivate([[maybe_unused]] EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = true;
HierarchyRootAddEvent(m_hierarchyRootNetIdChanged);
NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId());
if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent<AzFramework::TransformComponent>())
{
transformComponent->BindChildChangedEventHandler(m_childChangedHandler);
transformComponent->BindParentChangedEventHandler(m_parentChangedHandler);
}
}
void NetworkHierarchyChildComponent::OnDeactivate([[maybe_unused]] EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = false;
if (m_rootEntity)
{
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
NotifyChildrenHierarchyDisbanded();
NetworkHierarchyRequestBus::Handler::BusDisconnect();
}
bool NetworkHierarchyChildComponent::IsHierarchyEnabled() const
{
return m_isHierarchyEnabled;
}
bool NetworkHierarchyChildComponent::IsHierarchicalChild() const
{
return GetHierarchyRoot() != InvalidNetEntityId;
}
AZ::Entity* NetworkHierarchyChildComponent::GetHierarchicalRoot() const
{
return m_rootEntity;
}
AZStd::vector<AZ::Entity*> NetworkHierarchyChildComponent::GetHierarchicalEntities() const
{
if (m_rootEntity)
{
return m_rootEntity->FindComponent<NetworkHierarchyRootComponent>()->GetHierarchicalEntities();
}
return {};
}
void NetworkHierarchyChildComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyChangedEvent);
}
void NetworkHierarchyChildComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyLeaveEvent);
}
void NetworkHierarchyChildComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
{
m_rootEntity = hierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (m_rootEntity)
{
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(m_rootEntity->GetId());
controller->SetHierarchyRoot(netRootId);
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
else
{
controller->SetHierarchyRoot(InvalidNetEntityId);
m_networkHierarchyLeaveEvent.Signal();
}
}
if (m_rootEntity == nullptr)
{
NotifyChildrenHierarchyDisbanded();
}
}
void NetworkHierarchyChildComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child)
{
if (m_rootEntity)
{
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
}
void NetworkHierarchyChildComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, [[maybe_unused]] AZ::EntityId parent)
{
if (m_rootEntity)
{
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
}
void NetworkHierarchyChildComponent::OnHierarchyRootNetIdChanged(NetEntityId rootNetId)
{
ConstNetworkEntityHandle rootHandle = GetNetworkEntityManager()->GetEntity(rootNetId);
if (rootHandle.Exists())
{
AZ::Entity* newRoot = rootHandle.GetEntity();
if (m_rootEntity != newRoot)
{
m_rootEntity = newRoot;
m_networkHierarchyChangedEvent.Signal(m_rootEntity->GetId());
}
}
else
{
m_isHierarchyEnabled = false;
m_rootEntity = nullptr;
m_networkHierarchyLeaveEvent.Signal();
}
}
void NetworkHierarchyChildComponent::NotifyChildrenHierarchyDisbanded()
{
AZStd::vector<AZ::EntityId> allChildren;
AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& childEntityId : allChildren)
{
if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId))
{
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(nullptr);
}
else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>())
{
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(nullptr);
}
}
}
}
}
@@ -0,0 +1,329 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <AzCore/Component/Entity.h>
#include <AzCore/Component/TransformBus.h>
#include <AzCore/Console/ILogger.h>
#include <AzCore/Interface/Interface.h>
#include <AzCore/Serialization/EditContext.h>
#include <AzFramework/Components/TransformComponent.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
AZ_CVAR(uint32_t, bg_hierarchyEntityMaxLimit, 16, nullptr, AZ::ConsoleFunctorFlags::Null,
"Maximum allowed size of network entity hierarchies, including top level entity.");
namespace Multiplayer
{
void NetworkHierarchyRootComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetworkHierarchyRootComponent, NetworkHierarchyRootComponentBase>()
->Version(1);
if (AZ::EditContext* editContext = serializeContext->GetEditContext())
{
editContext->Class<NetworkHierarchyRootComponent>(
"Network Hierarchy Root", "Marks the entity as the root of an entity hierarchy.")
->ClassElement(AZ::Edit::ClassElements::EditorData, "")
->Attribute(AZ::Edit::Attributes::Category, "Multiplayer")
->Attribute(AZ::Edit::Attributes::AppearsInAddComponentMenu, AZ_CRC_CE("Game"))
;
}
}
NetworkHierarchyRootComponentBase::Reflect(context);
}
void NetworkHierarchyRootComponent::GetRequiredServices(AZ::ComponentDescriptor::DependencyArrayType& required)
{
required.push_back(AZ_CRC_CE("NetworkTransformComponent"));
}
void NetworkHierarchyRootComponent::GetProvidedServices(AZ::ComponentDescriptor::DependencyArrayType& provided)
{
provided.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent"));
}
void NetworkHierarchyRootComponent::GetIncompatibleServices(AZ::ComponentDescriptor::DependencyArrayType& incompatible)
{
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyChildComponent"));
incompatible.push_back(AZ_CRC_CE("NetworkHierarchyRootComponent"));
}
NetworkHierarchyRootComponent::NetworkHierarchyRootComponent()
: m_childChangedHandler([this](AZ::ChildChangeType type, AZ::EntityId child) { OnChildChanged(type, child); })
, m_parentChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId parent) { OnParentChanged(oldParent, parent); })
{
}
void NetworkHierarchyRootComponent::OnInit()
{
}
void NetworkHierarchyRootComponent::OnActivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = true;
m_hierarchicalEntities.push_back(GetEntity());
NetworkHierarchyRequestBus::Handler::BusConnect(GetEntityId());
if (AzFramework::TransformComponent* transformComponent = GetEntity()->FindComponent<AzFramework::TransformComponent>())
{
transformComponent->BindChildChangedEventHandler(m_childChangedHandler);
transformComponent->BindParentChangedEventHandler(m_parentChangedHandler);
}
}
void NetworkHierarchyRootComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
{
m_isHierarchyEnabled = false;
if (m_rootEntity)
{
// Tell parent to re-build the hierarchy
if (NetworkHierarchyRootComponent* root = m_rootEntity->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
else
{
// Notify children that the hierarchy is disbanding
AZStd::vector<AZ::EntityId> allChildren;
AZ::TransformBus::EventResult(allChildren, GetEntityId(), &AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& childEntityId : allChildren)
{
if (const AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(childEntityId))
{
SetRootForEntity(nullptr, childEntity);
}
}
}
m_childChangedHandler.Disconnect();
m_parentChangedHandler.Disconnect();
NetworkHierarchyRequestBus::Handler::BusDisconnect();
m_hierarchicalEntities.clear();
m_rootEntity = nullptr;
}
bool NetworkHierarchyRootComponent::IsHierarchyEnabled() const
{
return m_isHierarchyEnabled;
}
bool NetworkHierarchyRootComponent::IsHierarchicalRoot() const
{
return GetHierarchyRoot() == InvalidNetEntityId;
}
bool NetworkHierarchyRootComponent::IsHierarchicalChild() const
{
return !IsHierarchicalRoot();
}
AZStd::vector<AZ::Entity*> NetworkHierarchyRootComponent::GetHierarchicalEntities() const
{
return m_hierarchicalEntities;
}
AZ::Entity* NetworkHierarchyRootComponent::GetHierarchicalRoot() const
{
if (m_rootEntity)
{
return m_rootEntity;
}
return GetEntity();
}
void NetworkHierarchyRootComponent::BindNetworkHierarchyChangedEventHandler(NetworkHierarchyChangedEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyChangedEvent);
}
void NetworkHierarchyRootComponent::BindNetworkHierarchyLeaveEventHandler(NetworkHierarchyLeaveEvent::Handler& handler)
{
handler.Connect(m_networkHierarchyLeaveEvent);
}
void NetworkHierarchyRootComponent::OnChildChanged([[maybe_unused]] AZ::ChildChangeType type, [[maybe_unused]] AZ::EntityId child)
{
if (IsHierarchicalRoot())
{
// Parent-child notifications are not reliable enough to avoid duplicate notifications,
// so we will rebuild from scratch to avoid duplicate entries in @m_hierarchicalEntities.
RebuildHierarchy();
}
else if (NetworkHierarchyRootComponent* root = GetHierarchicalRoot()->FindComponent<NetworkHierarchyRootComponent>())
{
root->RebuildHierarchy();
}
}
void NetworkHierarchyRootComponent::OnParentChanged([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent)
{
// If the parent is part of a hierarchy, it will detect this entity as a new child and rebuild hierarchy.
// Thus, we only need to take care of a case when the parent is not part of a hierarchy,
// in which case, this entity will be a new root of a new hierarchy.
if (AZ::Entity* parentEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(newParent))
{
if (parentEntity->FindComponent<NetworkHierarchyRootComponent>() == nullptr &&
parentEntity->FindComponent<NetworkHierarchyChildComponent>() == nullptr)
{
RebuildHierarchy();
}
else
{
m_hierarchicalEntities.clear();
}
}
else
{
// Detached from parent
RebuildHierarchy();
}
}
void NetworkHierarchyRootComponent::RebuildHierarchy()
{
AZStd::vector<AZ::Entity*> previousEntities;
m_hierarchicalEntities.swap(previousEntities);
m_hierarchicalEntities.push_back(GetEntity()); // Add the root.
uint32_t currentEntityCount = aznumeric_cast<uint32_t>(m_hierarchicalEntities.size());
RecursiveAttachHierarchicalEntities(GetEntityId(), currentEntityCount);
bool hierarchyChanged = false;
// Send out join and leave events.
for (AZ::Entity* currentEntity : m_hierarchicalEntities)
{
const auto prevEntityIterator = AZStd::find(previousEntities.begin(), previousEntities.end(), currentEntity);
if (prevEntityIterator != previousEntities.end())
{
// This entity was here before the build of the hierarchy.
previousEntities.erase(prevEntityIterator);
}
else
{
// This is a newly added entity to the network hierarchy.
hierarchyChanged = true;
SetRootForEntity(GetEntity(), currentEntity);
}
}
// These entities were removed since last rebuild.
for (const AZ::Entity* previousEntity : previousEntities)
{
SetRootForEntity(nullptr, previousEntity);
}
if (!previousEntities.empty())
{
hierarchyChanged = true;
}
if (hierarchyChanged)
{
m_networkHierarchyChangedEvent.Signal(GetEntityId());
}
}
void NetworkHierarchyRootComponent::SetRootForEntity(AZ::Entity* root, const AZ::Entity* childEntity)
{
if (auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>())
{
hierarchyChildComponent->SetTopLevelHierarchyRootEntity(root);
}
else if (auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>())
{
hierarchyRootComponent->SetTopLevelHierarchyRootEntity(root);
}
}
bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalEntities(AZ::EntityId underEntity, uint32_t& currentEntityCount)
{
AZStd::vector<AZ::EntityId> allChildren;
AZ::TransformBus::EventResult(allChildren, underEntity, &AZ::TransformBus::Events::GetChildren);
for (const AZ::EntityId& newChildId : allChildren)
{
if (!RecursiveAttachHierarchicalChild(newChildId, currentEntityCount))
{
return false;
}
}
return true;
}
bool NetworkHierarchyRootComponent::RecursiveAttachHierarchicalChild(AZ::EntityId entity, uint32_t& currentEntityCount)
{
if (currentEntityCount >= bg_hierarchyEntityMaxLimit)
{
AZLOG_WARN("Entity %s is trying to build a network hierarchy that is too large. bg_hierarchyEntityMaxLimit is currently set to (%u)",
GetEntity()->GetName().c_str(), static_cast<uint32_t>(bg_hierarchyEntityMaxLimit));
return false;
}
if (AZ::Entity* childEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(entity))
{
auto* hierarchyChildComponent = childEntity->FindComponent<NetworkHierarchyChildComponent>();
auto* hierarchyRootComponent = childEntity->FindComponent<NetworkHierarchyRootComponent>();
if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchyEnabled()) ||
(hierarchyRootComponent && hierarchyRootComponent->IsHierarchyEnabled()))
{
m_hierarchicalEntities.push_back(childEntity);
++currentEntityCount;
if (!RecursiveAttachHierarchicalEntities(entity, currentEntityCount))
{
return false;
}
}
}
return true;
}
void NetworkHierarchyRootComponent::SetTopLevelHierarchyRootEntity(AZ::Entity* hierarchyRoot)
{
m_rootEntity = hierarchyRoot;
if (HasController() && GetNetBindComponent()->GetNetEntityRole() == NetEntityRole::Authority)
{
NetworkHierarchyChildComponentController* controller = static_cast<NetworkHierarchyChildComponentController*>(GetController());
if (hierarchyRoot)
{
const NetEntityId netRootId = GetNetworkEntityManager()->GetNetEntityIdById(hierarchyRoot->GetId());
controller->SetHierarchyRoot(netRootId);
}
else
{
controller->SetHierarchyRoot(InvalidNetEntityId);
}
}
if (m_rootEntity == nullptr)
{
// We lost the parent hierarchical entity, so as a root we need to re-build our own hierarchy.
RebuildHierarchy();
}
}
}
@@ -28,6 +28,7 @@ namespace Multiplayer
NetworkTransformComponent::NetworkTransformComponent()
: m_entityPreRenderEventHandler([this](float deltaTime) { OnPreRender(deltaTime); })
, m_entityCorrectionEventHandler([this]() { OnCorrection(); })
, m_parentChangedEventHandler([this](NetEntityId parentId) { OnParentChanged(parentId); })
{
;
}
@@ -41,6 +42,7 @@ namespace Multiplayer
{
GetNetBindComponent()->AddEntityPreRenderEventHandler(m_entityPreRenderEventHandler);
GetNetBindComponent()->AddEntityCorrectionEventHandler(m_entityCorrectionEventHandler);
ParentEntityIdAddEvent(m_parentChangedEventHandler);
}
void NetworkTransformComponent::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
@@ -97,10 +99,26 @@ namespace Multiplayer
}
}
void NetworkTransformComponent::OnParentChanged(NetEntityId parentId)
{
const ConstNetworkEntityHandle parentEntityHandle = GetNetworkEntityManager()->GetEntity(parentId);
if (parentEntityHandle.Exists())
{
if (const AZ::Entity* parentEntity = parentEntityHandle.GetEntity())
{
GetEntity()->GetTransform()->SetParent(parentEntity->GetId());
}
}
else
{
GetEntity()->GetTransform()->SetParent(AZ::EntityId());
}
}
NetworkTransformComponentController::NetworkTransformComponentController(NetworkTransformComponent& parent)
: NetworkTransformComponentControllerBase(parent)
, m_transformChangedHandler([this](const AZ::Transform&, const AZ::Transform& worldTm) { OnTransformChangedEvent(worldTm); })
, m_parentIdChangedHandler([this](AZ::EntityId oldParent, AZ::EntityId newParent) { OnParentIdChangedEvent(oldParent, newParent); })
{
;
}
@@ -109,6 +127,9 @@ namespace Multiplayer
{
GetParent().GetTransformComponent()->BindTransformChangedEventHandler(m_transformChangedHandler);
OnTransformChangedEvent(GetParent().GetTransformComponent()->GetWorldTM());
GetParent().GetTransformComponent()->BindParentChangedEventHandler(m_parentIdChangedHandler);
OnParentIdChangedEvent(AZ::EntityId(), GetParent().GetTransformComponent()->GetParentId());
}
void NetworkTransformComponentController::OnDeactivate([[maybe_unused]] Multiplayer::EntityIsMigrating entityIsMigrating)
@@ -122,4 +143,14 @@ namespace Multiplayer
SetTranslation(worldTm.GetTranslation());
SetScale(worldTm.GetUniformScale());
}
void NetworkTransformComponentController::OnParentIdChangedEvent([[maybe_unused]] AZ::EntityId oldParent, AZ::EntityId newParent)
{
AZ::Entity* parentEntity = AZ::Interface<AZ::ComponentApplicationRequests>::Get()->FindEntity(newParent);
if (parentEntity)
{
const ConstNetworkEntityHandle parentHandle(parentEntity, GetNetworkEntityTracker());
SetParentEntityId(parentHandle.GetNetEntityId());
}
}
}
@@ -6,13 +6,14 @@
*
*/
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <Source/MultiplayerGem.h>
#include <Source/MultiplayerSystemComponent.h>
#include <Source/AutoGen/AutoComponentTypes.h>
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <Source/Pipeline/NetworkSpawnableHolderComponent.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <AzNetworking/Framework/NetworkingSystemComponent.h>
namespace Multiplayer
{
@@ -23,7 +24,6 @@ namespace Multiplayer
AzNetworking::NetworkingSystemComponent::CreateDescriptor(),
MultiplayerSystemComponent::CreateDescriptor(),
NetBindComponent::CreateDescriptor(),
NetBindMarkerComponent::CreateDescriptor(),
NetworkSpawnableHolderComponent::CreateDescriptor(),
});
@@ -75,6 +75,8 @@ namespace Multiplayer
void EntityReplicationManager::ActivatePendingEntities()
{
AZStd::vector<NetEntityId> notReadyEntities;
const AZ::TimeMs endTimeMs = AZ::GetElapsedTimeMs() + m_entityActivationTimeSliceMs;
while (!m_entitiesPendingActivation.empty())
{
@@ -83,7 +85,14 @@ namespace Multiplayer
EntityReplicator* entityReplicator = GetEntityReplicator(entityId);
if (entityReplicator && !entityReplicator->IsMarkedForRemoval())
{
entityReplicator->ActivateNetworkEntity();
if (entityReplicator->IsReadyToActivate())
{
entityReplicator->ActivateNetworkEntity();
}
else
{
notReadyEntities.push_back(entityId);
}
}
if (m_entityActivationTimeSliceMs > AZ::TimeMs{ 0 } && AZ::GetElapsedTimeMs() > endTimeMs)
{
@@ -91,6 +100,11 @@ namespace Multiplayer
break;
}
}
for (NetEntityId netEntityId : notReadyEntities)
{
m_entitiesPendingActivation.push_back(netEntityId);
}
}
void EntityReplicationManager::SendUpdates(AZ::TimeMs hostTimeMs)
@@ -249,15 +263,15 @@ namespace Multiplayer
void EntityReplicationManager::SendEntityUpdates(AZ::TimeMs hostTimeMs)
{
EntityReplicatorList toSendList = GenerateEntityUpdateList();
AZLOG(NET_ReplicationInfo, "Sending %zd updates from %d to %d", toSendList.size(), (uint8_t)GetNetworkEntityManager()->GetHostId(), (uint8_t)GetRemoteHostId());
// prep a replication record for send, at this point, everything needs to be sent
for (EntityReplicator* replicator : toSendList)
{
replicator->GetPropertyPublisher()->PrepareSerialization();
}
// While our to send list is not empty, build up another packet to send
do
{
@@ -524,7 +538,7 @@ namespace Multiplayer
bool EntityReplicationManager::HandlePropertyChangeMessage
(
AzNetworking::IConnection* invokingConnection,
AzNetworking::IConnection* invokingConnection,
EntityReplicator* entityReplicator,
AzNetworking::PacketId packetId,
NetEntityId netEntityId,
@@ -1137,7 +1151,7 @@ namespace Multiplayer
AzNetworking::TrackChangedSerializer<AzNetworking::NetworkOutputSerializer> outputSerializer(message.m_propertyUpdateData.GetBuffer(), static_cast<uint32_t>(message.m_propertyUpdateData.GetSize()));
if (!HandlePropertyChangeMessage
(
invokingConnection,
invokingConnection,
replicator,
AzNetworking::InvalidPacketId,
message.m_entityId,
@@ -6,23 +6,25 @@
*
*/
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/Components/NetBindComponent.h>
#include <Multiplayer/Components/NetworkHierarchyChildComponent.h>
#include <Multiplayer/Components/NetworkHierarchyRootComponent.h>
#include <Multiplayer/Components/NetworkTransformComponent.h>
#include <Multiplayer/NetworkEntity/NetworkEntityRpcMessage.h>
#include <Source/AutoGen/Multiplayer.AutoPackets.h>
#include <Source/NetworkEntity/NetworkEntityAuthorityTracker.h>
#include <Source/NetworkEntity/NetworkEntityTracker.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicationManager.h>
#include <Source/NetworkEntity/EntityReplication/EntityReplicator.h>
#include <Source/NetworkEntity/EntityReplication/PropertyPublisher.h>
#include <Source/NetworkEntity/EntityReplication/PropertySubscriber.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzNetworking/PacketLayer/IPacket.h>
#include <AzNetworking/Serialization/ISerializer.h>
#include <AzNetworking/Serialization/NetworkInputSerializer.h>
#include <AzNetworking/Serialization/NetworkOutputSerializer.h>
#include <AzNetworking/ConnectionLayer/IConnection.h>
#include <AzCore/Component/ComponentApplicationBus.h>
#include <AzCore/Console/IConsole.h>
@@ -48,7 +50,7 @@ namespace Multiplayer
, m_onForwardRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
, m_onSendAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
, m_onForwardAutonomousRpcHandler([this](NetworkEntityRpcMessage& entityRpcMessage) { OnSendRpcEvent(entityRpcMessage); })
, m_onEntityStopHandler([this](const ConstNetworkEntityHandle &) { OnEntityRemovedEvent(); })
, m_onEntityStopHandler([this](const ConstNetworkEntityHandle&) { OnEntityRemovedEvent(); })
, m_proxyRemovalEvent([this] { OnProxyRemovalTimedEvent(); }, AZ::Name("ProxyRemovalTimedEvent"))
{
if (auto localEnt = m_entityHandle.GetEntity())
@@ -119,12 +121,12 @@ namespace Multiplayer
{
m_replicationManager.AddReplicatorToPendingSend(*this);
m_propertyPublisher = AZStd::make_unique<PropertyPublisher>
(
GetRemoteNetworkRole(),
!RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False,
m_netBindComponent,
*m_connection
);
(
GetRemoteNetworkRole(),
!RemoteManagerOwnsEntityLifetime() ? PropertyPublisher::OwnsLifetime::True : PropertyPublisher::OwnsLifetime::False,
m_netBindComponent,
*m_connection
);
m_netBindComponent->AddEntityDirtiedEventHandler(m_onEntityDirtiedHandler);
}
else
@@ -279,7 +281,7 @@ namespace Multiplayer
AZ_Assert(netBindComponent, "No Multiplayer::NetBindComponent");
bool isAuthority = (GetBoundLocalNetworkRole() == NetEntityRole::Authority)
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
&& (GetBoundLocalNetworkRole() == netBindComponent->GetNetEntityRole());
bool isClient = GetRemoteNetworkRole() == NetEntityRole::Client;
bool isAutonomous = GetBoundLocalNetworkRole() == NetEntityRole::Autonomous;
if (isAuthority || isClient || isAutonomous)
@@ -306,9 +308,9 @@ namespace Multiplayer
bool EntityReplicator::RemoteManagerOwnsEntityLifetime() const
{
bool isServer = (GetBoundLocalNetworkRole() == NetEntityRole::Server)
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
&& (GetRemoteNetworkRole() == NetEntityRole::Authority);
bool isClient = (GetBoundLocalNetworkRole() == NetEntityRole::Client)
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
|| (GetBoundLocalNetworkRole() == NetEntityRole::Autonomous);
return isServer || isClient;
}
@@ -405,6 +407,62 @@ namespace Multiplayer
return m_replicationManager.GetResendTimeoutTimeMs();
}
bool EntityReplicator::IsReadyToActivate() const
{
const AZ::Entity* entity = m_entityHandle.GetEntity();
AZ_Assert(entity, "Entity replicator entity unexpectedly missing");
const NetworkHierarchyChildComponent* hierarchyChildComponent = entity->FindComponent<NetworkHierarchyChildComponent>();
const NetworkHierarchyRootComponent* hierarchyRootComponent = nullptr;
if (hierarchyChildComponent == nullptr)
{
// Child and root hierarchy components are mutually exclusive
hierarchyRootComponent = entity->FindComponent<NetworkHierarchyRootComponent>();
}
if ((hierarchyChildComponent && hierarchyChildComponent->IsHierarchicalChild())
|| (hierarchyRootComponent && hierarchyRootComponent->IsHierarchicalChild()))
{
// If hierarchy is enabled for the entity, check if the parent is available
if (const NetworkTransformComponent* networkTransform = entity->FindComponent<NetworkTransformComponent>())
{
const NetEntityId parentId = networkTransform->GetParentEntityId();
/*
* For root entities attached to a level, a network parent won't be set.
* In this case, this entity is the root entity of the hierarchy and it will be activated first.
*/
if (parentId != InvalidNetEntityId)
{
ConstNetworkEntityHandle parentHandle = GetNetworkEntityManager()->GetEntity(parentId);
const AZ::Entity* parentEntity = parentHandle.GetEntity();
if (parentEntity && parentEntity->GetState() == AZ::Entity::State::Active)
{
AZLOG
(
NET_HierarchyActivationInfo,
"Hierchical entity %s asking for activation - granted",
entity->GetName().c_str()
);
return true;
}
AZLOG
(
NET_HierarchyActivationInfo,
"Hierchical entity %s asking for activation - waiting on the parent %u",
entity->GetName().c_str(),
aznumeric_cast<uint32_t>(parentId)
);
return false;
}
}
}
return true;
}
NetworkEntityUpdateMessage EntityReplicator::GenerateUpdatePacket()
{
if (IsMarkedForRemoval() && OwnsReplicatorLifetime()) // TODO: clean this up
@@ -36,7 +36,7 @@ namespace Multiplayer
{
public:
EntityReplicator(EntityReplicationManager& replicationManager, AzNetworking::IConnection* connection, NetEntityRole remoteNetworkRole, const ConstNetworkEntityHandle& entityHandle);
virtual ~EntityReplicator();
~EntityReplicator() override;
NetEntityRole GetBoundLocalNetworkRole() const;
NetEntityRole GetRemoteNetworkRole() const;
@@ -62,6 +62,8 @@ namespace Multiplayer
bool IsDeletionAcknowledged() const;
bool WasMigrated() const;
void SetWasMigrated(bool wasMigrated);
// If an entity is part of a network hierarchy then it is only ready to activate when its direct parent entity is active.
bool IsReadyToActivate() const;
NetworkEntityUpdateMessage GenerateUpdatePacket();
@@ -465,8 +465,60 @@ namespace Multiplayer
return netEntityId;
}
void NetworkEntityManager::OnRootSpawnableAssigned(
[[maybe_unused]] AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable, [[maybe_unused]] uint32_t generation)
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> NetworkEntityManager::RequestNetSpawnableInstantiation(
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform)
{
// Prepare the parameters for the spawning process
AzFramework::SpawnAllEntitiesOptionalArgs optionalArgs;
optionalArgs.m_priority = AzFramework::SpawnablePriority_High;
const AZ::Name netSpawnableName =
AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(netSpawnable.GetId());
if (netSpawnableName.IsEmpty())
{
AZ_Error("NetworkEntityManager", false,
"RequestNetSpawnableInstantiation: Requested spawnable %s doesn't exist in the NetworkSpawnableLibrary. Please make sure it is a network spawnable",
netSpawnable.GetHint().c_str());
return nullptr;
}
// Pre-insertion callback allows us to do network-specific setup for the entities before they are added to the scene
optionalArgs.m_preInsertionCallback = [netSpawnableName, rootTransform = transform]
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities)
{
bool shouldUpdateTransform = (rootTransform.IsClose(AZ::Transform::Identity()) == false);
for (uint32_t netEntityIndex = 0, entitiesSize = aznumeric_cast<uint32_t>(entities.size());
netEntityIndex < entitiesSize; ++netEntityIndex)
{
AZ::Entity* netEntity = *(entities.begin() + netEntityIndex);
if (shouldUpdateTransform)
{
AzFramework::TransformComponent* netEntityTransform =
netEntity->FindComponent<AzFramework::TransformComponent>();
AZ::Transform worldTm = netEntityTransform->GetWorldTM();
worldTm = rootTransform * worldTm;
netEntityTransform->SetWorldTM(worldTm);
}
PrefabEntityId prefabEntityId;
prefabEntityId.m_prefabName = netSpawnableName;
prefabEntityId.m_entityOffset = netEntityIndex;
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
}
};
// Spawn with the newly created ticket. This allows the calling code to manage the lifetime of the constructed entities
auto ticket = AZStd::make_unique<AzFramework::EntitySpawnTicket>(netSpawnable);
AzFramework::SpawnableEntitiesInterface::Get()->SpawnAllEntities(*ticket, AZStd::move(optionalArgs));
return ticket;
}
void NetworkEntityManager::OnRootSpawnableAssigned(AZ::Data::Asset<AzFramework::Spawnable> rootSpawnable,
[[maybe_unused]] uint32_t generation)
{
auto* multiplayer = GetMultiplayer();
const auto agentType = multiplayer->GetAgentType();
@@ -479,7 +531,6 @@ namespace Multiplayer
void NetworkEntityManager::OnRootSpawnableReleased([[maybe_unused]] uint32_t generation)
{
// TODO: Do we need to clear all entities here?
auto* multiplayer = GetMultiplayer();
const auto agentType = multiplayer->GetAgentType();
@@ -60,6 +60,9 @@ namespace Multiplayer
const AZ::Transform& transform
) override;
AZStd::unique_ptr<AzFramework::EntitySpawnTicket> RequestNetSpawnableInstantiation(
const AZ::Data::Asset<AzFramework::Spawnable>& netSpawnable, const AZ::Transform& transform) override;
void SetupNetEntity(AZ::Entity* netEntity, PrefabEntityId prefabEntityId, NetEntityRole netEntityRole) override;
uint32_t GetEntityCount() const override;
@@ -37,6 +37,7 @@ namespace Multiplayer
NetworkEntityHandle Get(NetEntityId netEntityId);
ConstNetworkEntityHandle Get(NetEntityId netEntityId) const;
//! Returns Net Entity ID for a given AZ Entity ID.
NetEntityId Get(const AZ::EntityId& entityId) const;
//! Returns true if the netEntityId exists.
@@ -1,115 +0,0 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project.
* For complete copyright and license terms please see the LICENSE at the root of this distribution.
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include <Source/Pipeline/NetBindMarkerComponent.h>
#include <AzCore/Asset/AssetManager.h>
#include <AzCore/Serialization/SerializeContext.h>
#include <Multiplayer/IMultiplayer.h>
#include <Multiplayer/INetworkSpawnableLibrary.h>
#include <AzCore/Component/TransformBus.h>
#include <AzFramework/Components/TransformComponent.h>
namespace Multiplayer
{
void NetBindMarkerComponent::Reflect(AZ::ReflectContext* context)
{
AZ::SerializeContext* serializeContext = azrtti_cast<AZ::SerializeContext*>(context);
if (serializeContext)
{
serializeContext->Class<NetBindMarkerComponent, AZ::Component>()
->Version(1)
->Field("NetEntityIndex", &NetBindMarkerComponent::m_netEntityIndex)
->Field("NetSpawnableAsset", &NetBindMarkerComponent::m_networkSpawnableAsset);
}
}
AzFramework::Spawnable* GetSpawnableFromAsset(AZ::Data::Asset<AzFramework::Spawnable>& asset)
{
AzFramework::Spawnable* spawnable = asset.GetAs<AzFramework::Spawnable>();
if (!spawnable)
{
asset =
AZ::Data::AssetManager::Instance().GetAsset<AzFramework::Spawnable>(asset.GetId(), AZ::Data::AssetLoadBehavior::PreLoad);
AZ::Data::AssetManager::Instance().BlockUntilLoadComplete(asset);
spawnable = asset.GetAs<AzFramework::Spawnable>();
}
return spawnable;
}
void NetBindMarkerComponent::Activate()
{
const auto agentType = AZ::Interface<IMultiplayer>::Get()->GetAgentType();
const bool spawnImmediately =
(agentType == MultiplayerAgentType::ClientServer || agentType == MultiplayerAgentType::DedicatedServer);
if (spawnImmediately && m_networkSpawnableAsset.GetId().IsValid())
{
AZ::Transform worldTm = GetEntity()->FindComponent<AzFramework::TransformComponent>()->GetWorldTM();
auto preInsertionCallback =
[worldTm = AZStd::move(worldTm), netEntityIndex = m_netEntityIndex, spawnableAssetId = m_networkSpawnableAsset.GetId()]
(AzFramework::EntitySpawnTicket::Id, AzFramework::SpawnableEntityContainerView entities)
{
if (entities.size() == 1)
{
AZ::Entity* netEntity = *entities.begin();
auto* transformComponent = netEntity->FindComponent<AzFramework::TransformComponent>();
transformComponent->SetWorldTM(worldTm);
AZ::Name spawnableName = AZ::Interface<INetworkSpawnableLibrary>::Get()->GetSpawnableNameFromAssetId(spawnableAssetId);
PrefabEntityId prefabEntityId;
prefabEntityId.m_prefabName = spawnableName;
prefabEntityId.m_entityOffset = static_cast<uint32_t>(netEntityIndex);
AZ::Interface<INetworkEntityManager>::Get()->SetupNetEntity(netEntity, prefabEntityId, NetEntityRole::Authority);
}
else
{
AZ_Error("NetBindMarkerComponent", false, "Requested to spawn 1 entity, but received %d", entities.size());
}
};
m_netSpawnTicket = AzFramework::EntitySpawnTicket(m_networkSpawnableAsset);
AzFramework::SpawnEntitiesOptionalArgs optionalArgs;
optionalArgs.m_preInsertionCallback = AZStd::move(preInsertionCallback);
AzFramework::SpawnableEntitiesInterface::Get()->SpawnEntities(
m_netSpawnTicket, { m_netEntityIndex }, AZStd::move(optionalArgs));
}
}
void NetBindMarkerComponent::Deactivate()
{
if(m_netSpawnTicket.IsValid())
{
AzFramework::SpawnableEntitiesInterface::Get()->DespawnAllEntities(m_netSpawnTicket);
}
}
size_t NetBindMarkerComponent::GetNetEntityIndex() const
{
return m_netEntityIndex;
}
void NetBindMarkerComponent::SetNetEntityIndex(size_t netEntityIndex)
{
m_netEntityIndex = netEntityIndex;
}
void NetBindMarkerComponent::SetNetworkSpawnableAsset(AZ::Data::Asset<AzFramework::Spawnable> networkSpawnableAsset)
{
m_networkSpawnableAsset = networkSpawnableAsset;
}
AZ::Data::Asset<AzFramework::Spawnable> NetBindMarkerComponent::GetNetworkSpawnableAsset() const
{
return m_networkSpawnableAsset;
}
}

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